Search in sources :

Example 1 with Socks5MultiDiscovery

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();
}
Also used : CopyOnWriteArraySet(java.util.concurrent.CopyOnWriteArraySet) Socks5Proxy(com.runjva.sourceforge.jsocks.protocol.Socks5Proxy) InetSocketAddress(java.net.InetSocketAddress) TimeoutException(java.util.concurrent.TimeoutException) UnknownHostException(java.net.UnknownHostException) Socks5MultiDiscovery(io.bitsquare.network.Socks5MultiDiscovery) Service(com.google.common.util.concurrent.Service) TimeoutException(java.util.concurrent.TimeoutException) IOException(java.io.IOException) UnknownHostException(java.net.UnknownHostException) Timer(io.bitsquare.common.Timer)

Aggregations

Service (com.google.common.util.concurrent.Service)1 Socks5Proxy (com.runjva.sourceforge.jsocks.protocol.Socks5Proxy)1 Timer (io.bitsquare.common.Timer)1 Socks5MultiDiscovery (io.bitsquare.network.Socks5MultiDiscovery)1 IOException (java.io.IOException)1 InetSocketAddress (java.net.InetSocketAddress)1 UnknownHostException (java.net.UnknownHostException)1 CopyOnWriteArraySet (java.util.concurrent.CopyOnWriteArraySet)1 TimeoutException (java.util.concurrent.TimeoutException)1