use of io.bitsquare.network.Socks5MultiDiscovery in project bitsquare by bitsquare.
the class WalletService method initialize.
///////////////////////////////////////////////////////////////////////////////////////////
// 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 WalletAppKitBitSquare(params, socks5Proxy, walletDir, "Bitsquare") {
@Override
protected void onSetupCompleted() {
// Don't make the user wait for confirmations for now, as the intention is they're sending it
// their own money!
walletAppKit.wallet().allowSpendingUnconfirmedTransactions();
final PeerGroup peerGroup = walletAppKit.peerGroup();
if (params != RegTestParams.get())
peerGroup.setMaxConnections(11);
// We don't want to get our node white list polluted with nodes from AddressMessage calls.
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
peerGroup.setAddPeersFromAddressMessage(false);
wallet = walletAppKit.wallet();
wallet.addEventListener(walletEventListener);
addressEntryList.onWalletReady(wallet);
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(peerGroup.getConnectedPeers());
}
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
numPeers.set(peerCount);
connectedPeers.set(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()) {
String[] nodes = StringUtils.deleteWhitespace(btcNodes).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.
// note: .onion hostnames will be unresolved.
InetSocketAddress addr;
if (socks5Proxy != null) {
try {
// proxy remote DNS request happens here. blocking.
addr = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, parts[0]), Integer.parseInt(parts[1]));
} catch (Exception e) {
log.warn("Dns lookup failed for host: {}", parts[0]);
addr = null;
}
} else {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
}
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;
}
}
// 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) {
// You should run a regtest mode bitcoind locally.}
walletAppKit.connectToLocalHost();
}
} else if (params == MainNetParams.get()) {
// 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"));
}
// disable it, but should be made aware of the reduced privacy.
if (socks5Proxy != null && !usePeerNodes) {
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
walletAppKit.setDiscovery(new Socks5MultiDiscovery(socks5Proxy, params, socks5DiscoverMode));
}
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();
}
Aggregations