mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 14:54:15 +00:00
API for setting version messages on outbound connections, and implementing BIP 14. Resolves issue 105.
This commit is contained in:
parent
7f82613559
commit
b72c170086
@ -61,7 +61,9 @@ public interface NetworkConnection {
|
||||
*/
|
||||
void writeMessage(Message message) throws IOException;
|
||||
|
||||
/** Returns the version message received from the other end of the connection during the handshake. */
|
||||
/**
|
||||
* Returns the version message received from the other end of the connection during the handshake.
|
||||
*/
|
||||
VersionMessage getVersionMessage();
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,7 @@ import java.util.concurrent.*;
|
||||
*/
|
||||
public class Peer {
|
||||
private static final Logger log = LoggerFactory.getLogger(Peer.class);
|
||||
public static final int CONNECT_TIMEOUT_MSEC = 60000;
|
||||
|
||||
private NetworkConnection conn;
|
||||
private final NetworkParameters params;
|
||||
@ -49,6 +50,9 @@ public class Peer {
|
||||
// primary peer. This is to avoid redundant work and concurrency problems with downloading the same chain
|
||||
// in parallel.
|
||||
private boolean downloadData = true;
|
||||
// The version data to announce to the other side of the connections we make: useful for setting our "user agent"
|
||||
// equivalent and other things.
|
||||
private VersionMessage versionMessage;
|
||||
|
||||
/**
|
||||
* Size of the pending transactions pool. Override this to reduce memory usage on constrained platforms. The pool
|
||||
@ -69,12 +73,6 @@ public class Peer {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If true, we do some things that may only make sense on constrained devices like Android phones. Currently this
|
||||
* only controls message deduplication.
|
||||
*/
|
||||
public static boolean MOBILE_OPTIMIZED = false;
|
||||
|
||||
// A time before which we only download block headers, after that point we download block bodies.
|
||||
private long fastCatchupTimeSecs;
|
||||
// Whether we are currently downloading headers only or block bodies. Defaults to true, if the fast catchup time
|
||||
@ -89,13 +87,23 @@ public class Peer {
|
||||
* @param bestHeight our current best chain height, to facilitate downloading
|
||||
*/
|
||||
public Peer(NetworkParameters params, PeerAddress address, int bestHeight, BlockChain blockChain) {
|
||||
this(params, address, blockChain, new VersionMessage(params, bestHeight));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a peer that reads/writes from the given block chain. Note that communication won't occur until
|
||||
* you call connect(), which will set up a new NetworkConnection.
|
||||
*
|
||||
* @param ver The version data to announce to the other side.
|
||||
*/
|
||||
public Peer(NetworkParameters params, PeerAddress address, BlockChain blockChain, VersionMessage ver) {
|
||||
this.params = params;
|
||||
this.address = address;
|
||||
this.bestHeight = bestHeight;
|
||||
this.blockChain = blockChain;
|
||||
this.pendingGetBlockFutures = new ArrayList<GetDataFuture<Block>>();
|
||||
this.eventListeners = new ArrayList<PeerEventListener>();
|
||||
this.fastCatchupTimeSecs = params.genesisBlock.getTimeSeconds();
|
||||
this.versionMessage = ver;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,7 +148,7 @@ public class Peer {
|
||||
*/
|
||||
public synchronized void connect() throws PeerException {
|
||||
try {
|
||||
conn = new TCPNetworkConnection(address, params, bestHeight, 60000, MOBILE_OPTIMIZED);
|
||||
conn = new TCPNetworkConnection(address, params, CONNECT_TIMEOUT_MSEC, false, versionMessage);
|
||||
} catch (IOException ex) {
|
||||
throw new PeerException(ex);
|
||||
} catch (ProtocolException ex) {
|
||||
|
@ -31,16 +31,16 @@ import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Maintain a number of connections to peers.
|
||||
* <p/>
|
||||
* <p>PeerGroup tries to maintain a constant number of connections to a set of distinct peers.
|
||||
* Maintain a number of connections to peers.<p>
|
||||
*
|
||||
* PeerGroup tries to maintain a constant number of connections to a set of distinct peers.
|
||||
* Each peer runs a network listener in its own thread. When a connection is lost, a new peer
|
||||
* will be tried after a delay as long as the number of connections less than the maximum.
|
||||
* <p/>
|
||||
* <p>Connections are made to addresses from a provided list. When that list is exhausted,
|
||||
* we start again from the head of the list.
|
||||
* <p/>
|
||||
* <p>The PeerGroup can broadcast a transaction to the currently connected set of peers. It can
|
||||
* will be tried after a delay as long as the number of connections less than the maximum.<p>
|
||||
*
|
||||
* Connections are made to addresses from a provided list. When that list is exhausted,
|
||||
* we start again from the head of the list.<p>
|
||||
*
|
||||
* The PeerGroup can broadcast a transaction to the currently connected set of peers. It can
|
||||
* also handle download of the blockchain from peers, restarting the process when peers die.
|
||||
*
|
||||
* @author miron@google.com (Miron Cuperman a.k.a devrandom)
|
||||
@ -71,6 +71,8 @@ public class PeerGroup {
|
||||
private List<PeerEventListener> peerEventListeners;
|
||||
// Peer discovery sources, will be polled occasionally if there aren't enough inactives.
|
||||
private Set<PeerDiscovery> peerDiscoverers;
|
||||
// The version message to use for new connections.
|
||||
private VersionMessage versionMessage;
|
||||
|
||||
private NetworkParameters params;
|
||||
private BlockChain chain;
|
||||
@ -101,6 +103,10 @@ public class PeerGroup {
|
||||
this.fastCatchupTimeSecs = params.genesisBlock.getTimeSeconds();
|
||||
this.wallets = new ArrayList<Wallet>(1);
|
||||
|
||||
// Set up a default template version message that doesn't tell the other side what kind of BitCoinJ user
|
||||
// this is.
|
||||
this.versionMessage = new VersionMessage(params, chain.getBestChainHeight());
|
||||
|
||||
inactives = new LinkedBlockingQueue<PeerAddress>();
|
||||
// TODO: Remove usage of synchronized sets here in favor of simple coarse-grained locking.
|
||||
peers = Collections.synchronizedSet(new HashSet<Peer>());
|
||||
@ -155,6 +161,55 @@ public class PeerGroup {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link VersionMessage} that will be announced on newly created connections. A version message is
|
||||
* primarily interesting because it lets you customize the "subVer" field which is used a bit like the User-Agent
|
||||
* field from HTTP. It means your client tells the other side what it is, see
|
||||
* <a href="https://en.bitcoin.it/wiki/BIP_0014">BIP 14</a>.
|
||||
*
|
||||
* The VersionMessage you provide is copied and the best chain height/time filled in for each new connection,
|
||||
* therefore you don't have to worry about setting that. The provided object is really more of a template.
|
||||
*/
|
||||
public synchronized void setVersionMessage(VersionMessage ver) {
|
||||
versionMessage = ver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version message provided by setVersionMessage or a default if none was given.
|
||||
*/
|
||||
public synchronized VersionMessage getVersionMessage() {
|
||||
return versionMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets information that identifies this software to remote nodes. This is a convenience wrapper for creating
|
||||
* a new {@link VersionMessage}, calling {@link VersionMessage#appendToSubVer(String, String, String)} on it,
|
||||
* and then calling {@link PeerGroup#setVersionMessage(VersionMessage)} on the result of that. See the docs for
|
||||
* {@link VersionMessage#appendToSubVer(String, String, String)} for information on what the fields should contain.
|
||||
*
|
||||
* @param name
|
||||
* @param version
|
||||
*/
|
||||
public void setUserAgent(String name, String version, String comments) {
|
||||
VersionMessage ver = new VersionMessage(params, 0);
|
||||
ver.appendToSubVer(name, version, comments);
|
||||
setVersionMessage(ver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets information that identifies this software to remote nodes. This is a convenience wrapper for creating
|
||||
* a new {@link VersionMessage}, calling {@link VersionMessage#appendToSubVer(String, String, String)} on it,
|
||||
* and then calling {@link PeerGroup#setVersionMessage(VersionMessage)} on the result of that. See the docs for
|
||||
* {@link VersionMessage#appendToSubVer(String, String, String)} for information on what the fields should contain.
|
||||
*
|
||||
* @param name
|
||||
* @param version
|
||||
*/
|
||||
public void setUserAgent(String name, String version) {
|
||||
setUserAgent(name, version, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Adds a listener that will be notified on a library controlled thread when:</p>
|
||||
* <ol>
|
||||
@ -407,7 +462,10 @@ public class PeerGroup {
|
||||
final PeerAddress address = inactives.take();
|
||||
while (true) {
|
||||
try {
|
||||
Peer peer = new Peer(params, address, chain.getChainHead().getHeight(), chain);
|
||||
VersionMessage ver = versionMessage.duplicate();
|
||||
ver.bestHeight = chain.getBestChainHeight();
|
||||
ver.time = Utils.now().getTime() / 1000;
|
||||
Peer peer = new Peer(params, address, chain, ver);
|
||||
executePeer(address, peer, true, ExecuteBlockMode.RETURN_IMMEDIATELY);
|
||||
break;
|
||||
} catch (RejectedExecutionException e) {
|
||||
|
@ -59,14 +59,14 @@ public class TCPNetworkConnection implements NetworkConnection {
|
||||
* @param peerAddress address to connect to. IPv6 is not currently supported by BitCoin. If
|
||||
* port is not positive the default port from params is used.
|
||||
* @param params Defines which network to connect to and details of the protocol.
|
||||
* @param bestHeight How many blocks are in our best chain
|
||||
* @param connectTimeoutMsec Timeout in milliseconds when initially connecting to peer
|
||||
* @param dedupe Whether to avoid parsing duplicate messages from the network (ie from other peers).
|
||||
* @param ver The VersionMessage to announce to the other side of the connection.
|
||||
* @throws IOException if there is a network related failure.
|
||||
* @throws ProtocolException if the version negotiation failed.
|
||||
*/
|
||||
public TCPNetworkConnection(PeerAddress peerAddress, NetworkParameters params,
|
||||
int bestHeight, int connectTimeoutMsec, boolean dedupe)
|
||||
int connectTimeoutMsec, boolean dedupe, VersionMessage ver)
|
||||
throws IOException, ProtocolException {
|
||||
this.params = params;
|
||||
this.remoteIp = peerAddress.getAddr();
|
||||
@ -76,7 +76,7 @@ public class TCPNetworkConnection implements NetworkConnection {
|
||||
InetSocketAddress address = new InetSocketAddress(remoteIp, port);
|
||||
socket = new Socket();
|
||||
socket.connect(address, connectTimeoutMsec);
|
||||
|
||||
|
||||
out = socket.getOutputStream();
|
||||
in = socket.getInputStream();
|
||||
|
||||
@ -87,7 +87,8 @@ public class TCPNetworkConnection implements NetworkConnection {
|
||||
|
||||
// Announce ourselves. This has to come first to connect to clients beyond v0.30.20.2 which wait to hear
|
||||
// from us until they send their version message back.
|
||||
writeMessage(new VersionMessage(params, bestHeight));
|
||||
log.info("Announcing ourselves as: {}", ver.subVer);
|
||||
writeMessage(ver);
|
||||
// When connecting, the remote peer sends us a version message with various bits of
|
||||
// useful data in it. We need to know the peer protocol version before we can talk to it.
|
||||
Message m = readMessage();
|
||||
@ -104,9 +105,9 @@ public class TCPNetworkConnection implements NetworkConnection {
|
||||
// Switch to the new protocol version.
|
||||
int peerVersion = versionMessage.clientVersion;
|
||||
log.info("Connected to peer: version={}, subVer='{}', services=0x{}, time={}, blocks={}", new Object[] {
|
||||
peerVersion,
|
||||
versionMessage.subVer,
|
||||
versionMessage.localServices,
|
||||
peerVersion,
|
||||
versionMessage.subVer,
|
||||
versionMessage.localServices,
|
||||
new Date(versionMessage.time * 1000),
|
||||
versionMessage.bestHeight
|
||||
});
|
||||
@ -126,6 +127,25 @@ public class TCPNetworkConnection implements NetworkConnection {
|
||||
// Handshake is done!
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the given IP address using the port specified as part of the network parameters. Once construction
|
||||
* is complete a functioning network channel is set up and running.
|
||||
*
|
||||
* @param peerAddress address to connect to. IPv6 is not currently supported by BitCoin. If
|
||||
* port is not positive the default port from params is used.
|
||||
* @param params Defines which network to connect to and details of the protocol.
|
||||
* @param connectTimeoutMsec Timeout in milliseconds when initially connecting to peer
|
||||
* @param dedupe Whether to avoid parsing duplicate messages from the network (ie from other peers).
|
||||
* @param bestHeight The height of the best chain we know about, sent to the other side.
|
||||
* @throws IOException if there is a network related failure.
|
||||
* @throws ProtocolException if the version negotiation failed.
|
||||
*/
|
||||
public TCPNetworkConnection(PeerAddress peerAddress, NetworkParameters params,
|
||||
int bestHeight, int connectTimeoutMsec, boolean dedupe)
|
||||
throws IOException, ProtocolException {
|
||||
this(peerAddress, params, connectTimeoutMsec, dedupe, new VersionMessage(params, bestHeight));
|
||||
}
|
||||
|
||||
public TCPNetworkConnection(InetAddress inetAddress, NetworkParameters params, int bestHeight, int connectTimeout)
|
||||
throws IOException, ProtocolException {
|
||||
this(new PeerAddress(inetAddress), params, bestHeight, connectTimeout, true);
|
||||
|
@ -21,6 +21,15 @@ import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
/**
|
||||
* A VersionMessage holds information exchanged during connection setup with another peer. Most of the fields are not
|
||||
* particularly interesting. The subVer field, since BIP 14, acts as a User-Agent string would. You can and should
|
||||
* append to or change the subVer for your own software so other implementations can identify it, and you can look at
|
||||
* the subVer field received from other nodes to see what they are running. If blank, it means the Satoshi client.<p>
|
||||
*
|
||||
* After creating yourself a VersionMessage, you can pass it to {@link PeerGroup#setVersionMessage(VersionMessage)}
|
||||
* to ensure it will be used for each new connection.
|
||||
*/
|
||||
public class VersionMessage extends Message {
|
||||
private static final long serialVersionUID = 7313594258967483180L;
|
||||
|
||||
@ -94,7 +103,7 @@ public class VersionMessage extends Message {
|
||||
|
||||
@Override
|
||||
protected void parseLite() throws ProtocolException {
|
||||
//NOP. VersionMessage is never lazy parsed.
|
||||
// NOP. VersionMessage is never lazy parsed.
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -185,7 +194,6 @@ public class VersionMessage extends Message {
|
||||
*/
|
||||
@Override
|
||||
void setChecksum(byte[] checksum) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -207,4 +215,50 @@ public class VersionMessage extends Message {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public VersionMessage duplicate() {
|
||||
VersionMessage v = new VersionMessage(params, (int) bestHeight);
|
||||
v.clientVersion = clientVersion;
|
||||
v.localServices = localServices;
|
||||
v.time = time;
|
||||
v.myAddr = myAddr;
|
||||
v.theirAddr = theirAddr;
|
||||
v.subVer = subVer;
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given user-agent information to the subVer field. The subVer is composed of a series of
|
||||
* name:version pairs separated by slashes in the form of a path. For example a typical subVer field for BitCoinJ
|
||||
* users might look like "/BitCoinJ:0.4-SNAPSHOT/MultiBit:1.2/" where libraries come further to the left.<p>
|
||||
*
|
||||
* There can be as many components as you feel a need for, and the version string can be anything, but it is
|
||||
* recommended to use A.B.C where A = major, B = minor and C = revision for software releases, and dates for
|
||||
* auto-generated source repository snapshots. A valid subVer begins and ends with a slash, therefore name
|
||||
* and version are not allowed to contain such characters. <p>
|
||||
*
|
||||
* Anything put in the "comments" field will appear in brackets and may be used for platform info, or anything
|
||||
* else. For example, calling <tt>appendToSubVer("MultiBit", "1.0", "Windows")</tt> will result in a subVer being
|
||||
* set of "/BitCoinJ:1.0/MultiBit:1.0(Windows)/. Therefore the / ( and ) characters are reserved in all these
|
||||
* components. If you don't want to add a comment (recommended), pass null.<p>
|
||||
*
|
||||
* See <a href="https://en.bitcoin.it/wiki/BIP_0014">BIP 14</a> for more information.
|
||||
*
|
||||
* @param comments Optional (can be null) platform or other node specific information.
|
||||
* @throws IllegalArgumentException if name, version or comments contains invalid characters.
|
||||
*/
|
||||
public void appendToSubVer(String name, String version, String comments) {
|
||||
checkSubVerComponent(name);
|
||||
checkSubVerComponent(version);
|
||||
if (comments != null) {
|
||||
checkSubVerComponent(comments);
|
||||
subVer = subVer.concat(String.format("%s:%s(%s)/", name, version, comments));
|
||||
} else {
|
||||
subVer = subVer.concat(String.format("%s:%s/", name, version));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSubVerComponent(String component) {
|
||||
if (component.contains("/") || component.contains("(") || component.contains(")"))
|
||||
throw new IllegalArgumentException("name contains invalid characters");
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,8 @@ public class PingService {
|
||||
chain = new BlockChain(params, wallet, blockStore);
|
||||
|
||||
peerGroup = new PeerGroup(params, chain);
|
||||
|
||||
// Set some version info.
|
||||
peerGroup.setUserAgent("PingService", "1.0");
|
||||
// Download headers only until a day ago.
|
||||
peerGroup.setFastCatchupTimeSecs((new Date().getTime() / 1000) - (60 * 60 * 24));
|
||||
if (peerHost != null) {
|
||||
|
@ -105,6 +105,7 @@ public class ToyWallet {
|
||||
chain = new BlockChain(params, wallet, new BoundedOverheadBlockStore(params, blockChainFile));
|
||||
|
||||
peerGroup = new PeerGroup(params, chain);
|
||||
peerGroup.setUserAgent("ToyWallet", "1.0");
|
||||
if (testnet) {
|
||||
peerGroup.addAddress(new PeerAddress(InetAddress.getByName("plan99.net"), 18333));
|
||||
peerGroup.addAddress(new PeerAddress(InetAddress.getByName("localhost"), 18333));
|
||||
|
@ -292,8 +292,7 @@ public class PeerGroupTest extends TestWithNetworkConnections {
|
||||
assertTrue(n3.outbound() instanceof InventoryMessage);
|
||||
peerGroup.stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void disconnectAndWait(MockNetworkConnection conn) throws IOException, InterruptedException {
|
||||
conn.disconnect();
|
||||
disconnectedPeers.take();
|
||||
|
Loading…
Reference in New Issue
Block a user