Refactor wallet (add WalletService) (WIP)

This commit is contained in:
Manfred Karrer 2016-12-08 01:29:24 +01:00
parent 7357a1afd1
commit 68845d197e
12 changed files with 595 additions and 330 deletions

View File

@ -54,8 +54,9 @@ public class BitcoinModule extends AppModule {
bindConstant().annotatedWith(named(AppOptionKeys.BTC_NODES)).to(env.getRequiredProperty(AppOptionKeys.BTC_NODES));
bindConstant().annotatedWith(named(AppOptionKeys.USE_TOR_FOR_BTC)).to(env.getRequiredProperty(AppOptionKeys.USE_TOR_FOR_BTC));
bindConstant().annotatedWith(named(AppOptionKeys.PROVIDERS)).to(env.getRequiredProperty(AppOptionKeys.PROVIDERS));
bind(AddressEntryList.class).in(Singleton.class);
bind(WalletSetup.class).in(Singleton.class);
bind(TradeWalletService.class).in(Singleton.class);
bind(BitcoinWalletService.class).in(Singleton.class);
bind(BlockchainService.class).in(Singleton.class);

View File

@ -21,31 +21,21 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.Service;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.app.Log;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.btc.provider.fee.FeeService;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.network.Socks5ProxyProvider;
import io.bitsquare.storage.FileUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Preferences;
import javafx.beans.property.*;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.net.discovery.SeedPeers;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.DeterministicSeed;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@ -56,15 +46,8 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
@ -85,6 +68,7 @@ public class BitcoinWalletService {
private final DownloadListener downloadListener = new DownloadListener();
private final WalletEventListener walletEventListener = new BitsquareWalletEventListener();
private WalletSetup walletSetup;
private final RegTestHost regTestHost;
private final TradeWalletService tradeWalletService;
private final AddressEntryList addressEntryList;
@ -103,7 +87,6 @@ public class BitcoinWalletService {
public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
private final Storage<Long> storage;
private final Long bloomFilterTweak;
private KeyParameter aesKey;
private String walletFileName = "Bitsquare";
private String tokenWalletFileName = "SQU";
@ -113,7 +96,8 @@ public class BitcoinWalletService {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public BitcoinWalletService(RegTestHost regTestHost,
public BitcoinWalletService(WalletSetup walletSetup,
RegTestHost regTestHost,
TradeWalletService tradeWalletService,
AddressEntryList addressEntryList,
UserAgent userAgent,
@ -121,6 +105,7 @@ public class BitcoinWalletService {
FeeService feeService,
Socks5ProxyProvider socks5ProxyProvider,
@Named(BtcOptionKeys.WALLET_DIR) File appDir) {
this.walletSetup = walletSetup;
this.regTestHost = regTestHost;
this.tradeWalletService = tradeWalletService;
this.addressEntryList = addressEntryList;
@ -139,6 +124,12 @@ public class BitcoinWalletService {
bloomFilterTweak = new Random().nextLong();
storage.queueUpForSave(bloomFilterTweak, 100);
}
walletSetup.addSetupCompletedHandler(() -> {
wallet = walletSetup.getWallet();
wallet.addEventListener(walletEventListener);
});
}
@ -146,249 +137,10 @@ public class BitcoinWalletService {
// Public Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
Log.traceCall();
// Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
// we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
// we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
// a future version.
Threading.USER_THREAD = UserThread.getExecutor();
Timer timeoutTimer = UserThread.runAfter(() ->
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
STARTUP_TIMEOUT_SEC + " seconds.")), STARTUP_TIMEOUT_SEC);
backupWallet();
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
log.debug("Use socks5Proxy for bitcoinj: " + socks5Proxy);
// If seed is non-null it means we are restoring from backup.
walletAppKit = new BitSquareWalletAppKit(params, socks5Proxy, walletDir, walletFileName, tokenWalletFileName) {
@Override
protected void onSetupCompleted() {
wallet = walletAppKit.wallet();
tokenWallet = walletAppKit.tokenWallet();
// Don't make the user wait for confirmations for now, as the intention is they're sending it
// their own money!
wallet.allowSpendingUnconfirmedTransactions();
tokenWallet.allowSpendingUnconfirmedTransactions();
if (params != RegTestParams.get())
walletAppKit.peerGroup().setMaxConnections(11);
wallet.addEventListener(walletEventListener);
addressEntryList.onWalletReady(wallet);
walletAppKit.peerGroup().addEventListener(new PeerEventListener() {
@Override
public void onPeersDiscovered(Set<PeerAddress> peerAddresses) {
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
}
@Override
public void onChainDownloadStarted(Peer peer, int blocksLeft) {
}
@Override
public void onPeerConnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
connectedPeers.set(walletAppKit.peerGroup().getConnectedPeers());
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
connectedPeers.set(walletAppKit.peerGroup().getConnectedPeers());
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
return null;
}
@Override
public void onTransaction(Peer peer, Transaction t) {
}
@Nullable
@Override
public List<Message> getData(Peer peer, GetDataMessage m) {
return null;
}
});
// set after wallet is ready
tradeWalletService.setWalletAppKit(walletAppKit);
tradeWalletService.setAddressEntryList(addressEntryList);
timeoutTimer.stop();
// 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);
}
};
// Bloom filters in BitcoinJ are completely broken
// See: https://jonasnick.github.io/blog/2015/02/12/privacy-in-bitcoinj/
// Here are a few improvements to fix a few vulnerabilities.
// Bitsquare's BitcoinJ fork has added a bloomFilterTweak (nonce) setter to reuse the same seed avoiding the trivial vulnerability
// by getting the real pub keys by intersections of several filters sent at each startup.
walletAppKit.setBloomFilterTweak(bloomFilterTweak);
// Avoid the simple attack (see: https://jonasnick.github.io/blog/2015/02/12/privacy-in-bitcoinj/) due to the
// default implementation using both pubkey and hash of pubkey. We have set a insertPubKey flag in BasicKeyChain to default false.
// Default only 266 keys are generated (2 * 100+33). That would trigger new bloom filters when we are reaching
// the threshold. To avoid reaching the threshold we create much more keys which are unlikely to cause update of the
// filter for most users. With lookaheadSize of 500 we get 1333 keys which should be enough for most users to
// never need to update a bloom filter, which would weaken privacy.
walletAppKit.setLookaheadSize(500);
// Calculation is derived from: https://www.reddit.com/r/Bitcoin/comments/2vrx6n/privacy_in_bitcoinj_android_wallet_multibit_hive/coknjuz
// No. of false positives (56M keys in the blockchain):
// First attempt for FP rate:
// FP rate = 0,0001; No. of false positives: 0,0001 * 56 000 000 = 5600
// We have 1333keys: 1333 / (5600 + 1333) = 0.19 -> 19 % probability that a pub key is in our wallet
// After tests I found out that the bandwidth consumption varies widely related to the generated filter.
// About 20- 40 MB for upload and 30-130 MB for download at first start up (spv chain).
// Afterwards its about 1 MB for upload and 20-80 MB for download.
// Probably better then a high FP rate would be to include foreign pubKeyHashes which are tested to not be used
// in many transactions. If we had a pool of 100 000 such keys (2 MB data dump) to random select 4000 we could mix it with our
// 1000 own keys and get a similar probability rate as with the current setup but less variation in bandwidth
// consumption.
// For now to reduce risks with high bandwidth consumption we reduce the FP rate by half.
// FP rate = 0,00005; No. of false positives: 0,00005 * 56 000 000 = 2800
// 1333 / (2800 + 1333) = 0.32 -> 32 % probability that a pub key is in our wallet
walletAppKit.setBloomFilterFalsePositiveRate(0.00005);
String btcNodes = preferences.getBitcoinNodes();
log.debug("btcNodes: " + btcNodes);
boolean usePeerNodes = false;
// Pass custom seed nodes if set in options
if (!btcNodes.isEmpty()) {
// TODO: this parsing should be more robust,
// give validation error if needed.
String[] nodes = btcNodes.replace(", ", ",").split(",");
List<PeerAddress> peerAddressList = new ArrayList<>();
for (String node : nodes) {
String[] parts = node.split(":");
if (parts.length == 1) {
// port not specified. Use default port for network.
parts = new String[]{parts[0], Integer.toString(params.getPort())};
}
if (parts.length == 2) {
// note: this will cause a DNS request if hostname used.
// note: DNS requests are routed over socks5 proxy, if used.
// fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
InetSocketAddress addr;
if (socks5Proxy != null) {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
// proxy remote DNS request happens here.
addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
} else {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
}
// note: isUnresolved check should be removed once we fix PeerAddress
if (addr != null && !addr.isUnresolved())
peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
}
}
if (peerAddressList.size() > 0) {
PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
log.debug("btcNodes parsed: " + Arrays.toString(peerAddressListFixed));
walletAppKit.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
usePeerNodes = true;
}
}
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
// or progress widget to keep the user engaged whilst we initialise, but we don't.
if (params == RegTestParams.get()) {
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
try {
walletAppKit.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
usePeerNodes = true;
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
} else if (regTestHost == RegTestHost.LOCALHOST) {
walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally.}
}
} else if (params == MainNetParams.get()) {
// Checkpoints are block headers that ship inside our app: for a new user, we pick the last header
// in the checkpoints file and then download the rest from the network. It makes things much faster.
// Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
// last months worth or more (takes a few seconds).
try {
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints"));
} catch (Exception e) {
e.printStackTrace();
log.error(e.toString());
}
} else if (params == TestNet3Params.get()) {
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
}
// If operating over a proxy and we haven't set any peer nodes, then
// we want to use SeedPeers for discovery instead of the default DnsDiscovery.
// This is only because we do not yet have a Dns discovery class that works
// reliably over proxy/tor.
//
// todo: There should be a user pref called "Use Local DNS for Proxy/Tor"
// that disables this. In that case, the default DnsDiscovery class will
// be used which should work, but is less private. The aim here is to
// be private by default when using proxy/tor. However, the seedpeers
// could become outdated, so it is important that the user be able to
// disable it, but should be made aware of the reduced privacy.
if (socks5Proxy != null && !usePeerNodes) {
// SeedPeersSocks5Dns should replace SeedPeers once working reliably.
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
walletAppKit.setDiscovery(new SeedPeers(params));
}
walletAppKit.setDownloadListener(downloadListener)
.setBlockingStartup(false)
.setUserAgent(userAgent.getName(), userAgent.getVersion())
.restoreWalletFromSeed(seed);
walletAppKit.addListener(new Service.Listener() {
@Override
public void failed(@NotNull Service.State from, @NotNull Throwable failure) {
walletAppKit = null;
log.error("walletAppKit failed");
timeoutTimer.stop();
UserThread.execute(() -> exceptionHandler.handleException(failure));
}
}, Threading.USER_THREAD);
walletAppKit.startAsync();
}
public void shutDown() {
if (wallet != null)
wallet.removeEventListener(walletEventListener);
if (walletAppKit != null) {
try {
walletAppKit.stopAsync();
walletAppKit.awaitTerminated(5, TimeUnit.SECONDS);
} catch (Throwable e) {
// ignore
}
shutDownDone.set(true);
}
}
public String exportWalletData(boolean includePrivKeys) {
@ -401,7 +153,6 @@ public class BitcoinWalletService {
"All pubkeys as hex:\n" +
wallet.printAllPubKeysAsHex();
}
public String exportTokenWalletData(boolean includePrivKeys) {
StringBuilder addressEntryListData = new StringBuilder();
getAddressEntryListAsImmutableList().stream().forEach(e -> addressEntryListData.append(e.toString()).append("\n"));
@ -413,8 +164,9 @@ public class BitcoinWalletService {
tokenWallet.printAllPubKeysAsHex();
}
//TODO
public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
Context ctx = Context.get();
/* Context ctx = Context.get();
new Thread(() -> {
try {
Context.propagate(ctx);
@ -425,57 +177,25 @@ public class BitcoinWalletService {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
}, "RestoreWallet-%d").start();
}
public void backupWallet() {
FileUtil.rollingBackup(walletDir, walletFileName + ".wallet", 20);
FileUtil.rollingBackup(walletDir, tokenWalletFileName + ".wallet", 20);
}
public void clearBackup() {
try {
FileUtil.deleteDirectory(new File(Paths.get(walletDir.getAbsolutePath(), "backup").toString()));
} catch (IOException e) {
log.error("Could not delete directory " + e.getMessage());
e.printStackTrace();
}
}
public void setAesKey(KeyParameter aesKey) {
this.aesKey = aesKey;
}, "RestoreWallet-%d").start();*/
}
public void decryptWallet(@NotNull KeyParameter key) {
wallet.decrypt(key);
tokenWallet.decrypt(key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
if (keyPair != null && keyPair.isEncrypted())
e.setDeterministicKey(keyPair.decrypt(key));
});
setAesKey(null);
addressEntryList.queueUpForSave();
}
public void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
if (this.aesKey != null) {
log.warn("encryptWallet called but we have a aesKey already set. " +
"We decryptWallet with the old key before we apply the new key.");
decryptWallet(this.aesKey);
}
wallet.encrypt(keyCrypterScrypt, key);
tokenWallet.encrypt(keyCrypterScrypt, key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
if (keyPair != null && keyPair.isEncrypted())
e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
});
setAesKey(key);
addressEntryList.queueUpForSave();
}
@ -789,7 +509,7 @@ public class BitcoinWalletService {
sendRequest = Wallet.SendRequest.forTx(newTransaction);
sendRequest.fee = fee;
sendRequest.feePerKb = Coin.ZERO;
sendRequest.aesKey = aesKey;
sendRequest.aesKey = walletSetup.getAesKey();
sendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress);
sendRequest.changeAddress = toAddress;
wallet.completeTx(sendRequest);
@ -808,7 +528,7 @@ public class BitcoinWalletService {
sendRequest = Wallet.SendRequest.forTx(newTransaction);
sendRequest.fee = fee;
sendRequest.feePerKb = Coin.ZERO;
sendRequest.aesKey = aesKey;
sendRequest.aesKey = walletSetup.getAesKey();
sendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress);
sendRequest.changeAddress = toAddress;
sendResult = wallet.sendCoins(sendRequest);
@ -824,7 +544,7 @@ public class BitcoinWalletService {
sendRequest = Wallet.SendRequest.forTx(newTransaction);
sendRequest.fee = fee;
sendRequest.feePerKb = Coin.ZERO;
sendRequest.aesKey = aesKey;
sendRequest.aesKey = walletSetup.getAesKey();
sendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress, false);
sendRequest.changeAddress = toAddress;
@ -905,7 +625,7 @@ public class BitcoinWalletService {
if (fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
Wallet.SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, aesKey, context);
Wallet.SendRequest sendRequest = getSendRequest(fromAddress, toAddress, amount, fee, walletSetup.getAesKey(), context);
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.bitcoinSerialize().length;
@ -955,7 +675,7 @@ public class BitcoinWalletService {
fee = txFeeForWithdrawalPerByte.multiply(txSize);
if (fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
Wallet.SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, fee, null, aesKey);
Wallet.SendRequest sendRequest = getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, fee, null, walletSetup.getAesKey());
wallet.completeTx(sendRequest);
tx = sendRequest.tx;
txSize = tx.bitcoinSerialize().length;
@ -1116,10 +836,6 @@ public class BitcoinWalletService {
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
public Wallet getWallet() {
return wallet;
}
@ -1132,14 +848,6 @@ public class BitcoinWalletService {
return new Transaction(params, tx);
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
public ReadOnlyObjectProperty<List<Peer>> connectedPeersProperty() {
return connectedPeers;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Util

View File

@ -0,0 +1,535 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import com.google.common.util.concurrent.Service;
import com.google.inject.Inject;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.app.Log;
import io.bitsquare.btc.provider.fee.FeeService;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.network.Socks5ProxyProvider;
import io.bitsquare.storage.FileUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Preferences;
import javafx.beans.property.*;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.net.discovery.SeedPeers;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.DeterministicSeed;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class WalletSetup {
private static final Logger log = LoggerFactory.getLogger(WalletSetup.class);
private static final long STARTUP_TIMEOUT_SEC = 60;
private final RegTestHost regTestHost;
private final TradeWalletService tradeWalletService;
private final AddressEntryList addressEntryList;
private final UserAgent userAgent;
private final Preferences preferences;
private final FeeService feeService;
private final Socks5ProxyProvider socks5ProxyProvider;
private final File appDir;
private final NetworkParameters params;
private final File walletDir;
private BitSquareWalletAppKit walletAppKit;
private Wallet wallet;
private Wallet tokenWallet;
private String walletFileName = "Bitsquare";
private String tokenWalletFileName = "SQU";
private final Long bloomFilterTweak;
private KeyParameter aesKey;
private final Storage<Long> storage;
public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>();
private final DownloadListener downloadListener = new DownloadListener();
// private final WalletEventListener walletEventListener = new BitsquareWalletEventListener();
private List<Runnable> setupCompletedHandlers = new ArrayList<>();
public void addSetupCompletedHandler(Runnable handler) {
setupCompletedHandlers.add(handler);
}
public Wallet getWallet() {
return wallet;
}
public Wallet getTokenWallet() {
return tokenWallet;
}
public KeyParameter getAesKey() {
return aesKey;
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
public ReadOnlyObjectProperty<List<Peer>> connectedPeersProperty() {
return connectedPeers;
}
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
public void setAesKey(KeyParameter aesKey) {
this.aesKey = aesKey;
}
public void decryptWallets(@NotNull KeyParameter key) {
wallet.decrypt(key);
tokenWallet.decrypt(key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
if (keyPair != null && keyPair.isEncrypted())
e.setDeterministicKey(keyPair.decrypt(key));
});
setAesKey(null);
addressEntryList.queueUpForSave();
}
public void encryptWallets(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
if (this.aesKey != null) {
log.warn("encryptWallet called but we have a aesKey already set. " +
"We decryptWallet with the old key before we apply the new key.");
decryptWallets(this.aesKey);
}
wallet.encrypt(keyCrypterScrypt, key);
tokenWallet.encrypt(keyCrypterScrypt, key);
addressEntryList.stream().forEach(e -> {
final DeterministicKey keyPair = e.getKeyPair();
if (keyPair != null && keyPair.isEncrypted())
e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
});
setAesKey(key);
addressEntryList.queueUpForSave();
}
@Inject
public WalletSetup(RegTestHost regTestHost,
TradeWalletService tradeWalletService,
AddressEntryList addressEntryList,
UserAgent userAgent,
Preferences preferences,
FeeService feeService,
Socks5ProxyProvider socks5ProxyProvider,
@Named(BtcOptionKeys.WALLET_DIR) File appDir) {
this.regTestHost = regTestHost;
this.tradeWalletService = tradeWalletService;
this.addressEntryList = addressEntryList;
this.userAgent = userAgent;
this.preferences = preferences;
this.feeService = feeService;
this.socks5ProxyProvider = socks5ProxyProvider;
this.appDir = appDir;
params = preferences.getBitcoinNetwork().getParameters();
walletDir = new File(appDir, "bitcoin");
storage = new Storage<>(walletDir);
Long persisted = storage.initAndGetPersistedWithFileName("BloomFilterNonce");
if (persisted != null) {
bloomFilterTweak = persisted;
} else {
bloomFilterTweak = new Random().nextLong();
storage.queueUpForSave(bloomFilterTweak, 100);
}
}
public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
Log.traceCall();
// Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
// we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
// we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
// a future version.
Threading.USER_THREAD = UserThread.getExecutor();
Timer timeoutTimer = UserThread.runAfter(() ->
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
STARTUP_TIMEOUT_SEC + " seconds.")), STARTUP_TIMEOUT_SEC);
backupWallets();
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
log.debug("Use socks5Proxy for bitcoinj: " + socks5Proxy);
// If seed is non-null it means we are restoring from backup.
walletAppKit = new BitSquareWalletAppKit(params, socks5Proxy, walletDir, walletFileName, tokenWalletFileName) {
@Override
protected void onSetupCompleted() {
wallet = walletAppKit.wallet();
tokenWallet = walletAppKit.tokenWallet();
// Don't make the user wait for confirmations for now, as the intention is they're sending it
// their own money!
wallet.allowSpendingUnconfirmedTransactions();
tokenWallet.allowSpendingUnconfirmedTransactions();
if (params != RegTestParams.get())
walletAppKit.peerGroup().setMaxConnections(11);
// wallet.addEventListener(walletEventListener);
addressEntryList.onWalletReady(wallet);
walletAppKit.peerGroup().addEventListener(new PeerEventListener() {
@Override
public void onPeersDiscovered(Set<PeerAddress> peerAddresses) {
}
@Override
public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
}
@Override
public void onChainDownloadStarted(Peer peer, int blocksLeft) {
}
@Override
public void onPeerConnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
connectedPeers.set(walletAppKit.peerGroup().getConnectedPeers());
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
connectedPeers.set(walletAppKit.peerGroup().getConnectedPeers());
}
@Override
public Message onPreMessageReceived(Peer peer, Message m) {
return null;
}
@Override
public void onTransaction(Peer peer, Transaction t) {
}
@Nullable
@Override
public List<Message> getData(Peer peer, GetDataMessage m) {
return null;
}
});
// set after wallet is ready
tradeWalletService.setWalletAppKit(walletAppKit);
tradeWalletService.setAddressEntryList(addressEntryList);
timeoutTimer.stop();
setupCompletedHandlers.stream().forEach(Runnable::run);
// 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);
}
};
// Bloom filters in BitcoinJ are completely broken
// See: https://jonasnick.github.io/blog/2015/02/12/privacy-in-bitcoinj/
// Here are a few improvements to fix a few vulnerabilities.
// Bitsquare's BitcoinJ fork has added a bloomFilterTweak (nonce) setter to reuse the same seed avoiding the trivial vulnerability
// by getting the real pub keys by intersections of several filters sent at each startup.
walletAppKit.setBloomFilterTweak(bloomFilterTweak);
// Avoid the simple attack (see: https://jonasnick.github.io/blog/2015/02/12/privacy-in-bitcoinj/) due to the
// default implementation using both pubkey and hash of pubkey. We have set a insertPubKey flag in BasicKeyChain to default false.
// Default only 266 keys are generated (2 * 100+33). That would trigger new bloom filters when we are reaching
// the threshold. To avoid reaching the threshold we create much more keys which are unlikely to cause update of the
// filter for most users. With lookaheadSize of 500 we get 1333 keys which should be enough for most users to
// never need to update a bloom filter, which would weaken privacy.
walletAppKit.setLookaheadSize(500);
// Calculation is derived from: https://www.reddit.com/r/Bitcoin/comments/2vrx6n/privacy_in_bitcoinj_android_wallet_multibit_hive/coknjuz
// No. of false positives (56M keys in the blockchain):
// First attempt for FP rate:
// FP rate = 0,0001; No. of false positives: 0,0001 * 56 000 000 = 5600
// We have 1333keys: 1333 / (5600 + 1333) = 0.19 -> 19 % probability that a pub key is in our wallet
// After tests I found out that the bandwidth consumption varies widely related to the generated filter.
// About 20- 40 MB for upload and 30-130 MB for download at first start up (spv chain).
// Afterwards its about 1 MB for upload and 20-80 MB for download.
// Probably better then a high FP rate would be to include foreign pubKeyHashes which are tested to not be used
// in many transactions. If we had a pool of 100 000 such keys (2 MB data dump) to random select 4000 we could mix it with our
// 1000 own keys and get a similar probability rate as with the current setup but less variation in bandwidth
// consumption.
// For now to reduce risks with high bandwidth consumption we reduce the FP rate by half.
// FP rate = 0,00005; No. of false positives: 0,00005 * 56 000 000 = 2800
// 1333 / (2800 + 1333) = 0.32 -> 32 % probability that a pub key is in our wallet
walletAppKit.setBloomFilterFalsePositiveRate(0.00005);
String btcNodes = preferences.getBitcoinNodes();
log.debug("btcNodes: " + btcNodes);
boolean usePeerNodes = false;
// Pass custom seed nodes if set in options
if (!btcNodes.isEmpty()) {
// TODO: this parsing should be more robust,
// give validation error if needed.
String[] nodes = btcNodes.replace(", ", ",").split(",");
List<PeerAddress> peerAddressList = new ArrayList<>();
for (String node : nodes) {
String[] parts = node.split(":");
if (parts.length == 1) {
// port not specified. Use default port for network.
parts = new String[]{parts[0], Integer.toString(params.getPort())};
}
if (parts.length == 2) {
// note: this will cause a DNS request if hostname used.
// note: DNS requests are routed over socks5 proxy, if used.
// fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
InetSocketAddress addr;
if (socks5Proxy != null) {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
// proxy remote DNS request happens here.
addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
} else {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
}
// note: isUnresolved check should be removed once we fix PeerAddress
if (addr != null && !addr.isUnresolved())
peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
}
}
if (peerAddressList.size() > 0) {
PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
log.debug("btcNodes parsed: " + Arrays.toString(peerAddressListFixed));
walletAppKit.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
usePeerNodes = true;
}
}
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
// or progress widget to keep the user engaged whilst we initialise, but we don't.
if (params == RegTestParams.get()) {
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
try {
walletAppKit.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
usePeerNodes = true;
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
} else if (regTestHost == RegTestHost.LOCALHOST) {
walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally.}
}
} else if (params == MainNetParams.get()) {
// Checkpoints are block headers that ship inside our app: for a new user, we pick the last header
// in the checkpoints file and then download the rest from the network. It makes things much faster.
// Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
// last months worth or more (takes a few seconds).
try {
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints"));
} catch (Exception e) {
e.printStackTrace();
log.error(e.toString());
}
} else if (params == TestNet3Params.get()) {
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
}
// If operating over a proxy and we haven't set any peer nodes, then
// we want to use SeedPeers for discovery instead of the default DnsDiscovery.
// This is only because we do not yet have a Dns discovery class that works
// reliably over proxy/tor.
//
// todo: There should be a user pref called "Use Local DNS for Proxy/Tor"
// that disables this. In that case, the default DnsDiscovery class will
// be used which should work, but is less private. The aim here is to
// be private by default when using proxy/tor. However, the seedpeers
// could become outdated, so it is important that the user be able to
// disable it, but should be made aware of the reduced privacy.
if (socks5Proxy != null && !usePeerNodes) {
// SeedPeersSocks5Dns should replace SeedPeers once working reliably.
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
walletAppKit.setDiscovery(new SeedPeers(params));
}
walletAppKit.setDownloadListener(downloadListener)
.setBlockingStartup(false)
.setUserAgent(userAgent.getName(), userAgent.getVersion())
.restoreWalletFromSeed(seed);
walletAppKit.addListener(new Service.Listener() {
@Override
public void failed(@NotNull Service.State from, @NotNull Throwable failure) {
walletAppKit = null;
log.error("walletAppKit failed");
timeoutTimer.stop();
UserThread.execute(() -> exceptionHandler.handleException(failure));
}
}, Threading.USER_THREAD);
walletAppKit.startAsync();
}
public void shutDown() {
if (walletAppKit != null) {
try {
walletAppKit.stopAsync();
walletAppKit.awaitTerminated(5, TimeUnit.SECONDS);
} catch (Throwable e) {
// ignore
}
shutDownDone.set(true);
}
}
public void backupWallets() {
FileUtil.rollingBackup(walletDir, walletFileName + ".wallet", 20);
FileUtil.rollingBackup(walletDir, tokenWalletFileName + ".wallet", 20);
}
public void clearBackup() {
try {
FileUtil.deleteDirectory(new File(Paths.get(walletDir.getAbsolutePath(), "backup").toString()));
} catch (IOException e) {
log.error("Could not delete directory " + e.getMessage());
e.printStackTrace();
}
}
/* public String exportWalletData(boolean includePrivKeys) {
StringBuilder addressEntryListData = new StringBuilder();
getAddressEntryListAsImmutableList().stream().forEach(e -> addressEntryListData.append(e.toString()).append("\n"));
return "BitcoinJ wallet:\n" +
wallet.toString(includePrivKeys, true, true, walletAppKit.chain()) + "\n\n" +
"Bitsquare address entry list:\n" +
addressEntryListData.toString() +
"All pubkeys as hex:\n" +
wallet.printAllPubKeysAsHex();
}
public String exportTokenWalletData(boolean includePrivKeys) {
StringBuilder addressEntryListData = new StringBuilder();
return "BitcoinJ SQU wallet:\n" +
tokenWallet.toString(includePrivKeys, true, true, walletAppKit.chain()) + "\n\n" +
"SQU address entry list:\n" +
addressEntryListData.toString() +
"All pubkeys as hex:\n" +
tokenWallet.printAllPubKeysAsHex();
}*/
///////////////////////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////////////////////
private static class DownloadListener extends DownloadProgressTracker {
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
@Override
protected void progress(double percentage, int blocksLeft, Date date) {
super.progress(percentage, blocksLeft, date);
UserThread.execute(() -> this.percentage.set(percentage / 100d));
}
@Override
protected void doneDownload() {
super.doneDownload();
UserThread.execute(() -> this.percentage.set(1d));
}
public ReadOnlyDoubleProperty percentageProperty() {
return percentage;
}
}
/*private class BitsquareWalletEventListener extends AbstractWalletEventListener {
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
notifyBalanceListeners(tx);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
notifyBalanceListeners(tx);
}
@Override
public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
for (AddressConfidenceListener addressConfidenceListener : addressConfidenceListeners) {
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
transactionConfidenceList.add(getTransactionConfidence(tx, addressConfidenceListener.getAddress()));
TransactionConfidence transactionConfidence = getMostRecentConfidence(transactionConfidenceList);
addressConfidenceListener.onTransactionConfidenceChanged(transactionConfidence);
}
txConfidenceListeners.stream()
.filter(txConfidenceListener -> tx != null &&
tx.getHashAsString() != null &&
txConfidenceListener != null &&
tx.getHashAsString().equals(txConfidenceListener.getTxID()))
.forEach(txConfidenceListener ->
txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
}
private void notifyBalanceListeners(Transaction tx) {
for (BalanceListener balanceListener : balanceListeners) {
Coin balance;
if (balanceListener.getAddress() != null)
balance = getBalanceForAddress(balanceListener.getAddress());
else
balance = getAvailableBalance();
balanceListener.onBalanceChanged(balance, tx);
}
}
}*/
}

