mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 18:33:43 +01:00
Refactor wallet (add WalletService) (WIP)
This commit is contained in:
parent
7357a1afd1
commit
68845d197e
@ -56,6 +56,7 @@ public class BitcoinModule extends AppModule {
|
||||
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);
|
||||
|
@ -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
|
||||
|
535
core/src/main/java/io/bitsquare/btc/WalletSetup.java
Normal file
535
core/src/main/java/io/bitsquare/btc/WalletSetup.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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()));
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user