mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 20:19:21 +00:00
Add API functions to initialize Haveno account (#216)
Co-authored-by: woodser@protonmail.com
This commit is contained in:
parent
dc4692d97a
commit
e3b9a9962b
81 changed files with 2755 additions and 1660 deletions
29
Makefile
29
Makefile
|
@ -51,17 +51,6 @@ arbitrator-desktop:
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=9998
|
--apiPort=9998
|
||||||
|
|
||||||
arbitrator-daemon:
|
|
||||||
# Arbitrator and mediator need to be registerd in the UI before launching the daemon.
|
|
||||||
./haveno-daemon \
|
|
||||||
--baseCurrencyNetwork=XMR_STAGENET \
|
|
||||||
--useLocalhostForP2P=true \
|
|
||||||
--useDevPrivilegeKeys=true \
|
|
||||||
--nodePort=4444 \
|
|
||||||
--appName=haveno-XMR_STAGENET_arbitrator \
|
|
||||||
--apiPassword=apitest \
|
|
||||||
--apiPort=9998
|
|
||||||
|
|
||||||
arbitrator-desktop2:
|
arbitrator-desktop2:
|
||||||
# Arbitrator and mediator need to be registerd in the UI after launching it.
|
# Arbitrator and mediator need to be registerd in the UI after launching it.
|
||||||
./haveno-desktop \
|
./haveno-desktop \
|
||||||
|
@ -73,6 +62,18 @@ arbitrator-desktop2:
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=10001
|
--apiPort=10001
|
||||||
|
|
||||||
|
arbitrator-daemon:
|
||||||
|
# Arbitrator and mediator need to be registerd in the UI before launching the daemon!
|
||||||
|
./haveno-daemon \
|
||||||
|
--baseCurrencyNetwork=XMR_STAGENET \
|
||||||
|
--useLocalhostForP2P=true \
|
||||||
|
--useDevPrivilegeKeys=true \
|
||||||
|
--nodePort=4444 \
|
||||||
|
--appName=haveno-XMR_STAGENET_arbitrator \
|
||||||
|
--apiPassword=apitest \
|
||||||
|
--apiPort=9998 \
|
||||||
|
--passwordRequired=false
|
||||||
|
|
||||||
alice-desktop:
|
alice-desktop:
|
||||||
./haveno-desktop \
|
./haveno-desktop \
|
||||||
--baseCurrencyNetwork=XMR_STAGENET \
|
--baseCurrencyNetwork=XMR_STAGENET \
|
||||||
|
@ -93,7 +94,8 @@ alice-daemon:
|
||||||
--appName=haveno-XMR_STAGENET_Alice \
|
--appName=haveno-XMR_STAGENET_Alice \
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=9999 \
|
--apiPort=9999 \
|
||||||
--walletRpcBindPort=38091
|
--walletRpcBindPort=38091 \
|
||||||
|
--passwordRequired=false
|
||||||
|
|
||||||
bob-desktop:
|
bob-desktop:
|
||||||
./haveno-desktop \
|
./haveno-desktop \
|
||||||
|
@ -115,7 +117,8 @@ bob-daemon:
|
||||||
--appName=haveno-XMR_STAGENET_Bob \
|
--appName=haveno-XMR_STAGENET_Bob \
|
||||||
--apiPassword=apitest \
|
--apiPassword=apitest \
|
||||||
--apiPort=10000 \
|
--apiPort=10000 \
|
||||||
--walletRpcBindPort=38092
|
--walletRpcBindPort=38092 \
|
||||||
|
--passwordRequired=false
|
||||||
|
|
||||||
monero-shared:
|
monero-shared:
|
||||||
./.localnet/monerod \
|
./.localnet/monerod \
|
||||||
|
|
|
@ -114,6 +114,7 @@ public class Config {
|
||||||
public static final String BTC_MIN_TX_FEE = "btcMinTxFee";
|
public static final String BTC_MIN_TX_FEE = "btcMinTxFee";
|
||||||
public static final String BTC_FEES_TS = "bitcoinFeesTs";
|
public static final String BTC_FEES_TS = "bitcoinFeesTs";
|
||||||
public static final String BYPASS_MEMPOOL_VALIDATION = "bypassMempoolValidation";
|
public static final String BYPASS_MEMPOOL_VALIDATION = "bypassMempoolValidation";
|
||||||
|
public static final String PASSWORD_REQUIRED = "passwordRequired";
|
||||||
|
|
||||||
// Default values for certain options
|
// Default values for certain options
|
||||||
public static final int UNSPECIFIED_PORT = -1;
|
public static final int UNSPECIFIED_PORT = -1;
|
||||||
|
@ -190,6 +191,7 @@ public class Config {
|
||||||
public final boolean preventPeriodicShutdownAtSeedNode;
|
public final boolean preventPeriodicShutdownAtSeedNode;
|
||||||
public final boolean republishMailboxEntries;
|
public final boolean republishMailboxEntries;
|
||||||
public final boolean bypassMempoolValidation;
|
public final boolean bypassMempoolValidation;
|
||||||
|
public final boolean passwordRequired;
|
||||||
|
|
||||||
// Properties derived from options but not exposed as options themselves
|
// Properties derived from options but not exposed as options themselves
|
||||||
public final File torDir;
|
public final File torDir;
|
||||||
|
@ -579,6 +581,13 @@ public class Config {
|
||||||
.ofType(boolean.class)
|
.ofType(boolean.class)
|
||||||
.defaultsTo(false);
|
.defaultsTo(false);
|
||||||
|
|
||||||
|
ArgumentAcceptingOptionSpec<Boolean> passwordRequiredOpt =
|
||||||
|
parser.accepts(PASSWORD_REQUIRED,
|
||||||
|
"Requires a password for creating a Haveno account")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(boolean.class)
|
||||||
|
.defaultsTo(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CompositeOptionSet options = new CompositeOptionSet();
|
CompositeOptionSet options = new CompositeOptionSet();
|
||||||
|
|
||||||
|
@ -686,6 +695,7 @@ public class Config {
|
||||||
this.preventPeriodicShutdownAtSeedNode = options.valueOf(preventPeriodicShutdownAtSeedNodeOpt);
|
this.preventPeriodicShutdownAtSeedNode = options.valueOf(preventPeriodicShutdownAtSeedNodeOpt);
|
||||||
this.republishMailboxEntries = options.valueOf(republishMailboxEntriesOpt);
|
this.republishMailboxEntries = options.valueOf(republishMailboxEntriesOpt);
|
||||||
this.bypassMempoolValidation = options.valueOf(bypassMempoolValidationOpt);
|
this.bypassMempoolValidation = options.valueOf(bypassMempoolValidationOpt);
|
||||||
|
this.passwordRequired = options.valueOf(passwordRequiredOpt);
|
||||||
} catch (OptionException ex) {
|
} catch (OptionException ex) {
|
||||||
throw new ConfigException("problem parsing option '%s': %s",
|
throw new ConfigException("problem parsing option '%s': %s",
|
||||||
ex.options().get(0),
|
ex.options().get(0),
|
||||||
|
|
|
@ -54,11 +54,13 @@ public class Encryption {
|
||||||
public static final String ASYM_KEY_ALGO = "RSA";
|
public static final String ASYM_KEY_ALGO = "RSA";
|
||||||
private static final String ASYM_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1PADDING";
|
private static final String ASYM_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1PADDING";
|
||||||
|
|
||||||
private static final String SYM_KEY_ALGO = "AES";
|
public static final String SYM_KEY_ALGO = "AES";
|
||||||
private static final String SYM_CIPHER = "AES";
|
private static final String SYM_CIPHER = "AES";
|
||||||
|
|
||||||
private static final String HMAC = "HmacSHA256";
|
private static final String HMAC = "HmacSHA256";
|
||||||
|
|
||||||
|
public static final String HMAC_ERROR_MSG = "Hmac does not match.";
|
||||||
|
|
||||||
public static KeyPair generateKeyPair() {
|
public static KeyPair generateKeyPair() {
|
||||||
long ts = System.currentTimeMillis();
|
long ts = System.currentTimeMillis();
|
||||||
try {
|
try {
|
||||||
|
@ -101,11 +103,6 @@ public class Encryption {
|
||||||
return new SecretKeySpec(secretKeyBytes, 0, secretKeyBytes.length, SYM_KEY_ALGO);
|
return new SecretKeySpec(secretKeyBytes, 0, secretKeyBytes.length, SYM_KEY_ALGO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] getSecretKeyBytes(SecretKey secretKey) {
|
|
||||||
return secretKey.getEncoded();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Hmac
|
// Hmac
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -179,7 +176,7 @@ public class Encryption {
|
||||||
if (verifyHmac(Hex.decode(payloadAsHex), Hex.decode(hmacAsHex), secretKey)) {
|
if (verifyHmac(Hex.decode(payloadAsHex), Hex.decode(hmacAsHex), secretKey)) {
|
||||||
return Hex.decode(payloadAsHex);
|
return Hex.decode(payloadAsHex);
|
||||||
} else {
|
} else {
|
||||||
throw new CryptoException("Hmac does not match.");
|
throw new CryptoException(HMAC_ERROR_MSG);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,27 +26,93 @@ import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Singleton
|
@Singleton
|
||||||
public final class KeyRing {
|
public final class KeyRing {
|
||||||
private final KeyPair signatureKeyPair;
|
|
||||||
private final KeyPair encryptionKeyPair;
|
|
||||||
private final PubKeyRing pubKeyRing;
|
|
||||||
|
|
||||||
|
private final KeyStorage keyStorage;
|
||||||
|
|
||||||
|
private KeyPair signatureKeyPair;
|
||||||
|
private KeyPair encryptionKeyPair;
|
||||||
|
private PubKeyRing pubKeyRing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the KeyRing. Unlocks if not encrypted. Does not generate keys.
|
||||||
|
*
|
||||||
|
* @param keyStorage Persisted storage
|
||||||
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public KeyRing(KeyStorage keyStorage) {
|
public KeyRing(KeyStorage keyStorage) {
|
||||||
if (keyStorage.allKeyFilesExist()) {
|
this(keyStorage, null, false);
|
||||||
signatureKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_SIGNATURE);
|
}
|
||||||
encryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_ENCRYPTION);
|
|
||||||
} else {
|
/**
|
||||||
// First time we create key pairs
|
* Creates KeyRing with a password. Attempts to generate keys if they don't exist.
|
||||||
signatureKeyPair = Sig.generateKeyPair();
|
*
|
||||||
encryptionKeyPair = Encryption.generateKeyPair();
|
* @param keyStorage Persisted storage
|
||||||
keyStorage.saveKeyRing(this);
|
* @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());
|
pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic());
|
||||||
|
keyStorage.saveKeyRing(this, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't print keys for security reasons
|
// Don't print keys for security reasons
|
||||||
|
|
|
@ -20,11 +20,19 @@ package bisq.common.crypto;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.file.FileUtil;
|
import bisq.common.file.FileUtil;
|
||||||
|
|
||||||
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -39,6 +47,9 @@ import java.security.spec.KeySpec;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.spec.RSAPublicKeySpec;
|
import java.security.spec.RSAPublicKeySpec;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -46,6 +57,7 @@ import java.io.IOException;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -58,6 +70,11 @@ import static bisq.common.util.Preconditions.checkDir;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class KeyStorage {
|
public class KeyStorage {
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);
|
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);
|
||||||
|
private static final int SALT_LENGTH = 20;
|
||||||
|
|
||||||
|
private static final byte[] ENCRYPTED_FORMAT_MAGIC = "HVNENC".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final int ENCRYPTED_FORMAT_VERSION = 1;
|
||||||
|
private static final int ENCRYPTED_FORMAT_LENGTH = 4*2; // version,salt
|
||||||
|
|
||||||
public enum KeyEntry {
|
public enum KeyEntry {
|
||||||
MSG_SIGNATURE("sig", Sig.KEY_ALGO),
|
MSG_SIGNATURE("sig", Sig.KEY_ALGO),
|
||||||
|
@ -104,7 +121,7 @@ public class KeyStorage {
|
||||||
return new File(storageDir + "/" + keyEntry.getFileName() + ".key").exists();
|
return new File(storageDir + "/" + keyEntry.getFileName() + ".key").exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyPair loadKeyPair(KeyEntry keyEntry) {
|
public KeyPair loadKeyPair(KeyEntry keyEntry, String password) throws IncorrectPasswordException {
|
||||||
FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20);
|
FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20);
|
||||||
// long now = System.currentTimeMillis();
|
// long now = System.currentTimeMillis();
|
||||||
try {
|
try {
|
||||||
|
@ -118,9 +135,53 @@ public class KeyStorage {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
fis.read(encodedPrivateKey);
|
fis.read(encodedPrivateKey);
|
||||||
|
|
||||||
|
// Read magic bytes
|
||||||
|
byte[] magicBytes = Arrays.copyOfRange(encodedPrivateKey, 0, ENCRYPTED_FORMAT_MAGIC.length);
|
||||||
|
boolean isEncryptedPassword = Arrays.compare(magicBytes, ENCRYPTED_FORMAT_MAGIC) == 0;
|
||||||
|
if (isEncryptedPassword && password == null) {
|
||||||
|
throw new IncorrectPasswordException("Cannot load encrypted keys, user must open account with password " + filePrivateKey);
|
||||||
|
} else if (password != null && !isEncryptedPassword) {
|
||||||
|
log.warn("Password not needed for unencrypted key " + filePrivateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt using password
|
||||||
|
if (password != null) {
|
||||||
|
int position = ENCRYPTED_FORMAT_MAGIC.length;
|
||||||
|
|
||||||
|
// Read remaining header
|
||||||
|
ByteBuffer buf = ByteBuffer.wrap(encodedPrivateKey, position, ENCRYPTED_FORMAT_LENGTH);
|
||||||
|
position += ENCRYPTED_FORMAT_LENGTH;
|
||||||
|
int version = buf.getInt();
|
||||||
|
if (version != 1) throw new RuntimeException("Unable to parse encrypted keys");
|
||||||
|
int saltLength = buf.getInt();
|
||||||
|
|
||||||
|
// Read salt
|
||||||
|
byte[] salt = Arrays.copyOfRange(encodedPrivateKey, position, position + saltLength);
|
||||||
|
position += saltLength;
|
||||||
|
|
||||||
|
// Payload key derived from password
|
||||||
|
KeyCrypterScrypt crypter = ScryptUtil.getKeyCrypterScrypt(salt);
|
||||||
|
KeyParameter pwKey = ScryptUtil.deriveKeyWithScrypt(crypter, password);
|
||||||
|
SecretKey secretKey = new SecretKeySpec(pwKey.getKey(), Encryption.SYM_KEY_ALGO);
|
||||||
|
byte[] encryptedPayload = Arrays.copyOfRange(encodedPrivateKey, position, encodedPrivateKey.length);
|
||||||
|
|
||||||
|
// Decrypt key, handling exceptions caused by an incorrect password key
|
||||||
|
try {
|
||||||
|
encodedPrivateKey = Encryption.decryptPayloadWithHmac(encryptedPayload, secretKey);
|
||||||
|
} catch (CryptoException ce) {
|
||||||
|
// Most of the time (probably of slightly less than 255/256, around 99.61%) a bad password
|
||||||
|
// will result in BadPaddingException before HMAC check.
|
||||||
|
// See https://stackoverflow.com/questions/8049872/given-final-block-not-properly-padded
|
||||||
|
if (ce.getCause() instanceof BadPaddingException || ce.getMessage() == Encryption.HMAC_ERROR_MSG)
|
||||||
|
throw new IncorrectPasswordException("Incorrect password");
|
||||||
|
else
|
||||||
|
throw ce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
|
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
|
||||||
privateKey = keyFactory.generatePrivate(privateKeySpec);
|
privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||||
} catch (InvalidKeySpecException | IOException e) {
|
} catch (InvalidKeySpecException | IOException | CryptoException e) {
|
||||||
log.error("Could not load key " + keyEntry.toString(), e.getMessage());
|
log.error("Could not load key " + keyEntry.toString(), e.getMessage());
|
||||||
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
|
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
|
||||||
}
|
}
|
||||||
|
@ -150,20 +211,44 @@ public class KeyStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveKeyRing(KeyRing keyRing) {
|
public void saveKeyRing(KeyRing keyRing, String password) {
|
||||||
savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName());
|
savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName(), password);
|
||||||
savePrivateKey(keyRing.getEncryptionKeyPair().getPrivate(), KeyEntry.MSG_ENCRYPTION.getFileName());
|
savePrivateKey(keyRing.getEncryptionKeyPair().getPrivate(), KeyEntry.MSG_ENCRYPTION.getFileName(), password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void savePrivateKey(PrivateKey privateKey, String name) {
|
private void savePrivateKey(PrivateKey privateKey, String name, String password) {
|
||||||
if (!storageDir.exists())
|
if (!storageDir.exists())
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
storageDir.mkdir();
|
storageDir.mkdirs();
|
||||||
|
|
||||||
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
|
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
|
||||||
try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) {
|
try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) {
|
||||||
fos.write(pkcs8EncodedKeySpec.getEncoded());
|
byte[] keyBytes = pkcs8EncodedKeySpec.getEncoded();
|
||||||
} catch (IOException e) {
|
// Encrypt
|
||||||
|
if (password != null) {
|
||||||
|
// Magic
|
||||||
|
fos.write(ENCRYPTED_FORMAT_MAGIC);
|
||||||
|
|
||||||
|
// Version, salt length
|
||||||
|
ByteBuffer header = ByteBuffer.allocate(ENCRYPTED_FORMAT_LENGTH);
|
||||||
|
header.putInt(ENCRYPTED_FORMAT_VERSION);
|
||||||
|
header.putInt(SALT_LENGTH);
|
||||||
|
fos.write(header.array());
|
||||||
|
|
||||||
|
// Salt value
|
||||||
|
byte[] salt = CryptoUtils.getRandomBytes(SALT_LENGTH);
|
||||||
|
fos.write(salt);
|
||||||
|
|
||||||
|
// Generate secret from password key and salt
|
||||||
|
KeyCrypterScrypt crypter = ScryptUtil.getKeyCrypterScrypt(salt);
|
||||||
|
KeyParameter pwKey = ScryptUtil.deriveKeyWithScrypt(crypter, password);
|
||||||
|
SecretKey secretKey = new SecretKeySpec(pwKey.getKey(), Encryption.SYM_KEY_ALGO);
|
||||||
|
|
||||||
|
// Encrypt payload
|
||||||
|
keyBytes = Encryption.encryptPayloadWithHmac(keyBytes, secretKey);
|
||||||
|
}
|
||||||
|
fos.write(keyBytes);
|
||||||
|
} catch (Exception e) {
|
||||||
log.error("Could not save key " + name, e);
|
log.error("Could not save key " + name, e);
|
||||||
throw new RuntimeException("Could not save key " + name, e);
|
throw new RuntimeException("Could not save key " + name, e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,22 @@ package bisq.common.crypto;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows User's static PubKeyRing to be injected into constructors without having to
|
||||||
|
* open the account yet. Once its opened, PubKeyRingProvider will return non-null PubKeyRing.
|
||||||
|
* Originally used via bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
||||||
|
*/
|
||||||
public class PubKeyRingProvider implements Provider<PubKeyRing> {
|
public class PubKeyRingProvider implements Provider<PubKeyRing> {
|
||||||
|
|
||||||
private final PubKeyRing pubKeyRing;
|
private final KeyRing keyRing;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PubKeyRingProvider(KeyRing keyRing) {
|
public PubKeyRingProvider(KeyRing keyRing) {
|
||||||
pubKeyRing = keyRing.getPubKeyRing();
|
this.keyRing = keyRing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PubKeyRing get() {
|
public PubKeyRing get() {
|
||||||
return pubKeyRing;
|
return keyRing.getPubKeyRing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package bisq.core.crypto;
|
package bisq.common.crypto;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.util.Utilities;
|
import bisq.common.util.Utilities;
|
||||||
|
@ -51,15 +51,26 @@ public class ScryptUtil {
|
||||||
.build();
|
.build();
|
||||||
return new KeyCrypterScrypt(scryptParameters);
|
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) {
|
public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, DeriveKeyResultHandler resultHandler) {
|
||||||
Utilities.getThreadPoolExecutor("ScryptUtil:deriveKeyWithScrypt-%d", 1, 2, 5L).submit(() -> {
|
Utilities.getThreadPoolExecutor("ScryptUtil:deriveKeyWithScrypt-%d", 1, 2, 5L).submit(() -> {
|
||||||
try {
|
try {
|
||||||
log.debug("Doing key derivation");
|
KeyParameter aesKey = deriveKeyWithScrypt(keyCrypterScrypt, password);
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
|
|
||||||
long duration = System.currentTimeMillis() - start;
|
|
||||||
log.debug("Key derivation took {} msec", duration);
|
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
try {
|
try {
|
||||||
resultHandler.handleResult(aesKey);
|
resultHandler.handleResult(aesKey);
|
|
@ -114,7 +114,6 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// We don't know from which thread we are called so we map to user thread
|
// We don't know from which thread we are called so we map to user thread
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
if (doShutdown) {
|
if (doShutdown) {
|
||||||
|
@ -382,6 +381,11 @@ public class PersistenceManager<T extends PersistableEnvelope> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!initCalled.get()) {
|
||||||
|
log.warn("requestPersistence() called before init. Ignoring request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
persistenceRequested = true;
|
persistenceRequested = true;
|
||||||
|
|
||||||
// If we have not initialized yet we postpone the start of the timer and call maybeStartTimerForPersistence at
|
// If we have not initialized yet we postpone the start of the timer and call maybeStartTimerForPersistence at
|
||||||
|
|
128
common/src/main/java/bisq/common/util/ZipUtils.java
Normal file
128
common/src/main/java/bisq/common/util/ZipUtils.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
core/src/main/java/bisq/core/api/AccountServiceListener.java
Normal file
14
core/src/main/java/bisq/core/api/AccountServiceListener.java
Normal 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) {}
|
||||||
|
}
|
|
@ -1,50 +1,163 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package bisq.core.api;
|
package bisq.core.api;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import bisq.common.config.Config;
|
||||||
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
|
import bisq.common.crypto.KeyRing;
|
||||||
|
import bisq.common.crypto.KeyStorage;
|
||||||
|
import bisq.common.file.FileUtil;
|
||||||
|
import bisq.common.persistence.PersistenceManager;
|
||||||
|
import bisq.common.util.ZipUtils;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PipedInputStream;
|
||||||
|
import java.io.PipedOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.function.Consumer;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Should be replaced by actual implementation once it is available
|
* Manages the account state. A created account must have a password which encrypts
|
||||||
|
* all persistence in the PersistenceManager. As a result, opening the account requires
|
||||||
|
* a correct password to be passed in to deserialize the account properties that are
|
||||||
|
* persisted. It is possible to persist the objects without a password (legacy).
|
||||||
|
*
|
||||||
|
* Backup and restore flushes the persistence objects in the app folder and sends or
|
||||||
|
* restores a zip stream.
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
@Deprecated
|
@Slf4j
|
||||||
public class CoreAccountService {
|
public class CoreAccountService {
|
||||||
|
|
||||||
|
private final Config config;
|
||||||
private static final String DEFAULT_PASSWORD = "abctesting123";
|
private final KeyStorage keyStorage;
|
||||||
|
private final KeyRing keyRing;
|
||||||
private String password = DEFAULT_PASSWORD;
|
|
||||||
|
@Getter
|
||||||
private final List<PasswordChangeListener> listeners = new CopyOnWriteArrayList<>();
|
private String password;
|
||||||
|
private List<AccountServiceListener> listeners = new ArrayList<AccountServiceListener>();
|
||||||
|
|
||||||
public String getPassword() {
|
@Inject
|
||||||
return password;
|
public CoreAccountService(Config config,
|
||||||
|
KeyStorage keyStorage,
|
||||||
|
KeyRing keyRing) {
|
||||||
|
this.config = config;
|
||||||
|
this.keyStorage = keyStorage;
|
||||||
|
this.keyRing = keyRing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String newPassword) {
|
public void addListener(AccountServiceListener listener) {
|
||||||
String oldPassword = password;
|
|
||||||
password = newPassword;
|
|
||||||
notifyListenerAboutPasswordChange(oldPassword, newPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addPasswordChangeListener(PasswordChangeListener listener) {
|
|
||||||
Objects.requireNonNull(listener, "listener");
|
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyListenerAboutPasswordChange(String oldPassword, String newPassword) {
|
public boolean removeListener(AccountServiceListener listener) {
|
||||||
for (PasswordChangeListener listener : listeners) {
|
return listeners.remove(listener);
|
||||||
listener.onPasswordChange(oldPassword, newPassword);
|
}
|
||||||
|
|
||||||
|
public boolean accountExists() {
|
||||||
|
return keyStorage.allKeyFilesExist(); // public and private key pair indicate the existence of the account
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccountOpen() {
|
||||||
|
return keyRing.isUnlocked() && accountExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAccountOpen() {
|
||||||
|
checkState(isAccountOpen(), "Account not open");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAccount(String password) {
|
||||||
|
if (accountExists()) throw new IllegalStateException("Cannot create account if account already exists");
|
||||||
|
keyRing.generateKeys(password);
|
||||||
|
this.password = password;
|
||||||
|
for (AccountServiceListener listener : listeners) listener.onAccountCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openAccount(String password) throws IncorrectPasswordException {
|
||||||
|
if (!accountExists()) throw new IllegalStateException("Cannot open account if account does not exist");
|
||||||
|
if (keyRing.unlockKeys(password, false)) {
|
||||||
|
this.password = password;
|
||||||
|
for (AccountServiceListener listener : listeners) listener.onAccountOpened();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("keyRing.unlockKeys() returned false, that should never happen");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public 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 {
|
// flush all known persistence objects to disk
|
||||||
|
PersistenceManager.flushAllDataToDiskAtBackup(() -> {
|
||||||
void onPasswordChange(String oldPassword, String newPassword);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import bisq.core.api.model.AddressBalanceInfo;
|
||||||
import bisq.core.api.model.BalancesInfo;
|
import bisq.core.api.model.BalancesInfo;
|
||||||
import bisq.core.api.model.MarketPriceInfo;
|
import bisq.core.api.model.MarketPriceInfo;
|
||||||
import bisq.core.api.model.TxFeeRateInfo;
|
import bisq.core.api.model.TxFeeRateInfo;
|
||||||
|
import bisq.core.app.AppStartupState;
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
|
@ -33,6 +34,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
|
@ -46,6 +48,8 @@ import javax.inject.Singleton;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -58,7 +62,6 @@ import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.common.MoneroRpcConnection;
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.wallet.model.MoneroDestination;
|
import monero.wallet.model.MoneroDestination;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
@ -73,6 +76,8 @@ public class CoreApi {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final Config config;
|
private final Config config;
|
||||||
|
private final AppStartupState appStartupState;
|
||||||
|
private final CoreAccountService coreAccountService;
|
||||||
private final CoreDisputeAgentsService coreDisputeAgentsService;
|
private final CoreDisputeAgentsService coreDisputeAgentsService;
|
||||||
private final CoreHelpService coreHelpService;
|
private final CoreHelpService coreHelpService;
|
||||||
private final CoreOffersService coreOffersService;
|
private final CoreOffersService coreOffersService;
|
||||||
|
@ -86,6 +91,8 @@ public class CoreApi {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoreApi(Config config,
|
public CoreApi(Config config,
|
||||||
|
AppStartupState appStartupState,
|
||||||
|
CoreAccountService coreAccountService,
|
||||||
CoreDisputeAgentsService coreDisputeAgentsService,
|
CoreDisputeAgentsService coreDisputeAgentsService,
|
||||||
CoreHelpService coreHelpService,
|
CoreHelpService coreHelpService,
|
||||||
CoreOffersService coreOffersService,
|
CoreOffersService coreOffersService,
|
||||||
|
@ -97,6 +104,8 @@ public class CoreApi {
|
||||||
CoreNotificationService notificationService,
|
CoreNotificationService notificationService,
|
||||||
CoreMoneroConnectionsService coreMoneroConnectionsService) {
|
CoreMoneroConnectionsService coreMoneroConnectionsService) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
this.appStartupState = appStartupState;
|
||||||
|
this.coreAccountService = coreAccountService;
|
||||||
this.coreDisputeAgentsService = coreDisputeAgentsService;
|
this.coreDisputeAgentsService = coreDisputeAgentsService;
|
||||||
this.coreHelpService = coreHelpService;
|
this.coreHelpService = coreHelpService;
|
||||||
this.coreOffersService = coreOffersService;
|
this.coreOffersService = coreOffersService;
|
||||||
|
@ -115,11 +124,197 @@ public class CoreApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Dispute Agents
|
// Help
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
public String getMethodHelp(String methodName) {
|
||||||
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
|
return coreHelpService.getMethodHelp(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Account Service
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public boolean accountExists() {
|
||||||
|
return coreAccountService.accountExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAccountOpen() {
|
||||||
|
return coreAccountService.isAccountOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAccount(String password) {
|
||||||
|
coreAccountService.createAccount(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openAccount(String password) throws IncorrectPasswordException {
|
||||||
|
coreAccountService.openAccount(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAppInitialized() {
|
||||||
|
return appStartupState.isApplicationFullyInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePassword(String password) {
|
||||||
|
coreAccountService.changePassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeAccount() {
|
||||||
|
coreAccountService.closeAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteAccount(Runnable onShutdown) {
|
||||||
|
coreAccountService.deleteAccount(onShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
|
||||||
|
coreAccountService.backupAccount(bufferSize, consume, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restoreAccount(InputStream zipStream, int bufferSize, Runnable onShutdown) throws Exception {
|
||||||
|
coreAccountService.restoreAccount(zipStream, bufferSize, onShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Monero Connections
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void addMoneroConnection(MoneroRpcConnection connection) {
|
||||||
|
coreMoneroConnectionsService.addConnection(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeMoneroConnection(String connectionUri) {
|
||||||
|
coreMoneroConnectionsService.removeConnection(connectionUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroRpcConnection getMoneroConnection() {
|
||||||
|
return coreMoneroConnectionsService.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MoneroRpcConnection> getMoneroConnections() {
|
||||||
|
return coreMoneroConnectionsService.getConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMoneroConnection(String connectionUri) {
|
||||||
|
coreMoneroConnectionsService.setConnection(connectionUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMoneroConnection(MoneroRpcConnection connection) {
|
||||||
|
coreMoneroConnectionsService.setConnection(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroRpcConnection checkMoneroConnection() {
|
||||||
|
return coreMoneroConnectionsService.checkConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MoneroRpcConnection> checkMoneroConnections() {
|
||||||
|
return coreMoneroConnectionsService.checkConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startCheckingMoneroConnection(Long refreshPeriod) {
|
||||||
|
coreMoneroConnectionsService.startCheckingConnection(refreshPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopCheckingMoneroConnection() {
|
||||||
|
coreMoneroConnectionsService.stopCheckingConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroRpcConnection getBestAvailableMoneroConnection() {
|
||||||
|
return coreMoneroConnectionsService.getBestAvailableConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
|
||||||
|
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Wallets
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public BalancesInfo getBalances(String currencyCode) {
|
||||||
|
return walletsService.getBalances(currencyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewDepositSubaddress() {
|
||||||
|
return walletsService.getNewDepositSubaddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MoneroTxWallet> getXmrTxs() {
|
||||||
|
return walletsService.getXmrTxs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
||||||
|
return walletsService.createXmrTx(destinations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String relayXmrTx(String metadata) {
|
||||||
|
return walletsService.relayXmrTx(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAddressBalance(String addressString) {
|
||||||
|
return walletsService.getAddressBalance(addressString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
|
||||||
|
return walletsService.getAddressBalanceInfo(addressString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AddressBalanceInfo> getFundingAddresses() {
|
||||||
|
return walletsService.getFundingAddresses();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendBtc(String address,
|
||||||
|
String amount,
|
||||||
|
String txFeeRate,
|
||||||
|
String memo,
|
||||||
|
FutureCallback<Transaction> callback) {
|
||||||
|
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void getTxFeeRate(ResultHandler resultHandler) {
|
||||||
|
walletsService.getTxFeeRate(resultHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTxFeeRatePreference(long txFeeRate,
|
||||||
|
ResultHandler resultHandler) {
|
||||||
|
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
|
||||||
|
walletsService.unsetTxFeeRatePreference(resultHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
|
||||||
|
return walletsService.getMostRecentTxFeeRateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transaction getTransaction(String txId) {
|
||||||
|
return walletsService.getTransaction(txId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWalletPassword(String password, String newPassword) {
|
||||||
|
walletsService.setWalletPassword(password, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void lockWallet() {
|
||||||
|
walletsService.lockWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unlockWallet(String password, long timeout) {
|
||||||
|
walletsService.unlockWallet(password, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeWalletPassword(String password) {
|
||||||
|
walletsService.removeWalletPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TradeStatistics3> getTradeStatistics() {
|
||||||
|
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
|
||||||
|
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -139,11 +334,11 @@ public class CoreApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Help
|
// Dispute Agents
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public String getMethodHelp(String methodName) {
|
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
|
||||||
return coreHelpService.getMethodHelp(methodName);
|
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -312,146 +507,4 @@ public class CoreApi {
|
||||||
public String getTradeRole(String tradeId) {
|
public String getTradeRole(String tradeId) {
|
||||||
return coreTradesService.getTradeRole(tradeId);
|
return coreTradesService.getTradeRole(tradeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Wallets
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public BalancesInfo getBalances(String currencyCode) {
|
|
||||||
return walletsService.getBalances(currencyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNewDepositSubaddress() {
|
|
||||||
return walletsService.getNewDepositSubaddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MoneroTxWallet> getXmrTxs() {
|
|
||||||
return walletsService.getXmrTxs();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
|
||||||
return walletsService.createXmrTx(destinations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String relayXmrTx(String metadata) {
|
|
||||||
return walletsService.relayXmrTx(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAddressBalance(String addressString) {
|
|
||||||
return walletsService.getAddressBalance(addressString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
|
|
||||||
return walletsService.getAddressBalanceInfo(addressString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AddressBalanceInfo> getFundingAddresses() {
|
|
||||||
return walletsService.getFundingAddresses();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendBtc(String address,
|
|
||||||
String amount,
|
|
||||||
String txFeeRate,
|
|
||||||
String memo,
|
|
||||||
FutureCallback<Transaction> callback) {
|
|
||||||
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void getTxFeeRate(ResultHandler resultHandler) {
|
|
||||||
walletsService.getTxFeeRate(resultHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTxFeeRatePreference(long txFeeRate,
|
|
||||||
ResultHandler resultHandler) {
|
|
||||||
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
|
|
||||||
walletsService.unsetTxFeeRatePreference(resultHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
|
|
||||||
return walletsService.getMostRecentTxFeeRateInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transaction getTransaction(String txId) {
|
|
||||||
return walletsService.getTransaction(txId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWalletPassword(String password, String newPassword) {
|
|
||||||
walletsService.setWalletPassword(password, newPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void lockWallet() {
|
|
||||||
walletsService.lockWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unlockWallet(String password, long timeout) {
|
|
||||||
walletsService.unlockWallet(password, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeWalletPassword(String password) {
|
|
||||||
walletsService.removeWalletPassword(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TradeStatistics3> getTradeStatistics() {
|
|
||||||
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
|
|
||||||
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Monero Connections
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public void addMoneroConnection(MoneroRpcConnection connection) {
|
|
||||||
coreMoneroConnectionsService.addConnection(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeMoneroConnection(String connectionUri) {
|
|
||||||
coreMoneroConnectionsService.removeConnection(connectionUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroRpcConnection getMoneroConnection() {
|
|
||||||
return coreMoneroConnectionsService.getConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MoneroRpcConnection> getMoneroConnections() {
|
|
||||||
return coreMoneroConnectionsService.getConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMoneroConnection(String connectionUri) {
|
|
||||||
coreMoneroConnectionsService.setConnection(connectionUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMoneroConnection(MoneroRpcConnection connection) {
|
|
||||||
coreMoneroConnectionsService.setConnection(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroRpcConnection checkMoneroConnection() {
|
|
||||||
return coreMoneroConnectionsService.checkConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MoneroRpcConnection> checkMoneroConnections() {
|
|
||||||
return coreMoneroConnectionsService.checkConnections();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startCheckingMoneroConnection(Long refreshPeriod) {
|
|
||||||
coreMoneroConnectionsService.startCheckingConnection(refreshPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopCheckingMoneroConnection() {
|
|
||||||
coreMoneroConnectionsService.stopCheckingConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroRpcConnection getBestAvailableMoneroConnection() {
|
|
||||||
return coreMoneroConnectionsService.getBestAvailableConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
|
|
||||||
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,38 @@
|
||||||
package bisq.core.api;
|
package bisq.core.api;
|
||||||
|
|
||||||
|
import bisq.common.UserThread;
|
||||||
import bisq.core.btc.model.EncryptedConnectionList;
|
import bisq.core.btc.model.EncryptedConnectionList;
|
||||||
|
import bisq.core.btc.setup.DownloadListener;
|
||||||
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.LongProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.common.MoneroConnectionManager;
|
import monero.common.MoneroConnectionManager;
|
||||||
import monero.common.MoneroConnectionManagerListener;
|
import monero.common.MoneroConnectionManagerListener;
|
||||||
import monero.common.MoneroRpcConnection;
|
import monero.common.MoneroRpcConnection;
|
||||||
|
import monero.daemon.MoneroDaemon;
|
||||||
|
import monero.daemon.MoneroDaemonRpc;
|
||||||
|
import monero.daemon.model.MoneroPeer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Singleton
|
@Singleton
|
||||||
public class CoreMoneroConnectionsService {
|
public final class CoreMoneroConnectionsService {
|
||||||
|
|
||||||
// TODO: this connection manager should update app status, don't poll in WalletsSetup every 30 seconds
|
private static final int MIN_BROADCAST_CONNECTIONS = 0; // TODO: 0 for stagenet, 5+ for mainnet
|
||||||
private static final long DEFAULT_REFRESH_PERIOD = 15_000L; // check the connection every 15 seconds per default
|
private static final long DAEMON_REFRESH_PERIOD_MS = 15000L; // check connection periodically in ms
|
||||||
|
private static final long DAEMON_INFO_POLL_PERIOD_MS = 20000L; // collect daemon info periodically in ms
|
||||||
|
|
||||||
// TODO (woodser): support each network type, move to config, remove localhost authentication
|
// TODO (woodser): support each network type, move to config, remove localhost authentication
|
||||||
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
|
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
|
||||||
|
@ -24,21 +41,222 @@ public class CoreMoneroConnectionsService {
|
||||||
);
|
);
|
||||||
|
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
private final CoreAccountService accountService;
|
||||||
private final MoneroConnectionManager connectionManager;
|
private final MoneroConnectionManager connectionManager;
|
||||||
private final EncryptedConnectionList connectionList;
|
private final EncryptedConnectionList connectionList;
|
||||||
|
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
|
||||||
|
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
|
||||||
|
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||||
|
private final DownloadListener downloadListener = new DownloadListener();
|
||||||
|
|
||||||
|
private MoneroDaemon daemon;
|
||||||
|
private boolean isInitialized = false;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public CoreMoneroConnectionsService(MoneroConnectionManager connectionManager,
|
public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
|
||||||
|
CoreAccountService accountService,
|
||||||
|
MoneroConnectionManager connectionManager,
|
||||||
EncryptedConnectionList connectionList) {
|
EncryptedConnectionList connectionList) {
|
||||||
|
this.accountService = accountService;
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
this.connectionList = connectionList;
|
this.connectionList = connectionList;
|
||||||
|
|
||||||
|
// initialize after account open and basic setup
|
||||||
|
walletsSetup.addSetupTaskHandler(() -> { // TODO: use something better than legacy WalletSetup for notification to initialize
|
||||||
|
|
||||||
|
// initialize from connections read from disk
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
// listen for account to be opened or password changed
|
||||||
|
accountService.addListener(new AccountServiceListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccountOpened() {
|
||||||
|
try {
|
||||||
|
log.info(getClass() + ".onAccountOpened() called");
|
||||||
|
initialize();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.addListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addConnection(MoneroRpcConnection connection) {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionList.addConnection(connection);
|
||||||
|
connectionManager.addConnection(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeConnection(String uri) {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionList.removeConnection(uri);
|
||||||
|
connectionManager.removeConnection(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroRpcConnection getConnection() {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
return connectionManager.getConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MoneroRpcConnection> getConnections() {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
return connectionManager.getConnections();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnection(String connectionUri) {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.setConnection(connectionUri); // listener will update connection list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnection(MoneroRpcConnection connection) {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.setConnection(connection); // listener will update connection list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroRpcConnection checkConnection() {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.checkConnection();
|
||||||
|
return getConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MoneroRpcConnection> checkConnections() {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.checkConnections();
|
||||||
|
return getConnections();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startCheckingConnection(Long refreshPeriod) {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.startCheckingConnection(refreshPeriod == null ? DAEMON_REFRESH_PERIOD_MS : refreshPeriod);
|
||||||
|
connectionList.setRefreshPeriod(refreshPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopCheckingConnection() {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.stopCheckingConnection();
|
||||||
|
connectionList.setRefreshPeriod(-1L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MoneroRpcConnection getBestAvailableConnection() {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
return connectionManager.getBestAvailableConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoSwitch(boolean autoSwitch) {
|
||||||
|
synchronized (lock) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
|
connectionManager.setAutoSwitch(autoSwitch);
|
||||||
|
connectionList.setAutoSwitch(autoSwitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------- APP METHODS ------------------------------
|
||||||
|
|
||||||
|
public boolean isChainHeightSyncedWithinTolerance() {
|
||||||
|
if (daemon == null) return false;
|
||||||
|
Long targetHeight = daemon.getSyncInfo().getTargetHeight();
|
||||||
|
if (targetHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
|
||||||
|
long currentHeight = chainHeight.get();
|
||||||
|
if (Math.abs(targetHeight - currentHeight) <= 3) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), targetHeight);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyIntegerProperty numPeersProperty() {
|
||||||
|
return numPeers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSufficientPeersForBroadcast() {
|
||||||
|
return numPeers.get() >= getMinBroadcastConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongProperty chainHeightProperty() {
|
||||||
|
return chainHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyDoubleProperty downloadPercentageProperty() {
|
||||||
|
return downloadListener.percentageProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinBroadcastConnections() {
|
||||||
|
return MIN_BROADCAST_CONNECTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDownloadComplete() {
|
||||||
|
return downloadPercentageProperty().get() == 1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that both the daemon and wallet have synced.
|
||||||
|
*
|
||||||
|
* TODO: separate daemon and wallet download/done listeners
|
||||||
|
*/
|
||||||
|
public void doneDownload() {
|
||||||
|
downloadListener.doneDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------- HELPERS --------------------------------
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
synchronized (lock) {
|
||||||
|
|
||||||
|
// reset connection manager's connections and listeners
|
||||||
|
connectionManager.reset();
|
||||||
|
|
||||||
// load connections
|
// load connections
|
||||||
connectionList.getConnections().forEach(connectionManager::addConnection);
|
connectionList.getConnections().forEach(connectionManager::addConnection);
|
||||||
|
log.info("Read " + connectionList.getConnections().size() + " connections from disk");
|
||||||
|
|
||||||
// add default connections
|
// add default connections
|
||||||
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) {
|
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) {
|
||||||
|
@ -50,24 +268,38 @@ public class CoreMoneroConnectionsService {
|
||||||
connectionList.getCurrentConnectionUri().ifPresentOrElse(connectionManager::setConnection, () -> {
|
connectionList.getCurrentConnectionUri().ifPresentOrElse(connectionManager::setConnection, () -> {
|
||||||
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
|
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// initialize daemon
|
||||||
|
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
|
||||||
|
updateDaemonInfo();
|
||||||
|
|
||||||
// restore configuration
|
// restore configuration
|
||||||
connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
|
connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
|
||||||
long refreshPeriod = connectionList.getRefreshPeriod();
|
long refreshPeriod = connectionList.getRefreshPeriod();
|
||||||
if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod);
|
if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod);
|
||||||
else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DEFAULT_REFRESH_PERIOD);
|
else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DAEMON_REFRESH_PERIOD_MS);
|
||||||
else checkConnection();
|
else checkConnection();
|
||||||
|
|
||||||
// register connection change listener
|
// run once
|
||||||
connectionManager.addListener(this::onConnectionChanged);
|
if (!isInitialized) {
|
||||||
|
|
||||||
|
// register connection change listener
|
||||||
|
connectionManager.addListener(this::onConnectionChanged);
|
||||||
|
|
||||||
|
// poll daemon periodically
|
||||||
|
startPollingDaemon();
|
||||||
|
isInitialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (currentConnection == null) {
|
if (currentConnection == null) {
|
||||||
|
daemon = null;
|
||||||
connectionList.setCurrentConnectionUri(null);
|
connectionList.setCurrentConnectionUri(null);
|
||||||
} else {
|
} else {
|
||||||
|
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
|
||||||
connectionList.removeConnection(currentConnection.getUri());
|
connectionList.removeConnection(currentConnection.getUri());
|
||||||
connectionList.addConnection(currentConnection);
|
connectionList.addConnection(currentConnection);
|
||||||
connectionList.setCurrentConnectionUri(currentConnection.getUri());
|
connectionList.setCurrentConnectionUri(currentConnection.getUri());
|
||||||
|
@ -75,88 +307,26 @@ public class CoreMoneroConnectionsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addConnectionListener(MoneroConnectionManagerListener listener) {
|
private void startPollingDaemon() {
|
||||||
synchronized (lock) {
|
UserThread.runPeriodically(() -> {
|
||||||
connectionManager.addListener(listener);
|
updateDaemonInfo();
|
||||||
|
}, DAEMON_INFO_POLL_PERIOD_MS / 1000l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDaemonInfo() {
|
||||||
|
try {
|
||||||
|
if (daemon == null) throw new RuntimeException("No daemon connection");
|
||||||
|
peers.set(getOnlinePeers());
|
||||||
|
numPeers.set(peers.get().size());
|
||||||
|
chainHeight.set(daemon.getHeight());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Could not update daemon info: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addConnection(MoneroRpcConnection connection) {
|
private List<MoneroPeer> getOnlinePeers() {
|
||||||
synchronized (lock) {
|
return daemon.getPeers().stream()
|
||||||
connectionList.addConnection(connection);
|
.filter(peer -> peer.isOnline())
|
||||||
connectionManager.addConnection(connection);
|
.collect(Collectors.toList());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeConnection(String uri) {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionList.removeConnection(uri);
|
|
||||||
connectionManager.removeConnection(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroRpcConnection getConnection() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return connectionManager.getConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MoneroRpcConnection> getConnections() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return connectionManager.getConnections();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnection(String connectionUri) {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.setConnection(connectionUri); // listener will update connection list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnection(MoneroRpcConnection connection) {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.setConnection(connection); // listener will update connection list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroRpcConnection checkConnection() {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.checkConnection();
|
|
||||||
return getConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MoneroRpcConnection> checkConnections() {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.checkConnections();
|
|
||||||
return getConnections();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startCheckingConnection(Long refreshPeriod) {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.startCheckingConnection(refreshPeriod == null ? DEFAULT_REFRESH_PERIOD : refreshPeriod);
|
|
||||||
connectionList.setRefreshPeriod(refreshPeriod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopCheckingConnection() {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.stopCheckingConnection();
|
|
||||||
connectionList.setRefreshPeriod(-1L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroRpcConnection getBestAvailableConnection() {
|
|
||||||
synchronized (lock) {
|
|
||||||
return connectionManager.getBestAvailableConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAutoSwitch(boolean autoSwitch) {
|
|
||||||
synchronized (lock) {
|
|
||||||
connectionManager.setAutoSwitch(autoSwitch);
|
|
||||||
connectionList.setAutoSwitch(autoSwitch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
public void sendTradeNotification(Trade trade, String title, String message) {
|
||||||
sendNotification(NotificationMessage.newBuilder()
|
sendNotification(NotificationMessage.newBuilder()
|
||||||
.setType(NotificationType.TRADE_UPDATE)
|
.setType(NotificationType.TRADE_UPDATE)
|
||||||
|
|
|
@ -89,6 +89,7 @@ import monero.wallet.model.MoneroTxWallet;
|
||||||
class CoreWalletsService {
|
class CoreWalletsService {
|
||||||
|
|
||||||
private final AppStartupState appStartupState;
|
private final AppStartupState appStartupState;
|
||||||
|
private final CoreAccountService accountService;
|
||||||
private final CoreContext coreContext;
|
private final CoreContext coreContext;
|
||||||
private final Balances balances;
|
private final Balances balances;
|
||||||
private final WalletsManager walletsManager;
|
private final WalletsManager walletsManager;
|
||||||
|
@ -110,6 +111,7 @@ class CoreWalletsService {
|
||||||
@Inject
|
@Inject
|
||||||
public CoreWalletsService(AppStartupState appStartupState,
|
public CoreWalletsService(AppStartupState appStartupState,
|
||||||
CoreContext coreContext,
|
CoreContext coreContext,
|
||||||
|
CoreAccountService accountService,
|
||||||
Balances balances,
|
Balances balances,
|
||||||
WalletsManager walletsManager,
|
WalletsManager walletsManager,
|
||||||
WalletsSetup walletsSetup,
|
WalletsSetup walletsSetup,
|
||||||
|
@ -120,6 +122,7 @@ class CoreWalletsService {
|
||||||
Preferences preferences) {
|
Preferences preferences) {
|
||||||
this.appStartupState = appStartupState;
|
this.appStartupState = appStartupState;
|
||||||
this.coreContext = coreContext;
|
this.coreContext = coreContext;
|
||||||
|
this.accountService = accountService;
|
||||||
this.balances = balances;
|
this.balances = balances;
|
||||||
this.walletsManager = walletsManager;
|
this.walletsManager = walletsManager;
|
||||||
this.walletsSetup = walletsSetup;
|
this.walletsSetup = walletsSetup;
|
||||||
|
@ -141,6 +144,7 @@ class CoreWalletsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
BalancesInfo getBalances(String currencyCode) {
|
BalancesInfo getBalances(String currencyCode) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
verifyWalletCurrencyCodeIsValid(currencyCode);
|
verifyWalletCurrencyCodeIsValid(currencyCode);
|
||||||
verifyWalletsAreAvailable();
|
verifyWalletsAreAvailable();
|
||||||
verifyEncryptedWalletIsUnlocked();
|
verifyEncryptedWalletIsUnlocked();
|
||||||
|
@ -158,14 +162,17 @@ class CoreWalletsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getNewDepositSubaddress() {
|
String getNewDepositSubaddress() {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
return xmrWalletService.getWallet().createSubaddress(0).getAddress();
|
return xmrWalletService.getWallet().createSubaddress(0).getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MoneroTxWallet> getXmrTxs(){
|
List<MoneroTxWallet> getXmrTxs() {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
return xmrWalletService.getWallet().getTxs();
|
return xmrWalletService.getWallet().getTxs();
|
||||||
}
|
}
|
||||||
|
|
||||||
MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
verifyWalletsAreAvailable();
|
verifyWalletsAreAvailable();
|
||||||
verifyEncryptedWalletIsUnlocked();
|
verifyEncryptedWalletIsUnlocked();
|
||||||
try {
|
try {
|
||||||
|
@ -177,6 +184,7 @@ class CoreWalletsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String relayXmrTx(String metadata) {
|
String relayXmrTx(String metadata) {
|
||||||
|
accountService.checkAccountOpen();
|
||||||
verifyWalletsAreAvailable();
|
verifyWalletsAreAvailable();
|
||||||
verifyEncryptedWalletIsUnlocked();
|
verifyEncryptedWalletIsUnlocked();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import lombok.Value;
|
||||||
@Builder(toBuilder = true)
|
@Builder(toBuilder = true)
|
||||||
public class EncryptedConnection implements PersistablePayload {
|
public class EncryptedConnection implements PersistablePayload {
|
||||||
|
|
||||||
String uri;
|
String url;
|
||||||
String username;
|
String username;
|
||||||
byte[] encryptedPassword;
|
byte[] encryptedPassword;
|
||||||
byte[] encryptionSalt;
|
byte[] encryptionSalt;
|
||||||
|
@ -21,7 +21,7 @@ public class EncryptedConnection implements PersistablePayload {
|
||||||
@Override
|
@Override
|
||||||
public protobuf.EncryptedConnection toProtoMessage() {
|
public protobuf.EncryptedConnection toProtoMessage() {
|
||||||
return protobuf.EncryptedConnection.newBuilder()
|
return protobuf.EncryptedConnection.newBuilder()
|
||||||
.setUri(uri)
|
.setUrl(url)
|
||||||
.setUsername(username)
|
.setUsername(username)
|
||||||
.setEncryptedPassword(ByteString.copyFrom(encryptedPassword))
|
.setEncryptedPassword(ByteString.copyFrom(encryptedPassword))
|
||||||
.setEncryptionSalt(ByteString.copyFrom(encryptionSalt))
|
.setEncryptionSalt(ByteString.copyFrom(encryptionSalt))
|
||||||
|
@ -31,7 +31,7 @@ public class EncryptedConnection implements PersistablePayload {
|
||||||
|
|
||||||
public static EncryptedConnection fromProto(protobuf.EncryptedConnection encryptedConnection) {
|
public static EncryptedConnection fromProto(protobuf.EncryptedConnection encryptedConnection) {
|
||||||
return new EncryptedConnection(
|
return new EncryptedConnection(
|
||||||
encryptedConnection.getUri(),
|
encryptedConnection.getUrl(),
|
||||||
encryptedConnection.getUsername(),
|
encryptedConnection.getUsername(),
|
||||||
encryptedConnection.getEncryptedPassword().toByteArray(),
|
encryptedConnection.getEncryptedPassword().toByteArray(),
|
||||||
encryptedConnection.getEncryptionSalt().toByteArray(),
|
encryptedConnection.getEncryptionSalt().toByteArray(),
|
||||||
|
|
|
@ -17,22 +17,18 @@
|
||||||
|
|
||||||
package bisq.core.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
|
import bisq.core.api.CoreNotificationService;
|
||||||
import bisq.network.p2p.BootstrapListener;
|
import bisq.network.p2p.BootstrapListener;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We often need to wait until network and wallet is ready or other combination of startup states.
|
* We often need to wait until network and wallet is ready or other combination of startup states.
|
||||||
|
@ -53,7 +49,9 @@ public class AppStartupState {
|
||||||
private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty();
|
private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AppStartupState(WalletsSetup walletsSetup, P2PService p2PService) {
|
public AppStartupState(CoreNotificationService notificationService,
|
||||||
|
CoreMoneroConnectionsService connectionsService,
|
||||||
|
P2PService p2PService) {
|
||||||
|
|
||||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,13 +60,13 @@ public class AppStartupState {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
connectionsService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (walletsSetup.isDownloadComplete())
|
if (connectionsService.isDownloadComplete())
|
||||||
isBlockDownloadComplete.set(true);
|
isBlockDownloadComplete.set(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
connectionsService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (walletsSetup.hasSufficientPeersForBroadcast())
|
if (connectionsService.hasSufficientPeersForBroadcast())
|
||||||
hasSufficientPeersForBroadcast.set(true);
|
hasSufficientPeersForBroadcast.set(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,6 +75,7 @@ public class AppStartupState {
|
||||||
hasSufficientPeersForBroadcast,
|
hasSufficientPeersForBroadcast,
|
||||||
allDomainServicesInitialized,
|
allDomainServicesInitialized,
|
||||||
(a, b, c, d) -> {
|
(a, b, c, d) -> {
|
||||||
|
log.info("p2pNetworkAndWalletInitialized = {} = updatedDataReceived={} && isBlockDownloadComplete={} && hasSufficientPeersForBroadcast={} && allDomainServicesInitialized={}", (a && b && c && d), updatedDataReceived.get(), isBlockDownloadComplete.get(), hasSufficientPeersForBroadcast.get(), allDomainServicesInitialized.get());
|
||||||
if (a && b && c) {
|
if (a && b && c) {
|
||||||
walletAndNetworkReady.set(true);
|
walletAndNetworkReady.set(true);
|
||||||
}
|
}
|
||||||
|
@ -85,6 +84,7 @@ public class AppStartupState {
|
||||||
p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> {
|
p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
applicationFullyInitialized.set(true);
|
applicationFullyInitialized.set(true);
|
||||||
|
notificationService.sendAppInitializedNotification();
|
||||||
log.info("Application fully initialized");
|
log.info("Application fully initialized");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,8 +41,6 @@ import bisq.network.p2p.seed.SeedNodeRepository;
|
||||||
|
|
||||||
import bisq.common.app.AppModule;
|
import bisq.common.app.AppModule;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.crypto.PubKeyRingProvider;
|
|
||||||
import bisq.common.proto.network.NetworkProtoResolver;
|
import bisq.common.proto.network.NetworkProtoResolver;
|
||||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||||
|
|
||||||
|
@ -93,6 +91,5 @@ public class CoreModule extends AppModule {
|
||||||
install(new FilterModule(config));
|
install(new FilterModule(config));
|
||||||
install(new CorePresentationModule(config));
|
install(new CorePresentationModule(config));
|
||||||
install(new MoneroConnectionModule(config));
|
install(new MoneroConnectionModule(config));
|
||||||
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,14 +230,14 @@ public class DomainInitialisation {
|
||||||
triggerPriceService.onAllServicesInitialized();
|
triggerPriceService.onAllServicesInitialized();
|
||||||
mempoolService.onAllServicesInitialized();
|
mempoolService.onAllServicesInitialized();
|
||||||
|
|
||||||
if (revolutAccountsUpdateHandler != null) {
|
if (revolutAccountsUpdateHandler != null && user.getPaymentAccountsAsObservable() != null) {
|
||||||
revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
||||||
.filter(paymentAccount -> paymentAccount instanceof RevolutAccount)
|
.filter(paymentAccount -> paymentAccount instanceof RevolutAccount)
|
||||||
.map(paymentAccount -> (RevolutAccount) paymentAccount)
|
.map(paymentAccount -> (RevolutAccount) paymentAccount)
|
||||||
.filter(RevolutAccount::userNameNotSet)
|
.filter(RevolutAccount::userNameNotSet)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
if (amazonGiftCardAccountsUpdateHandler != null) {
|
if (amazonGiftCardAccountsUpdateHandler != null && user.getPaymentAccountsAsObservable() != null) {
|
||||||
amazonGiftCardAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
amazonGiftCardAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
|
||||||
.filter(paymentAccount -> paymentAccount instanceof AmazonGiftCardAccount)
|
.filter(paymentAccount -> paymentAccount instanceof AmazonGiftCardAccount)
|
||||||
.map(paymentAccount -> (AmazonGiftCardAccount) paymentAccount)
|
.map(paymentAccount -> (AmazonGiftCardAccount) paymentAccount)
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package bisq.core.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
|
import bisq.core.api.AccountServiceListener;
|
||||||
|
import bisq.core.api.CoreAccountService;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
@ -27,7 +29,6 @@ import bisq.core.setup.CoreSetup;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||||
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
|
@ -35,6 +36,7 @@ import bisq.common.app.AppModule;
|
||||||
import bisq.common.config.HavenoHelpFormatter;
|
import bisq.common.config.HavenoHelpFormatter;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.config.ConfigException;
|
import bisq.common.config.ConfigException;
|
||||||
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
import bisq.common.persistence.PersistenceManager;
|
import bisq.common.persistence.PersistenceManager;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
|
@ -45,7 +47,6 @@ import bisq.common.util.Utilities;
|
||||||
|
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@ -64,11 +65,12 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
private final String appName;
|
private final String appName;
|
||||||
private final String version;
|
private final String version;
|
||||||
|
|
||||||
|
protected CoreAccountService accountService;
|
||||||
protected Injector injector;
|
protected Injector injector;
|
||||||
protected AppModule module;
|
protected AppModule module;
|
||||||
protected Config config;
|
protected Config config;
|
||||||
private boolean isShutdownInProgress;
|
private boolean isShutdownInProgress;
|
||||||
private boolean hasDowngraded;
|
private boolean isReadOnly;
|
||||||
|
|
||||||
public HavenoExecutable(String fullName, String scriptName, String appName, String version) {
|
public HavenoExecutable(String fullName, String scriptName, String appName, String version) {
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
|
@ -136,17 +138,61 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
setupGuice();
|
setupGuice();
|
||||||
setupAvoidStandbyMode();
|
setupAvoidStandbyMode();
|
||||||
|
|
||||||
hasDowngraded = HavenoSetup.hasDowngraded();
|
// If user tried to downgrade we do not read the persisted data to avoid data corruption
|
||||||
if (hasDowngraded) {
|
// We call startApplication to enable UI to show popup. We prevent in HavenoSetup to go further
|
||||||
// If user tried to downgrade we do not read the persisted data to avoid data corruption
|
// in the process and require a shut down.
|
||||||
// We call startApplication to enable UI to show popup. We prevent in HavenoSetup to go further
|
isReadOnly = HavenoSetup.hasDowngraded();
|
||||||
// in the process and require a shut down.
|
|
||||||
startApplication();
|
// Account service should be available before attempting to login.
|
||||||
} else {
|
accountService = injector.getInstance(CoreAccountService.class);
|
||||||
|
|
||||||
|
// Application needs to restart on delete and restore of account.
|
||||||
|
accountService.addListener(new AccountServiceListener() {
|
||||||
|
@Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
|
||||||
|
@Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attempt to login, subclasses should implement interactive login and or rpc login.
|
||||||
|
if (!isReadOnly && loginAccount()) {
|
||||||
readAllPersisted(this::startApplication);
|
readAllPersisted(this::startApplication);
|
||||||
|
} else {
|
||||||
|
log.warn("Running application in readonly mode");
|
||||||
|
startApplication();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not persist when shutting down after account restore and restarts since
|
||||||
|
* that causes the current persistables to overwrite the restored or deleted state.
|
||||||
|
*/
|
||||||
|
protected void shutDownNoPersist(Runnable onShutdown) {
|
||||||
|
this.isReadOnly = true;
|
||||||
|
gracefulShutDown(() -> {
|
||||||
|
log.info("Shutdown without persisting");
|
||||||
|
if (onShutdown != null) onShutdown.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to login. TODO: supply a password in config or args
|
||||||
|
*
|
||||||
|
* @return true if account is opened successfully.
|
||||||
|
*/
|
||||||
|
protected boolean loginAccount() {
|
||||||
|
if (accountService.accountExists()) {
|
||||||
|
log.info("Account already exists, attempting to open");
|
||||||
|
try {
|
||||||
|
accountService.openAccount(null);
|
||||||
|
} catch (IncorrectPasswordException ipe) {
|
||||||
|
log.info("Account password protected, password required");
|
||||||
|
}
|
||||||
|
} else if (!config.passwordRequired) {
|
||||||
|
log.info("Creating Haveno account with null password");
|
||||||
|
accountService.createAccount(null);
|
||||||
|
}
|
||||||
|
return accountService.isAccountOpen();
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// We continue with a series of synchronous execution tasks
|
// We continue with a series of synchronous execution tasks
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -198,9 +244,9 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void runHavenoSetup() {
|
protected void runHavenoSetup() {
|
||||||
HavenoSetup bisqSetup = injector.getInstance(HavenoSetup.class);
|
HavenoSetup havenoSetup = injector.getInstance(HavenoSetup.class);
|
||||||
bisqSetup.addHavenoSetupListener(this);
|
havenoSetup.addHavenoSetupListener(this);
|
||||||
bisqSetup.start();
|
havenoSetup.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -233,7 +279,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||||
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc?
|
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService
|
||||||
log.info("OpenOfferManager shutdown started");
|
log.info("OpenOfferManager shutdown started");
|
||||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
||||||
log.info("OpenOfferManager shutdown completed");
|
log.info("OpenOfferManager shutdown completed");
|
||||||
|
@ -248,16 +294,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
injector.getInstance(P2PService.class).shutDown(() -> {
|
injector.getInstance(P2PService.class).shutDown(() -> {
|
||||||
log.info("P2PService shutdown completed");
|
log.info("P2PService shutdown completed");
|
||||||
module.close(injector);
|
module.close(injector);
|
||||||
if (!hasDowngraded) {
|
completeShutdown(resultHandler, EXIT_SUCCESS);
|
||||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
|
||||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
|
||||||
log.info("Graceful shutdown completed. Exiting now.");
|
|
||||||
resultHandler.handleResult();
|
|
||||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
walletsSetup.shutDown();
|
walletsSetup.shutDown();
|
||||||
|
@ -267,31 +304,26 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
// Wait max 20 sec.
|
// Wait max 20 sec.
|
||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler.");
|
log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler.");
|
||||||
if (!hasDowngraded) {
|
completeShutdown(resultHandler, EXIT_SUCCESS);
|
||||||
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
|
||||||
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
|
||||||
log.info("Graceful shutdown resulted in a timeout. Exiting now.");
|
|
||||||
resultHandler.handleResult();
|
|
||||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, 20);
|
}, 20);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.error("App shutdown failed with exception {}", t.toString());
|
log.error("App shutdown failed with exception {}", t.toString());
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
if (!hasDowngraded) {
|
completeShutdown(resultHandler, EXIT_FAILURE);
|
||||||
// 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();
|
private void completeShutdown(ResultHandler resultHandler, int exitCode) {
|
||||||
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
|
if (!isReadOnly) {
|
||||||
});
|
// If user tried to downgrade we do not write the persistable data to avoid data corruption
|
||||||
} else {
|
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
|
||||||
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
public class HavenoHeadlessApp implements HeadlessApp {
|
public class HavenoHeadlessApp implements HeadlessApp {
|
||||||
@Getter
|
@Getter
|
||||||
private static Runnable shutDownHandler;
|
private static Runnable shutDownHandler;
|
||||||
|
@Setter
|
||||||
|
public static Runnable onGracefulShutDownHandler;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
protected Injector injector;
|
protected Injector injector;
|
||||||
|
@ -50,6 +52,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
||||||
shutDownHandler = this::stop;
|
shutDownHandler = this::stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void startApplication() {
|
public void startApplication() {
|
||||||
try {
|
try {
|
||||||
bisqSetup = injector.getInstance(HavenoSetup.class);
|
bisqSetup = injector.getInstance(HavenoSetup.class);
|
||||||
|
@ -103,13 +106,13 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
||||||
UserThread.runAfter(() -> {
|
UserThread.runAfter(() -> {
|
||||||
gracefulShutDownHandler.gracefulShutDown(() -> {
|
gracefulShutDownHandler.gracefulShutDown(() -> {
|
||||||
log.debug("App shutdown complete");
|
log.debug("App shutdown complete");
|
||||||
|
if (onGracefulShutDownHandler != null) onGracefulShutDownHandler.run();
|
||||||
});
|
});
|
||||||
}, 200, TimeUnit.MILLISECONDS);
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
shutDownRequested = true;
|
shutDownRequested = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// UncaughtExceptionHandler implementation
|
// UncaughtExceptionHandler implementation
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -115,6 +115,7 @@ public class HavenoHeadlessAppMain extends HavenoExecutable {
|
||||||
onApplicationStarted();
|
onApplicationStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: implement interactive console which allows user to input commands; login, logoff, exit
|
||||||
private void keepRunning() {
|
private void keepRunning() {
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -195,7 +195,7 @@ public class HavenoSetup {
|
||||||
private boolean allBasicServicesInitialized;
|
private boolean allBasicServicesInitialized;
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
||||||
private final List<HavenoSetupListener> bisqSetupListeners = new ArrayList<>();
|
private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HavenoSetup(DomainInitialisation domainInitialisation,
|
public HavenoSetup(DomainInitialisation domainInitialisation,
|
||||||
|
@ -274,7 +274,7 @@ public class HavenoSetup {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void addHavenoSetupListener(HavenoSetupListener listener) {
|
public void addHavenoSetupListener(HavenoSetupListener listener) {
|
||||||
bisqSetupListeners.add(listener);
|
havenoSetupListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
|
@ -284,7 +284,7 @@ public class HavenoSetup {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
persistBisqVersion();
|
persistHavenoVersion();
|
||||||
maybeReSyncSPVChain();
|
maybeReSyncSPVChain();
|
||||||
maybeShowTac(this::step2);
|
maybeShowTac(this::step2);
|
||||||
}
|
}
|
||||||
|
@ -303,7 +303,7 @@ public class HavenoSetup {
|
||||||
private void step4() {
|
private void step4() {
|
||||||
initDomainServices();
|
initDomainServices();
|
||||||
|
|
||||||
bisqSetupListeners.forEach(HavenoSetupListener::onSetupComplete);
|
havenoSetupListeners.forEach(HavenoSetupListener::onSetupComplete);
|
||||||
|
|
||||||
// We set that after calling the setupCompleteHandler to not trigger a popup from the dev dummy accounts
|
// We set that after calling the setupCompleteHandler to not trigger a popup from the dev dummy accounts
|
||||||
// in MainViewModel
|
// in MainViewModel
|
||||||
|
@ -373,7 +373,7 @@ public class HavenoSetup {
|
||||||
}, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
}, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES);
|
||||||
|
|
||||||
log.info("Init P2P network");
|
log.info("Init P2P network");
|
||||||
bisqSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
|
havenoSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
|
||||||
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
|
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
|
||||||
|
|
||||||
// We only init wallet service here if not using Tor for bitcoinj.
|
// We only init wallet service here if not using Tor for bitcoinj.
|
||||||
|
@ -402,10 +402,10 @@ public class HavenoSetup {
|
||||||
|
|
||||||
private void initWallet() {
|
private void initWallet() {
|
||||||
log.info("Init wallet");
|
log.info("Init wallet");
|
||||||
bisqSetupListeners.forEach(HavenoSetupListener::onInitWallet);
|
havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet);
|
||||||
Runnable walletPasswordHandler = () -> {
|
Runnable walletPasswordHandler = () -> {
|
||||||
log.info("Wallet password required");
|
log.info("Wallet password required");
|
||||||
bisqSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword);
|
havenoSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword);
|
||||||
if (p2pNetworkReady.get())
|
if (p2pNetworkReady.get())
|
||||||
p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true);
|
p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true);
|
||||||
|
|
||||||
|
@ -581,7 +581,7 @@ public class HavenoSetup {
|
||||||
return hasDowngraded;
|
return hasDowngraded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void persistBisqVersion() {
|
public static void persistHavenoVersion() {
|
||||||
File versionFile = getVersionFile();
|
File versionFile = getVersionFile();
|
||||||
if (!versionFile.exists()) {
|
if (!versionFile.exists()) {
|
||||||
try {
|
try {
|
||||||
|
@ -639,6 +639,7 @@ public class HavenoSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeShowSecurityRecommendation() {
|
private void maybeShowSecurityRecommendation() {
|
||||||
|
if (user.getPaymentAccountsAsObservable() == null) return;
|
||||||
String key = "remindPasswordAndBackup";
|
String key = "remindPasswordAndBackup";
|
||||||
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) change -> {
|
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) change -> {
|
||||||
if (!walletsManager.areWalletsEncrypted() && !user.isPaymentAccountImport() && preferences.showAgain(key) && change.wasAdded() &&
|
if (!walletsManager.areWalletsEncrypted() && !user.isPaymentAccountImport() && preferences.showAgain(key) && change.wasAdded() &&
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
|
@ -51,7 +51,7 @@ import javax.annotation.Nullable;
|
||||||
public class P2PNetworkSetup {
|
public class P2PNetworkSetup {
|
||||||
private final PriceFeedService priceFeedService;
|
private final PriceFeedService priceFeedService;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
|
@ -75,12 +75,12 @@ public class P2PNetworkSetup {
|
||||||
@Inject
|
@Inject
|
||||||
public P2PNetworkSetup(PriceFeedService priceFeedService,
|
public P2PNetworkSetup(PriceFeedService priceFeedService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
Preferences preferences) {
|
Preferences preferences) {
|
||||||
|
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ public class P2PNetworkSetup {
|
||||||
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
|
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
|
||||||
|
|
||||||
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
|
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
|
||||||
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
connectionService.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
|
||||||
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
|
||||||
String result;
|
String result;
|
||||||
int p2pPeers = (int) numP2pPeers;
|
int p2pPeers = (int) numP2pPeers;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package bisq.core.app;
|
package bisq.core.app;
|
||||||
|
|
||||||
import bisq.core.api.CoreContext;
|
import bisq.core.api.CoreContext;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.exceptions.InvalidHostException;
|
import bisq.core.btc.exceptions.InvalidHostException;
|
||||||
import bisq.core.btc.exceptions.RejectedTxException;
|
import bisq.core.btc.exceptions.RejectedTxException;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
|
@ -67,6 +68,7 @@ public class WalletAppSetup {
|
||||||
private final CoreContext coreContext;
|
private final CoreContext coreContext;
|
||||||
private final WalletsManager walletsManager;
|
private final WalletsManager walletsManager;
|
||||||
private final WalletsSetup walletsSetup;
|
private final WalletsSetup walletsSetup;
|
||||||
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final FeeService feeService;
|
private final FeeService feeService;
|
||||||
private final Config config;
|
private final Config config;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
|
@ -91,12 +93,14 @@ public class WalletAppSetup {
|
||||||
public WalletAppSetup(CoreContext coreContext,
|
public WalletAppSetup(CoreContext coreContext,
|
||||||
WalletsManager walletsManager,
|
WalletsManager walletsManager,
|
||||||
WalletsSetup walletsSetup,
|
WalletsSetup walletsSetup,
|
||||||
|
CoreMoneroConnectionsService connectionService,
|
||||||
FeeService feeService,
|
FeeService feeService,
|
||||||
Config config,
|
Config config,
|
||||||
Preferences preferences) {
|
Preferences preferences) {
|
||||||
this.coreContext = coreContext;
|
this.coreContext = coreContext;
|
||||||
this.walletsManager = walletsManager;
|
this.walletsManager = walletsManager;
|
||||||
this.walletsSetup = walletsSetup;
|
this.walletsSetup = walletsSetup;
|
||||||
|
this.connectionService = connectionService;
|
||||||
this.feeService = feeService;
|
this.feeService = feeService;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
|
@ -115,8 +119,8 @@ public class WalletAppSetup {
|
||||||
VersionMessage.BITCOINJ_VERSION, "2a80db4");
|
VersionMessage.BITCOINJ_VERSION, "2a80db4");
|
||||||
|
|
||||||
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
|
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
|
||||||
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(),
|
btcInfoBinding = EasyBind.combine(connectionService.downloadPercentageProperty(), // TODO (woodser): update to XMR
|
||||||
walletsSetup.chainHeightProperty(),
|
connectionService.chainHeightProperty(),
|
||||||
feeService.feeUpdateCounterProperty(),
|
feeService.feeUpdateCounterProperty(),
|
||||||
walletServiceException,
|
walletServiceException,
|
||||||
(downloadPercentage, chainHeight, feeUpdate, exception) -> {
|
(downloadPercentage, chainHeight, feeUpdate, exception) -> {
|
||||||
|
@ -124,10 +128,8 @@ public class WalletAppSetup {
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
double percentage = (double) downloadPercentage;
|
double percentage = (double) downloadPercentage;
|
||||||
btcSyncProgress.set(percentage);
|
btcSyncProgress.set(percentage);
|
||||||
int bestChainHeight = walletsSetup.getChain() != null ?
|
Long bestChainHeight = connectionService.getDaemon() == null ? null : connectionService.getDaemon().getInfo().getHeight();
|
||||||
walletsSetup.getChain().getBestChainHeight() :
|
String chainHeightAsString = bestChainHeight != null && bestChainHeight > 0 ?
|
||||||
0;
|
|
||||||
String chainHeightAsString = bestChainHeight > 0 ?
|
|
||||||
String.valueOf(bestChainHeight) :
|
String.valueOf(bestChainHeight) :
|
||||||
"";
|
"";
|
||||||
if (percentage == 1) {
|
if (percentage == 1) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import bisq.core.proto.persistable.CorePersistenceProtoResolver;
|
||||||
import bisq.core.trade.TradeModule;
|
import bisq.core.trade.TradeModule;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
import bisq.core.xmr.connection.MoneroConnectionModule;
|
||||||
import bisq.network.crypto.EncryptionServiceModule;
|
import bisq.network.crypto.EncryptionServiceModule;
|
||||||
import bisq.network.p2p.P2PModule;
|
import bisq.network.p2p.P2PModule;
|
||||||
import bisq.network.p2p.network.BridgeAddressProvider;
|
import bisq.network.p2p.network.BridgeAddressProvider;
|
||||||
|
@ -41,8 +41,6 @@ import bisq.common.app.AppModule;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.crypto.KeyStorage;
|
import bisq.common.crypto.KeyStorage;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.crypto.PubKeyRingProvider;
|
|
||||||
import bisq.common.proto.network.NetworkProtoResolver;
|
import bisq.common.proto.network.NetworkProtoResolver;
|
||||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||||
|
|
||||||
|
@ -93,6 +91,6 @@ public class ModuleForAppWithP2p extends AppModule {
|
||||||
install(new BitcoinModule(config));
|
install(new BitcoinModule(config));
|
||||||
install(new AlertModule(config));
|
install(new AlertModule(config));
|
||||||
install(new FilterModule(config));
|
install(new FilterModule(config));
|
||||||
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
|
install(new MoneroConnectionModule(config));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ package bisq.core.btc.model;
|
||||||
|
|
||||||
import bisq.common.crypto.CryptoException;
|
import bisq.common.crypto.CryptoException;
|
||||||
import bisq.common.crypto.Encryption;
|
import bisq.common.crypto.Encryption;
|
||||||
|
import bisq.common.crypto.ScryptUtil;
|
||||||
import bisq.common.persistence.PersistenceManager;
|
import bisq.common.persistence.PersistenceManager;
|
||||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
import bisq.core.api.CoreAccountService;
|
import bisq.core.api.CoreAccountService;
|
||||||
import bisq.core.api.model.EncryptedConnection;
|
import bisq.core.api.model.EncryptedConnection;
|
||||||
import bisq.core.crypto.ScryptUtil;
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.Message;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -22,8 +24,6 @@ import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.Message;
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import monero.common.MoneroRpcConnection;
|
import monero.common.MoneroRpcConnection;
|
||||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
|
@ -60,7 +60,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
transient private PersistenceManager<EncryptedConnectionList> persistenceManager;
|
transient private PersistenceManager<EncryptedConnectionList> persistenceManager;
|
||||||
|
|
||||||
private final Map<String, EncryptedConnection> items = new HashMap<>();
|
private final Map<String, EncryptedConnection> items = new HashMap<>();
|
||||||
private @NonNull String currentConnectionUri = "";
|
private @NonNull String currentConnectionUrl = "";
|
||||||
private long refreshPeriod;
|
private long refreshPeriod;
|
||||||
private boolean autoSwitch;
|
private boolean autoSwitch;
|
||||||
|
|
||||||
|
@ -70,17 +70,16 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
this.accountService = accountService;
|
this.accountService = accountService;
|
||||||
this.persistenceManager = persistenceManager;
|
this.persistenceManager = persistenceManager;
|
||||||
this.persistenceManager.initialize(this, "EncryptedConnectionList", PersistenceManager.Source.PRIVATE);
|
this.persistenceManager.initialize(this, "EncryptedConnectionList", PersistenceManager.Source.PRIVATE);
|
||||||
this.accountService.addPasswordChangeListener(this::onPasswordChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptedConnectionList(byte[] salt,
|
private EncryptedConnectionList(byte[] salt,
|
||||||
List<EncryptedConnection> items,
|
List<EncryptedConnection> items,
|
||||||
@NonNull String currentConnectionUri,
|
@NonNull String currentConnectionUrl,
|
||||||
long refreshPeriod,
|
long refreshPeriod,
|
||||||
boolean autoSwitch) {
|
boolean autoSwitch) {
|
||||||
this.keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(salt);
|
this.keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(salt);
|
||||||
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUri, Function.identity())));
|
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUrl, Function.identity())));
|
||||||
this.currentConnectionUri = currentConnectionUri;
|
this.currentConnectionUrl = currentConnectionUrl;
|
||||||
this.refreshPeriod = refreshPeriod;
|
this.refreshPeriod = refreshPeriod;
|
||||||
this.autoSwitch = autoSwitch;
|
this.autoSwitch = autoSwitch;
|
||||||
}
|
}
|
||||||
|
@ -93,9 +92,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
initializeEncryption(persistedEncryptedConnectionList.keyCrypterScrypt);
|
initializeEncryption(persistedEncryptedConnectionList.keyCrypterScrypt);
|
||||||
items.clear();
|
items.clear();
|
||||||
items.putAll(persistedEncryptedConnectionList.items);
|
items.putAll(persistedEncryptedConnectionList.items);
|
||||||
currentConnectionUri = persistedEncryptedConnectionList.currentConnectionUri;
|
currentConnectionUrl = persistedEncryptedConnectionList.currentConnectionUrl;
|
||||||
refreshPeriod = persistedEncryptedConnectionList.refreshPeriod;
|
refreshPeriod = persistedEncryptedConnectionList.refreshPeriod;
|
||||||
autoSwitch = persistedEncryptedConnectionList.autoSwitch;
|
autoSwitch = persistedEncryptedConnectionList.autoSwitch;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -104,6 +105,8 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
try {
|
try {
|
||||||
initializeEncryption(ScryptUtil.getKeyCrypterScrypt());
|
initializeEncryption(ScryptUtil.getKeyCrypterScrypt());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -203,11 +206,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentConnectionUri(String currentConnectionUri) {
|
public void setCurrentConnectionUri(String currentConnectionUrl) {
|
||||||
boolean changed;
|
boolean changed;
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
try {
|
try {
|
||||||
changed = !this.currentConnectionUri.equals(this.currentConnectionUri = currentConnectionUri == null ? "" : currentConnectionUri);
|
changed = !this.currentConnectionUrl.equals(this.currentConnectionUrl = currentConnectionUrl == null ? "" : currentConnectionUrl);
|
||||||
} finally {
|
} finally {
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
|
@ -219,17 +222,54 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
public Optional<String> getCurrentConnectionUri() {
|
public Optional<String> getCurrentConnectionUri() {
|
||||||
readLock.lock();
|
readLock.lock();
|
||||||
try {
|
try {
|
||||||
return Optional.of(currentConnectionUri).filter(s -> !s.isEmpty());
|
return Optional.of(currentConnectionUrl).filter(s -> !s.isEmpty());
|
||||||
} finally {
|
} finally {
|
||||||
readLock.unlock();
|
readLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestPersistence() {
|
public void requestPersistence() {
|
||||||
persistenceManager.requestPersistence();
|
persistenceManager.requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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();
|
writeLock.lock();
|
||||||
try {
|
try {
|
||||||
SecretKey oldSecret = encryptionKey;
|
SecretKey oldSecret = encryptionKey;
|
||||||
|
@ -243,9 +283,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey toSecretKey(String password) {
|
private SecretKey toSecretKey(String password) {
|
||||||
if (password == null) {
|
if (password == null) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Encryption.getSecretKeyFromBytes(keyCrypterScrypt.deriveKey(password).getKey());
|
return Encryption.getSecretKeyFromBytes(keyCrypterScrypt.deriveKey(password).getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +303,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] decrypt(byte[] encrypted, SecretKey secret) {
|
private static byte[] decrypt(byte[] encrypted, SecretKey secret) {
|
||||||
|
if (secret == null) return encrypted; // no encryption
|
||||||
try {
|
try {
|
||||||
return Encryption.decrypt(encrypted, secret);
|
return Encryption.decrypt(encrypted, secret);
|
||||||
} catch (CryptoException e) {
|
} catch (CryptoException e) {
|
||||||
|
@ -273,6 +312,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] encrypt(byte[] unencrypted, SecretKey secretKey) {
|
private static byte[] encrypt(byte[] unencrypted, SecretKey secretKey) {
|
||||||
|
if (secretKey == null) return unencrypted; // no encryption
|
||||||
try {
|
try {
|
||||||
return Encryption.encrypt(unencrypted, secretKey);
|
return Encryption.encrypt(unencrypted, secretKey);
|
||||||
} catch (CryptoException e) {
|
} catch (CryptoException e) {
|
||||||
|
@ -286,7 +326,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
byte[] passwordSalt = generateSalt(passwordBytes);
|
byte[] passwordSalt = generateSalt(passwordBytes);
|
||||||
byte[] encryptedPassword = encryptPassword(passwordBytes, passwordSalt);
|
byte[] encryptedPassword = encryptPassword(passwordBytes, passwordSalt);
|
||||||
return EncryptedConnection.builder()
|
return EncryptedConnection.builder()
|
||||||
.uri(connection.getUri())
|
.url(connection.getUri())
|
||||||
.username(connection.getUsername() == null ? "" : connection.getUsername())
|
.username(connection.getUsername() == null ? "" : connection.getUsername())
|
||||||
.encryptedPassword(encryptedPassword)
|
.encryptedPassword(encryptedPassword)
|
||||||
.encryptionSalt(passwordSalt)
|
.encryptionSalt(passwordSalt)
|
||||||
|
@ -298,7 +338,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
byte[] decryptedPasswordBytes = decryptPassword(connection.getEncryptedPassword(), connection.getEncryptionSalt());
|
byte[] decryptedPasswordBytes = decryptPassword(connection.getEncryptedPassword(), connection.getEncryptionSalt());
|
||||||
String password = decryptedPasswordBytes == null ? null : new String(decryptedPasswordBytes, StandardCharsets.UTF_8);
|
String password = decryptedPasswordBytes == null ? null : new String(decryptedPasswordBytes, StandardCharsets.UTF_8);
|
||||||
String username = connection.getUsername().isEmpty() ? null : connection.getUsername();
|
String username = connection.getUsername().isEmpty() ? null : connection.getUsername();
|
||||||
MoneroRpcConnection moneroRpcConnection = new MoneroRpcConnection(connection.getUri(), username, password);
|
MoneroRpcConnection moneroRpcConnection = new MoneroRpcConnection(connection.getUrl(), username, password);
|
||||||
moneroRpcConnection.setPriority(connection.getPriority());
|
moneroRpcConnection.setPriority(connection.getPriority());
|
||||||
return moneroRpcConnection;
|
return moneroRpcConnection;
|
||||||
}
|
}
|
||||||
|
@ -357,39 +397,4 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message toProtoMessage() {
|
|
||||||
List<protobuf.EncryptedConnection> connections;
|
|
||||||
ByteString saltString;
|
|
||||||
String currentConnectionUri;
|
|
||||||
boolean autoSwitchEnabled;
|
|
||||||
long refreshPeriod;
|
|
||||||
readLock.lock();
|
|
||||||
try {
|
|
||||||
connections = items.values().stream()
|
|
||||||
.map(EncryptedConnection::toProtoMessage).collect(Collectors.toList());
|
|
||||||
saltString = keyCrypterScrypt.getScryptParameters().getSalt();
|
|
||||||
currentConnectionUri = this.currentConnectionUri;
|
|
||||||
autoSwitchEnabled = this.autoSwitch;
|
|
||||||
refreshPeriod = this.refreshPeriod;
|
|
||||||
} finally {
|
|
||||||
readLock.unlock();
|
|
||||||
}
|
|
||||||
return protobuf.PersistableEnvelope.newBuilder()
|
|
||||||
.setEncryptedConnectionList(protobuf.EncryptedConnectionList.newBuilder()
|
|
||||||
.setSalt(saltString)
|
|
||||||
.addAllItems(connections)
|
|
||||||
.setCurrentConnectionUri(currentConnectionUri)
|
|
||||||
.setRefreshPeriod(refreshPeriod)
|
|
||||||
.setAutoSwitch(autoSwitchEnabled))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EncryptedConnectionList fromProto(protobuf.EncryptedConnectionList proto) {
|
|
||||||
List<EncryptedConnection> items = proto.getItemsList().stream()
|
|
||||||
.map(EncryptedConnection::fromProto)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return new EncryptedConnectionList(proto.getSalt().toByteArray(), items, proto.getCurrentConnectionUri(), proto.getRefreshPeriod(), proto.getAutoSwitch());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ import com.google.inject.Inject;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -37,10 +35,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
import monero.wallet.model.MoneroOutputWallet;
|
|
||||||
import monero.wallet.model.MoneroWalletListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
|
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
|
||||||
* associated protobuf message.
|
* associated protobuf message.
|
||||||
|
@ -48,7 +42,6 @@ import monero.wallet.model.MoneroWalletListener;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost {
|
public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost {
|
||||||
transient private PersistenceManager<XmrAddressEntryList> persistenceManager;
|
transient private PersistenceManager<XmrAddressEntryList> persistenceManager;
|
||||||
transient private MoneroWallet wallet;
|
|
||||||
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
|
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -100,61 +93,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void onWalletReady(MoneroWallet wallet) {
|
|
||||||
this.wallet = wallet;
|
|
||||||
|
|
||||||
if (!entrySet.isEmpty()) {
|
|
||||||
// Set<XmrAddressEntry> toBeRemoved = new HashSet<>();
|
|
||||||
// entrySet.forEach(addressEntry -> {
|
|
||||||
// DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
|
|
||||||
// addressEntry.getPubKeyHash(),
|
|
||||||
// Script.ScriptType.P2PKH);
|
|
||||||
// if (keyFromPubHash != null) {
|
|
||||||
// Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash);
|
|
||||||
// // We want to ensure key and address matches in case we have address in entry available already
|
|
||||||
// if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
|
|
||||||
// addressEntry.setDeterministicKey(keyFromPubHash);
|
|
||||||
// } else {
|
|
||||||
// log.error("We found an address entry without key but cannot apply the key as the address " +
|
|
||||||
// "is not matching. " +
|
|
||||||
// "We remove that entry as it seems it is not compatible with our wallet. " +
|
|
||||||
// "addressFromKey={}, addressEntry.getAddress()={}",
|
|
||||||
// addressFromKey, addressEntry.getAddress());
|
|
||||||
// toBeRemoved.add(addressEntry);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " +
|
|
||||||
// "This is expected at restore from seeds.", addressEntry.toString());
|
|
||||||
// toBeRemoved.add(addressEntry);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// toBeRemoved.forEach(entrySet::remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
|
|
||||||
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
|
|
||||||
// incoming txs at blockchain sync to add the rest.
|
|
||||||
if (wallet.getBalance().compareTo(new BigInteger("0")) > 0) {
|
|
||||||
wallet.getAccounts().forEach(acct -> {
|
|
||||||
log.info("Create XmrAddressEntry for IssuedReceiveAddress. address={}", acct.getPrimaryAddress());
|
|
||||||
if (acct.getIndex() != 0) entrySet.add(new XmrAddressEntry(acct.getIndex(), acct.getPrimaryAddress(), XmrAddressEntry.Context.AVAILABLE));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// We add those listeners to get notified about potential new transactions and
|
|
||||||
// add an address entry list in case it does not exist yet. This is mainly needed for restore from seed words
|
|
||||||
// but can help as well in case the addressEntry list would miss an address where the wallet was received
|
|
||||||
// funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the
|
|
||||||
// wallet details window).
|
|
||||||
wallet.addListener(new MoneroWalletListener() {
|
|
||||||
@Override public void onOutputReceived(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
|
|
||||||
@Override public void onOutputSpent(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
|
|
||||||
});
|
|
||||||
|
|
||||||
requestPersistence();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableList<XmrAddressEntry> getAddressEntriesAsListImmutable() {
|
public ImmutableList<XmrAddressEntry> getAddressEntriesAsListImmutable() {
|
||||||
return ImmutableList.copyOf(entrySet);
|
return ImmutableList.copyOf(entrySet);
|
||||||
}
|
}
|
||||||
|
@ -202,25 +140,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
|
||||||
persistenceManager.requestPersistence();
|
persistenceManager.requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Private
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// TODO (woodser): this should be removed since only using account 0
|
|
||||||
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
|
|
||||||
if (output.getAccountIndex() == 0) return;
|
|
||||||
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
|
|
||||||
if (!isAddressInEntries(address)) addAddressEntry(new XmrAddressEntry(output.getAccountIndex(), address, XmrAddressEntry.Context.AVAILABLE));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAddressInEntries(String address) {
|
|
||||||
for (XmrAddressEntry entry : entrySet) {
|
|
||||||
if (entry.getAddressString().equals(address)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "XmrAddressEntryList{" +
|
return "XmrAddressEntryList{" +
|
||||||
|
|
|
@ -8,14 +8,14 @@ import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
class DownloadListener {
|
public class DownloadListener {
|
||||||
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
|
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
|
||||||
|
|
||||||
protected void progress(double percentage, int blocksLeft, Date date) {
|
public void progress(double percentage, int blocksLeft, Date date) {
|
||||||
UserThread.execute(() -> this.percentage.set(percentage / 100d));
|
UserThread.execute(() -> this.percentage.set(percentage / 100d));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doneDownload() {
|
public void doneDownload() {
|
||||||
UserThread.execute(() -> this.percentage.set(1d));
|
UserThread.execute(() -> this.percentage.set(1d));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package bisq.core.btc.setup;
|
package bisq.core.btc.setup;
|
||||||
|
|
||||||
import bisq.core.api.CoreMoneroConnectionsService;
|
|
||||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||||
import bisq.core.btc.nodes.ProxySocketFactory;
|
import bisq.core.btc.nodes.ProxySocketFactory;
|
||||||
import bisq.core.btc.wallet.HavenoRiskAnalysis;
|
import bisq.core.btc.wallet.HavenoRiskAnalysis;
|
||||||
|
@ -72,9 +71,6 @@ import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -89,15 +85,6 @@ import static bisq.common.util.Preconditions.checkDir;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import monero.common.MoneroRpcConnection;
|
|
||||||
import monero.common.MoneroUtils;
|
|
||||||
import monero.daemon.MoneroDaemon;
|
|
||||||
import monero.daemon.MoneroDaemonRpc;
|
|
||||||
import monero.daemon.model.MoneroNetworkType;
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
import monero.wallet.MoneroWalletRpc;
|
|
||||||
import monero.wallet.model.MoneroWalletConfig;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
|
* <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
|
||||||
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
|
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
|
||||||
|
@ -125,27 +112,13 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
|
|
||||||
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
|
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
|
||||||
|
|
||||||
// Monero configuration
|
|
||||||
// TODO: don't hard code configuration, inject into classes?
|
|
||||||
private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
|
|
||||||
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
|
|
||||||
private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
|
|
||||||
private static final String MONERO_WALLET_RPC_PATH = MONERO_WALLET_RPC_DIR + File.separator + "monero-wallet-rpc";
|
|
||||||
private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user";
|
|
||||||
private static final String MONERO_WALLET_RPC_PASSWORD = "abc123";
|
|
||||||
private static final long MONERO_WALLET_SYNC_RATE = 5000l;
|
|
||||||
|
|
||||||
protected final NetworkParameters params;
|
protected final NetworkParameters params;
|
||||||
protected final String filePrefix;
|
protected final String filePrefix;
|
||||||
protected final CoreMoneroConnectionsService moneroConnectionsManager;
|
|
||||||
protected volatile BlockChain vChain;
|
protected volatile BlockChain vChain;
|
||||||
protected volatile SPVBlockStore vStore;
|
protected volatile SPVBlockStore vStore;
|
||||||
protected volatile MoneroDaemonRpc vXmrDaemon;
|
|
||||||
protected volatile MoneroWalletRpc vXmrWallet;
|
|
||||||
protected volatile Wallet vBtcWallet;
|
protected volatile Wallet vBtcWallet;
|
||||||
protected volatile PeerGroup vPeerGroup;
|
protected volatile PeerGroup vPeerGroup;
|
||||||
|
|
||||||
protected final int rpcBindPort;
|
|
||||||
protected final File directory;
|
protected final File directory;
|
||||||
protected volatile File vXmrWalletFile;
|
protected volatile File vXmrWalletFile;
|
||||||
protected volatile File vBtcWalletFile;
|
protected volatile File vBtcWalletFile;
|
||||||
|
@ -176,21 +149,17 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
*/
|
*/
|
||||||
public WalletConfig(NetworkParameters params,
|
public WalletConfig(NetworkParameters params,
|
||||||
File directory,
|
File directory,
|
||||||
int rpcBindPort,
|
|
||||||
CoreMoneroConnectionsService connectionsManager,
|
|
||||||
String filePrefix) {
|
String filePrefix) {
|
||||||
this(new Context(params), directory, rpcBindPort, connectionsManager, filePrefix);
|
this(new Context(params), directory, filePrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory.
|
* Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory.
|
||||||
*/
|
*/
|
||||||
private WalletConfig(Context context, File directory, int rpcBindPort, CoreMoneroConnectionsService connectionsManager, String filePrefix) {
|
private WalletConfig(Context context, File directory, String filePrefix) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.params = checkNotNull(context.getParams());
|
this.params = checkNotNull(context.getParams());
|
||||||
this.directory = checkDir(directory);
|
this.directory = checkDir(directory);
|
||||||
this.rpcBindPort = rpcBindPort;
|
|
||||||
this.moneroConnectionsManager = connectionsManager;
|
|
||||||
this.filePrefix = checkNotNull(filePrefix);
|
this.filePrefix = checkNotNull(filePrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,85 +262,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
// Meant to be overridden by subclasses
|
// Meant to be overridden by subclasses
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean walletExists(String walletName) {
|
|
||||||
String path = directory.toString() + File.separator + walletName;
|
|
||||||
return new File(path + ".keys").exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) {
|
|
||||||
|
|
||||||
// start monero-wallet-rpc instance
|
|
||||||
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
|
|
||||||
|
|
||||||
// create wallet
|
|
||||||
try {
|
|
||||||
walletRpc.createWallet(config);
|
|
||||||
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
|
|
||||||
return walletRpc;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroWalletRpc openWallet(MoneroWalletConfig config, Integer port) {
|
|
||||||
|
|
||||||
// start monero-wallet-rpc instance
|
|
||||||
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
|
|
||||||
|
|
||||||
// open wallet
|
|
||||||
try {
|
|
||||||
walletRpc.openWallet(config);
|
|
||||||
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
|
|
||||||
return walletRpc;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MoneroWalletRpc startWalletRpcInstance(Integer port) {
|
|
||||||
|
|
||||||
// check if monero-wallet-rpc exists
|
|
||||||
if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH + "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system");
|
|
||||||
|
|
||||||
// get app's current daemon connection
|
|
||||||
MoneroRpcConnection connection = moneroConnectionsManager.getConnection();
|
|
||||||
|
|
||||||
// start monero-wallet-rpc instance and return connected client
|
|
||||||
List<String> cmd = new ArrayList<>(Arrays.asList( // modifiable list
|
|
||||||
MONERO_WALLET_RPC_PATH,
|
|
||||||
"--" + MONERO_NETWORK_TYPE.toString().toLowerCase(),
|
|
||||||
"--daemon-address", connection.getUri(),
|
|
||||||
"--rpc-login", MONERO_WALLET_RPC_USERNAME + ":" + MONERO_WALLET_RPC_PASSWORD,
|
|
||||||
"--wallet-dir", directory.toString()
|
|
||||||
));
|
|
||||||
if (connection.getUsername() != null) {
|
|
||||||
cmd.add("--daemon-login");
|
|
||||||
cmd.add(connection.getUsername() + ":" + connection.getPassword());
|
|
||||||
}
|
|
||||||
if (port != null && port > 0) {
|
|
||||||
cmd.add("--rpc-bind-port");
|
|
||||||
cmd.add(Integer.toString(port));
|
|
||||||
}
|
|
||||||
return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeWallet(MoneroWallet walletRpc, boolean save) {
|
|
||||||
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc, save);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteWallet(String walletName) {
|
|
||||||
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
|
|
||||||
String path = directory.toString() + File.separator + walletName;
|
|
||||||
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
|
||||||
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
|
||||||
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
|
||||||
//WalletsSetup.deleteRollingBackup(walletName); // TODO (woodser): necessary to delete rolling backup?
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void startUp() throws Exception {
|
protected void startUp() throws Exception {
|
||||||
// Runs in a separate thread.
|
// Runs in a separate thread.
|
||||||
|
@ -380,31 +270,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
File chainFile = new File(directory, filePrefix + ".spvchain");
|
File chainFile = new File(directory, filePrefix + ".spvchain");
|
||||||
boolean chainFileExists = chainFile.exists();
|
boolean chainFileExists = chainFile.exists();
|
||||||
|
|
||||||
// set XMR daemon and listen for updates
|
|
||||||
vXmrDaemon = new MoneroDaemonRpc(moneroConnectionsManager.getConnection());
|
|
||||||
moneroConnectionsManager.addConnectionListener(newConnection -> {
|
|
||||||
vXmrDaemon = newConnection == null ? null : new MoneroDaemonRpc(newConnection);
|
|
||||||
});
|
|
||||||
|
|
||||||
// XMR wallet
|
|
||||||
String xmrPrefix = "_XMR";
|
|
||||||
vXmrWalletFile = new File(directory, filePrefix + xmrPrefix);
|
|
||||||
if (MoneroUtils.walletExists(vXmrWalletFile.getPath())) {
|
|
||||||
vXmrWallet = openWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"), rpcBindPort);
|
|
||||||
} else {
|
|
||||||
vXmrWallet = createWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"), rpcBindPort);
|
|
||||||
}
|
|
||||||
System.out.println("Monero wallet path: " + vXmrWallet.getPath());
|
|
||||||
System.out.println("Monero wallet address: " + vXmrWallet.getPrimaryAddress());
|
|
||||||
System.out.println("Monero wallet uri: " + vXmrWallet.getRpcConnection().getUri());
|
|
||||||
// vXmrWallet.rescanSpent();
|
|
||||||
// vXmrWallet.rescanBlockchain();
|
|
||||||
vXmrWallet.sync(); // blocking
|
|
||||||
downloadListener.doneDownload();
|
|
||||||
vXmrWallet.save();
|
|
||||||
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance(0));
|
|
||||||
System.out.println("Loaded wallet unlocked balance: " + vXmrWallet.getUnlockedBalance(0));
|
|
||||||
|
|
||||||
String btcPrefix = "_BTC";
|
String btcPrefix = "_BTC";
|
||||||
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
|
||||||
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
|
||||||
|
@ -647,16 +512,6 @@ public class WalletConfig extends AbstractIdleService {
|
||||||
return vBtcWallet;
|
return vBtcWallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroDaemon getXmrDaemon() {
|
|
||||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
|
||||||
return vXmrDaemon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroWallet getXmrWallet() {
|
|
||||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
|
||||||
return vXmrWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PeerGroup peerGroup() {
|
public PeerGroup peerGroup() {
|
||||||
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
|
||||||
return vPeerGroup;
|
return vPeerGroup;
|
||||||
|
|
|
@ -17,12 +17,10 @@
|
||||||
|
|
||||||
package bisq.core.btc.setup;
|
package bisq.core.btc.setup;
|
||||||
|
|
||||||
import bisq.core.api.CoreMoneroConnectionsService;
|
|
||||||
import bisq.core.btc.exceptions.InvalidHostException;
|
import bisq.core.btc.exceptions.InvalidHostException;
|
||||||
import bisq.core.btc.exceptions.RejectedTxException;
|
import bisq.core.btc.exceptions.RejectedTxException;
|
||||||
import bisq.core.btc.model.AddressEntry;
|
import bisq.core.btc.model.AddressEntry;
|
||||||
import bisq.core.btc.model.AddressEntryList;
|
import bisq.core.btc.model.AddressEntryList;
|
||||||
import bisq.core.btc.model.XmrAddressEntryList;
|
|
||||||
import bisq.core.btc.nodes.BtcNetworkConfig;
|
import bisq.core.btc.nodes.BtcNetworkConfig;
|
||||||
import bisq.core.btc.nodes.BtcNodes;
|
import bisq.core.btc.nodes.BtcNodes;
|
||||||
import bisq.core.btc.nodes.BtcNodes.BtcNode;
|
import bisq.core.btc.nodes.BtcNodes.BtcNode;
|
||||||
|
@ -66,14 +64,11 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.LongProperty;
|
import javafx.beans.property.LongProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleLongProperty;
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
@ -100,9 +95,6 @@ import javax.annotation.Nullable;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
import monero.daemon.MoneroDaemon;
|
|
||||||
import monero.daemon.model.MoneroPeer;
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
// Setup wallets and use WalletConfig for BitcoinJ wiring.
|
// Setup wallets and use WalletConfig for BitcoinJ wiring.
|
||||||
// Other like WalletConfig we are here always on the user thread. That is one reason why we do not
|
// Other like WalletConfig we are here always on the user thread. That is one reason why we do not
|
||||||
|
@ -111,8 +103,7 @@ import monero.wallet.MoneroWallet;
|
||||||
public class WalletsSetup {
|
public class WalletsSetup {
|
||||||
|
|
||||||
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_haveno_BTC.wallet.backup";
|
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_haveno_BTC.wallet.backup";
|
||||||
private static final int MIN_BROADCAST_CONNECTIONS = 2;
|
private static final int MIN_BROADCAST_CONNECTIONS = 0;
|
||||||
private static final long DAEMON_POLL_INTERVAL_SECONDS = 20;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
|
||||||
|
@ -122,25 +113,20 @@ public class WalletsSetup {
|
||||||
|
|
||||||
private final RegTestHost regTestHost;
|
private final RegTestHost regTestHost;
|
||||||
private final AddressEntryList addressEntryList;
|
private final AddressEntryList addressEntryList;
|
||||||
private final XmrAddressEntryList xmrAddressEntryList;
|
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private final Socks5ProxyProvider socks5ProxyProvider;
|
private final Socks5ProxyProvider socks5ProxyProvider;
|
||||||
private final Config config;
|
private final Config config;
|
||||||
private final LocalBitcoinNode localBitcoinNode;
|
private final LocalBitcoinNode localBitcoinNode;
|
||||||
private final BtcNodes btcNodes;
|
private final BtcNodes btcNodes;
|
||||||
@Getter
|
|
||||||
private final CoreMoneroConnectionsService moneroConnectionsManager;
|
|
||||||
private final String xmrWalletFileName;
|
|
||||||
private final int numConnectionsForBtc;
|
private final int numConnectionsForBtc;
|
||||||
private final String userAgent;
|
private final String userAgent;
|
||||||
private final NetworkParameters params;
|
private final NetworkParameters params;
|
||||||
private final File walletDir;
|
private final File walletDir;
|
||||||
private final int walletRpcBindPort;
|
|
||||||
private final int socks5DiscoverMode;
|
private final int socks5DiscoverMode;
|
||||||
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
|
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
|
||||||
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
private final LongProperty chainHeight = new SimpleLongProperty(0);
|
||||||
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
|
|
||||||
private final DownloadListener downloadListener = new DownloadListener();
|
private final DownloadListener downloadListener = new DownloadListener();
|
||||||
|
private final List<Runnable> setupTaskHandlers = new ArrayList<>();
|
||||||
private final List<Runnable> setupCompletedHandlers = new ArrayList<>();
|
private final List<Runnable> setupCompletedHandlers = new ArrayList<>();
|
||||||
public final BooleanProperty shutDownComplete = new SimpleBooleanProperty();
|
public final BooleanProperty shutDownComplete = new SimpleBooleanProperty();
|
||||||
private final boolean useAllProvidedNodes;
|
private final boolean useAllProvidedNodes;
|
||||||
|
@ -153,36 +139,29 @@ public class WalletsSetup {
|
||||||
@Inject
|
@Inject
|
||||||
public WalletsSetup(RegTestHost regTestHost,
|
public WalletsSetup(RegTestHost regTestHost,
|
||||||
AddressEntryList addressEntryList,
|
AddressEntryList addressEntryList,
|
||||||
XmrAddressEntryList xmrAddressEntryList,
|
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
Socks5ProxyProvider socks5ProxyProvider,
|
Socks5ProxyProvider socks5ProxyProvider,
|
||||||
Config config,
|
Config config,
|
||||||
LocalBitcoinNode localBitcoinNode,
|
LocalBitcoinNode localBitcoinNode,
|
||||||
BtcNodes btcNodes,
|
BtcNodes btcNodes,
|
||||||
CoreMoneroConnectionsService moneroConnectionsManager,
|
|
||||||
@Named(Config.USER_AGENT) String userAgent,
|
@Named(Config.USER_AGENT) String userAgent,
|
||||||
@Named(Config.WALLET_DIR) File walletDir,
|
@Named(Config.WALLET_DIR) File walletDir,
|
||||||
@Named(Config.WALLET_RPC_BIND_PORT) int walletRpcBindPort,
|
|
||||||
@Named(Config.USE_ALL_PROVIDED_NODES) boolean useAllProvidedNodes,
|
@Named(Config.USE_ALL_PROVIDED_NODES) boolean useAllProvidedNodes,
|
||||||
@Named(Config.NUM_CONNECTIONS_FOR_BTC) int numConnectionsForBtc,
|
@Named(Config.NUM_CONNECTIONS_FOR_BTC) int numConnectionsForBtc,
|
||||||
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
|
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
|
||||||
this.regTestHost = regTestHost;
|
this.regTestHost = regTestHost;
|
||||||
this.addressEntryList = addressEntryList;
|
this.addressEntryList = addressEntryList;
|
||||||
this.xmrAddressEntryList = xmrAddressEntryList;
|
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.socks5ProxyProvider = socks5ProxyProvider;
|
this.socks5ProxyProvider = socks5ProxyProvider;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.localBitcoinNode = localBitcoinNode;
|
this.localBitcoinNode = localBitcoinNode;
|
||||||
this.btcNodes = btcNodes;
|
this.btcNodes = btcNodes;
|
||||||
this.moneroConnectionsManager = moneroConnectionsManager;
|
|
||||||
this.numConnectionsForBtc = numConnectionsForBtc;
|
this.numConnectionsForBtc = numConnectionsForBtc;
|
||||||
this.useAllProvidedNodes = useAllProvidedNodes;
|
this.useAllProvidedNodes = useAllProvidedNodes;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.socks5DiscoverMode = evaluateMode(socks5DiscoverModeString);
|
this.socks5DiscoverMode = evaluateMode(socks5DiscoverModeString);
|
||||||
this.walletDir = walletDir;
|
this.walletDir = walletDir;
|
||||||
this.walletRpcBindPort = walletRpcBindPort;
|
|
||||||
|
|
||||||
xmrWalletFileName = "haveno_" + config.baseCurrencyNetwork.getCurrencyCode();
|
|
||||||
params = Config.baseCurrencyNetworkParameters();
|
params = Config.baseCurrencyNetworkParameters();
|
||||||
PeerGroup.setIgnoreHttpSeeds(true);
|
PeerGroup.setIgnoreHttpSeeds(true);
|
||||||
}
|
}
|
||||||
|
@ -206,31 +185,23 @@ public class WalletsSetup {
|
||||||
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
|
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
|
||||||
STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT);
|
STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT);
|
||||||
|
|
||||||
// initialize Monero connection manager
|
|
||||||
moneroConnectionsManager.initialize();
|
|
||||||
|
|
||||||
backupWallets();
|
backupWallets();
|
||||||
|
|
||||||
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
|
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
|
||||||
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
|
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
|
||||||
|
|
||||||
walletConfig = new WalletConfig(params, walletDir, walletRpcBindPort, moneroConnectionsManager, "haveno") {
|
walletConfig = new WalletConfig(params, walletDir, "haveno") {
|
||||||
@Override
|
@Override
|
||||||
protected void onSetupCompleted() {
|
protected void onSetupCompleted() {
|
||||||
//We are here in the btcj thread Thread[ STARTING,5,main]
|
//We are here in the btcj thread Thread[ STARTING,5,main]
|
||||||
super.onSetupCompleted();
|
super.onSetupCompleted();
|
||||||
|
|
||||||
final PeerGroup peerGroup = walletConfig.peerGroup();
|
final PeerGroup peerGroup = walletConfig.peerGroup();
|
||||||
final BlockChain chain = walletConfig.chain();
|
|
||||||
|
|
||||||
// We don't want to get our node white list polluted with nodes from AddressMessage calls.
|
// We don't want to get our node white list polluted with nodes from AddressMessage calls.
|
||||||
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
|
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
|
||||||
peerGroup.setAddPeersFromAddressMessage(false);
|
peerGroup.setAddPeersFromAddressMessage(false);
|
||||||
|
|
||||||
UserThread.runPeriodically(() -> {
|
|
||||||
updateDaemonInfo();
|
|
||||||
}, DAEMON_POLL_INTERVAL_SECONDS);
|
|
||||||
|
|
||||||
// Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener
|
// Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener
|
||||||
peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> {
|
peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> {
|
||||||
if (message instanceof RejectMessage) {
|
if (message instanceof RejectMessage) {
|
||||||
|
@ -244,11 +215,12 @@ public class WalletsSetup {
|
||||||
return message;
|
return message;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// run external startup handlers
|
||||||
|
setupTaskHandlers.forEach(Runnable::run);
|
||||||
|
|
||||||
// Map to user thread
|
// Map to user thread
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
updateDaemonInfo();
|
|
||||||
addressEntryList.onWalletReady(walletConfig.btcWallet());
|
addressEntryList.onWalletReady(walletConfig.btcWallet());
|
||||||
xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet());
|
|
||||||
timeoutTimer.stop();
|
timeoutTimer.stop();
|
||||||
setupCompletedHandlers.forEach(Runnable::run);
|
setupCompletedHandlers.forEach(Runnable::run);
|
||||||
});
|
});
|
||||||
|
@ -256,23 +228,6 @@ public class WalletsSetup {
|
||||||
// onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay
|
// onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay
|
||||||
UserThread.runAfter(resultHandler::handleResult, 100, TimeUnit.MILLISECONDS);
|
UserThread.runAfter(resultHandler::handleResult, 100, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDaemonInfo() {
|
|
||||||
try {
|
|
||||||
if (vXmrDaemon == null) throw new RuntimeException("No daemon connection");
|
|
||||||
peers.set(getOnlinePeers());
|
|
||||||
numPeers.set(peers.get().size());
|
|
||||||
chainHeight.set(vXmrDaemon.getHeight());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Could not update daemon info: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MoneroPeer> getOnlinePeers() {
|
|
||||||
return vXmrDaemon.getPeers().stream()
|
|
||||||
.filter(peer -> peer.isOnline())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
walletConfig.setSocks5Proxy(socks5Proxy);
|
walletConfig.setSocks5Proxy(socks5Proxy);
|
||||||
walletConfig.setConfig(config);
|
walletConfig.setConfig(config);
|
||||||
|
@ -427,9 +382,7 @@ public class WalletsSetup {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void backupWallets() {
|
public void backupWallets() {
|
||||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20);
|
// TODO: remove?
|
||||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
|
|
||||||
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearBackups() {
|
public void clearBackups() {
|
||||||
|
@ -479,6 +432,10 @@ public class WalletsSetup {
|
||||||
// Handlers
|
// Handlers
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public void addSetupTaskHandler(Runnable handler) {
|
||||||
|
setupTaskHandlers.add(handler);
|
||||||
|
}
|
||||||
|
|
||||||
public void addSetupCompletedHandler(Runnable handler) {
|
public void addSetupCompletedHandler(Runnable handler) {
|
||||||
setupCompletedHandlers.add(handler);
|
setupCompletedHandlers.add(handler);
|
||||||
}
|
}
|
||||||
|
@ -492,14 +449,6 @@ public class WalletsSetup {
|
||||||
return walletConfig.btcWallet();
|
return walletConfig.btcWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroDaemon getXmrDaemon() {
|
|
||||||
return walletConfig.getXmrDaemon();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroWallet getXmrWallet() {
|
|
||||||
return walletConfig.getXmrWallet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkParameters getParams() {
|
public NetworkParameters getParams() {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
@ -521,10 +470,6 @@ public class WalletsSetup {
|
||||||
return numPeers;
|
return numPeers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LongProperty chainHeightProperty() {
|
public LongProperty chainHeightProperty() {
|
||||||
return chainHeight;
|
return chainHeight;
|
||||||
}
|
}
|
||||||
|
@ -538,14 +483,7 @@ public class WalletsSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChainHeightSyncedWithinTolerance() {
|
public boolean isChainHeightSyncedWithinTolerance() {
|
||||||
Long peersChainHeight = walletConfig.vXmrDaemon.getSyncInfo().getTargetHeight();
|
throw new RuntimeException("WalletsSetup.isChainHeightSyncedWithinTolerance() not implemented for BTC");
|
||||||
if (peersChainHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
|
|
||||||
long bestChainHeight = chainHeight.get();
|
|
||||||
if (Math.abs(peersChainHeight - bestChainHeight) <= 3) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), peersChainHeight);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
||||||
|
|
|
@ -20,15 +20,12 @@ package bisq.core.btc.wallet;
|
||||||
import bisq.core.btc.exceptions.SigningException;
|
import bisq.core.btc.exceptions.SigningException;
|
||||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
import bisq.core.btc.exceptions.WalletException;
|
||||||
import bisq.core.btc.model.AddressEntry;
|
|
||||||
import bisq.core.btc.model.InputsAndChangeOutput;
|
import bisq.core.btc.model.InputsAndChangeOutput;
|
||||||
import bisq.core.btc.model.PreparedDepositTxAndMakerInputs;
|
import bisq.core.btc.model.PreparedDepositTxAndMakerInputs;
|
||||||
import bisq.core.btc.model.RawTransactionInput;
|
import bisq.core.btc.model.RawTransactionInput;
|
||||||
import bisq.core.btc.setup.WalletConfig;
|
import bisq.core.btc.setup.WalletConfig;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.locale.Res;
|
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.util.ParsingUtils;
|
|
||||||
|
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
|
@ -37,7 +34,6 @@ import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.ECKey;
|
import org.bitcoinj.core.ECKey;
|
||||||
import org.bitcoinj.core.InsufficientMoneyException;
|
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
import org.bitcoinj.core.SegwitAddress;
|
import org.bitcoinj.core.SegwitAddress;
|
||||||
import org.bitcoinj.core.Sha256Hash;
|
import org.bitcoinj.core.Sha256Hash;
|
||||||
|
@ -78,11 +74,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
import monero.wallet.model.MoneroDestination;
|
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
|
||||||
|
|
||||||
public class TradeWalletService {
|
public class TradeWalletService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
|
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
|
||||||
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
|
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
|
||||||
|
@ -94,8 +85,6 @@ public class TradeWalletService {
|
||||||
@Nullable
|
@Nullable
|
||||||
private Wallet wallet;
|
private Wallet wallet;
|
||||||
@Nullable
|
@Nullable
|
||||||
private MoneroWallet xmrWallet;
|
|
||||||
@Nullable
|
|
||||||
private WalletConfig walletConfig;
|
private WalletConfig walletConfig;
|
||||||
@Nullable
|
@Nullable
|
||||||
private KeyParameter aesKey;
|
private KeyParameter aesKey;
|
||||||
|
@ -113,7 +102,6 @@ public class TradeWalletService {
|
||||||
walletsSetup.addSetupCompletedHandler(() -> {
|
walletsSetup.addSetupCompletedHandler(() -> {
|
||||||
walletConfig = walletsSetup.getWalletConfig();
|
walletConfig = walletsSetup.getWalletConfig();
|
||||||
wallet = walletsSetup.getBtcWallet();
|
wallet = walletsSetup.getBtcWallet();
|
||||||
xmrWallet = walletsSetup.getXmrWallet();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,25 +120,6 @@ public class TradeWalletService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Trade fee
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
public MoneroTxWallet createXmrTradingFeeTx(
|
|
||||||
String reservedForTradeAddress,
|
|
||||||
Coin reservedFundsForOffer,
|
|
||||||
Coin makerFee,
|
|
||||||
Coin txFee,
|
|
||||||
String feeReceiver,
|
|
||||||
boolean broadcastTx) {
|
|
||||||
return xmrWallet.createTx(new MoneroTxConfig()
|
|
||||||
.setAccountIndex(0)
|
|
||||||
.setDestinations(
|
|
||||||
new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)),
|
|
||||||
new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer)))
|
|
||||||
.setRelay(broadcastTx));
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Deposit tx
|
// Deposit tx
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1061,16 +1030,6 @@ public class TradeWalletService {
|
||||||
// Misc
|
// Misc
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
|
|
||||||
*
|
|
||||||
* @param txHash the transaction hash of the transaction we want to lookup
|
|
||||||
*/
|
|
||||||
public MoneroTxWallet getWalletTx(String txHash) {
|
|
||||||
checkNotNull(xmrWallet);
|
|
||||||
return xmrWallet.getTx(txHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
|
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,9 +18,8 @@
|
||||||
package bisq.core.btc.wallet;
|
package bisq.core.btc.wallet;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.crypto.ScryptUtil;
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.common.crypto.ScryptUtil;
|
||||||
import bisq.common.handlers.ExceptionHandler;
|
import bisq.common.handlers.ExceptionHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -24,8 +24,7 @@ import bisq.core.notifications.MobileNotificationService;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
|
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.PubKeyRingProvider;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -41,15 +40,15 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Singleton
|
@Singleton
|
||||||
public class TradeEvents {
|
public class TradeEvents {
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRingProvider pubKeyRingProvider;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
private final MobileNotificationService mobileNotificationService;
|
private final MobileNotificationService mobileNotificationService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TradeEvents(TradeManager tradeManager, KeyRing keyRing, MobileNotificationService mobileNotificationService) {
|
public TradeEvents(TradeManager tradeManager, PubKeyRingProvider pubKeyRingProvider, MobileNotificationService mobileNotificationService) {
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.mobileNotificationService = mobileNotificationService;
|
this.mobileNotificationService = mobileNotificationService;
|
||||||
this.pubKeyRing = keyRing.getPubKeyRing();
|
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
|
@ -74,19 +73,19 @@ public class TradeEvents {
|
||||||
case DEPOSIT_PUBLISHED:
|
case DEPOSIT_PUBLISHED:
|
||||||
break;
|
break;
|
||||||
case DEPOSIT_CONFIRMED:
|
case DEPOSIT_CONFIRMED:
|
||||||
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getBuyerPubKeyRing()))
|
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
||||||
msg = Res.get("account.notifications.trade.message.msg.conf", shortId);
|
msg = Res.get("account.notifications.trade.message.msg.conf", shortId);
|
||||||
break;
|
break;
|
||||||
case FIAT_SENT:
|
case FIAT_SENT:
|
||||||
// We only notify the seller
|
// We only notify the seller
|
||||||
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getSellerPubKeyRing()))
|
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getSellerPubKeyRing()))
|
||||||
msg = Res.get("account.notifications.trade.message.msg.started", shortId);
|
msg = Res.get("account.notifications.trade.message.msg.started", shortId);
|
||||||
break;
|
break;
|
||||||
case FIAT_RECEIVED:
|
case FIAT_RECEIVED:
|
||||||
break;
|
break;
|
||||||
case PAYOUT_PUBLISHED:
|
case PAYOUT_PUBLISHED:
|
||||||
// We only notify the buyer
|
// We only notify the buyer
|
||||||
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getBuyerPubKeyRing()))
|
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
||||||
msg = Res.get("account.notifications.trade.message.msg.completed", shortId);
|
msg = Res.get("account.notifications.trade.message.msg.completed", shortId);
|
||||||
break;
|
break;
|
||||||
case WITHDRAWN:
|
case WITHDRAWN:
|
||||||
|
|
|
@ -39,7 +39,7 @@ import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRingProvider;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
import bisq.common.util.Utilities;
|
import bisq.common.util.Utilities;
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class CreateOfferService {
|
||||||
private final TxFeeEstimationService txFeeEstimationService;
|
private final TxFeeEstimationService txFeeEstimationService;
|
||||||
private final PriceFeedService priceFeedService;
|
private final PriceFeedService priceFeedService;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRingProvider pubKeyRingProvider;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final TradeStatisticsManager tradeStatisticsManager;
|
private final TradeStatisticsManager tradeStatisticsManager;
|
||||||
|
@ -81,7 +81,7 @@ public class CreateOfferService {
|
||||||
TxFeeEstimationService txFeeEstimationService,
|
TxFeeEstimationService txFeeEstimationService,
|
||||||
PriceFeedService priceFeedService,
|
PriceFeedService priceFeedService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRingProvider pubKeyRingProvider,
|
||||||
User user,
|
User user,
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
TradeStatisticsManager tradeStatisticsManager,
|
TradeStatisticsManager tradeStatisticsManager,
|
||||||
|
@ -90,7 +90,7 @@ public class CreateOfferService {
|
||||||
this.txFeeEstimationService = txFeeEstimationService;
|
this.txFeeEstimationService = txFeeEstimationService;
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.tradeStatisticsManager = tradeStatisticsManager;
|
this.tradeStatisticsManager = tradeStatisticsManager;
|
||||||
|
@ -190,14 +190,14 @@ public class CreateOfferService {
|
||||||
paymentAccount,
|
paymentAccount,
|
||||||
currencyCode,
|
currencyCode,
|
||||||
makerFeeAsCoin);
|
makerFeeAsCoin);
|
||||||
|
|
||||||
// select signing arbitrator
|
// select signing arbitrator
|
||||||
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
|
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
|
||||||
|
|
||||||
OfferPayload offerPayload = new OfferPayload(offerId,
|
OfferPayload offerPayload = new OfferPayload(offerId,
|
||||||
creationTime,
|
creationTime,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
pubKeyRing,
|
pubKeyRingProvider.get(),
|
||||||
OfferPayload.Direction.valueOf(direction.name()),
|
OfferPayload.Direction.valueOf(direction.name()),
|
||||||
priceAsLong,
|
priceAsLong,
|
||||||
marketPriceMarginParam,
|
marketPriceMarginParam,
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class OfferFilter {
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null && user.getPaymentAccountsAsObservable() != null) {
|
||||||
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
|
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
|
||||||
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
|
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
|
||||||
myInsufficientTradeLimitCache.clear());
|
myInsufficientTradeLimitCache.clear());
|
||||||
|
@ -212,13 +212,13 @@ public class OfferFilter {
|
||||||
myInsufficientTradeLimitCache.put(offerId, result);
|
myInsufficientTradeLimitCache.put(offerId, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasValidSignature(Offer offer) {
|
public boolean hasValidSignature(Offer offer) {
|
||||||
|
|
||||||
// get arbitrator
|
// get arbitrator
|
||||||
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
||||||
if (arbitrator == null) return false; // invalid arbitrator
|
if (arbitrator == null) return false; // invalid arbitrator
|
||||||
|
|
||||||
// validate arbitrator signature
|
// validate arbitrator signature
|
||||||
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
|
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package bisq.core.offer;
|
package bisq.core.offer;
|
||||||
|
|
||||||
import bisq.core.api.CoreContext;
|
import bisq.core.api.CoreContext;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
@ -110,6 +111,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
private final KeyRing keyRing;
|
private final KeyRing keyRing;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final TradeWalletService tradeWalletService;
|
private final TradeWalletService tradeWalletService;
|
||||||
|
@ -144,6 +146,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
User user,
|
User user,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
|
CoreMoneroConnectionsService connectionService,
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
|
@ -163,6 +166,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.connectionService = connectionService;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.tradeWalletService = tradeWalletService;
|
this.tradeWalletService = tradeWalletService;
|
||||||
|
@ -751,8 +755,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow trade start if BitcoinJ is not fully synced (bisq issue #4764)
|
// Don't allow trade start if Monero node is not fully synced
|
||||||
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) {
|
if (!connectionService.isChainHeightSyncedWithinTolerance()) {
|
||||||
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
|
||||||
log.info(errorMessage);
|
log.info(errorMessage);
|
||||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.support;
|
package bisq.core.support;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.support.messages.ChatMessage;
|
import bisq.core.support.messages.ChatMessage;
|
||||||
import bisq.core.support.messages.SupportMessage;
|
import bisq.core.support.messages.SupportMessage;
|
||||||
|
@ -47,7 +47,7 @@ import javax.annotation.Nullable;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class SupportManager {
|
public abstract class SupportManager {
|
||||||
protected final P2PService p2PService;
|
protected final P2PService p2PService;
|
||||||
protected final WalletsSetup walletsSetup;
|
protected final CoreMoneroConnectionsService connectionService;
|
||||||
protected final Map<String, Timer> delayMsgMap = new HashMap<>();
|
protected final Map<String, Timer> delayMsgMap = new HashMap<>();
|
||||||
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedMailboxMessageWithPubKeys = new CopyOnWriteArraySet<>();
|
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedMailboxMessageWithPubKeys = new CopyOnWriteArraySet<>();
|
||||||
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>();
|
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>();
|
||||||
|
@ -59,12 +59,11 @@ public abstract class SupportManager {
|
||||||
// Constructor
|
// Constructor
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public SupportManager(P2PService p2PService, WalletsSetup walletsSetup) {
|
public SupportManager(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.connectionService = connectionService;
|
||||||
mailboxMessageService = p2PService.getMailboxMessageService();
|
mailboxMessageService = p2PService.getMailboxMessageService();
|
||||||
|
|
||||||
this.walletsSetup = walletsSetup;
|
|
||||||
|
|
||||||
// We get first the message handler called then the onBootstrapped
|
// We get first the message handler called then the onBootstrapped
|
||||||
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
|
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
|
||||||
// As decryptedDirectMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was
|
// As decryptedDirectMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was
|
||||||
|
@ -293,8 +292,8 @@ public abstract class SupportManager {
|
||||||
private boolean isReady() {
|
private boolean isReady() {
|
||||||
return allServicesInitialized &&
|
return allServicesInitialized &&
|
||||||
p2PService.isBootstrapped() &&
|
p2PService.isBootstrapped() &&
|
||||||
walletsSetup.isDownloadComplete() &&
|
connectionService.isDownloadComplete() &&
|
||||||
walletsSetup.hasSufficientPeersForBroadcast();
|
connectionService.hasSufficientPeersForBroadcast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.support.dispute;
|
package bisq.core.support.dispute;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.Restrictions;
|
import bisq.core.btc.wallet.Restrictions;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
@ -39,7 +39,6 @@ import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeDataValidation;
|
import bisq.core.trade.TradeDataValidation;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.closed.ClosedTradableManager;
|
import bisq.core.trade.closed.ClosedTradableManager;
|
||||||
|
|
||||||
import bisq.network.p2p.BootstrapListener;
|
import bisq.network.p2p.BootstrapListener;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
@ -111,7 +110,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
public DisputeManager(P2PService p2PService,
|
public DisputeManager(P2PService p2PService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
|
@ -119,7 +118,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
DisputeListService<T> disputeListService,
|
DisputeListService<T> disputeListService,
|
||||||
Config config,
|
Config config,
|
||||||
PriceFeedService priceFeedService) {
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, walletsSetup);
|
super(p2PService, connectionService);
|
||||||
|
|
||||||
this.tradeWalletService = tradeWalletService;
|
this.tradeWalletService = tradeWalletService;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
|
@ -252,13 +251,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
connectionService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (walletsSetup.isDownloadComplete())
|
if (connectionService.isDownloadComplete())
|
||||||
tryApplyMessages();
|
tryApplyMessages();
|
||||||
});
|
});
|
||||||
|
|
||||||
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (walletsSetup.hasSufficientPeersForBroadcast())
|
if (connectionService.hasSufficientPeersForBroadcast())
|
||||||
tryApplyMessages();
|
tryApplyMessages();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.support.dispute.arbitration;
|
package bisq.core.support.dispute.arbitration;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
@ -95,7 +95,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
public ArbitrationManager(P2PService p2PService,
|
public ArbitrationManager(P2PService p2PService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
XmrWalletService walletService,
|
XmrWalletService walletService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
|
@ -103,7 +103,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
ArbitrationDisputeListService arbitrationDisputeListService,
|
ArbitrationDisputeListService arbitrationDisputeListService,
|
||||||
Config config,
|
Config config,
|
||||||
PriceFeedService priceFeedService) {
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
|
||||||
openOfferManager, keyRing, arbitrationDisputeListService, config, priceFeedService);
|
openOfferManager, keyRing, arbitrationDisputeListService, config, priceFeedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,19 +365,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
cleanupRetryMap(uid);
|
cleanupRetryMap(uid);
|
||||||
|
|
||||||
// update multisig wallet
|
// update multisig wallet
|
||||||
|
// TODO: multisig wallet may already be deleted if peer completed trade with arbitrator. refactor trade completion?
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||||
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
if (multisigWallet != null) {
|
||||||
|
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
|
||||||
// parse payout tx
|
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
||||||
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
||||||
|
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
||||||
|
}
|
||||||
|
|
||||||
// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
|
// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
|
||||||
// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
|
// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
|
||||||
// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
|
// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
|
||||||
|
|
||||||
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
|
||||||
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
|
||||||
|
|
||||||
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
|
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
|
||||||
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
|
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
|
@ -436,7 +436,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
SupportType.ARBITRATION,
|
SupportType.ARBITRATION,
|
||||||
payoutTx.getTxSet().getMultisigTxHex());
|
payoutTx.getTxSet().getMultisigTxHex());
|
||||||
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), response.getUid());
|
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
|
||||||
p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
|
p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
|
||||||
senderPubKeyRing,
|
senderPubKeyRing,
|
||||||
response,
|
response,
|
||||||
|
@ -644,8 +644,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
numAttempts++;
|
numAttempts++;
|
||||||
payoutTx = multisigWallet.createTx(txConfig);
|
payoutTx = multisigWallet.createTx(txConfig);
|
||||||
} catch (MoneroError e) {
|
} catch (MoneroError e) {
|
||||||
System.out.println(e.toString());
|
|
||||||
System.out.println(e.getStackTrace());
|
|
||||||
// exception expected // TODO: better way of estimating fee?
|
// exception expected // TODO: better way of estimating fee?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -716,7 +714,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
|
|
||||||
// TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE
|
// TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE
|
||||||
|
|
||||||
|
|
||||||
// if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
// if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
|
||||||
|
|
||||||
// verify seller destination amount is payout amount - 1/2 tx costs
|
// verify seller destination amount is payout amount - 1/2 tx costs
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.support.dispute.mediation;
|
package bisq.core.support.dispute.mediation;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
@ -77,7 +77,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
||||||
public MediationManager(P2PService p2PService,
|
public MediationManager(P2PService p2PService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
XmrWalletService walletService,
|
XmrWalletService walletService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
|
@ -85,7 +85,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
||||||
MediationDisputeListService mediationDisputeListService,
|
MediationDisputeListService mediationDisputeListService,
|
||||||
Config config,
|
Config config,
|
||||||
PriceFeedService priceFeedService) {
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
|
||||||
openOfferManager, keyRing, mediationDisputeListService, config, priceFeedService);
|
openOfferManager, keyRing, mediationDisputeListService, config, priceFeedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.support.dispute.refund;
|
package bisq.core.support.dispute.refund;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
@ -71,7 +71,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
||||||
public RefundManager(P2PService p2PService,
|
public RefundManager(P2PService p2PService,
|
||||||
TradeWalletService tradeWalletService,
|
TradeWalletService tradeWalletService,
|
||||||
XmrWalletService walletService,
|
XmrWalletService walletService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
|
@ -80,7 +80,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
||||||
RefundDisputeListService refundDisputeListService,
|
RefundDisputeListService refundDisputeListService,
|
||||||
Config config,
|
Config config,
|
||||||
PriceFeedService priceFeedService) {
|
PriceFeedService priceFeedService) {
|
||||||
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
|
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
|
||||||
openOfferManager, keyRing, refundDisputeListService, config, priceFeedService);
|
openOfferManager, keyRing, refundDisputeListService, config, priceFeedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.support.traderchat;
|
package bisq.core.support.traderchat;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.support.SupportManager;
|
import bisq.core.support.SupportManager;
|
||||||
import bisq.core.support.SupportType;
|
import bisq.core.support.SupportType;
|
||||||
|
@ -31,6 +31,7 @@ import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.crypto.PubKeyRingProvider;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -46,7 +47,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class TraderChatManager extends SupportManager {
|
public class TraderChatManager extends SupportManager {
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRingProvider pubKeyRingProvider;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -55,12 +56,12 @@ public class TraderChatManager extends SupportManager {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TraderChatManager(P2PService p2PService,
|
public TraderChatManager(P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
PubKeyRing pubKeyRing) {
|
PubKeyRingProvider pubKeyRingProvider) {
|
||||||
super(p2PService, walletsSetup);
|
super(p2PService, connectionService);
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,7 +83,7 @@ public class TraderChatManager extends SupportManager {
|
||||||
public NodeAddress getPeerNodeAddress(ChatMessage message) {
|
public NodeAddress getPeerNodeAddress(ChatMessage message) {
|
||||||
return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
|
return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
|
||||||
if (trade.getContract() != null) {
|
if (trade.getContract() != null) {
|
||||||
return trade.getContract().getPeersNodeAddress(pubKeyRing);
|
return trade.getContract().getPeersNodeAddress(pubKeyRingProvider.get());
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +94,7 @@ public class TraderChatManager extends SupportManager {
|
||||||
public PubKeyRing getPeerPubKeyRing(ChatMessage message) {
|
public PubKeyRing getPeerPubKeyRing(ChatMessage message) {
|
||||||
return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
|
return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
|
||||||
if (trade.getContract() != null) {
|
if (trade.getContract() != null) {
|
||||||
return trade.getContract().getPeersPubKeyRing(pubKeyRing);
|
return trade.getContract().getPeersPubKeyRing(pubKeyRingProvider.get());
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -139,11 +140,13 @@ public class TraderChatManager extends SupportManager {
|
||||||
// API
|
// API
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onAllServicesInitialized() {
|
public void onAllServicesInitialized() {
|
||||||
super.onAllServicesInitialized();
|
super.onAllServicesInitialized();
|
||||||
tryApplyMessages();
|
tryApplyMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void onSupportMessage(SupportMessage message) {
|
public void onSupportMessage(SupportMessage message) {
|
||||||
if (canProcessMessage(message)) {
|
if (canProcessMessage(message)) {
|
||||||
log.info("Received {} with tradeId {} and uid {}",
|
log.info("Received {} with tradeId {} and uid {}",
|
||||||
|
|
|
@ -1108,7 +1108,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy arbitration is not handled anymore as not used anymore.
|
// check for closed disputed case
|
||||||
|
if (disputeState == DisputeState.DISPUTE_CLOSED) return false;
|
||||||
|
|
||||||
// In mediation case we check for the mediationResultState. As there are multiple sub-states we use ordinal.
|
// In mediation case we check for the mediationResultState. As there are multiple sub-states we use ordinal.
|
||||||
if (disputeState == DisputeState.MEDIATION_CLOSED) {
|
if (disputeState == DisputeState.MEDIATION_CLOSED) {
|
||||||
|
|
|
@ -118,10 +118,9 @@ import javax.annotation.Nullable;
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
|
|
||||||
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
|
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
|
||||||
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
|
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
|
||||||
|
|
||||||
|
@ -283,6 +282,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
|
getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
|
||||||
onTradesChanged();
|
onTradesChanged();
|
||||||
|
|
||||||
|
xmrWalletService.setTradeManager(this);
|
||||||
xmrWalletService.getAddressEntriesForAvailableBalanceStream()
|
xmrWalletService.getAddressEntriesForAvailableBalanceStream()
|
||||||
.filter(addressEntry -> addressEntry.getOfferId() != null)
|
.filter(addressEntry -> addressEntry.getOfferId() != null)
|
||||||
.forEach(addressEntry -> {
|
.forEach(addressEntry -> {
|
||||||
|
@ -1014,7 +1014,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
}
|
}
|
||||||
|
|
||||||
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
||||||
xmrWalletService.deleteMultisigWallet(trade.getId());
|
xmrWalletService.deleteMultisigWallet(trade.getId()); // TODO (woodser): don't delete multisig wallet until payout tx unlocked?
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.trade.ArbitratorTrade;
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
import bisq.core.trade.MakerTrade;
|
import bisq.core.trade.MakerTrade;
|
||||||
import bisq.core.trade.TakerTrade;
|
import bisq.core.trade.TakerTrade;
|
||||||
|
@ -68,6 +69,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
||||||
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
|
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
|
||||||
checkNotNull(request);
|
checkNotNull(request);
|
||||||
checkTradeId(processModel.getOfferId(), request);
|
checkTradeId(processModel.getOfferId(), request);
|
||||||
|
XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService();
|
||||||
|
|
||||||
System.out.println("PROCESS MULTISIG MESSAGE");
|
System.out.println("PROCESS MULTISIG MESSAGE");
|
||||||
System.out.println(request);
|
System.out.println(request);
|
||||||
|
@ -98,18 +100,18 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
||||||
boolean updateParticipants = false;
|
boolean updateParticipants = false;
|
||||||
if (processModel.getPreparedMultisigHex() == null) {
|
if (processModel.getPreparedMultisigHex() == null) {
|
||||||
System.out.println("Preparing multisig wallet!");
|
System.out.println("Preparing multisig wallet!");
|
||||||
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId());
|
multisigWallet = xmrWalletService.createMultisigWallet(trade.getId());
|
||||||
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
||||||
updateParticipants = true;
|
updateParticipants = true;
|
||||||
} else {
|
} else {
|
||||||
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
|
multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// make multisig if applicable
|
// make multisig if applicable
|
||||||
TradingPeer[] peers = getMultisigPeers();
|
TradingPeer[] peers = getMultisigPeers();
|
||||||
if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) {
|
if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) {
|
||||||
System.out.println("Making multisig wallet!");
|
System.out.println("Making multisig wallet!");
|
||||||
MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, "abctesting123"); // TODO (woodser): move this to config
|
MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, xmrWalletService.getWalletPassword()); // TODO (woodser): xmrWalletService.makeMultisig(tradeId, multisigHexes, threshold)?
|
||||||
processModel.setMadeMultisigHex(result.getMultisigHex());
|
processModel.setMadeMultisigHex(result.getMultisigHex());
|
||||||
updateParticipants = true;
|
updateParticipants = true;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +119,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
||||||
// exchange multisig keys if applicable
|
// exchange multisig keys if applicable
|
||||||
if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
|
if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
|
||||||
System.out.println("Exchanging multisig wallet!");
|
System.out.println("Exchanging multisig wallet!");
|
||||||
multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), "abctesting123"); // TODO (woodser): move this to config
|
multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), xmrWalletService.getWalletPassword());
|
||||||
processModel.setMultisigSetupComplete(true);
|
processModel.setMultisigSetupComplete(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
package bisq.core.trade.txproof.xmr;
|
package bisq.core.trade.txproof.xmr;
|
||||||
|
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.filter.FilterManager;
|
import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.support.dispute.mediation.MediationManager;
|
import bisq.core.support.dispute.mediation.MediationManager;
|
||||||
|
@ -76,7 +76,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
||||||
private final MediationManager mediationManager;
|
private final MediationManager mediationManager;
|
||||||
private final RefundManager refundManager;
|
private final RefundManager refundManager;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final Socks5ProxyProvider socks5ProxyProvider;
|
private final Socks5ProxyProvider socks5ProxyProvider;
|
||||||
private final Map<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>();
|
private final Map<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>();
|
||||||
private AutoConfirmSettings autoConfirmSettings;
|
private AutoConfirmSettings autoConfirmSettings;
|
||||||
|
@ -101,7 +101,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
||||||
MediationManager mediationManager,
|
MediationManager mediationManager,
|
||||||
RefundManager refundManager,
|
RefundManager refundManager,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
Socks5ProxyProvider socks5ProxyProvider) {
|
Socks5ProxyProvider socks5ProxyProvider) {
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
|
@ -111,7 +111,7 @@ public class XmrTxProofService implements AssetTxProofService {
|
||||||
this.mediationManager = mediationManager;
|
this.mediationManager = mediationManager;
|
||||||
this.refundManager = refundManager;
|
this.refundManager = refundManager;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.socks5ProxyProvider = socks5ProxyProvider;
|
this.socks5ProxyProvider = socks5ProxyProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,32 +289,32 @@ public class XmrTxProofService implements AssetTxProofService {
|
||||||
|
|
||||||
private BooleanProperty isXmrBlockDownloadComplete() {
|
private BooleanProperty isXmrBlockDownloadComplete() {
|
||||||
BooleanProperty result = new SimpleBooleanProperty();
|
BooleanProperty result = new SimpleBooleanProperty();
|
||||||
if (walletsSetup.isDownloadComplete()) {
|
if (connectionService.isDownloadComplete()) {
|
||||||
result.set(true);
|
result.set(true);
|
||||||
} else {
|
} else {
|
||||||
xmrBlockListener = (observable, oldValue, newValue) -> {
|
xmrBlockListener = (observable, oldValue, newValue) -> {
|
||||||
if (walletsSetup.isDownloadComplete()) {
|
if (connectionService.isDownloadComplete()) {
|
||||||
walletsSetup.downloadPercentageProperty().removeListener(xmrBlockListener);
|
connectionService.downloadPercentageProperty().removeListener(xmrBlockListener);
|
||||||
result.set(true);
|
result.set(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
walletsSetup.downloadPercentageProperty().addListener(xmrBlockListener);
|
connectionService.downloadPercentageProperty().addListener(xmrBlockListener);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BooleanProperty hasSufficientXmrPeers() {
|
private BooleanProperty hasSufficientXmrPeers() {
|
||||||
BooleanProperty result = new SimpleBooleanProperty();
|
BooleanProperty result = new SimpleBooleanProperty();
|
||||||
if (walletsSetup.hasSufficientPeersForBroadcast()) {
|
if (connectionService.hasSufficientPeersForBroadcast()) {
|
||||||
result.set(true);
|
result.set(true);
|
||||||
} else {
|
} else {
|
||||||
xmrPeersListener = (observable, oldValue, newValue) -> {
|
xmrPeersListener = (observable, oldValue, newValue) -> {
|
||||||
if (walletsSetup.hasSufficientPeersForBroadcast()) {
|
if (connectionService.hasSufficientPeersForBroadcast()) {
|
||||||
walletsSetup.numPeersProperty().removeListener(xmrPeersListener);
|
connectionService.numPeersProperty().removeListener(xmrPeersListener);
|
||||||
result.set(true);
|
result.set(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
walletsSetup.numPeersProperty().addListener(xmrPeersListener);
|
connectionService.numPeersProperty().addListener(xmrPeersListener);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -399,6 +399,7 @@ public class User implements PersistedDataHost {
|
||||||
return userPayload.getPaymentAccounts();
|
return userPayload.getPaymentAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() {
|
public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() {
|
||||||
return paymentAccountsAsObservable;
|
return paymentAccountsAsObservable;
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,8 +162,8 @@ public class AccountAgeWitnessServiceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testArbitratorSignWitness() {
|
public void testArbitratorSignWitness() {
|
||||||
KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1));
|
KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1), null, true);
|
||||||
KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2));
|
KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2), null, true);
|
||||||
|
|
||||||
// Setup dispute for arbitrator to sign both sides
|
// Setup dispute for arbitrator to sign both sides
|
||||||
List<Dispute> disputes = new ArrayList<>();
|
List<Dispute> disputes = new ArrayList<>();
|
||||||
|
@ -278,9 +278,9 @@ public class AccountAgeWitnessServiceTest {
|
||||||
public void testArbitratorSignDummyWitness() throws CryptoException {
|
public void testArbitratorSignDummyWitness() throws CryptoException {
|
||||||
ECKey arbitratorKey = new ECKey();
|
ECKey arbitratorKey = new ECKey();
|
||||||
// Init 2 user accounts
|
// Init 2 user accounts
|
||||||
var user1KeyRing = new KeyRing(new KeyStorage(dir1));
|
var user1KeyRing = new KeyRing(new KeyStorage(dir1), null, true);
|
||||||
var user2KeyRing = new KeyRing(new KeyStorage(dir2));
|
var user2KeyRing = new KeyRing(new KeyStorage(dir2), null, true);
|
||||||
var user3KeyRing = new KeyRing(new KeyStorage(dir3));
|
var user3KeyRing = new KeyRing(new KeyStorage(dir3), null, true);
|
||||||
var pubKeyRing1 = user1KeyRing.getPubKeyRing();
|
var pubKeyRing1 = user1KeyRing.getPubKeyRing();
|
||||||
var pubKeyRing2 = user2KeyRing.getPubKeyRing();
|
var pubKeyRing2 = user2KeyRing.getPubKeyRing();
|
||||||
var pubKeyRing3 = user3KeyRing.getPubKeyRing();
|
var pubKeyRing3 = user3KeyRing.getPubKeyRing();
|
||||||
|
|
|
@ -49,7 +49,7 @@ public class EncryptionTest {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
dir.mkdir();
|
dir.mkdir();
|
||||||
KeyStorage keyStorage = new KeyStorage(dir);
|
KeyStorage keyStorage = new KeyStorage(dir);
|
||||||
keyRing = new KeyRing(keyStorage);
|
keyRing = new KeyRing(keyStorage, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
|
@ -51,7 +51,7 @@ public class SigTest {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
dir.mkdir();
|
dir.mkdir();
|
||||||
KeyStorage keyStorage = new KeyStorage(dir);
|
KeyStorage keyStorage = new KeyStorage(dir);
|
||||||
keyRing = new KeyRing(keyStorage);
|
keyRing = new KeyRing(keyStorage, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
|
@ -60,6 +60,7 @@ public class OpenOfferManagerTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
offerBookService,
|
offerBookService,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -106,6 +107,7 @@ public class OpenOfferManagerTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
offerBookService,
|
offerBookService,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -146,6 +148,7 @@ public class OpenOfferManagerTest {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
offerBookService,
|
offerBookService,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
64
daemon/src/main/java/bisq/daemon/app/ConsoleInput.java
Normal file
64
daemon/src/main/java/bisq/daemon/app/ConsoleInput.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,21 +19,24 @@ package bisq.daemon.app;
|
||||||
|
|
||||||
import bisq.core.app.HavenoHeadlessAppMain;
|
import bisq.core.app.HavenoHeadlessAppMain;
|
||||||
import bisq.core.app.HavenoSetup;
|
import bisq.core.app.HavenoSetup;
|
||||||
|
import bisq.core.api.AccountServiceListener;
|
||||||
import bisq.core.app.CoreModule;
|
import bisq.core.app.CoreModule;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.app.AppModule;
|
import bisq.common.app.AppModule;
|
||||||
|
import bisq.common.crypto.IncorrectPasswordException;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
|
import java.io.Console;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.daemon.grpc.GrpcServer;
|
import bisq.daemon.grpc.GrpcServer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -61,7 +64,6 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
|
||||||
@Override
|
@Override
|
||||||
protected void launchApplication() {
|
protected void launchApplication() {
|
||||||
headlessApp = new HavenoDaemon();
|
headlessApp = new HavenoDaemon();
|
||||||
|
|
||||||
UserThread.execute(this::onApplicationLaunched);
|
UserThread.execute(this::onApplicationLaunched);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,15 +103,116 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
|
||||||
@Override
|
@Override
|
||||||
protected void onApplicationStarted() {
|
protected void onApplicationStarted() {
|
||||||
super.onApplicationStarted();
|
super.onApplicationStarted();
|
||||||
|
|
||||||
grpcServer = injector.getInstance(GrpcServer.class);
|
|
||||||
grpcServer.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void gracefulShutDown(ResultHandler resultHandler) {
|
public void gracefulShutDown(ResultHandler resultHandler) {
|
||||||
super.gracefulShutDown(resultHandler);
|
super.gracefulShutDown(resultHandler);
|
||||||
|
if (grpcServer != null) grpcServer.shutdown(); // could be null if application attempted to shutdown early
|
||||||
|
}
|
||||||
|
|
||||||
grpcServer.shutdown();
|
/**
|
||||||
|
* Start the grpcServer to allow logging in remotely.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean loginAccount() {
|
||||||
|
boolean opened = super.loginAccount();
|
||||||
|
|
||||||
|
// Start rpc server in case login is coming in from rpc
|
||||||
|
grpcServer = injector.getInstance(GrpcServer.class);
|
||||||
|
grpcServer.start();
|
||||||
|
|
||||||
|
if (!opened) {
|
||||||
|
// Nonblocking, we need to stop if the login occurred through rpc.
|
||||||
|
// TODO: add a mode to mask password
|
||||||
|
ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
|
||||||
|
Thread t = new Thread(() -> {
|
||||||
|
interactiveLogin(reader);
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
|
||||||
|
// Handle asynchronous account opens.
|
||||||
|
// Will need to also close and reopen account.
|
||||||
|
AccountServiceListener accountListener = new AccountServiceListener() {
|
||||||
|
@Override public void onAccountCreated() { onLogin(); }
|
||||||
|
@Override public void onAccountOpened() { onLogin(); }
|
||||||
|
private void onLogin() {
|
||||||
|
log.info("Logged in successfully");
|
||||||
|
reader.cancel(); // closing the reader will stop all read attempts and end the interactive login thread
|
||||||
|
}
|
||||||
|
};
|
||||||
|
accountService.addListener(accountListener);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Wait until interactive login or rpc. Check one more time if account is open to close race condition.
|
||||||
|
if (!accountService.isAccountOpen()) {
|
||||||
|
log.info("Interactive login required");
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
accountService.removeListener(accountListener);
|
||||||
|
opened = accountService.isAccountOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks user for login. TODO: Implement in the desktop app.
|
||||||
|
* @return True if user logged in interactively.
|
||||||
|
*/
|
||||||
|
protected boolean interactiveLogin(ConsoleInput reader) {
|
||||||
|
Console console = System.console();
|
||||||
|
if (console == null) {
|
||||||
|
// The ConsoleInput class reads from system.in, can wait for input without a console.
|
||||||
|
log.info("No console available, account must be opened through rpc");
|
||||||
|
try {
|
||||||
|
// If user logs in through rpc, the reader will be interrupted through the event.
|
||||||
|
reader.readLine();
|
||||||
|
} catch (InterruptedException | CancellationException ex) {
|
||||||
|
log.info("Reader interrupted, continuing startup");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String openedOrCreated = "Account unlocked\n";
|
||||||
|
boolean accountExists = accountService.accountExists();
|
||||||
|
while (!accountService.isAccountOpen()) {
|
||||||
|
try {
|
||||||
|
if (accountExists) {
|
||||||
|
try {
|
||||||
|
// readPassword will not return until the user inputs something
|
||||||
|
// which is not suitable if we are waiting for rpc call which
|
||||||
|
// could login the account. Must be able to interrupt the read.
|
||||||
|
//new String(console.readPassword("Password:"));
|
||||||
|
System.out.printf("Password:\n");
|
||||||
|
String password = reader.readLine();
|
||||||
|
accountService.openAccount(password);
|
||||||
|
} catch (IncorrectPasswordException ipe) {
|
||||||
|
System.out.printf("Incorrect password\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.printf("Creating a new account\n");
|
||||||
|
System.out.printf("Password:\n");
|
||||||
|
String password = reader.readLine();
|
||||||
|
System.out.printf("Confirm:\n");
|
||||||
|
String passwordConfirm = reader.readLine();
|
||||||
|
if (password.equals(passwordConfirm)) {
|
||||||
|
accountService.createAccount(password);
|
||||||
|
openedOrCreated = "Account created\n";
|
||||||
|
} else {
|
||||||
|
System.out.printf("Passwords did not match\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.debug(ex.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.printf(openedOrCreated);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
286
daemon/src/main/java/bisq/daemon/grpc/GrpcAccountService.java
Normal file
286
daemon/src/main/java/bisq/daemon/grpc/GrpcAccountService.java
Normal 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));
|
||||||
|
}}
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,9 +42,9 @@ import bisq.proto.grpc.StartCheckingConnectionsReply;
|
||||||
import bisq.proto.grpc.StartCheckingConnectionsRequest;
|
import bisq.proto.grpc.StartCheckingConnectionsRequest;
|
||||||
import bisq.proto.grpc.StopCheckingConnectionsReply;
|
import bisq.proto.grpc.StopCheckingConnectionsReply;
|
||||||
import bisq.proto.grpc.StopCheckingConnectionsRequest;
|
import bisq.proto.grpc.StopCheckingConnectionsRequest;
|
||||||
import bisq.proto.grpc.UriConnection;
|
import bisq.proto.grpc.UrlConnection;
|
||||||
import java.net.URI;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URL;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -84,7 +84,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
public void removeConnection(RemoveConnectionRequest request,
|
public void removeConnection(RemoveConnectionRequest request,
|
||||||
StreamObserver<RemoveConnectionReply> responseObserver) {
|
StreamObserver<RemoveConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
coreApi.removeMoneroConnection(validateUri(request.getUri()));
|
coreApi.removeMoneroConnection(validateUri(request.getUrl()));
|
||||||
return RemoveConnectionReply.newBuilder().build();
|
return RemoveConnectionReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
public void getConnection(GetConnectionRequest request,
|
public void getConnection(GetConnectionRequest request,
|
||||||
StreamObserver<GetConnectionReply> responseObserver) {
|
StreamObserver<GetConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
UriConnection replyConnection = toUriConnection(coreApi.getMoneroConnection());
|
UrlConnection replyConnection = toUrlConnection(coreApi.getMoneroConnection());
|
||||||
GetConnectionReply.Builder builder = GetConnectionReply.newBuilder();
|
GetConnectionReply.Builder builder = GetConnectionReply.newBuilder();
|
||||||
if (replyConnection != null) {
|
if (replyConnection != null) {
|
||||||
builder.setConnection(replyConnection);
|
builder.setConnection(replyConnection);
|
||||||
|
@ -107,8 +107,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
StreamObserver<GetConnectionsReply> responseObserver) {
|
StreamObserver<GetConnectionsReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
List<MoneroRpcConnection> connections = coreApi.getMoneroConnections();
|
List<MoneroRpcConnection> connections = coreApi.getMoneroConnections();
|
||||||
List<UriConnection> replyConnections = connections.stream()
|
List<UrlConnection> replyConnections = connections.stream()
|
||||||
.map(GrpcMoneroConnectionsService::toUriConnection).collect(Collectors.toList());
|
.map(GrpcMoneroConnectionsService::toUrlConnection).collect(Collectors.toList());
|
||||||
return GetConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
|
return GetConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -117,8 +117,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
public void setConnection(SetConnectionRequest request,
|
public void setConnection(SetConnectionRequest request,
|
||||||
StreamObserver<SetConnectionReply> responseObserver) {
|
StreamObserver<SetConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
if (request.getUri() != null && !request.getUri().isEmpty())
|
if (request.getUrl() != null && !request.getUrl().isEmpty())
|
||||||
coreApi.setMoneroConnection(validateUri(request.getUri()));
|
coreApi.setMoneroConnection(validateUri(request.getUrl()));
|
||||||
else if (request.hasConnection())
|
else if (request.hasConnection())
|
||||||
coreApi.setMoneroConnection(toMoneroRpcConnection(request.getConnection()));
|
coreApi.setMoneroConnection(toMoneroRpcConnection(request.getConnection()));
|
||||||
else coreApi.setMoneroConnection((MoneroRpcConnection) null); // disconnect from client
|
else coreApi.setMoneroConnection((MoneroRpcConnection) null); // disconnect from client
|
||||||
|
@ -131,7 +131,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
StreamObserver<CheckConnectionReply> responseObserver) {
|
StreamObserver<CheckConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
MoneroRpcConnection connection = coreApi.checkMoneroConnection();
|
MoneroRpcConnection connection = coreApi.checkMoneroConnection();
|
||||||
UriConnection replyConnection = toUriConnection(connection);
|
UrlConnection replyConnection = toUrlConnection(connection);
|
||||||
CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder();
|
CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder();
|
||||||
if (replyConnection != null) {
|
if (replyConnection != null) {
|
||||||
builder.setConnection(replyConnection);
|
builder.setConnection(replyConnection);
|
||||||
|
@ -145,8 +145,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
StreamObserver<CheckConnectionsReply> responseObserver) {
|
StreamObserver<CheckConnectionsReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
List<MoneroRpcConnection> connections = coreApi.checkMoneroConnections();
|
List<MoneroRpcConnection> connections = coreApi.checkMoneroConnections();
|
||||||
List<UriConnection> replyConnections = connections.stream()
|
List<UrlConnection> replyConnections = connections.stream()
|
||||||
.map(GrpcMoneroConnectionsService::toUriConnection).collect(Collectors.toList());
|
.map(GrpcMoneroConnectionsService::toUrlConnection).collect(Collectors.toList());
|
||||||
return CheckConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
|
return CheckConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
StreamObserver<GetBestAvailableConnectionReply> responseObserver) {
|
StreamObserver<GetBestAvailableConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
MoneroRpcConnection connection = coreApi.getBestAvailableMoneroConnection();
|
MoneroRpcConnection connection = coreApi.getBestAvailableMoneroConnection();
|
||||||
UriConnection replyConnection = toUriConnection(connection);
|
UrlConnection replyConnection = toUrlConnection(connection);
|
||||||
GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder();
|
GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder();
|
||||||
if (replyConnection != null) {
|
if (replyConnection != null) {
|
||||||
builder.setConnection(replyConnection);
|
builder.setConnection(replyConnection);
|
||||||
|
@ -211,43 +211,40 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static UriConnection toUriConnection(MoneroRpcConnection rpcConnection) {
|
private static UrlConnection toUrlConnection(MoneroRpcConnection rpcConnection) {
|
||||||
if (rpcConnection == null) return null;
|
if (rpcConnection == null) return null;
|
||||||
return UriConnection.newBuilder()
|
return UrlConnection.newBuilder()
|
||||||
.setUri(rpcConnection.getUri())
|
.setUrl(rpcConnection.getUri())
|
||||||
.setPriority(rpcConnection.getPriority())
|
.setPriority(rpcConnection.getPriority())
|
||||||
.setOnlineStatus(toOnlineStatus(rpcConnection.isOnline()))
|
.setOnlineStatus(toOnlineStatus(rpcConnection.isOnline()))
|
||||||
.setAuthenticationStatus(toAuthenticationStatus(rpcConnection.isAuthenticated()))
|
.setAuthenticationStatus(toAuthenticationStatus(rpcConnection.isAuthenticated()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UriConnection.AuthenticationStatus toAuthenticationStatus(Boolean authenticated) {
|
private static UrlConnection.AuthenticationStatus toAuthenticationStatus(Boolean authenticated) {
|
||||||
if (authenticated == null) return UriConnection.AuthenticationStatus.NO_AUTHENTICATION;
|
if (authenticated == null) return UrlConnection.AuthenticationStatus.NO_AUTHENTICATION;
|
||||||
else if (authenticated) return UriConnection.AuthenticationStatus.AUTHENTICATED;
|
else if (authenticated) return UrlConnection.AuthenticationStatus.AUTHENTICATED;
|
||||||
else return UriConnection.AuthenticationStatus.NOT_AUTHENTICATED;
|
else return UrlConnection.AuthenticationStatus.NOT_AUTHENTICATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UriConnection.OnlineStatus toOnlineStatus(Boolean online) {
|
private static UrlConnection.OnlineStatus toOnlineStatus(Boolean online) {
|
||||||
if (online == null) return UriConnection.OnlineStatus.UNKNOWN;
|
if (online == null) return UrlConnection.OnlineStatus.UNKNOWN;
|
||||||
else if (online) return UriConnection.OnlineStatus.ONLINE;
|
else if (online) return UrlConnection.OnlineStatus.ONLINE;
|
||||||
else return UriConnection.OnlineStatus.OFFLINE;
|
else return UrlConnection.OnlineStatus.OFFLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MoneroRpcConnection toMoneroRpcConnection(UriConnection uriConnection) throws URISyntaxException {
|
private static MoneroRpcConnection toMoneroRpcConnection(UrlConnection uriConnection) throws MalformedURLException {
|
||||||
if (uriConnection == null) return null;
|
if (uriConnection == null) return null;
|
||||||
return new MoneroRpcConnection(
|
return new MoneroRpcConnection(
|
||||||
validateUri(uriConnection.getUri()),
|
validateUri(uriConnection.getUrl()),
|
||||||
nullIfEmpty(uriConnection.getUsername()),
|
nullIfEmpty(uriConnection.getUsername()),
|
||||||
nullIfEmpty(uriConnection.getPassword()))
|
nullIfEmpty(uriConnection.getPassword()))
|
||||||
.setPriority(uriConnection.getPriority());
|
.setPriority(uriConnection.getPriority());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String validateUri(String uri) throws URISyntaxException {
|
private static String validateUri(String url) throws MalformedURLException {
|
||||||
if (uri.isEmpty()) {
|
if (url.isEmpty()) throw new IllegalArgumentException("URL is required");
|
||||||
throw new IllegalArgumentException("URI is required");
|
return new URL(url).toString(); // validate and return
|
||||||
}
|
|
||||||
// Create new URI for validation, internally String is used again
|
|
||||||
return new URI(uri).toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String nullIfEmpty(String value) {
|
private static String nullIfEmpty(String value) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class GrpcServer {
|
||||||
public GrpcServer(CoreContext coreContext,
|
public GrpcServer(CoreContext coreContext,
|
||||||
Config config,
|
Config config,
|
||||||
PasswordAuthInterceptor passwordAuthInterceptor,
|
PasswordAuthInterceptor passwordAuthInterceptor,
|
||||||
|
GrpcAccountService accountService,
|
||||||
GrpcDisputeAgentsService disputeAgentsService,
|
GrpcDisputeAgentsService disputeAgentsService,
|
||||||
GrpcHelpService helpService,
|
GrpcHelpService helpService,
|
||||||
GrpcOffersService offersService,
|
GrpcOffersService offersService,
|
||||||
|
@ -63,6 +64,7 @@ public class GrpcServer {
|
||||||
GrpcMoneroConnectionsService moneroConnectionsService) {
|
GrpcMoneroConnectionsService moneroConnectionsService) {
|
||||||
this.server = ServerBuilder.forPort(config.apiPort)
|
this.server = ServerBuilder.forPort(config.apiPort)
|
||||||
.executor(UserThread.getExecutor())
|
.executor(UserThread.getExecutor())
|
||||||
|
.addService(interceptForward(accountService, accountService.interceptors()))
|
||||||
.addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors()))
|
.addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors()))
|
||||||
.addService(interceptForward(helpService, helpService.interceptors()))
|
.addService(interceptForward(helpService, helpService.interceptors()))
|
||||||
.addService(interceptForward(offersService, offersService.interceptors()))
|
.addService(interceptForward(offersService, offersService.interceptors()))
|
||||||
|
|
|
@ -48,9 +48,14 @@ class GrpcShutdownService extends ShutdownServerGrpc.ShutdownServerImplBase {
|
||||||
StreamObserver<StopReply> responseObserver) {
|
StreamObserver<StopReply> responseObserver) {
|
||||||
try {
|
try {
|
||||||
log.info("Shutdown request received.");
|
log.info("Shutdown request received.");
|
||||||
var reply = StopReply.newBuilder().build();
|
HavenoHeadlessApp.setOnGracefulShutDownHandler(new Runnable() {
|
||||||
responseObserver.onNext(reply);
|
@Override
|
||||||
responseObserver.onCompleted();
|
public void run() {
|
||||||
|
var reply = StopReply.newBuilder().build();
|
||||||
|
responseObserver.onNext(reply);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
}
|
||||||
|
});
|
||||||
UserThread.runAfter(HavenoHeadlessApp.getShutDownHandler(), 500, MILLISECONDS);
|
UserThread.runAfter(HavenoHeadlessApp.getShutDownHandler(), 500, MILLISECONDS);
|
||||||
} catch (Throwable cause) {
|
} catch (Throwable cause) {
|
||||||
exceptionHandler.handleException(log, cause, responseObserver);
|
exceptionHandler.handleException(log, cause, responseObserver);
|
||||||
|
|
|
@ -40,9 +40,9 @@ import bisq.desktop.util.GUIUtil;
|
||||||
import bisq.core.account.sign.SignedWitnessService;
|
import bisq.core.account.sign.SignedWitnessService;
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.alert.PrivateNotificationManager;
|
import bisq.core.alert.PrivateNotificationManager;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.app.HavenoSetup;
|
import bisq.core.app.HavenoSetup;
|
||||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.locale.CryptoCurrency;
|
import bisq.core.locale.CryptoCurrency;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
|
@ -53,7 +53,6 @@ import bisq.core.payment.AliPayAccount;
|
||||||
import bisq.core.payment.AmazonGiftCardAccount;
|
import bisq.core.payment.AmazonGiftCardAccount;
|
||||||
import bisq.core.payment.CryptoCurrencyAccount;
|
import bisq.core.payment.CryptoCurrencyAccount;
|
||||||
import bisq.core.payment.RevolutAccount;
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.payment.payload.AssetsAccountPayload;
|
|
||||||
import bisq.core.presentation.BalancePresentation;
|
import bisq.core.presentation.BalancePresentation;
|
||||||
import bisq.core.presentation.SupportTicketsPresentation;
|
import bisq.core.presentation.SupportTicketsPresentation;
|
||||||
import bisq.core.presentation.TradePresentation;
|
import bisq.core.presentation.TradePresentation;
|
||||||
|
@ -109,7 +108,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener {
|
public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener {
|
||||||
private final HavenoSetup bisqSetup;
|
private final HavenoSetup bisqSetup;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final BalancePresentation balancePresentation;
|
private final BalancePresentation balancePresentation;
|
||||||
private final TradePresentation tradePresentation;
|
private final TradePresentation tradePresentation;
|
||||||
|
@ -140,7 +139,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||||
private final DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1);
|
private final DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1);
|
||||||
private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty();
|
private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty();
|
||||||
private final StringProperty footerVersionInfo = new SimpleStringProperty();
|
private final StringProperty footerVersionInfo = new SimpleStringProperty();
|
||||||
private Timer checkNumberOfBtcPeersTimer;
|
private Timer checkNumberOfXmrPeersTimer;
|
||||||
private Timer checkNumberOfP2pNetworkPeersTimer;
|
private Timer checkNumberOfP2pNetworkPeersTimer;
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private MonadicBinding<Boolean> tradesAndUIReady;
|
private MonadicBinding<Boolean> tradesAndUIReady;
|
||||||
|
@ -153,7 +152,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MainViewModel(HavenoSetup bisqSetup,
|
public MainViewModel(HavenoSetup bisqSetup,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
User user,
|
User user,
|
||||||
BalancePresentation balancePresentation,
|
BalancePresentation balancePresentation,
|
||||||
|
@ -178,7 +177,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||||
TorNetworkSettingsWindow torNetworkSettingsWindow,
|
TorNetworkSettingsWindow torNetworkSettingsWindow,
|
||||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||||
this.bisqSetup = bisqSetup;
|
this.bisqSetup = bisqSetup;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.balancePresentation = balancePresentation;
|
this.balancePresentation = balancePresentation;
|
||||||
this.tradePresentation = tradePresentation;
|
this.tradePresentation = tradePresentation;
|
||||||
|
@ -258,7 +257,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||||
});
|
});
|
||||||
|
|
||||||
setupP2PNumPeersWatcher();
|
setupP2PNumPeersWatcher();
|
||||||
setupBtcNumPeersWatcher();
|
setupXmrNumPeersWatcher();
|
||||||
|
|
||||||
marketPricePresentation.setup();
|
marketPricePresentation.setup();
|
||||||
accountPresentation.setup();
|
accountPresentation.setup();
|
||||||
|
@ -509,19 +508,19 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupBtcNumPeersWatcher() {
|
private void setupXmrNumPeersWatcher() {
|
||||||
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
int numPeers = (int) newValue;
|
int numPeers = (int) newValue;
|
||||||
if ((int) oldValue > 0 && numPeers == 0) {
|
if ((int) oldValue > 0 && numPeers == 0) {
|
||||||
if (checkNumberOfBtcPeersTimer != null)
|
if (checkNumberOfXmrPeersTimer != null)
|
||||||
checkNumberOfBtcPeersTimer.stop();
|
checkNumberOfXmrPeersTimer.stop();
|
||||||
|
|
||||||
checkNumberOfBtcPeersTimer = UserThread.runAfter(() -> {
|
checkNumberOfXmrPeersTimer = UserThread.runAfter(() -> {
|
||||||
// check again numPeers
|
// check again numPeers
|
||||||
if (walletsSetup.numPeersProperty().get() == 0) {
|
if (connectionService.numPeersProperty().get() == 0) {
|
||||||
if (localBitcoinNode.shouldBeUsed())
|
if (localBitcoinNode.shouldBeUsed())
|
||||||
getWalletServiceErrorMsg().set(
|
getWalletServiceErrorMsg().set(
|
||||||
Res.get("mainView.networkWarning.localhostBitcoinLost",
|
Res.get("mainView.networkWarning.localhostBitcoinLost", // TODO: update error message for XMR
|
||||||
Res.getBaseCurrencyName().toLowerCase()));
|
Res.getBaseCurrencyName().toLowerCase()));
|
||||||
else
|
else
|
||||||
getWalletServiceErrorMsg().set(
|
getWalletServiceErrorMsg().set(
|
||||||
|
@ -532,8 +531,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||||
}
|
}
|
||||||
}, 5);
|
}, 5);
|
||||||
} else if ((int) oldValue == 0 && numPeers > 0) {
|
} else if ((int) oldValue == 0 && numPeers > 0) {
|
||||||
if (checkNumberOfBtcPeersTimer != null)
|
if (checkNumberOfXmrPeersTimer != null)
|
||||||
checkNumberOfBtcPeersTimer.stop();
|
checkNumberOfXmrPeersTimer.stop();
|
||||||
getWalletServiceErrorMsg().set(null);
|
getWalletServiceErrorMsg().set(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,9 +33,8 @@ import bisq.desktop.util.Layout;
|
||||||
import bisq.desktop.util.validation.PasswordValidator;
|
import bisq.desktop.util.validation.PasswordValidator;
|
||||||
|
|
||||||
import bisq.core.btc.wallet.WalletsManager;
|
import bisq.core.btc.wallet.WalletsManager;
|
||||||
import bisq.core.crypto.ScryptUtil;
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
import bisq.common.crypto.ScryptUtil;
|
||||||
import bisq.common.util.Tuple4;
|
import bisq.common.util.Tuple4;
|
||||||
|
|
||||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
|
|
|
@ -61,7 +61,6 @@ import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepo
|
||||||
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
|
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
|
||||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
|
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
|
||||||
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
|
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
|
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
|
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
|
||||||
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
|
||||||
|
@ -119,7 +118,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
FXCollections.observableArrayList(Arrays.asList(
|
FXCollections.observableArrayList(Arrays.asList(
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
TakerVerifyMakerFeePayment.class,
|
TakerVerifyMakerFeePayment.class,
|
||||||
TakerCreateFeeTx.class, // TODO (woodser): rename to TakerCreateFeeTx
|
|
||||||
SellerAsTakerCreatesDepositTxInputs.class,
|
SellerAsTakerCreatesDepositTxInputs.class,
|
||||||
|
|
||||||
TakerProcessesInputsForDepositTxResponse.class,
|
TakerProcessesInputsForDepositTxResponse.class,
|
||||||
|
@ -182,7 +180,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
FXCollections.observableArrayList(Arrays.asList(
|
FXCollections.observableArrayList(Arrays.asList(
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
TakerVerifyMakerFeePayment.class,
|
TakerVerifyMakerFeePayment.class,
|
||||||
TakerCreateFeeTx.class,
|
|
||||||
BuyerAsTakerCreatesDepositTxInputs.class,
|
BuyerAsTakerCreatesDepositTxInputs.class,
|
||||||
|
|
||||||
TakerProcessesInputsForDepositTxResponse.class,
|
TakerProcessesInputsForDepositTxResponse.class,
|
||||||
|
|
|
@ -24,7 +24,7 @@ import bisq.core.support.dispute.refund.RefundManager;
|
||||||
import bisq.core.trade.Tradable;
|
import bisq.core.trade.Tradable;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRingProvider;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -35,17 +35,17 @@ public class TransactionAwareTradableFactory {
|
||||||
private final ArbitrationManager arbitrationManager;
|
private final ArbitrationManager arbitrationManager;
|
||||||
private final RefundManager refundManager;
|
private final RefundManager refundManager;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRingProvider pubKeyRingProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TransactionAwareTradableFactory(ArbitrationManager arbitrationManager,
|
TransactionAwareTradableFactory(ArbitrationManager arbitrationManager,
|
||||||
RefundManager refundManager,
|
RefundManager refundManager,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
PubKeyRing pubKeyRing) {
|
PubKeyRingProvider pubKeyRingProvider) {
|
||||||
this.arbitrationManager = arbitrationManager;
|
this.arbitrationManager = arbitrationManager;
|
||||||
this.refundManager = refundManager;
|
this.refundManager = refundManager;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionAwareTradable create(Tradable delegate) {
|
TransactionAwareTradable create(Tradable delegate) {
|
||||||
|
@ -56,7 +56,7 @@ public class TransactionAwareTradableFactory {
|
||||||
arbitrationManager,
|
arbitrationManager,
|
||||||
refundManager,
|
refundManager,
|
||||||
xmrWalletService,
|
xmrWalletService,
|
||||||
pubKeyRing);
|
pubKeyRingProvider.get());
|
||||||
} else {
|
} else {
|
||||||
return new DummyTransactionAwareTradable(delegate);
|
return new DummyTransactionAwareTradable(delegate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
|
||||||
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.OpenOffer;
|
import bisq.core.offer.OpenOffer;
|
||||||
|
@ -103,7 +102,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
|
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private final TradeDetailsWindow tradeDetailsWindow;
|
private final TradeDetailsWindow tradeDetailsWindow;
|
||||||
private final OfferDetailsWindow offerDetailsWindow;
|
private final OfferDetailsWindow offerDetailsWindow;
|
||||||
|
@ -120,14 +119,14 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
@Inject
|
@Inject
|
||||||
private TransactionsView(BtcWalletService btcWalletService,
|
private TransactionsView(BtcWalletService btcWalletService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
TradeDetailsWindow tradeDetailsWindow,
|
TradeDetailsWindow tradeDetailsWindow,
|
||||||
OfferDetailsWindow offerDetailsWindow,
|
OfferDetailsWindow offerDetailsWindow,
|
||||||
DisplayedTransactionsFactory displayedTransactionsFactory) {
|
DisplayedTransactionsFactory displayedTransactionsFactory) {
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.tradeDetailsWindow = tradeDetailsWindow;
|
this.tradeDetailsWindow = tradeDetailsWindow;
|
||||||
this.offerDetailsWindow = offerDetailsWindow;
|
this.offerDetailsWindow = offerDetailsWindow;
|
||||||
|
@ -537,7 +536,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revertTransaction(String txId, @Nullable Tradable tradable) {
|
private void revertTransaction(String txId, @Nullable Tradable tradable) {
|
||||||
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
|
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
|
||||||
try {
|
try {
|
||||||
btcWalletService.doubleSpendTransaction(txId, () -> {
|
btcWalletService.doubleSpendTransaction(txId, () -> {
|
||||||
if (tradable != null)
|
if (tradable != null)
|
||||||
|
|
|
@ -27,7 +27,7 @@ import bisq.desktop.util.DisplayUtils;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
|
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.locale.BankUtil;
|
import bisq.core.locale.BankUtil;
|
||||||
import bisq.core.locale.CountryUtil;
|
import bisq.core.locale.CountryUtil;
|
||||||
import bisq.core.locale.CryptoCurrency;
|
import bisq.core.locale.CryptoCurrency;
|
||||||
|
@ -97,8 +97,8 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
private final User user;
|
private final User user;
|
||||||
private final OfferBook offerBook;
|
private final OfferBook offerBook;
|
||||||
final Preferences preferences;
|
final Preferences preferences;
|
||||||
private final WalletsSetup walletsSetup;
|
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
final PriceFeedService priceFeedService;
|
final PriceFeedService priceFeedService;
|
||||||
private final ClosedTradableManager closedTradableManager;
|
private final ClosedTradableManager closedTradableManager;
|
||||||
final AccountAgeWitnessService accountAgeWitnessService;
|
final AccountAgeWitnessService accountAgeWitnessService;
|
||||||
|
@ -142,7 +142,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
OfferBook offerBook,
|
OfferBook offerBook,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
PriceFeedService priceFeedService,
|
PriceFeedService priceFeedService,
|
||||||
ClosedTradableManager closedTradableManager,
|
ClosedTradableManager closedTradableManager,
|
||||||
|
@ -157,7 +157,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.offerBook = offerBook;
|
this.offerBook = offerBook;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.priceFeedService = priceFeedService;
|
this.priceFeedService = priceFeedService;
|
||||||
this.closedTradableManager = closedTradableManager;
|
this.closedTradableManager = closedTradableManager;
|
||||||
|
@ -561,7 +561,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
||||||
|
|
||||||
boolean canCreateOrTakeOffer() {
|
boolean canCreateOrTakeOffer() {
|
||||||
return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) &&
|
return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) &&
|
||||||
GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(walletsSetup) &&
|
GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(connectionService) &&
|
||||||
GUIUtil.isBootstrappedOrShowPopup(p2PService);
|
GUIUtil.isBootstrappedOrShowPopup(p2PService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ import bisq.desktop.main.overlays.Overlay;
|
||||||
import bisq.desktop.main.overlays.popups.Popup;
|
import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
import bisq.desktop.util.Transitions;
|
import bisq.desktop.util.Transitions;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.btc.wallet.Restrictions;
|
import bisq.core.btc.wallet.Restrictions;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
|
@ -53,7 +52,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
|
||||||
private final WalletPasswordWindow walletPasswordWindow;
|
private final WalletPasswordWindow walletPasswordWindow;
|
||||||
private final OpenOfferManager openOfferManager;
|
private final OpenOfferManager openOfferManager;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final CoinFormatter btcFormatter;
|
private final CoinFormatter btcFormatter;
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
|
||||||
public BtcEmptyWalletWindow(WalletPasswordWindow walletPasswordWindow,
|
public BtcEmptyWalletWindow(WalletPasswordWindow walletPasswordWindow,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
|
||||||
headLine(Res.get("emptyWalletWindow.headline", "BTC"));
|
headLine(Res.get("emptyWalletWindow.headline", "BTC"));
|
||||||
|
@ -73,13 +72,14 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
|
||||||
type = Type.Instruction;
|
type = Type.Instruction;
|
||||||
|
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.btcFormatter = btcFormatter;
|
this.btcFormatter = btcFormatter;
|
||||||
this.walletPasswordWindow = walletPasswordWindow;
|
this.walletPasswordWindow = walletPasswordWindow;
|
||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void show() {
|
public void show() {
|
||||||
createGridPane();
|
createGridPane();
|
||||||
addHeadLine();
|
addHeadLine();
|
||||||
|
@ -143,7 +143,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doEmptyWallet(KeyParameter aesKey) {
|
private void doEmptyWallet(KeyParameter aesKey) {
|
||||||
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
|
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
|
||||||
if (!openOfferManager.getObservableList().isEmpty()) {
|
if (!openOfferManager.getObservableList().isEmpty()) {
|
||||||
UserThread.runAfter(() ->
|
UserThread.runAfter(() ->
|
||||||
new Popup().warning(Res.get("emptyWalletWindow.openOffers.warn"))
|
new Popup().warning(Res.get("emptyWalletWindow.openOffers.warn"))
|
||||||
|
|
|
@ -25,11 +25,10 @@ import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
import bisq.desktop.util.validation.LengthValidator;
|
import bisq.desktop.util.validation.LengthValidator;
|
||||||
import bisq.desktop.util.validation.PercentageNumberValidator;
|
import bisq.desktop.util.validation.PercentageNumberValidator;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.exceptions.TransactionVerificationException;
|
import bisq.core.btc.exceptions.TransactionVerificationException;
|
||||||
import bisq.core.btc.exceptions.TxBroadcastException;
|
import bisq.core.btc.exceptions.TxBroadcastException;
|
||||||
import bisq.core.btc.exceptions.WalletException;
|
import bisq.core.btc.exceptions.WalletException;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
|
||||||
import bisq.core.btc.wallet.TradeWalletService;
|
import bisq.core.btc.wallet.TradeWalletService;
|
||||||
import bisq.core.btc.wallet.TxBroadcaster;
|
import bisq.core.btc.wallet.TxBroadcaster;
|
||||||
import bisq.core.btc.wallet.WalletsManager;
|
import bisq.core.btc.wallet.WalletsManager;
|
||||||
|
@ -92,7 +91,6 @@ import java.time.Instant;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -111,7 +109,7 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final MediationManager mediationManager;
|
private final MediationManager mediationManager;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
private final WalletsManager walletsManager;
|
private final WalletsManager walletsManager;
|
||||||
GridPane inputsGridPane;
|
GridPane inputsGridPane;
|
||||||
GridPane importTxGridPane;
|
GridPane importTxGridPane;
|
||||||
|
@ -150,17 +148,18 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
MediationManager mediationManager,
|
MediationManager mediationManager,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
WalletsManager walletsManager) {
|
WalletsManager walletsManager) {
|
||||||
this.tradeWalletService = tradeWalletService;
|
this.tradeWalletService = tradeWalletService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.mediationManager = mediationManager;
|
this.mediationManager = mediationManager;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.walletsManager = walletsManager;
|
this.walletsManager = walletsManager;
|
||||||
type = Type.Attention;
|
type = Type.Attention;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void show() {
|
public void show() {
|
||||||
if (headLine == null)
|
if (headLine == null)
|
||||||
headLine = "Emergency MultiSig payout tool"; // We dont translate here as it is for dev only purpose
|
headLine = "Emergency MultiSig payout tool"; // We dont translate here as it is for dev only purpose
|
||||||
|
@ -810,7 +809,7 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
|
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
|
||||||
try {
|
try {
|
||||||
tradeWalletService.emergencyPublishPayoutTxFrom2of2MultiSig(
|
tradeWalletService.emergencyPublishPayoutTxFrom2of2MultiSig(
|
||||||
txIdAndHex.second,
|
txIdAndHex.second,
|
||||||
|
|
|
@ -28,12 +28,12 @@ import bisq.desktop.util.Layout;
|
||||||
import bisq.desktop.util.Transitions;
|
import bisq.desktop.util.Transitions;
|
||||||
|
|
||||||
import bisq.core.btc.wallet.WalletsManager;
|
import bisq.core.btc.wallet.WalletsManager;
|
||||||
import bisq.core.crypto.ScryptUtil;
|
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
|
import bisq.common.crypto.ScryptUtil;
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
|
|
||||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||||
|
|
|
@ -29,7 +29,7 @@ import bisq.desktop.main.support.dispute.client.mediation.MediationClientView;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
|
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
@ -55,6 +55,7 @@ import bisq.core.user.Preferences;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.crypto.PubKeyRingProvider;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.FaultHandler;
|
import bisq.common.handlers.FaultHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
@ -97,7 +98,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
public final ArbitrationManager arbitrationManager;
|
public final ArbitrationManager arbitrationManager;
|
||||||
public final MediationManager mediationManager;
|
public final MediationManager mediationManager;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
private final WalletsSetup walletsSetup;
|
private final CoreMoneroConnectionsService connectionService;
|
||||||
@Getter
|
@Getter
|
||||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||||
public final Navigation navigation;
|
public final Navigation navigation;
|
||||||
|
@ -120,7 +121,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
private ChangeListener<Trade.State> tradeStateChangeListener;
|
private ChangeListener<Trade.State> tradeStateChangeListener;
|
||||||
private Trade selectedTrade;
|
private Trade selectedTrade;
|
||||||
@Getter
|
@Getter
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRingProvider pubKeyRingProvider;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor, initialization
|
// Constructor, initialization
|
||||||
|
@ -129,13 +130,13 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
@Inject
|
@Inject
|
||||||
public PendingTradesDataModel(TradeManager tradeManager,
|
public PendingTradesDataModel(TradeManager tradeManager,
|
||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRingProvider pubKeyRingProvider,
|
||||||
ArbitrationManager arbitrationManager,
|
ArbitrationManager arbitrationManager,
|
||||||
MediationManager mediationManager,
|
MediationManager mediationManager,
|
||||||
TraderChatManager traderChatManager,
|
TraderChatManager traderChatManager,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
WalletsSetup walletsSetup,
|
CoreMoneroConnectionsService connectionService,
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
Navigation navigation,
|
Navigation navigation,
|
||||||
WalletPasswordWindow walletPasswordWindow,
|
WalletPasswordWindow walletPasswordWindow,
|
||||||
|
@ -143,13 +144,13 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
OfferUtil offerUtil) {
|
OfferUtil offerUtil) {
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRingProvider = pubKeyRingProvider;
|
||||||
this.arbitrationManager = arbitrationManager;
|
this.arbitrationManager = arbitrationManager;
|
||||||
this.mediationManager = mediationManager;
|
this.mediationManager = mediationManager;
|
||||||
this.traderChatManager = traderChatManager;
|
this.traderChatManager = traderChatManager;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
this.walletsSetup = walletsSetup;
|
this.connectionService = connectionService;
|
||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
this.navigation = navigation;
|
this.navigation = navigation;
|
||||||
this.walletPasswordWindow = walletPasswordWindow;
|
this.walletPasswordWindow = walletPasswordWindow;
|
||||||
|
@ -513,11 +514,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr
|
byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr
|
||||||
Dispute dispute = new Dispute(new Date().getTime(),
|
Dispute dispute = new Dispute(new Date().getTime(),
|
||||||
trade.getId(),
|
trade.getId(),
|
||||||
pubKeyRing.hashCode(), // traderId
|
pubKeyRingProvider.get().hashCode(), // trader id
|
||||||
true,
|
true,
|
||||||
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
|
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
|
||||||
isMaker,
|
isMaker,
|
||||||
pubKeyRing,
|
pubKeyRingProvider.get(),
|
||||||
trade.getDate().getTime(),
|
trade.getDate().getTime(),
|
||||||
trade.getMaxTradePeriodDate().getTime(),
|
trade.getMaxTradePeriodDate().getTime(),
|
||||||
trade.getContract(),
|
trade.getContract(),
|
||||||
|
@ -549,11 +550,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
String depositTxHashAsString = null; // depositTx.getHashAsString(); TODO (woodser)
|
String depositTxHashAsString = null; // depositTx.getHashAsString(); TODO (woodser)
|
||||||
Dispute dispute = new Dispute(new Date().getTime(),
|
Dispute dispute = new Dispute(new Date().getTime(),
|
||||||
trade.getId(),
|
trade.getId(),
|
||||||
pubKeyRing.hashCode(), // traderId,
|
pubKeyRingProvider.get().hashCode(), // trader id,
|
||||||
true,
|
true,
|
||||||
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
|
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
|
||||||
isMaker,
|
isMaker,
|
||||||
pubKeyRing,
|
pubKeyRingProvider.get(),
|
||||||
trade.getDate().getTime(),
|
trade.getDate().getTime(),
|
||||||
trade.getMaxTradePeriodDate().getTime(),
|
trade.getMaxTradePeriodDate().getTime(),
|
||||||
trade.getContract(),
|
trade.getContract(),
|
||||||
|
@ -595,7 +596,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReadyForTxBroadcast() {
|
public boolean isReadyForTxBroadcast() {
|
||||||
return GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup);
|
return GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBootstrappedOrShowPopup() {
|
public boolean isBootstrappedOrShowPopup() {
|
||||||
|
|
|
@ -398,7 +398,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
||||||
return Res.get("portfolio.pending.failedTrade.missingContract");
|
return Res.get("portfolio.pending.failedTrade.missingContract");
|
||||||
}
|
}
|
||||||
|
|
||||||
PubKeyRing myPubKeyRing = model.dataModel.getPubKeyRing();
|
PubKeyRing myPubKeyRing = model.dataModel.getPubKeyRingProvider().get();
|
||||||
boolean isMyRoleBuyer = contract.isMyRoleBuyer(myPubKeyRing);
|
boolean isMyRoleBuyer = contract.isMyRoleBuyer(myPubKeyRing);
|
||||||
boolean isMyRoleMaker = contract.isMyRoleMaker(myPubKeyRing);
|
boolean isMyRoleMaker = contract.isMyRoleMaker(myPubKeyRing);
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
||||||
if (trade.getMakerDepositTx() == null) {
|
if (trade.getMakerDepositTx() == null) {
|
||||||
return Res.get("portfolio.pending.failedTrade.missingDepositTx");
|
return Res.get("portfolio.pending.failedTrade.missingDepositTx");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trade.getTakerDepositTx() == null) {
|
if (trade.getTakerDepositTx() == null) {
|
||||||
return Res.get("portfolio.pending.failedTrade.missingDepositTx"); // TODO (woodser): use .missingTakerDepositTx, .missingMakerDepositTx
|
return Res.get("portfolio.pending.failedTrade.missingDepositTx"); // TODO (woodser): use .missingTakerDepositTx, .missingMakerDepositTx
|
||||||
}
|
}
|
||||||
|
|
|
@ -409,11 +409,11 @@ public abstract class TradeStepView extends AnchorPane {
|
||||||
|
|
||||||
private void updateTimeLeft() {
|
private void updateTimeLeft() {
|
||||||
if (timeLeftTextField != null) {
|
if (timeLeftTextField != null) {
|
||||||
|
|
||||||
// TODO (woodser): extra TradeStepView created but not deactivated on trade.setState(), so deactivate when model's trade is 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) {
|
if (model.dataModel.getTrade() == null) {
|
||||||
log.warn("deactivating TradeStepView because model's trade is null");
|
log.warn("deactivating TradeStepView because model's trade is null");
|
||||||
|
|
||||||
// schedule deactivation to avoid concurrent modification of clock listeners
|
// schedule deactivation to avoid concurrent modification of clock listeners
|
||||||
Platform.runLater(new Runnable() {
|
Platform.runLater(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -423,7 +423,7 @@ public abstract class TradeStepView extends AnchorPane {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String remainingTime = model.getRemainingTradeDurationAsWords();
|
String remainingTime = model.getRemainingTradeDurationAsWords();
|
||||||
timeLeftProgressBar.setProgress(model.getRemainingTradeDurationAsPercentage());
|
timeLeftProgressBar.setProgress(model.getRemainingTradeDurationAsPercentage());
|
||||||
if (!remainingTime.isEmpty()) {
|
if (!remainingTime.isEmpty()) {
|
||||||
|
@ -675,7 +675,7 @@ public abstract class TradeStepView extends AnchorPane {
|
||||||
|
|
||||||
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
|
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
|
||||||
Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
|
Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
|
||||||
boolean isMyRoleBuyer = contract.isMyRoleBuyer(model.dataModel.getPubKeyRing());
|
boolean isMyRoleBuyer = contract.isMyRoleBuyer(model.dataModel.getPubKeyRingProvider().get());
|
||||||
String buyerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount());
|
String buyerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount());
|
||||||
String sellerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount());
|
String sellerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount());
|
||||||
String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount;
|
String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import bisq.desktop.main.overlays.popups.Popup;
|
||||||
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
|
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
|
||||||
import bisq.desktop.util.GUIUtil;
|
import bisq.desktop.util.GUIUtil;
|
||||||
|
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.btc.nodes.BtcNodes;
|
import bisq.core.btc.nodes.BtcNodes;
|
||||||
import bisq.core.btc.nodes.LocalBitcoinNode;
|
import bisq.core.btc.nodes.LocalBitcoinNode;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
|
@ -38,7 +39,6 @@ import bisq.core.user.Preferences;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
import bisq.core.util.validation.RegexValidator;
|
import bisq.core.util.validation.RegexValidator;
|
||||||
import bisq.core.util.validation.RegexValidatorFactory;
|
import bisq.core.util.validation.RegexValidatorFactory;
|
||||||
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.network.Statistic;
|
import bisq.network.p2p.network.Statistic;
|
||||||
|
|
||||||
|
@ -119,6 +119,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
||||||
private final ClockWatcher clockWatcher;
|
private final ClockWatcher clockWatcher;
|
||||||
private final WalletsSetup walletsSetup;
|
private final WalletsSetup walletsSetup;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private final CoreMoneroConnectionsService connectionManager;
|
||||||
|
|
||||||
private final ObservableList<P2pNetworkListItem> p2pNetworkListItems = FXCollections.observableArrayList();
|
private final ObservableList<P2pNetworkListItem> p2pNetworkListItems = FXCollections.observableArrayList();
|
||||||
private final SortedList<P2pNetworkListItem> p2pSortedList = new SortedList<>(p2pNetworkListItems);
|
private final SortedList<P2pNetworkListItem> p2pSortedList = new SortedList<>(p2pNetworkListItems);
|
||||||
|
@ -139,6 +140,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
||||||
@Inject
|
@Inject
|
||||||
public NetworkSettingsView(WalletsSetup walletsSetup,
|
public NetworkSettingsView(WalletsSetup walletsSetup,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
|
CoreMoneroConnectionsService connectionManager,
|
||||||
Preferences preferences,
|
Preferences preferences,
|
||||||
BtcNodes btcNodes,
|
BtcNodes btcNodes,
|
||||||
FilterManager filterManager,
|
FilterManager filterManager,
|
||||||
|
@ -148,6 +150,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
||||||
super();
|
super();
|
||||||
this.walletsSetup = walletsSetup;
|
this.walletsSetup = walletsSetup;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
this.btcNodes = btcNodes;
|
this.btcNodes = btcNodes;
|
||||||
this.filterManager = filterManager;
|
this.filterManager = filterManager;
|
||||||
|
@ -291,10 +294,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
|
||||||
|
|
||||||
reSyncSPVChainButton.setOnAction(event -> GUIUtil.reSyncSPVChain(preferences));
|
reSyncSPVChainButton.setOnAction(event -> GUIUtil.reSyncSPVChain(preferences));
|
||||||
|
|
||||||
moneroPeersSubscription = EasyBind.subscribe(walletsSetup.peerConnectionsProperty(),
|
moneroPeersSubscription = EasyBind.subscribe(connectionManager.peerConnectionsProperty(),
|
||||||
this::updateMoneroPeersTable);
|
this::updateMoneroPeersTable);
|
||||||
|
|
||||||
moneroBlockHeightSubscription = EasyBind.subscribe(walletsSetup.chainHeightProperty(),
|
moneroBlockHeightSubscription = EasyBind.subscribe(connectionManager.chainHeightProperty(),
|
||||||
this::updateChainHeightTextField);
|
this::updateChainHeightTextField);
|
||||||
|
|
||||||
nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(),
|
nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(),
|
||||||
|
|
|
@ -30,29 +30,24 @@ import bisq.desktop.main.overlays.popups.Popup;
|
||||||
|
|
||||||
import bisq.core.account.witness.AccountAgeWitness;
|
import bisq.core.account.witness.AccountAgeWitness;
|
||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
|
import bisq.core.api.CoreMoneroConnectionsService;
|
||||||
import bisq.core.app.HavenoSetup;
|
import bisq.core.app.HavenoSetup;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
|
||||||
import bisq.core.locale.Country;
|
import bisq.core.locale.Country;
|
||||||
import bisq.core.locale.CountryUtil;
|
import bisq.core.locale.CountryUtil;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.locale.TradeCurrency;
|
import bisq.core.locale.TradeCurrency;
|
||||||
import bisq.core.monetary.Price;
|
|
||||||
import bisq.core.monetary.Volume;
|
|
||||||
import bisq.core.offer.OfferRestrictions;
|
import bisq.core.offer.OfferRestrictions;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.PaymentAccountList;
|
import bisq.core.payment.PaymentAccountList;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
|
||||||
import bisq.core.trade.txproof.AssetTxProofResult;
|
import bisq.core.trade.txproof.AssetTxProofResult;
|
||||||
import bisq.core.user.DontShowAgainLookup;
|
import bisq.core.user.DontShowAgainLookup;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
import bisq.core.util.FormattingUtils;
|
import bisq.core.util.FormattingUtils;
|
||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
import bisq.core.util.coin.CoinUtil;
|
|
||||||
|
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
|
||||||
|
@ -63,7 +58,6 @@ import bisq.common.file.CorruptedStorageFileHandler;
|
||||||
import bisq.common.persistence.PersistenceManager;
|
import bisq.common.persistence.PersistenceManager;
|
||||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||||
import bisq.common.util.MathUtils;
|
|
||||||
import bisq.common.util.Tuple2;
|
import bisq.common.util.Tuple2;
|
||||||
import bisq.common.util.Tuple3;
|
import bisq.common.util.Tuple3;
|
||||||
import bisq.common.util.Utilities;
|
import bisq.common.util.Utilities;
|
||||||
|
@ -72,7 +66,6 @@ import org.bitcoinj.core.Address;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.TransactionConfidence;
|
import org.bitcoinj.core.TransactionConfidence;
|
||||||
import org.bitcoinj.uri.BitcoinURI;
|
import org.bitcoinj.uri.BitcoinURI;
|
||||||
import org.bitcoinj.utils.Fiat;
|
|
||||||
|
|
||||||
import com.googlecode.jcsv.CSVStrategy;
|
import com.googlecode.jcsv.CSVStrategy;
|
||||||
import com.googlecode.jcsv.writer.CSVEntryConverter;
|
import com.googlecode.jcsv.writer.CSVEntryConverter;
|
||||||
|
@ -757,17 +750,17 @@ public class GUIUtil {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, WalletsSetup walletsSetup) {
|
public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
|
||||||
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
|
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!walletsSetup.hasSufficientPeersForBroadcast()) {
|
if (!connectionService.hasSufficientPeersForBroadcast()) {
|
||||||
new Popup().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", walletsSetup.getMinBroadcastConnections())).show();
|
new Popup().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", connectionService.getMinBroadcastConnections())).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!walletsSetup.isDownloadComplete()) {
|
if (!connectionService.isDownloadComplete()) {
|
||||||
new Popup().information(Res.get("popup.warning.downloadNotComplete")).show();
|
new Popup().information(Res.get("popup.warning.downloadNotComplete")).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -775,8 +768,8 @@ public class GUIUtil {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(WalletsSetup walletsSetup) {
|
public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(CoreMoneroConnectionsService connectionService) {
|
||||||
if (!walletsSetup.isChainHeightSyncedWithinTolerance()) {
|
if (!connectionService.isChainHeightSyncedWithinTolerance()) {
|
||||||
new Popup().information(Res.get("popup.warning.chainNotSynced")).show();
|
new Popup().information(Res.get("popup.warning.chainNotSynced")).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,6 @@ public class GuiceSetupTest {
|
||||||
assertSingleton(TradeLimits.class);
|
assertSingleton(TradeLimits.class);
|
||||||
assertSingleton(KeyStorage.class);
|
assertSingleton(KeyStorage.class);
|
||||||
assertSingleton(KeyRing.class);
|
assertSingleton(KeyRing.class);
|
||||||
assertSingleton(PubKeyRing.class);
|
|
||||||
assertSingleton(User.class);
|
assertSingleton(User.class);
|
||||||
assertSingleton(ClockWatcher.class);
|
assertSingleton(ClockWatcher.class);
|
||||||
assertSingleton(Preferences.class);
|
assertSingleton(Preferences.class);
|
||||||
|
|
|
@ -20,7 +20,6 @@ package bisq.network.crypto;
|
||||||
import bisq.common.crypto.CryptoException;
|
import bisq.common.crypto.CryptoException;
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.crypto.KeyStorage;
|
import bisq.common.crypto.KeyStorage;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.file.FileUtil;
|
import bisq.common.file.FileUtil;
|
||||||
import bisq.common.proto.network.NetworkEnvelope;
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
|
|
||||||
|
@ -45,7 +44,6 @@ public class EncryptionServiceTests {
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
private PubKeyRing pubKeyRing;
|
|
||||||
private KeyRing keyRing;
|
private KeyRing keyRing;
|
||||||
private File dir;
|
private File dir;
|
||||||
|
|
||||||
|
@ -58,8 +56,7 @@ public class EncryptionServiceTests {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
dir.mkdir();
|
dir.mkdir();
|
||||||
KeyStorage keyStorage = new KeyStorage(dir);
|
KeyStorage keyStorage = new KeyStorage(dir);
|
||||||
keyRing = new KeyRing(keyStorage);
|
keyRing = new KeyRing(keyStorage, null, true);
|
||||||
pubKeyRing = keyRing.getPubKeyRing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class AddDataMessageTest {
|
||||||
dir1.delete();
|
dir1.delete();
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
dir1.mkdir();
|
dir1.mkdir();
|
||||||
keyRing1 = new KeyRing(new KeyStorage(dir1));
|
keyRing1 = new KeyRing(new KeyStorage(dir1), null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -24,20 +24,134 @@ option java_package = "bisq.proto.grpc";
|
||||||
option java_multiple_files = true;
|
option java_multiple_files = true;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// DisputeAgents
|
// Help
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
service DisputeAgents {
|
service Help {
|
||||||
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) {
|
rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message RegisterDisputeAgentRequest {
|
message GetMethodHelpRequest {
|
||||||
string dispute_agent_type = 1;
|
string method_name = 1;
|
||||||
string registration_key = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message RegisterDisputeAgentReply {
|
message GetMethodHelpReply {
|
||||||
|
string method_help = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Version
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
service GetVersion {
|
||||||
|
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetVersionRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetVersionReply {
|
||||||
|
string version = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Account
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
service Account {
|
||||||
|
rpc AccountExists (AccountExistsRequest) returns (AccountExistsReply) {
|
||||||
|
}
|
||||||
|
rpc IsAccountOpen (IsAccountOpenRequest) returns (IsAccountOpenReply) {
|
||||||
|
}
|
||||||
|
rpc CreateAccount (CreateAccountRequest) returns (CreateAccountReply) {
|
||||||
|
}
|
||||||
|
rpc OpenAccount (OpenAccountRequest) returns (OpenAccountReply) {
|
||||||
|
}
|
||||||
|
rpc IsAppInitialized (IsAppInitializedRequest) returns (IsAppInitializedReply) {
|
||||||
|
}
|
||||||
|
rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordReply) {
|
||||||
|
}
|
||||||
|
rpc CloseAccount (CloseAccountRequest) returns (CloseAccountReply) {
|
||||||
|
}
|
||||||
|
rpc DeleteAccount (DeleteAccountRequest) returns (DeleteAccountReply) {
|
||||||
|
}
|
||||||
|
rpc BackupAccount (BackupAccountRequest) returns (stream BackupAccountReply) {
|
||||||
|
}
|
||||||
|
rpc RestoreAccount (RestoreAccountRequest) returns (RestoreAccountReply) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AccountExistsRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message AccountExistsReply {
|
||||||
|
bool account_exists = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsAccountOpenRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsAccountOpenReply {
|
||||||
|
bool is_account_open = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateAccountRequest {
|
||||||
|
string password = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateAccountReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message OpenAccountRequest {
|
||||||
|
string password = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OpenAccountReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsAppInitializedRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsAppInitializedReply {
|
||||||
|
bool is_app_initialized = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChangePasswordRequest {
|
||||||
|
string password = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ChangePasswordReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message CloseAccountRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message CloseAccountReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteAccountRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteAccountReply {
|
||||||
|
}
|
||||||
|
|
||||||
|
message BackupAccountRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
message BackupAccountReply {
|
||||||
|
bytes zip_bytes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RestoreAccountRequest {
|
||||||
|
bytes zip_bytes = 1;
|
||||||
|
uint64 offset = 2;
|
||||||
|
uint64 total_length = 3;
|
||||||
|
bool has_more = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RestoreAccountReply {
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -56,9 +170,10 @@ message RegisterNotificationListenerRequest {
|
||||||
|
|
||||||
message NotificationMessage {
|
message NotificationMessage {
|
||||||
enum NotificationType {
|
enum NotificationType {
|
||||||
TRADE_UPDATE = 0;
|
APP_INITIALIZED = 0;
|
||||||
CHAT_MESSAGE = 1;
|
KEEP_ALIVE = 1;
|
||||||
KEEP_ALIVE = 2;
|
TRADE_UPDATE = 2;
|
||||||
|
CHAT_MESSAGE = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
string id = 1;
|
string id = 1;
|
||||||
|
@ -78,20 +193,134 @@ message SendNotificationReply {
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Help
|
// MoneroConnections
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
service Help {
|
service MoneroConnections {
|
||||||
rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) {
|
rpc AddConnection (AddConnectionRequest) returns (AddConnectionReply) {
|
||||||
|
}
|
||||||
|
rpc RemoveConnection(RemoveConnectionRequest) returns (RemoveConnectionReply) {
|
||||||
|
}
|
||||||
|
rpc GetConnection(GetConnectionRequest) returns (GetConnectionReply) {
|
||||||
|
}
|
||||||
|
rpc GetConnections(GetConnectionsRequest) returns (GetConnectionsReply) {
|
||||||
|
}
|
||||||
|
rpc SetConnection(SetConnectionRequest) returns (SetConnectionReply) {
|
||||||
|
}
|
||||||
|
rpc CheckConnection(CheckConnectionRequest) returns (CheckConnectionReply) {
|
||||||
|
}
|
||||||
|
rpc CheckConnections(CheckConnectionsRequest) returns (CheckConnectionsReply) {
|
||||||
|
}
|
||||||
|
rpc StartCheckingConnections(StartCheckingConnectionsRequest) returns (StartCheckingConnectionsReply) {
|
||||||
|
}
|
||||||
|
rpc StopCheckingConnections(StopCheckingConnectionsRequest) returns (StopCheckingConnectionsReply) {
|
||||||
|
}
|
||||||
|
rpc GetBestAvailableConnection(GetBestAvailableConnectionRequest) returns (GetBestAvailableConnectionReply) {
|
||||||
|
}
|
||||||
|
rpc SetAutoSwitch(SetAutoSwitchRequest) returns (SetAutoSwitchReply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetMethodHelpRequest {
|
message UrlConnection {
|
||||||
string method_name = 1;
|
enum OnlineStatus {
|
||||||
|
UNKNOWN = 0;
|
||||||
|
ONLINE = 1;
|
||||||
|
OFFLINE = 2;
|
||||||
|
}
|
||||||
|
enum AuthenticationStatus {
|
||||||
|
NO_AUTHENTICATION = 0;
|
||||||
|
AUTHENTICATED = 1;
|
||||||
|
NOT_AUTHENTICATED = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
string url = 1;
|
||||||
|
string username = 2; // request only
|
||||||
|
string password = 3; // request only
|
||||||
|
int32 priority = 4;
|
||||||
|
OnlineStatus online_status = 5; // reply only
|
||||||
|
AuthenticationStatus authentication_status = 6; // reply only
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetMethodHelpReply {
|
message AddConnectionRequest {
|
||||||
string method_help = 1;
|
UrlConnection connection = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddConnectionReply {}
|
||||||
|
|
||||||
|
message RemoveConnectionRequest {
|
||||||
|
string url = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveConnectionReply {}
|
||||||
|
|
||||||
|
message GetConnectionRequest {}
|
||||||
|
|
||||||
|
message GetConnectionReply {
|
||||||
|
UrlConnection connection = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetConnectionsRequest {}
|
||||||
|
|
||||||
|
message GetConnectionsReply {
|
||||||
|
repeated UrlConnection connections = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetConnectionRequest {
|
||||||
|
string url = 1;
|
||||||
|
UrlConnection connection = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetConnectionReply {}
|
||||||
|
|
||||||
|
message CheckConnectionRequest {}
|
||||||
|
|
||||||
|
message CheckConnectionReply {
|
||||||
|
UrlConnection connection = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckConnectionsRequest {}
|
||||||
|
|
||||||
|
message CheckConnectionsReply {
|
||||||
|
repeated UrlConnection connections = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartCheckingConnectionsRequest {
|
||||||
|
int32 refresh_period = 1; // milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartCheckingConnectionsReply {}
|
||||||
|
|
||||||
|
message StopCheckingConnectionsRequest {}
|
||||||
|
|
||||||
|
message StopCheckingConnectionsReply {}
|
||||||
|
|
||||||
|
message GetBestAvailableConnectionRequest {}
|
||||||
|
|
||||||
|
message GetBestAvailableConnectionReply {
|
||||||
|
UrlConnection connection = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetAutoSwitchRequest {
|
||||||
|
bool auto_switch = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetAutoSwitchReply {}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DisputeAgents
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
service DisputeAgents {
|
||||||
|
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterDisputeAgentRequest {
|
||||||
|
string dispute_agent_type = 1;
|
||||||
|
string registration_key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterDisputeAgentReply {
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -703,134 +932,4 @@ message AddressBalanceInfo {
|
||||||
int64 balance = 2;
|
int64 balance = 2;
|
||||||
int64 num_confirmations = 3;
|
int64 num_confirmations = 3;
|
||||||
bool is_address_unused = 4;
|
bool is_address_unused = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// MoneroConnections
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
service MoneroConnections {
|
|
||||||
rpc AddConnection (AddConnectionRequest) returns (AddConnectionReply) {
|
|
||||||
}
|
|
||||||
rpc RemoveConnection(RemoveConnectionRequest) returns (RemoveConnectionReply) {
|
|
||||||
}
|
|
||||||
rpc GetConnection(GetConnectionRequest) returns (GetConnectionReply) {
|
|
||||||
}
|
|
||||||
rpc GetConnections(GetConnectionsRequest) returns (GetConnectionsReply) {
|
|
||||||
}
|
|
||||||
rpc SetConnection(SetConnectionRequest) returns (SetConnectionReply) {
|
|
||||||
}
|
|
||||||
rpc CheckConnection(CheckConnectionRequest) returns (CheckConnectionReply) {
|
|
||||||
}
|
|
||||||
rpc CheckConnections(CheckConnectionsRequest) returns (CheckConnectionsReply) {
|
|
||||||
}
|
|
||||||
rpc StartCheckingConnections(StartCheckingConnectionsRequest) returns (StartCheckingConnectionsReply) {
|
|
||||||
}
|
|
||||||
rpc StopCheckingConnections(StopCheckingConnectionsRequest) returns (StopCheckingConnectionsReply) {
|
|
||||||
}
|
|
||||||
rpc GetBestAvailableConnection(GetBestAvailableConnectionRequest) returns (GetBestAvailableConnectionReply) {
|
|
||||||
}
|
|
||||||
rpc SetAutoSwitch(SetAutoSwitchRequest) returns (SetAutoSwitchReply) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message UriConnection {
|
|
||||||
enum OnlineStatus {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
ONLINE = 1;
|
|
||||||
OFFLINE = 2;
|
|
||||||
}
|
|
||||||
enum AuthenticationStatus {
|
|
||||||
NO_AUTHENTICATION = 0;
|
|
||||||
AUTHENTICATED = 1;
|
|
||||||
NOT_AUTHENTICATED = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
string uri = 1;
|
|
||||||
string username = 2; // request only
|
|
||||||
string password = 3; // request only
|
|
||||||
int32 priority = 4;
|
|
||||||
OnlineStatus online_status = 5; // reply only
|
|
||||||
AuthenticationStatus authentication_status = 6; // reply only
|
|
||||||
}
|
|
||||||
|
|
||||||
message AddConnectionRequest {
|
|
||||||
UriConnection connection = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message AddConnectionReply {}
|
|
||||||
|
|
||||||
message RemoveConnectionRequest {
|
|
||||||
string uri = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RemoveConnectionReply {}
|
|
||||||
|
|
||||||
message GetConnectionRequest {}
|
|
||||||
|
|
||||||
message GetConnectionReply {
|
|
||||||
UriConnection connection = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetConnectionsRequest {}
|
|
||||||
|
|
||||||
message GetConnectionsReply {
|
|
||||||
repeated UriConnection connections = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetConnectionRequest {
|
|
||||||
string uri = 1;
|
|
||||||
UriConnection connection = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetConnectionReply {}
|
|
||||||
|
|
||||||
message CheckConnectionRequest {}
|
|
||||||
|
|
||||||
message CheckConnectionReply {
|
|
||||||
UriConnection connection = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CheckConnectionsRequest {}
|
|
||||||
|
|
||||||
message CheckConnectionsReply {
|
|
||||||
repeated UriConnection connections = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StartCheckingConnectionsRequest {
|
|
||||||
int32 refresh_period = 1; // milliseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
message StartCheckingConnectionsReply {}
|
|
||||||
|
|
||||||
message StopCheckingConnectionsRequest {}
|
|
||||||
|
|
||||||
message StopCheckingConnectionsReply {}
|
|
||||||
|
|
||||||
message GetBestAvailableConnectionRequest {}
|
|
||||||
|
|
||||||
message GetBestAvailableConnectionReply {
|
|
||||||
UriConnection connection = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetAutoSwitchRequest {
|
|
||||||
bool auto_switch = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetAutoSwitchReply {}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Version
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
service GetVersion {
|
|
||||||
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetVersionRequest {
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetVersionReply {
|
|
||||||
string version = 1;
|
|
||||||
}
|
|
|
@ -1696,7 +1696,7 @@ message TradingPeer {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
message EncryptedConnection {
|
message EncryptedConnection {
|
||||||
string uri = 1;
|
string url = 1;
|
||||||
string username = 2;
|
string username = 2;
|
||||||
bytes encrypted_password = 3;
|
bytes encrypted_password = 3;
|
||||||
bytes encryption_salt = 4;
|
bytes encryption_salt = 4;
|
||||||
|
@ -1706,7 +1706,7 @@ message EncryptedConnection {
|
||||||
message EncryptedConnectionList {
|
message EncryptedConnectionList {
|
||||||
bytes salt = 1;
|
bytes salt = 1;
|
||||||
repeated EncryptedConnection items = 2;
|
repeated EncryptedConnection items = 2;
|
||||||
string current_connection_uri = 3;
|
string current_connection_url = 3;
|
||||||
int64 refresh_period = 4; // negative: no automated refresh is activated, zero: automated refresh with default period, positive: automated refresh with configured period (value)
|
int64 refresh_period = 4; // negative: no automated refresh is activated, zero: automated refresh with default period, positive: automated refresh with configured period (value)
|
||||||
bool auto_switch = 5;
|
bool auto_switch = 5;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue