mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2024-11-19 09:50:32 +01:00
Integrate Tor support into the PeerGroup and WalletAppKit API.
This commit is contained in:
parent
6c3b6ce1b3
commit
3f3f637779
@ -17,10 +17,12 @@
|
||||
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.net.BlockingClientManager;
|
||||
import com.google.bitcoin.net.ClientConnectionManager;
|
||||
import com.google.bitcoin.net.NioClientManager;
|
||||
import com.google.bitcoin.net.discovery.PeerDiscovery;
|
||||
import com.google.bitcoin.net.discovery.PeerDiscoveryException;
|
||||
import com.google.bitcoin.net.discovery.TorDiscovery;
|
||||
import com.google.bitcoin.script.Script;
|
||||
import com.google.bitcoin.utils.ExponentialBackoff;
|
||||
import com.google.bitcoin.utils.ListenerRegistration;
|
||||
@ -31,11 +33,16 @@ import com.google.common.collect.Sets;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.subgraph.orchid.Tor;
|
||||
import com.subgraph.orchid.TorClient;
|
||||
import net.jcip.annotations.GuardedBy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@ -69,9 +76,10 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
* network IO, but starting and stopping the service should be fine.</p>
|
||||
*/
|
||||
public class PeerGroup extends AbstractExecutionThreadService implements TransactionBroadcaster {
|
||||
private static final int DEFAULT_CONNECTIONS = 4;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PeerGroup.class);
|
||||
private static final int DEFAULT_CONNECTIONS = 4;
|
||||
private static final int TOR_TIMEOUT_SECONDS = 20;
|
||||
|
||||
protected final ReentrantLock lock = Threading.lock("peergroup");
|
||||
|
||||
// Addresses to try to connect to, excluding active peers.
|
||||
@ -83,6 +91,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
|
||||
// Currently connecting peers.
|
||||
private final CopyOnWriteArrayList<Peer> pendingPeers;
|
||||
private final ClientConnectionManager channels;
|
||||
@Nullable private final TorClient torClient;
|
||||
|
||||
// The peer that has been selected for the purposes of downloading announced data.
|
||||
@GuardedBy("lock") private Peer downloadPeer;
|
||||
@ -273,16 +282,51 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
|
||||
this(params, chain, new NioClientManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a PeerGroup that accesses the network via the Tor network. The provided TorClient is used so you can
|
||||
* preconfigure it beforehand. It should not have been already started. You can just use "new TorClient()" if
|
||||
* you don't have any particular configuration requirements.</p>
|
||||
*
|
||||
* <p>Peer discovery is automatically configured to use DNS seeds resolved via a random selection of exit nodes.
|
||||
* If running on the Oracle JDK the unlimited strength jurisdiction checks will also be overridden,
|
||||
* as they no longer apply anyway and can cause startup failures due to the requirement for AES-256.</p>
|
||||
*
|
||||
* <p>The user does not need any additional software for this: it's all pure Java. As of April 2014 <b>this mode
|
||||
* is experimental</b>.</p>
|
||||
*
|
||||
* @throws java.util.concurrent.TimeoutException if Tor fails to start within 20 seconds.
|
||||
*/
|
||||
public static PeerGroup newWithTor(NetworkParameters params, @Nullable AbstractBlockChain chain, TorClient torClient) throws TimeoutException {
|
||||
checkNotNull(torClient);
|
||||
maybeDisableExportControls();
|
||||
BlockingClientManager manager = new BlockingClientManager(torClient.getSocketFactory());
|
||||
final int CONNECT_TIMEOUT_MSEC = TOR_TIMEOUT_SECONDS * 1000;
|
||||
manager.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
|
||||
PeerGroup result = new PeerGroup(params, chain, manager, torClient);
|
||||
result.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
|
||||
result.addPeerDiscovery(new TorDiscovery(params, torClient));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PeerGroup allowing you to specify the {@link ClientConnectionManager} which is used to create new
|
||||
* connections and keep track of existing ones.
|
||||
*/
|
||||
public PeerGroup(NetworkParameters params, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager) {
|
||||
this(params, chain, connectionManager, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new PeerGroup allowing you to specify the {@link ClientConnectionManager} which is used to create new
|
||||
* connections and keep track of existing ones.
|
||||
*/
|
||||
private PeerGroup(NetworkParameters params, @Nullable AbstractBlockChain chain, ClientConnectionManager connectionManager, @Nullable TorClient torClient) {
|
||||
this.params = checkNotNull(params);
|
||||
this.chain = chain;
|
||||
this.fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
|
||||
this.wallets = new CopyOnWriteArrayList<Wallet>();
|
||||
this.peerFilterProviders = new CopyOnWriteArrayList<PeerFilterProvider>();
|
||||
this.torClient = torClient;
|
||||
|
||||
// This default sentinel value will be overridden by one of two actions:
|
||||
// - adding a peer discovery source sets it to the default
|
||||
@ -697,6 +741,12 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
|
||||
protected void startUp() throws Exception {
|
||||
// This is run in a background thread by the Service implementation.
|
||||
vPingTimer = new Timer("Peer pinging thread", true);
|
||||
if (torClient != null) {
|
||||
log.info("Starting Tor/Orchid ...");
|
||||
torClient.start();
|
||||
torClient.waitUntilReady(TOR_TIMEOUT_SECONDS * 1000);
|
||||
log.info("Tor ready");
|
||||
}
|
||||
channels.startAsync();
|
||||
channels.awaitRunning();
|
||||
triggerConnections();
|
||||
@ -712,6 +762,9 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
|
||||
for (PeerDiscovery peerDiscovery : peerDiscoverers) {
|
||||
peerDiscovery.shutdown();
|
||||
}
|
||||
if (torClient != null) {
|
||||
torClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1503,4 +1556,36 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public TorClient getTorClient() {
|
||||
return torClient;
|
||||
}
|
||||
|
||||
private static void maybeDisableExportControls() {
|
||||
// This sorry story is documented in https://bugs.openjdk.java.net/browse/JDK-7024850
|
||||
// Oracle received permission to ship AES-256 by default in 2011, but didn't get around to it for Java 8
|
||||
// even though that shipped in 2014! That's dumb. So we disable the ridiculous US government mandated DRM
|
||||
// for AES-256 here, as Tor requires it.
|
||||
if (Tor.isAndroidRuntime())
|
||||
return;
|
||||
try {
|
||||
Field gate = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
|
||||
gate.setAccessible(true);
|
||||
gate.setBoolean(null, false);
|
||||
final Field allPerm = Class.forName("javax.crypto.CryptoAllPermission").getDeclaredField("INSTANCE");
|
||||
allPerm.setAccessible(true);
|
||||
Object accessAllAreasCard = allPerm.get(null);
|
||||
final Constructor<?> constructor = Class.forName("javax.crypto.CryptoPermissions").getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
Object coll = constructor.newInstance();
|
||||
Method addPerm = Class.forName("javax.crypto.CryptoPermissions").getDeclaredMethod("add", java.security.Permission.class);
|
||||
addPerm.setAccessible(true);
|
||||
addPerm.invoke(coll, accessAllAreasCard);
|
||||
Field defaultPolicy = Class.forName("javax.crypto.JceSecurity").getDeclaredField("defaultPolicy");
|
||||
defaultPolicy.setAccessible(true);
|
||||
defaultPolicy.set(null, coll);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to deactivate AES-256 barrier logic, Tor mode may crash if this JVM requires it: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,9 @@ import com.google.bitcoin.store.BlockStoreException;
|
||||
import com.google.bitcoin.store.SPVBlockStore;
|
||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||
import com.google.common.util.concurrent.AbstractIdleService;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.Service;
|
||||
import com.subgraph.orchid.TorClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@ -35,6 +34,7 @@ import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
@ -78,6 +78,7 @@ public class WalletAppKit extends AbstractIdleService {
|
||||
protected boolean autoStop = true;
|
||||
protected InputStream checkpoints;
|
||||
protected boolean blockingStartup = true;
|
||||
protected boolean useTor = false; // Perhaps in future we can change this to true.
|
||||
protected String userAgent, version;
|
||||
|
||||
public WalletAppKit(NetworkParameters params, File directory, String filePrefix) {
|
||||
@ -157,6 +158,15 @@ public class WalletAppKit extends AbstractIdleService {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If called, then an embedded Tor client library will be used to connect to the P2P network. The user does not need
|
||||
* any additional software for this: it's all pure Java. As of April 2014 <b>this mode is experimental</b>.
|
||||
*/
|
||||
public WalletAppKit useTor() {
|
||||
this.useTor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Override this to load all wallet extensions if any are necessary.</p>
|
||||
*
|
||||
@ -262,8 +272,12 @@ public class WalletAppKit extends AbstractIdleService {
|
||||
}
|
||||
}
|
||||
|
||||
protected PeerGroup createPeerGroup() {
|
||||
return new PeerGroup(params, vChain);
|
||||
protected PeerGroup createPeerGroup() throws TimeoutException {
|
||||
if (useTor) {
|
||||
return PeerGroup.newWithTor(params, vChain, new TorClient());
|
||||
}
|
||||
else
|
||||
return new PeerGroup(params, vChain);
|
||||
}
|
||||
|
||||
private void installShutdownHook() {
|
||||
|
@ -19,10 +19,8 @@ package com.google.bitcoin.tools;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||
import com.google.bitcoin.net.BlockingClientManager;
|
||||
import com.google.bitcoin.net.discovery.DnsDiscovery;
|
||||
import com.google.bitcoin.net.discovery.PeerDiscovery;
|
||||
import com.google.bitcoin.net.discovery.TorDiscovery;
|
||||
import com.google.bitcoin.params.MainNetParams;
|
||||
import com.google.bitcoin.params.RegTestParams;
|
||||
import com.google.bitcoin.params.TestNet3Params;
|
||||
@ -37,7 +35,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.subgraph.orchid.TorClient;
|
||||
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
@ -60,6 +57,7 @@ import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogManager;
|
||||
|
||||
@ -695,10 +693,13 @@ public class WalletTool {
|
||||
// This will ensure the wallet is saved when it changes.
|
||||
wallet.autosaveToFile(walletFile, 200, TimeUnit.MILLISECONDS, null);
|
||||
if (options.has("tor")) {
|
||||
torClient = new TorClient();
|
||||
torClient.start();
|
||||
peers = new PeerGroup(params, chain, new BlockingClientManager(torClient.getSocketFactory()));
|
||||
} else {
|
||||
try {
|
||||
peers = PeerGroup.newWithTor(params, chain, new TorClient());
|
||||
} catch (TimeoutException e) {
|
||||
System.err.println("Tor startup timed out, falling back to clear net ...");
|
||||
}
|
||||
}
|
||||
if (peers == null) {
|
||||
peers = new PeerGroup(params, chain);
|
||||
}
|
||||
peers.setUserAgent("WalletTool", "1.0");
|
||||
@ -714,9 +715,8 @@ public class WalletTool {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
} else if (options.has("tor")) {
|
||||
peers.addPeerDiscovery(new TorDiscovery(params, torClient));
|
||||
} else {
|
||||
} else if (!options.has("tor")) {
|
||||
// If Tor mode then PeerGroup already has discovery set up.
|
||||
if (params == RegTestParams.get()) {
|
||||
log.info("Assuming regtest node on localhost");
|
||||
peers.addAddress(PeerAddress.localhost(params));
|
||||
|
@ -86,6 +86,8 @@ public class Main extends Application {
|
||||
// Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
|
||||
// last months worth or more (takes a few seconds).
|
||||
bitcoin.setCheckpoints(getClass().getResourceAsStream("checkpoints"));
|
||||
// As an example!
|
||||
bitcoin.useTor();
|
||||
}
|
||||
|
||||
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
|
||||
@ -165,7 +167,8 @@ public class Main extends Application {
|
||||
public void stop() throws Exception {
|
||||
bitcoin.stopAsync();
|
||||
bitcoin.awaitTerminated();
|
||||
super.stop();
|
||||
// Forcibly terminate the JVM because Orchid likes to spew non-daemon threads everywhere.
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
Loading…
Reference in New Issue
Block a user