From 06ba160361e2ce12cd54c72280bc1899bb207f41 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 28 Apr 2015 22:56:23 +0200 Subject: [PATCH] Tor: accelerate startup by using HTTP/Cartographer seeding when possible instead of DNS. We still use TorDiscovery for networks where we don't have any Cartographer seeds. Switch to OkHTTP because the standard Java HTTP client doesn't let you customise the socket factory and thus cannot be used via Tor directly (doh). --- core/pom.xml | 5 ++ .../org/bitcoinj/core/NetworkParameters.java | 19 +++-- .../java/org/bitcoinj/core/PeerGroup.java | 13 +++- .../bitcoinj/net/discovery/HttpDiscovery.java | 74 ++++++++++++------- .../org/bitcoinj/params/MainNetParams.java | 19 ++++- 5 files changed, 92 insertions(+), 38 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 3b72d86cc..15a948915 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -479,6 +479,11 @@ orchid 1.1 + + com.squareup.okhttp + okhttp + 2.2.0 + diff --git a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java index eee374e7d..64d8eac98 100644 --- a/core/src/main/java/org/bitcoinj/core/NetworkParameters.java +++ b/core/src/main/java/org/bitcoinj/core/NetworkParameters.java @@ -17,15 +17,14 @@ package org.bitcoinj.core; -import org.bitcoinj.params.*; -import org.bitcoinj.script.Script; -import org.bitcoinj.script.ScriptOpCodes; import com.google.common.base.Objects; +import org.bitcoinj.net.discovery.*; +import org.bitcoinj.params.*; +import org.bitcoinj.script.*; -import javax.annotation.Nullable; -import java.io.ByteArrayOutputStream; -import java.io.Serializable; -import java.math.BigInteger; +import javax.annotation.*; +import java.io.*; +import java.math.*; import java.util.*; import static org.bitcoinj.core.Coin.*; @@ -95,6 +94,7 @@ public abstract class NetworkParameters implements Serializable { protected int[] acceptableAddressCodes; protected String[] dnsSeeds; + protected HttpDiscovery.Details[] httpSeeds = new HttpDiscovery.Details[] {}; protected Map checkpoints = new HashMap(); protected NetworkParameters() { @@ -265,6 +265,11 @@ public abstract class NetworkParameters implements Serializable { return dnsSeeds; } + /** Returns discovery objects for seeds implementing the Cartographer protocol. See {@link org.bitcoinj.net.discovery.HttpDiscovery} for more info. */ + public HttpDiscovery.Details[] getHttpSeeds() { + return httpSeeds; + } + /** *

Genesis block for this chain.

* diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 0b2a2b24e..f24e3ce24 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -23,6 +23,7 @@ import com.google.common.collect.*; import com.google.common.net.*; import com.google.common.primitives.*; import com.google.common.util.concurrent.*; +import com.squareup.okhttp.*; import com.subgraph.orchid.*; import net.jcip.annotations.*; import org.bitcoinj.crypto.*; @@ -304,7 +305,17 @@ public class PeerGroup implements TransactionBroadcaster { manager.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC); PeerGroup result = new PeerGroup(context, chain, manager, torClient); result.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC); - result.addPeerDiscovery(new TorDiscovery(context.getParams(), torClient)); + + NetworkParameters params = context.getParams(); + HttpDiscovery.Details[] httpSeeds = params.getHttpSeeds(); + if (httpSeeds.length > 0) { + // Use HTTP discovery when Tor is active and there is a Cartographer seed, for a much needed speed boost. + OkHttpClient client = new OkHttpClient(); + client.setSocketFactory(torClient.getSocketFactory()); + result.addPeerDiscovery(new HttpDiscovery(params, httpSeeds[0], client)); + } else { + result.addPeerDiscovery(new TorDiscovery(params, torClient)); + } return result; } diff --git a/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java index 06c333b45..55c1e8ca6 100644 --- a/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java +++ b/core/src/main/java/org/bitcoinj/net/discovery/HttpDiscovery.java @@ -15,22 +15,21 @@ */ package org.bitcoinj.net.discovery; -import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.InvalidProtocolBufferException; -import org.bitcoin.crawler.PeerSeedProtos; +import com.google.common.annotations.*; +import com.google.protobuf.*; +import com.squareup.okhttp.*; +import org.bitcoin.crawler.*; import org.bitcoinj.core.*; -import javax.annotation.Nullable; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.URI; -import java.security.SignatureException; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; +import javax.annotation.*; +import java.io.*; +import java.net.*; +import java.security.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.zip.*; -import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.*; /** * A class that knows how to read signed sets of seeds over HTTP, using a simple protobuf based protocol. See the @@ -40,33 +39,56 @@ import static com.google.common.base.Preconditions.checkArgument; public class HttpDiscovery implements PeerDiscovery { private static final int TIMEOUT_SECS = 20; - private final ECKey pubkey; - private final URI uri; + public static class Details { + @Nullable public final ECKey pubkey; + public final URI uri; + + public Details(@Nullable ECKey pubkey, URI uri) { + this.pubkey = pubkey; + this.uri = uri; + } + } + + private final Details details; private final NetworkParameters params; + private final OkHttpClient client; /** * Constructs a discovery object that will read data from the given HTTP[S] URI and, if a public key is provided, * will check the signature using that key. */ public HttpDiscovery(NetworkParameters params, URI uri, @Nullable ECKey pubkey) { - checkArgument(uri.getScheme().startsWith("http")); - this.uri = uri; - this.pubkey = pubkey; + this(params, new Details(pubkey, uri)); + } + + /** + * Constructs a discovery object that will read data from the given HTTP[S] URI and, if a public key is provided, + * will check the signature using that key. + */ + public HttpDiscovery(NetworkParameters params, Details details) { + this(params, details, new OkHttpClient()); + } + + public HttpDiscovery(NetworkParameters params, Details details, OkHttpClient client) { + checkArgument(details.uri.getScheme().startsWith("http")); + this.details = details; this.params = params; + this.client = client; } @Override public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException { try { - HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection(); - conn.setReadTimeout(TIMEOUT_SECS * 1000); - conn.setConnectTimeout(TIMEOUT_SECS * 1000); - conn.setRequestProperty("User-Agent", "bitcoinj " + VersionMessage.BITCOINJ_VERSION); - InputStream stream = conn.getInputStream(); + Response response = client.newCall(new Request.Builder().url(details.uri.toURL()).build()).execute(); + if (!response.isSuccessful()) + throw new PeerDiscoveryException("HTTP request failed: " + response.code() + " " + response.message()); + InputStream stream = response.body().byteStream(); GZIPInputStream zip = new GZIPInputStream(stream); PeerSeedProtos.SignedPeerSeeds proto = PeerSeedProtos.SignedPeerSeeds.parseDelimitedFrom(zip); stream.close(); return protoToAddrs(proto); + } catch (PeerDiscoveryException e1) { + throw e1; } catch (Exception e) { throw new PeerDiscoveryException(e); } @@ -74,11 +96,11 @@ public class HttpDiscovery implements PeerDiscovery { @VisibleForTesting public InetSocketAddress[] protoToAddrs(PeerSeedProtos.SignedPeerSeeds proto) throws PeerDiscoveryException, InvalidProtocolBufferException, SignatureException { - if (pubkey != null) { - if (!Arrays.equals(proto.getPubkey().toByteArray(), pubkey.getPubKey())) + if (details.pubkey != null) { + if (!Arrays.equals(proto.getPubkey().toByteArray(), details.pubkey.getPubKey())) throw new PeerDiscoveryException("Public key mismatch"); Sha256Hash hash = Sha256Hash.hash(proto.getPeerSeeds().toByteArray()); - pubkey.verifyOrThrow(hash.getBytes(), proto.getSignature().toByteArray()); + details.pubkey.verifyOrThrow(hash.getBytes(), proto.getSignature().toByteArray()); } PeerSeedProtos.PeerSeeds seeds = PeerSeedProtos.PeerSeeds.parseFrom(proto.getPeerSeeds()); if (seeds.getTimestamp() < Utils.currentTimeSeconds() - (60 * 60 * 24)) diff --git a/core/src/main/java/org/bitcoinj/params/MainNetParams.java b/core/src/main/java/org/bitcoinj/params/MainNetParams.java index f58669bf4..8d7123baf 100644 --- a/core/src/main/java/org/bitcoinj/params/MainNetParams.java +++ b/core/src/main/java/org/bitcoinj/params/MainNetParams.java @@ -16,11 +16,13 @@ package org.bitcoinj.params; -import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Utils; +import com.google.common.io.*; +import org.bitcoinj.core.*; +import org.bitcoinj.net.discovery.*; -import static com.google.common.base.Preconditions.checkState; +import java.net.*; + +import static com.google.common.base.Preconditions.*; /** * Parameters for the main production network on which people trade goods and services. @@ -67,6 +69,15 @@ public class MainNetParams extends NetworkParameters { "seed.bitcoinstats.com", // Chris Decker "seed.bitnodes.io", // Addy Yeow }; + httpSeeds = new HttpDiscovery.Details[] { + new HttpDiscovery.Details( + ECKey.fromPublicOnly(BaseEncoding.base16().decode( + "027a79143a4de36341494d21b6593015af6b2500e720ad2eda1c0b78165f4f38c4".toUpperCase() + )), + + URI.create("http://main.seed.vinumeris.com/peers") + ) + }; } private static MainNetParams instance;