Search in sources :

Example 1 with SegwitAddress

use of org.bitcoinj.core.SegwitAddress in project bitcoin-wallet by bitcoin-wallet.

the class RequestWalletBalanceTask method requestWalletBalance.

public void requestWalletBalance(final AssetManager assets, final ECKey key) {
    backgroundHandler.post(new Runnable() {

        @Override
        public void run() {
            org.bitcoinj.core.Context.propagate(Constants.CONTEXT);
            final Address legacyAddress = LegacyAddress.fromKey(Constants.NETWORK_PARAMETERS, key);
            final Script[] outputScripts;
            final String addressesStr;
            if (key.isCompressed()) {
                final Address segwitAddress = SegwitAddress.fromKey(Constants.NETWORK_PARAMETERS, key);
                outputScripts = new Script[] { ScriptBuilder.createP2PKHOutputScript(legacyAddress.getHash()), ScriptBuilder.createP2WPKHOutputScript(segwitAddress.getHash()) };
                addressesStr = legacyAddress.toString() + "," + segwitAddress.toString();
            } else {
                outputScripts = new Script[] { ScriptBuilder.createP2PKHOutputScript(legacyAddress.getHash()) };
                addressesStr = legacyAddress.toString();
            }
            final List<ElectrumServer> servers = loadElectrumServers(Assets.open(assets, Constants.Files.ELECTRUM_SERVERS_ASSET));
            final List<Callable<Set<UTXO>>> tasks = new ArrayList<>(servers.size());
            for (final ElectrumServer server : servers) {
                tasks.add(() -> {
                    log.info("{} - trying to request wallet balance for {}", server.socketAddress, addressesStr);
                    try (final Socket socket = connect(server)) {
                        final BufferedSink sink = Okio.buffer(Okio.sink(socket));
                        sink.timeout().timeout(5000, TimeUnit.MILLISECONDS);
                        final BufferedSource source = Okio.buffer(Okio.source(socket));
                        source.timeout().timeout(5000, TimeUnit.MILLISECONDS);
                        final Moshi moshi = new Moshi.Builder().build();
                        final JsonAdapter<ElectrumRequest> requestAdapter = moshi.adapter(ElectrumRequest.class);
                        final JsonAdapter<ListunspentResponse> listunspentResponseAdapter = moshi.adapter(ListunspentResponse.class);
                        final JsonAdapter<TransactionResponse> transactionResponseAdapter = moshi.adapter(TransactionResponse.class);
                        final Set<UTXO> utxos = new HashSet<>();
                        for (final Script outputScript : outputScripts) {
                            requestAdapter.toJson(sink, new ElectrumRequest(outputScript.getScriptType().ordinal(), "blockchain.scripthash.listunspent", new String[] { Constants.HEX.encode(Sha256Hash.of(outputScript.getProgram()).getReversedBytes()) }));
                            sink.writeUtf8("\n").flush();
                            final ListunspentResponse listunspentResponse = listunspentResponseAdapter.fromJson(source);
                            final int expectedResponseId = outputScript.getScriptType().ordinal();
                            if (listunspentResponse.id != expectedResponseId) {
                                log.warn("{} - id mismatch listunspentResponse:{} vs request:{}", server.socketAddress, listunspentResponse.id, expectedResponseId);
                                return null;
                            }
                            if (listunspentResponse.error != null) {
                                log.info("{} - server error {}: {}", server.socketAddress, listunspentResponse.error.code, listunspentResponse.error.message);
                                return null;
                            }
                            if (listunspentResponse.result == null) {
                                log.info("{} - missing result", server.socketAddress);
                                return null;
                            }
                            for (final ListunspentResponse.Utxo responseUtxo : listunspentResponse.result) {
                                final Sha256Hash utxoHash = Sha256Hash.wrap(responseUtxo.tx_hash);
                                final int utxoIndex = responseUtxo.tx_pos;
                                // the value cannot be trusted; will be validated below
                                final Coin utxoValue = Coin.valueOf(responseUtxo.value);
                                final UTXO utxo = new UTXO(utxoHash, utxoIndex, utxoValue, responseUtxo.height, false, outputScript);
                                // validation of value and some sanity checks
                                requestAdapter.toJson(sink, new ElectrumRequest("blockchain.transaction.get", new String[] { Constants.HEX.encode(utxo.getHash().getBytes()) }));
                                sink.writeUtf8("\n").flush();
                                final TransactionResponse transactionResponse = transactionResponseAdapter.fromJson(source);
                                if (transactionResponse.error != null) {
                                    log.info("{} - server error {}: {}", server.socketAddress, transactionResponse.error.code, transactionResponse.error.message);
                                    return null;
                                }
                                if (transactionResponse.result == null) {
                                    log.info("{} - missing result", server.socketAddress);
                                    return null;
                                }
                                final Transaction tx = new Transaction(Constants.NETWORK_PARAMETERS, Constants.HEX.decode(transactionResponse.result));
                                if (!tx.getTxId().equals(utxo.getHash()))
                                    log.warn("{} - lied about txid", server.socketAddress);
                                else if (!tx.getOutput(utxo.getIndex()).getValue().equals(utxo.getValue()))
                                    log.warn("{} - lied about amount", server.socketAddress);
                                else if (!tx.getOutput(utxo.getIndex()).getScriptPubKey().equals(outputScript))
                                    log.warn("{} - lied about output script", server.socketAddress);
                                else
                                    // use valid UTXO
                                    utxos.add(utxo);
                            }
                        }
                        log.info("{} - got {} UTXOs {}", server.socketAddress, utxos.size(), utxos);
                        return utxos;
                    } catch (final ConnectException | SSLPeerUnverifiedException | JsonDataException x) {
                        log.warn("{} - {}", server.socketAddress, x.getMessage());
                        return null;
                    } catch (final IOException x) {
                        log.info(server.socketAddress.toString(), x);
                        return null;
                    } catch (final RuntimeException x) {
                        log.error(server.socketAddress.toString(), x);
                        throw x;
                    }
                });
            }
            final ExecutorService threadPool = Executors.newFixedThreadPool(servers.size(), new ContextPropagatingThreadFactory("request"));
            final List<Future<Set<UTXO>>> futures;
            try {
                futures = threadPool.invokeAll(tasks, 10, TimeUnit.SECONDS);
            } catch (final InterruptedException x) {
                throw new RuntimeException(x);
            } finally {
                threadPool.shutdown();
            }
            final Multiset<UTXO> countedUtxos = HashMultiset.create();
            int numSuccess = 0, numFail = 0, numTimeOuts = 0;
            for (Future<Set<UTXO>> future : futures) {
                if (!future.isCancelled()) {
                    try {
                        final Set<UTXO> utxos = future.get();
                        if (utxos != null) {
                            countedUtxos.addAll(utxos);
                            numSuccess++;
                        } else {
                            numFail++;
                        }
                    } catch (InterruptedException | ExecutionException x) {
                        throw new RuntimeException(x);
                    }
                } else {
                    numTimeOuts++;
                }
            }
            final int trustThreshold = servers.size() / 2;
            for (final Iterator<Multiset.Entry<UTXO>> i = countedUtxos.entrySet().iterator(); i.hasNext(); ) {
                final Multiset.Entry<UTXO> entry = i.next();
                if (entry.getCount() < trustThreshold)
                    i.remove();
            }
            final Set<UTXO> utxos = countedUtxos.elementSet();
            log.info("{} successes, {} fails, {} time-outs, {} UTXOs {}", numSuccess, numFail, numTimeOuts, utxos.size(), utxos);
            if (numSuccess < trustThreshold)
                onFail(R.string.sweep_wallet_fragment_request_wallet_balance_failed_connection);
            else if (utxos.isEmpty())
                onFail(R.string.sweep_wallet_fragment_request_wallet_balance_empty);
            else
                onResult(utxos);
        }

        private Socket connect(final ElectrumServer server) throws IOException {
            final Socket socket;
            if (server.type == ElectrumServer.Type.TLS) {
                final SocketFactory sf = sslTrustAllCertificates();
                socket = sf.createSocket(server.socketAddress.getHostName(), server.socketAddress.getPort());
                final SSLSession sslSession = ((SSLSocket) socket).getSession();
                final Certificate certificate = sslSession.getPeerCertificates()[0];
                final String certificateFingerprint = sslCertificateFingerprint(certificate);
                if (server.certificateFingerprint == null) {
                    // signed by CA
                    if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(server.socketAddress.getHostName(), sslSession))
                        throw new SSLPeerUnverifiedException("Expected " + server.socketAddress.getHostName() + ", got " + sslSession.getPeerPrincipal());
                } else {
                    // self-signed
                    if (!certificateFingerprint.equals(server.certificateFingerprint))
                        throw new SSLPeerUnverifiedException("Expected " + server.certificateFingerprint + " for " + server.socketAddress.getHostName() + ", got " + certificateFingerprint);
                }
            } else if (server.type == ElectrumServer.Type.TCP) {
                socket = new Socket();
                socket.connect(server.socketAddress, 5000);
            } else {
                throw new IllegalStateException("Cannot handle: " + server.type);
            }
            return socket;
        }
    });
}
Also used : Set(java.util.Set) HashSet(java.util.HashSet) Moshi(com.squareup.moshi.Moshi) LegacyAddress(org.bitcoinj.core.LegacyAddress) InetSocketAddress(java.net.InetSocketAddress) Address(org.bitcoinj.core.Address) SegwitAddress(org.bitcoinj.core.SegwitAddress) ScriptBuilder(org.bitcoinj.script.ScriptBuilder) Sha256Hash(org.bitcoinj.core.Sha256Hash) BufferedSink(okio.BufferedSink) JsonAdapter(com.squareup.moshi.JsonAdapter) ContextPropagatingThreadFactory(org.bitcoinj.utils.ContextPropagatingThreadFactory) Coin(org.bitcoinj.core.Coin) Iterator(java.util.Iterator) List(java.util.List) ArrayList(java.util.ArrayList) LinkedList(java.util.LinkedList) BufferedSource(okio.BufferedSource) Script(org.bitcoinj.script.Script) SSLSocketFactory(javax.net.ssl.SSLSocketFactory) SocketFactory(javax.net.SocketFactory) SSLPeerUnverifiedException(javax.net.ssl.SSLPeerUnverifiedException) SSLSession(javax.net.ssl.SSLSession) IOException(java.io.IOException) UTXO(org.bitcoinj.core.UTXO) Transaction(org.bitcoinj.core.Transaction) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future) HashMultiset(com.google.common.collect.HashMultiset) Multiset(com.google.common.collect.Multiset) SSLSocket(javax.net.ssl.SSLSocket) Socket(java.net.Socket) X509Certificate(java.security.cert.X509Certificate) Certificate(java.security.cert.Certificate)

Aggregations

HashMultiset (com.google.common.collect.HashMultiset)1 Multiset (com.google.common.collect.Multiset)1 JsonAdapter (com.squareup.moshi.JsonAdapter)1 Moshi (com.squareup.moshi.Moshi)1 IOException (java.io.IOException)1 InetSocketAddress (java.net.InetSocketAddress)1 Socket (java.net.Socket)1 Certificate (java.security.cert.Certificate)1 X509Certificate (java.security.cert.X509Certificate)1 ArrayList (java.util.ArrayList)1 HashSet (java.util.HashSet)1 Iterator (java.util.Iterator)1 LinkedList (java.util.LinkedList)1 List (java.util.List)1 Set (java.util.Set)1 ExecutorService (java.util.concurrent.ExecutorService)1 Future (java.util.concurrent.Future)1 SocketFactory (javax.net.SocketFactory)1 SSLPeerUnverifiedException (javax.net.ssl.SSLPeerUnverifiedException)1 SSLSession (javax.net.ssl.SSLSession)1