3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 02:05:53 +00:00

Use a local Bitcoin node if one is detected instead of the p2p network. This allows any user of a bitcoinj based app to upgrade from SPV to full mode security by just installing and running Core on the same machine. Can be controlled with a new property on PeerGroup.

This commit is contained in:
Mike Hearn 2014-07-29 18:13:52 +02:00
parent 812a4f59c4
commit 6679f42f4a
4 changed files with 105 additions and 18 deletions

View File

@ -100,11 +100,7 @@ public class PeerAddress extends ChildMessage {
}
public static PeerAddress localhost(NetworkParameters params) {
try {
return new PeerAddress(InetAddress.getLocalHost(), params.getPort());
} catch (UnknownHostException e) {
throw new RuntimeException(e); // Broken system.
}
return new PeerAddress(InetAddress.getLoopbackAddress(), params.getPort());
}
@Override

View File

@ -33,6 +33,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.io.Closeables;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.*;
@ -42,8 +43,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
@ -119,6 +122,8 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
public static final long DEFAULT_PING_INTERVAL_MSEC = 2000;
private long pingIntervalMsec = DEFAULT_PING_INTERVAL_MSEC;
@GuardedBy("lock") private boolean useLocalhostPeerWhenPossible = true;
private final NetworkParameters params;
private final AbstractBlockChain chain;
@GuardedBy("lock") private long fastCatchupTimeSecs;
@ -236,7 +241,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
}
// Visible for testing
@VisibleForTesting
PeerEventListener startupListener = new PeerStartupListener();
/**
@ -687,6 +692,32 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
}
private enum LocalhostCheckState {
NOT_TRIED,
FOUND,
FOUND_AND_CONNECTED,
NOT_THERE
}
private LocalhostCheckState localhostCheckState = LocalhostCheckState.NOT_TRIED;
private boolean maybeCheckForLocalhostPeer() {
checkState(lock.isHeldByCurrentThread());
if (localhostCheckState == LocalhostCheckState.NOT_TRIED) {
// Do a fast blocking connect to see if anything is listening.
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), params.getPort()), vConnectTimeoutMillis);
localhostCheckState = LocalhostCheckState.FOUND;
Closeables.close(socket, true);
return true;
} catch (IOException e) {
log.info("Localhost peer not detected.");
localhostCheckState = LocalhostCheckState.NOT_THERE;
}
}
return false;
}
/** Picks a peer from discovery and connects to it. If connection fails, picks another and tries again. */
protected void connectToAnyPeer() throws PeerDiscoveryException {
final State state = state();
@ -698,6 +729,12 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
long retryTime = 0;
lock.lock();
try {
if (useLocalhostPeerWhenPossible && maybeCheckForLocalhostPeer()) {
log.info("Localhost peer detected, trying to use it instead of P2P discovery");
maxConnections = 0;
connectToLocalHost();
return;
}
if (!haveReadyInactivePeer(nowMillis)) {
discoverPeers();
groupBackoff.trackSuccess();
@ -716,14 +753,14 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
if (retryTime > nowMillis) {
// Sleep until retry time
final long millis = retryTime - nowMillis;
log.info("Waiting {} msec before next connect attempt {}", millis, addr == null ? "" : " to " + addr);
log.info("Waiting {} msec before next connect attempt {}", millis, addr == null ? "" : "to " + addr);
Utils.sleep(millis);
}
}
// This method constructs a Peer and puts it into pendingPeers.
checkNotNull(addr); // Help static analysis which can't see that addr is always set if we didn't throw above.
connectTo(addr, false);
connectTo(addr, false, vConnectTimeoutMillis);
}
private boolean haveReadyInactivePeer(long nowMillis) {
@ -931,7 +968,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
public Peer connectTo(InetSocketAddress address) {
PeerAddress peerAddress = new PeerAddress(address);
backoffMap.put(peerAddress, new ExponentialBackoff(peerBackoffParams));
return connectTo(peerAddress, true);
return connectTo(peerAddress, true, vConnectTimeoutMillis);
}
/**
@ -941,12 +978,19 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
public Peer connectToLocalHost() {
final PeerAddress localhost = PeerAddress.localhost(params);
backoffMap.put(localhost, new ExponentialBackoff(peerBackoffParams));
return connectTo(localhost, true);
return connectTo(localhost, true, vConnectTimeoutMillis);
}
// Internal version.
/**
* Creates a version message to send, constructs a Peer object and attempts to connect it. Returns the peer on
* success or null on failure.
* @param address Remote network address
* @param incrementMaxConnections Whether to consider this connection an attempt to fill our quota, or something
* explicitly requested.
* @return Peer or null.
*/
@Nullable
protected Peer connectTo(PeerAddress address, boolean incrementMaxConnections) {
protected Peer connectTo(PeerAddress address, boolean incrementMaxConnections, int connectTimeoutMillis) {
VersionMessage ver = getVersionMessage().duplicate();
ver.bestHeight = chain == null ? 0 : chain.getBestChainHeight();
ver.time = Utils.currentTimeSeconds();
@ -963,7 +1007,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
handlePeerDeath(peer);
return null;
}
peer.setSocketTimeout(vConnectTimeoutMillis);
peer.setSocketTimeout(connectTimeoutMillis);
// When the channel has connected and version negotiated successfully, handleNewPeer will end up being called on
// a worker thread.
@ -1576,8 +1620,35 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
}
/**
* Returns the {@link com.subgraph.orchid.TorClient} object for this peer group, if Tor is in use, null otherwise.
*/
@Nullable
public TorClient getTorClient() {
return torClient;
}
/** See {@link #setUseLocalhostPeerWhenPossible(boolean)} */
public boolean getUseLocalhostPeerWhenPossible() {
lock.lock();
try {
return useLocalhostPeerWhenPossible;
} finally {
lock.unlock();
}
}
/**
* When true (the default), PeerGroup will attempt to connect to a Bitcoin node running on localhost before
* attempting to use the P2P network. If successful, only localhost will be used. This makes for a simple
* and easy way for a user to upgrade a bitcoinj based app running in SPV mode to fully validating security.
*/
public void setUseLocalhostPeerWhenPossible(boolean useLocalhostPeerWhenPossible) {
lock.lock();
try {
this.useLocalhostPeerWhenPossible = useLocalhostPeerWhenPossible;
} finally {
lock.unlock();
}
}
}

View File

@ -34,7 +34,10 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
@ -645,4 +648,21 @@ public class PeerGroupTest extends TestWithPeerGroup {
future.get();
assertTrue(future.isDone());
}
@Test
public void preferLocalPeer() throws IOException {
// Check that if we have a localhost port 8333 then it's used instead of the p2p network.
ServerSocket local = new ServerSocket(params.getPort(), 100, InetAddress.getLoopbackAddress());
try {
peerGroup.startAsync();
peerGroup.awaitRunning();
local.accept().close(); // Probe connect
local.accept(); // Real connect
// If we get here it used the local peer. Check no others are in use.
assertEquals(1, peerGroup.getMaxConnections());
assertEquals(PeerAddress.localhost(params), peerGroup.getPendingPeers().get(0).getAddress());
} finally {
local.close();
}
}
}

View File

@ -823,12 +823,12 @@ public class WalletTool {
}
} 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));
} else {
// if (params == RegTestParams.get()) {
// log.info("Assuming regtest node on localhost");
// peers.addAddress(PeerAddress.localhost(params));
// } else {
peers.addPeerDiscovery(new DnsDiscovery(params));
}
//}
}
}