diff --git a/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java
index 3af85cb4..c8d0998e 100644
--- a/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java
+++ b/core/src/main/java/org/bitcoinj/net/discovery/DnsDiscovery.java
@@ -18,13 +18,10 @@
package org.bitcoinj.net.discovery;
import org.bitcoinj.core.NetworkParameters;
-import com.google.common.collect.Lists;
-import org.bitcoinj.utils.DaemonThreadFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.*;
@@ -39,12 +36,7 @@ import java.util.concurrent.*;
* will return up to 30 random peers from the set of those returned within the timeout period. If you want more peers
* to connect to, you need to discover them via other means (like addr broadcasts).
*/
-public class DnsDiscovery implements PeerDiscovery {
- private static final Logger log = LoggerFactory.getLogger(DnsDiscovery.class);
-
- private final String[] dnsSeeds;
- private final NetworkParameters netParams;
-
+public class DnsDiscovery extends MultiplexingDiscovery {
/**
* Supports finding peers through DNS A records. Community run DNS entry points will be used.
*
@@ -58,65 +50,44 @@ public class DnsDiscovery implements PeerDiscovery {
* Supports finding peers through DNS A records.
*
* @param dnsSeeds Host names to be examined for seed addresses.
- * @param netParams Network parameters to be used for port information.
+ * @param params Network parameters to be used for port information.
*/
- public DnsDiscovery(String[] dnsSeeds, NetworkParameters netParams) {
- this.dnsSeeds = dnsSeeds;
- this.netParams = netParams;
+ public DnsDiscovery(String[] dnsSeeds, NetworkParameters params) {
+ super(params, buildDiscoveries(params, dnsSeeds));
}
- @Override
- public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
- if (dnsSeeds == null || dnsSeeds.length == 0)
- throw new PeerDiscoveryException("No DNS seeds configured; unable to find any peers");
+ private static List buildDiscoveries(NetworkParameters params, String[] seeds) {
+ List discoveries = new ArrayList(seeds.length);
+ for (String seed : seeds)
+ discoveries.add(new DnsSeedDiscovery(params, seed));
+ return discoveries;
+ }
- // Java doesn't have an async DNS API so we have to do all lookups in a thread pool, as sometimes seeds go
- // hard down and it takes ages to give up and move on.
- ExecutorService threadPool = Executors.newFixedThreadPool(dnsSeeds.length, new DaemonThreadFactory());
- try {
- List> tasks = Lists.newArrayList();
- for (final String seed : dnsSeeds) {
- tasks.add(new Callable() {
- @Override
- public InetAddress[] call() throws Exception {
- return InetAddress.getAllByName(seed);
- }
- });
+ /** Implements discovery from a single DNS host. */
+ public static class DnsSeedDiscovery implements PeerDiscovery {
+ private final String hostname;
+ private final NetworkParameters params;
+
+ public DnsSeedDiscovery(NetworkParameters params, String hostname) {
+ this.hostname = hostname;
+ this.params = params;
+ }
+
+ @Override
+ public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
+ try {
+ InetAddress[] response = InetAddress.getAllByName(hostname);
+ InetSocketAddress[] result = new InetSocketAddress[response.length];
+ for (int i = 0; i < response.length; i++)
+ result[i] = new InetSocketAddress(response[i], params.getPort());
+ return result;
+ } catch (UnknownHostException e) {
+ throw new PeerDiscoveryException(e);
}
- final List> futures = threadPool.invokeAll(tasks, timeoutValue, timeoutUnit);
- ArrayList addrs = Lists.newArrayList();
- for (int i = 0; i < futures.size(); i++) {
- Future future = futures.get(i);
- if (future.isCancelled()) {
- log.warn("DNS seed {}: timed out", dnsSeeds[i]);
- continue; // Timed out.
- }
- final InetAddress[] inetAddresses;
- try {
- inetAddresses = future.get();
- log.info("DNS seed {}: got {} peers", dnsSeeds[i], inetAddresses.length);
- } catch (ExecutionException e) {
- log.error("DNS seed {}: failed to look up: {}", dnsSeeds[i], e.getMessage());
- continue;
- }
- for (InetAddress addr : inetAddresses) {
- addrs.add(new InetSocketAddress(addr, netParams.getPort()));
- }
- }
- if (addrs.size() == 0)
- throw new PeerDiscoveryException("Unable to find any peers via DNS");
- Collections.shuffle(addrs);
- threadPool.shutdownNow();
- return addrs.toArray(new InetSocketAddress[addrs.size()]);
- } catch (InterruptedException e) {
- throw new PeerDiscoveryException(e);
- } finally {
- threadPool.shutdown();
+ }
+
+ @Override
+ public void shutdown() {
}
}
-
- /** We don't have a way to abort a DNS lookup, so this does nothing */
- @Override
- public void shutdown() {
- }
}
diff --git a/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java b/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java
new file mode 100644
index 00000000..f65a4365
--- /dev/null
+++ b/core/src/main/java/org/bitcoinj/net/discovery/MultiplexingDiscovery.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright 2014 Mike Hearn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.bitcoinj.net.discovery;
+
+import com.google.common.collect.Lists;
+import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.utils.DaemonThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.*;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * MultiplexingDiscovery queries multiple PeerDiscovery objects, shuffles their responses and then returns the results,
+ * thus selecting randomly between them and reducing the influence of any particular seed. Any that don't respond
+ * within the timeout are ignored. Backends are queried in parallel. Backends may block
+ */
+public class MultiplexingDiscovery implements PeerDiscovery {
+ private static final Logger log = LoggerFactory.getLogger(MultiplexingDiscovery.class);
+
+ protected final List seeds;
+ protected final NetworkParameters netParams;
+ private volatile ExecutorService vThreadPool;
+
+ /**
+ * Will query the given seeds in parallel before producing a merged response.
+ */
+ public MultiplexingDiscovery(NetworkParameters params, List seeds) {
+ checkArgument(!seeds.isEmpty());
+ this.netParams = params;
+ this.seeds = seeds;
+ }
+
+ @Override
+ public InetSocketAddress[] getPeers(final long timeoutValue, final TimeUnit timeoutUnit) throws PeerDiscoveryException {
+ vThreadPool = Executors.newFixedThreadPool(seeds.size(), new DaemonThreadFactory());
+ try {
+ List> tasks = Lists.newArrayList();
+ for (final PeerDiscovery seed : seeds) {
+ tasks.add(new Callable() {
+ @Override
+ public InetSocketAddress[] call() throws Exception {
+ return seed.getPeers(timeoutValue, timeoutUnit);
+ }
+ });
+ }
+ final List> futures = vThreadPool.invokeAll(tasks, timeoutValue, timeoutUnit);
+ ArrayList addrs = Lists.newArrayList();
+ for (int i = 0; i < futures.size(); i++) {
+ Future future = futures.get(i);
+ if (future.isCancelled()) {
+ log.warn("Seed {}: timed out", seeds.get(i));
+ continue; // Timed out.
+ }
+ final InetSocketAddress[] inetAddresses;
+ try {
+ inetAddresses = future.get();
+ } catch (ExecutionException e) {
+ log.warn("Seed {}: failed to look up: {}", seeds.get(i), e.getMessage());
+ continue;
+ }
+ Collections.addAll(addrs, inetAddresses);
+ }
+ if (addrs.size() == 0)
+ throw new PeerDiscoveryException("No peer discovery returned any results: check internet connection?");
+ Collections.shuffle(addrs);
+ vThreadPool.shutdownNow();
+ return addrs.toArray(new InetSocketAddress[addrs.size()]);
+ } catch (InterruptedException e) {
+ throw new PeerDiscoveryException(e);
+ } finally {
+ vThreadPool.shutdown();
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ ExecutorService tp = vThreadPool;
+ if (tp != null)
+ tp.shutdown();
+ }
+}