View File

@ -25,6 +25,7 @@ import io.bitsquare.alert.AlertManager;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
@ -407,6 +408,7 @@ public class BitsquareApp extends Application {
log.debug("Graceful shutdown completed");
resultHandler.handleResult();
});
injector.getInstance(WalletSetup.class).shutDown();
injector.getInstance(BitcoinWalletService.class).shutDown();
});
});

View File

@ -32,6 +32,7 @@ import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.provider.fee.FeeService;
import io.bitsquare.btc.provider.price.MarketPrice;
@ -157,6 +158,7 @@ public class MainViewModel implements ViewModel {
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
private MonadicBinding<Boolean> allServicesDone, tradesAndUIReady;
private WalletSetup walletSetup;
final PriceFeedService priceFeedService;
private final User user;
private int numBtcPeers = 0;
@ -176,7 +178,7 @@ public class MainViewModel implements ViewModel {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MainViewModel(BitcoinWalletService walletService, TradeWalletService tradeWalletService,
public MainViewModel(WalletSetup walletSetup, BitcoinWalletService walletService, TradeWalletService tradeWalletService,
PriceFeedService priceFeedService,
ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager,
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
@ -184,6 +186,7 @@ public class MainViewModel implements ViewModel {
FilterManager filterManager, WalletPasswordWindow walletPasswordWindow, AddBitcoinNodesWindow addBitcoinNodesWindow,
NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock, FeeService feeService,
KeyRing keyRing, Navigation navigation, BSFormatter formatter) {
this.walletSetup = walletSetup;
this.priceFeedService = priceFeedService;
this.user = user;
this.walletService = walletService;
@ -433,7 +436,7 @@ public class MainViewModel implements ViewModel {
private void initWalletService() {
Log.traceCall();
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(walletService.downloadPercentageProperty(), walletService.numPeersProperty(), walletServiceException,
btcInfoBinding = EasyBind.combine(walletSetup.downloadPercentageProperty(), walletSetup.numPeersProperty(), walletServiceException,
(downloadPercentage, numPeers, exception) -> {
String result = "";
if (exception == null) {
@ -478,9 +481,9 @@ public class MainViewModel implements ViewModel {
btcInfo.set(newValue);
});
walletService.initialize(null,
walletSetup.initialize(null,
() -> {
numBtcPeers = walletService.numPeersProperty().get();
numBtcPeers = walletSetup.numPeersProperty().get();
if (walletService.getWallet().isEncrypted()) {
if (p2pNetWorkReady.get())
@ -488,7 +491,7 @@ public class MainViewModel implements ViewModel {
walletPasswordWindow
.onAesKey(aesKey -> {
walletService.setAesKey(aesKey);
walletSetup.setAesKey(aesKey);
tradeWalletService.setAesKey(aesKey);
walletInitialized.set(true);
})
@ -745,7 +748,7 @@ public class MainViewModel implements ViewModel {
}
private void setupBtcNumPeersWatcher() {
walletService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
walletSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
int numPeers = (int) newValue;
if ((int) oldValue > 0 && numPeers == 0) {
if (checkNumberOfBtcPeersTimer != null)
@ -753,7 +756,7 @@ public class MainViewModel implements ViewModel {
checkNumberOfBtcPeersTimer = UserThread.runAfter(() -> {
// check again numPeers
if (walletService.numPeersProperty().get() == 0) {
if (walletSetup.numPeersProperty().get() == 0) {
walletServiceErrorMsg.set("You lost the connection to all bitcoin network peers.\n" +
"Maybe you lost your internet connection or your computer was in standby mode.");
} else {

View File

@ -19,6 +19,7 @@ package io.bitsquare.gui.main.account.content.password;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.crypto.ScryptUtil;
@ -47,6 +48,7 @@ import static io.bitsquare.gui.util.FormBuilder.*;
public class PasswordView extends ActivatableView<GridPane, Void> {
private final PasswordValidator passwordValidator;
private final WalletSetup walletSetup;
private final BitcoinWalletService walletService;
private final TradeWalletService tradeWalletService;
@ -65,8 +67,9 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private PasswordView(PasswordValidator passwordValidator, BitcoinWalletService walletService, TradeWalletService tradeWalletService) {
private PasswordView(PasswordValidator passwordValidator, WalletSetup walletSetup, BitcoinWalletService walletService, TradeWalletService tradeWalletService) {
this.passwordValidator = passwordValidator;
this.walletSetup = walletSetup;
this.walletService = walletService;
this.tradeWalletService = tradeWalletService;
}
@ -113,6 +116,7 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
if (wallet.isEncrypted()) {
if (wallet.checkAESKey(aesKey)) {
walletSetup.decryptWallets(aesKey);
walletService.decryptWallet(aesKey);
tradeWalletService.setAesKey(null);
new Popup()
@ -120,7 +124,7 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
.show();
passwordField.setText("");
repeatedPasswordField.setText("");
walletService.backupWallet();
walletSetup.backupWallets();
} else {
pwButton.setDisable(false);
new Popup()
@ -130,6 +134,7 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
}
} else {
// we save the key for the trade wallet as we don't require passwords here
walletSetup.encryptWallets(keyCrypterScrypt, aesKey);
walletService.encryptWallet(keyCrypterScrypt, aesKey);
tradeWalletService.setAesKey(aesKey);
new Popup()
@ -137,8 +142,8 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
.show();
passwordField.setText("");
repeatedPasswordField.setText("");
walletService.clearBackup();
walletService.backupWallet();
walletSetup.clearBackup();
walletSetup.backupWallets();
}
setText();
});

View File

@ -34,15 +34,15 @@ import static io.bitsquare.gui.util.FormBuilder.addLabelTextArea;
public class ShowWalletDataWindow extends Overlay<ShowWalletDataWindow> {
private static final Logger log = LoggerFactory.getLogger(ShowWalletDataWindow.class);
private BitcoinWalletService walletService;
private BitcoinWalletService bitcoinWalletService;
///////////////////////////////////////////////////////////////////////////////////////////
// Public API
///////////////////////////////////////////////////////////////////////////////////////////
public ShowWalletDataWindow(BitcoinWalletService walletService) {
this.walletService = walletService;
public ShowWalletDataWindow(BitcoinWalletService bitcoinWalletService) {
this.bitcoinWalletService = bitcoinWalletService;
type = Type.Attention;
}
@ -86,10 +86,10 @@ public class ShowWalletDataWindow extends Overlay<ShowWalletDataWindow> {
isUpdateCheckBox.setSelected(false);
isUpdateCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
textArea.setText(walletService.exportWalletData(isUpdateCheckBox.isSelected()));
textArea.setText(bitcoinWalletService.exportWalletData(isUpdateCheckBox.isSelected()));
});
textArea.setText(walletService.exportWalletData(isUpdateCheckBox.isSelected()));
textArea.setText(bitcoinWalletService.exportWalletData(isUpdateCheckBox.isSelected()));
actionButtonText("Copy to clipboard");
onAction(() -> Utilities.copyToClipboard(textArea.getText()));

View File

@ -19,6 +19,7 @@ package io.bitsquare.gui.main.settings.network;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.common.Clock;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.model.Activatable;
@ -71,6 +72,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
private final Preferences preferences;
private Clock clock;
private final BSFormatter formatter;
private final WalletSetup walletSetup;
private final P2PService p2PService;
private Subscription numP2PPeersSubscription;
private Subscription bitcoinPeersSubscription;
@ -81,10 +83,11 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
private String btcNodesPreFocusText;
@Inject
public NetworkSettingsView(BitcoinWalletService walletService, P2PService p2PService, Preferences preferences, Clock clock,
public NetworkSettingsView(BitcoinWalletService walletService, WalletSetup walletSetup, P2PService p2PService, Preferences preferences, Clock clock,
BSFormatter formatter) {
super();
this.walletService = walletService;
this.walletSetup = walletSetup;
this.p2PService = p2PService;
this.preferences = preferences;
this.clock = clock;
@ -134,7 +137,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
}
});
bitcoinPeersSubscription = EasyBind.subscribe(walletService.connectedPeersProperty(), connectedPeers -> updateBitcoinPeersTextArea());
bitcoinPeersSubscription = EasyBind.subscribe(walletSetup.connectedPeersProperty(), connectedPeers -> updateBitcoinPeersTextArea());
nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(),
nodeAddress -> onionAddress.setText(nodeAddress == null ? "Not known yet..." : p2PService.getAddress().getFullAddress()));
@ -201,7 +204,7 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
private void updateBitcoinPeersTextArea() {
bitcoinPeersTextArea.clear();
List<Peer> peerList = walletService.connectedPeersProperty().get();
List<Peer> peerList = walletSetup.connectedPeersProperty().get();
if (peerList != null) {
peerList.stream().forEach(e -> {
if (bitcoinPeersTextArea.getText().length() > 0)

View File

@ -9,6 +9,7 @@ import io.bitsquare.app.Log;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
@ -140,6 +141,7 @@ public class Headless {
log.debug("Graceful shutdown completed");
resultHandler.handleResult();
});
injector.getInstance(WalletSetup.class).shutDown();
injector.getInstance(BitcoinWalletService.class).shutDown();
});
});

View File

@ -9,6 +9,7 @@ import io.bitsquare.app.Log;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
@ -143,6 +144,7 @@ public class Monitor {
log.debug("Graceful shutdown completed");
resultHandler.handleResult();
});
injector.getInstance(WalletSetup.class).shutDown();
injector.getInstance(BitcoinWalletService.class).shutDown();
});
});

View File

@ -9,6 +9,7 @@ import io.bitsquare.app.Log;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
@ -109,6 +110,7 @@ public class SeedNode {
log.debug("Graceful shutdown completed");
resultHandler.handleResult();
});
injector.getInstance(WalletSetup.class).shutDown();
injector.getInstance(BitcoinWalletService.class).shutDown();
});
});

View File

@ -9,6 +9,7 @@ import io.bitsquare.app.Log;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.BitcoinWalletService;
import io.bitsquare.btc.WalletSetup;
import io.bitsquare.btc.provider.price.PriceFeedService;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
@ -122,6 +123,7 @@ public class Statistics {
log.debug("Graceful shutdown completed");
resultHandler.handleResult();
});
injector.getInstance(WalletSetup.class).shutDown();
injector.getInstance(BitcoinWalletService.class).shutDown();
});
});