mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
Some changes to how block chain download is done:
- Progress is now made available - Fixes bug: can now wait for downloads of chains < 500 blocks - Flesh out VersionMessage parsing, send BitCoinJ name in subVer field
This commit is contained in:
parent
c40b7ce668
commit
57caa5503d
@ -24,7 +24,7 @@ public class AddressMessage extends Message {
|
||||
throw new ProtocolException("Address message too large.");
|
||||
addresses = new ArrayList<PeerAddress>((int)numAddresses);
|
||||
for (int i = 0; i < numAddresses; i++) {
|
||||
PeerAddress addr = new PeerAddress(params, bytes, cursor);
|
||||
PeerAddress addr = new PeerAddress(params, bytes, cursor, protocolVersion);
|
||||
addresses.add(addr);
|
||||
cursor += addr.getMessageSize();
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ public class GetBlocksMessage extends Message {
|
||||
try {
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
// Version, for some reason.
|
||||
Utils.uint32ToByteStreamLE(VersionMessage.PROTOCOL_VERSION, buf);
|
||||
Utils.uint32ToByteStreamLE(NetworkParameters.PROTOCOL_VERSION, buf);
|
||||
// Then a vector of block hashes. This is actually a "block locator", a set of block
|
||||
// identifiers that spans the entire chain with exponentially increasing gaps between
|
||||
// them, until we end up at the genesis block. See CBlockLocator::Set()
|
||||
|
@ -16,10 +16,7 @@
|
||||
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -44,8 +41,11 @@ public abstract class Message implements Serializable {
|
||||
// Note that it's relative to the start of the array NOT the start of the message.
|
||||
protected transient int cursor;
|
||||
|
||||
// The raw message bytes themselves.
|
||||
protected transient byte[] bytes;
|
||||
|
||||
protected transient int protocolVersion;
|
||||
|
||||
// This will be saved by subclasses that implement Serializable.
|
||||
protected NetworkParameters params;
|
||||
|
||||
@ -58,7 +58,8 @@ public abstract class Message implements Serializable {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
|
||||
Message(NetworkParameters params, byte[] msg, int offset, int protocolVersion) throws ProtocolException {
|
||||
this.protocolVersion = protocolVersion;
|
||||
this.params = params;
|
||||
this.bytes = msg;
|
||||
this.cursor = this.offset = offset;
|
||||
@ -74,6 +75,10 @@ public abstract class Message implements Serializable {
|
||||
}
|
||||
this.bytes = null;
|
||||
}
|
||||
|
||||
Message(NetworkParameters params, byte[] msg, int offset) throws ProtocolException {
|
||||
this(params, msg, offset, NetworkParameters.PROTOCOL_VERSION);
|
||||
}
|
||||
|
||||
// These methods handle the serialization/deserialization using the custom BitCoin protocol.
|
||||
// It's somewhat painful to work with in Java, so some of these objects support a second
|
||||
@ -141,4 +146,20 @@ public abstract class Message implements Serializable {
|
||||
cursor += length;
|
||||
return b;
|
||||
}
|
||||
|
||||
String readStr() {
|
||||
VarInt varInt = new VarInt(bytes, cursor);
|
||||
if (varInt.value == 0) {
|
||||
cursor += 1;
|
||||
return "";
|
||||
}
|
||||
byte[] characters = new byte[(int)varInt.value];
|
||||
System.arraycopy(bytes, cursor, characters, 0, characters.length);
|
||||
cursor += varInt.getSizeInBytes();
|
||||
try {
|
||||
return new String(characters, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e); // Cannot happen, UTF-8 is always supported.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,8 @@ public class NetworkConnection {
|
||||
private final InetAddress remoteIp;
|
||||
private boolean usesChecksumming;
|
||||
private final NetworkParameters params;
|
||||
static final private boolean PROTOCOL_LOG = false;
|
||||
private final VersionMessage versionMessage;
|
||||
private static final boolean PROTOCOL_LOG = false;
|
||||
|
||||
/**
|
||||
* Connect to the given IP address using the port specified as part of the network parameters. Once construction
|
||||
@ -74,7 +75,7 @@ public class NetworkConnection {
|
||||
|
||||
// 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.
|
||||
VersionMessage ver = (VersionMessage) readMessage();
|
||||
versionMessage = (VersionMessage) readMessage();
|
||||
// Now it's our turn ...
|
||||
writeMessage(MSG_VERSION, new VersionMessage(params));
|
||||
// Send an ACK message stating we accept the peers protocol version.
|
||||
@ -82,12 +83,13 @@ public class NetworkConnection {
|
||||
// And get one back ...
|
||||
readMessage();
|
||||
// Switch to the new protocol version.
|
||||
int peerVersion = ver.clientVersion;
|
||||
LOG("Connected to peer, version is " + peerVersion + ", services=" + Long.toHexString(
|
||||
ver.localServices) + ", time=" + new Date(ver.time.longValue() * 1000).toString());
|
||||
int peerVersion = versionMessage.clientVersion;
|
||||
LOG(String.format("Connected to peer: version=%d, subVer='%s', services=0x%x, time=%s, blocks=%d",
|
||||
peerVersion, versionMessage.subVer,
|
||||
versionMessage.localServices, new Date(versionMessage.time * 1000).toString(), versionMessage.bestHeight));
|
||||
// BitCoinJ is a client mode implementation. That means there's not much point in us talking to other client
|
||||
// mode nodes because we can't download the data from them we need to find/verify transactions.
|
||||
if (!ver.hasBlockChain())
|
||||
if (!versionMessage.hasBlockChain())
|
||||
throw new ProtocolException("Peer does not have a copy of the block chain.");
|
||||
usesChecksumming = peerVersion >= 209;
|
||||
// Handshake is done!
|
||||
@ -198,9 +200,6 @@ public class NetworkConnection {
|
||||
if (size > Message.MAX_SIZE)
|
||||
throw new ProtocolException("Message size too large: " + size);
|
||||
|
||||
if (PROTOCOL_LOG)
|
||||
LOG("Received " + size + " byte '" + command + "' command");
|
||||
|
||||
// Old clients don't send the checksum.
|
||||
byte[] checksum = new byte[4];
|
||||
if (usesChecksumming) {
|
||||
@ -231,6 +230,9 @@ public class NetworkConnection {
|
||||
}
|
||||
}
|
||||
|
||||
if (PROTOCOL_LOG)
|
||||
LOG("Received " + size + " byte '" + command + "' message: " + Utils.bytesToHexString(payloadBytes));
|
||||
|
||||
try {
|
||||
Message message;
|
||||
if (command.equals(MSG_VERSION))
|
||||
@ -292,4 +294,9 @@ public class NetworkConnection {
|
||||
// TODO: Requiring "tag" here is redundant, the message object should know its own protocol tag.
|
||||
writeMessage(tag, message.bitcoinSerialize());
|
||||
}
|
||||
|
||||
/** Returns the version message received from the other end of the connection during the handshake. */
|
||||
public VersionMessage getVersionMessage() {
|
||||
return versionMessage;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ import java.math.BigInteger;
|
||||
* evolves there may be more. You can create your own as long as they don't conflict.
|
||||
*/
|
||||
public class NetworkParameters implements Serializable {
|
||||
/**
|
||||
* The protocol version this library implements. A value of 31800 means 0.3.18.00.
|
||||
*/
|
||||
public static final int PROTOCOL_VERSION = 31800;
|
||||
|
||||
private static final long serialVersionUID = 2579833727976661964L;
|
||||
|
||||
// TODO: Seed nodes and checkpoint values should be here as well.
|
||||
|
@ -106,9 +106,6 @@ public class Peer {
|
||||
}
|
||||
}
|
||||
|
||||
// This tracks whether we have received a block we could not connect to the chain in this session.
|
||||
private boolean hasSeenUnconnectedBlock = false;
|
||||
|
||||
private void processBlock(Block m) throws IOException {
|
||||
assert Thread.currentThread() == thread;
|
||||
try {
|
||||
@ -128,17 +125,13 @@ public class Peer {
|
||||
// Otherwise it's a block sent to us because the peer thought we needed it, so add it to the block chain.
|
||||
// This call will synchronize on blockChain.
|
||||
if (blockChain.add(m)) {
|
||||
// The block was successfully linked into the chain.
|
||||
if (hasSeenUnconnectedBlock && blockChain.getUnconnectedBlock() == null) {
|
||||
// We cleared out our unconnected blocks. This likely means block chain download is "done" in the
|
||||
// sense that we were downloading blocks as part of the chained download,
|
||||
// and there's no more to come. To some extent of course the download is never done.
|
||||
LOG("Block chain download done.");
|
||||
if (chainCompletionLatch != null) {
|
||||
chainCompletionLatch.countDown();
|
||||
// The block was successfully linked into the chain. Notify the user of our progress.
|
||||
if (chainCompletionLatch != null) {
|
||||
chainCompletionLatch.countDown();
|
||||
if (chainCompletionLatch.getCount() == 0) {
|
||||
// All blocks fetched, so we don't need this anymore.
|
||||
chainCompletionLatch = null;
|
||||
}
|
||||
hasSeenUnconnectedBlock = false;
|
||||
}
|
||||
} else {
|
||||
// This block is unconnected - we don't know how to get from it back to the genesis block yet. That
|
||||
@ -148,7 +141,6 @@ public class Peer {
|
||||
// the others.
|
||||
|
||||
// TODO: Should actually request root of orphan chain here.
|
||||
hasSeenUnconnectedBlock = true;
|
||||
blockChainDownload(m.getHash());
|
||||
}
|
||||
} catch (VerificationException e) {
|
||||
@ -319,14 +311,20 @@ public class Peer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an asynchronous download of the block chain. Completion of the download is a somewhat vague concept in
|
||||
* BitCoin as the chain is constantly growing, but essentially we deem the download complete once we have
|
||||
* received the block that the peer told us was the head when we first started the download.
|
||||
* Starts an asynchronous download of the block chain. The chain download is deemed to be complete once we've
|
||||
* downloaded the same number of blocks that the peer advertised having in its version handshake message.
|
||||
*
|
||||
* @return a {@link CountDownLatch} that can be used to wait until the chain download is "complete".
|
||||
* @return a {@link CountDownLatch} that can be used to track progress and wait for completion.
|
||||
*/
|
||||
public CountDownLatch startBlockChainDownload() throws IOException {
|
||||
chainCompletionLatch = new CountDownLatch(1);
|
||||
// Chain will overflow signed int blocks in ~41,000 years.
|
||||
int chainHeight = (int) conn.getVersionMessage().bestHeight;
|
||||
if (chainHeight <= 0) {
|
||||
// This should not happen because we shouldn't have given the user a Peer that is to another client-mode
|
||||
// node. If that happens it means the user overrode us somewhere.
|
||||
throw new RuntimeException("Peer does not have block chain");
|
||||
}
|
||||
chainCompletionLatch = new CountDownLatch(chainHeight);
|
||||
blockChainDownload(params.genesisBlock.getHash());
|
||||
return chainCompletionLatch;
|
||||
}
|
||||
|
@ -36,9 +36,10 @@ public class PeerAddress extends Message {
|
||||
InetAddress addr;
|
||||
int port;
|
||||
BigInteger services;
|
||||
long time;
|
||||
|
||||
public PeerAddress(NetworkParameters params, byte[] payload, int offset) throws ProtocolException {
|
||||
super(params, payload, offset);
|
||||
public PeerAddress(NetworkParameters params, byte[] payload, int offset, int protocolVersion) throws ProtocolException {
|
||||
super(params, payload, offset, protocolVersion);
|
||||
}
|
||||
|
||||
public PeerAddress(InetAddress addr, int port) {
|
||||
@ -72,7 +73,10 @@ public class PeerAddress extends Message {
|
||||
// uint64 services (flags determining what the node can do)
|
||||
// 16 bytes ip address
|
||||
// 2 bytes port num
|
||||
long time = readUint32();
|
||||
if (protocolVersion > 31402)
|
||||
time = readUint32();
|
||||
else
|
||||
time = -1;
|
||||
services = readUint64();
|
||||
byte[] addrBytes = readBytes(16);
|
||||
try {
|
||||
|
@ -25,22 +25,28 @@ import java.net.UnknownHostException;
|
||||
public class VersionMessage extends Message {
|
||||
private static final long serialVersionUID = 7313594258967483180L;
|
||||
|
||||
/**
|
||||
* The protocol version this library implements. A value of 31800 means 0.3.18.00.
|
||||
*/
|
||||
public static final int PROTOCOL_VERSION = 31800;
|
||||
|
||||
/**
|
||||
* A services flag that denotes whether the peer has a copy of the block chain or not.
|
||||
*/
|
||||
public static final int NODE_NETWORK = 1;
|
||||
|
||||
/** The version number of the protocol spoken. */
|
||||
public int clientVersion;
|
||||
// Flags defining what the other side supports. Right now there's only one flag and it's
|
||||
// always set 1 by the official client, but we have to set it to zero as we don't store
|
||||
// the block chain. In future there may be more services bits.
|
||||
/** Flags defining what is supported. Right now {@link #NODE_NETWORK} is the only flag defined. */
|
||||
public long localServices;
|
||||
public BigInteger time;
|
||||
/** What the other side believes the current time to be, in seconds. */
|
||||
public long time;
|
||||
/** What the other side believes the address of this program is. Not used. */
|
||||
public PeerAddress myAddr;
|
||||
/** What the other side believes their own address is. Not used. */
|
||||
public PeerAddress theirAddr;
|
||||
/**
|
||||
* An additional string that today the official client sets to the empty string. We treat it as something like an
|
||||
* HTTP User-Agent header.
|
||||
*/
|
||||
public String subVer;
|
||||
/** How many blocks are in the chain, according to the other side. */
|
||||
public long bestHeight;
|
||||
|
||||
public VersionMessage(NetworkParameters params, byte[] msg) throws ProtocolException {
|
||||
super(params, msg, 0);
|
||||
@ -48,43 +54,51 @@ public class VersionMessage extends Message {
|
||||
|
||||
public VersionMessage(NetworkParameters params) {
|
||||
super(params);
|
||||
clientVersion = PROTOCOL_VERSION;
|
||||
clientVersion = NetworkParameters.PROTOCOL_VERSION;
|
||||
localServices = 0;
|
||||
time = BigInteger.valueOf(System.currentTimeMillis() / 1000);
|
||||
time = System.currentTimeMillis() / 1000;
|
||||
// Note that the official client doesn't do anything with these, and finding out your own external IP address
|
||||
// is kind of tricky anyway, so we just put nonsense here for now.
|
||||
try {
|
||||
myAddr = new PeerAddress(InetAddress.getLocalHost(), params.port);
|
||||
theirAddr = new PeerAddress(InetAddress.getLocalHost(), params.port);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
subVer = "BitCoinJ 0.1.99";
|
||||
bestHeight = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse() throws ProtocolException {
|
||||
// There is probably a more Java-ish way to do this.
|
||||
clientVersion = (int) readUint32();
|
||||
localServices = readUint64().longValue();
|
||||
time = readUint64();
|
||||
// The next fields are:
|
||||
// CAddress my address
|
||||
// CAddress their address
|
||||
// uint64 localHostNonce (random data)
|
||||
time = readUint64().longValue();
|
||||
myAddr = new PeerAddress(params, bytes, cursor, 0);
|
||||
cursor += myAddr.getMessageSize();
|
||||
theirAddr = new PeerAddress(params, bytes, cursor, 0);
|
||||
cursor += theirAddr.getMessageSize();
|
||||
// uint64 localHostNonce (random data)
|
||||
// We don't care about the localhost nonce. It's used to detect connecting back to yourself in cases where
|
||||
// there are NATs and proxies in the way. However we don't listen for inbound connections so it's irrelevant.
|
||||
readUint64();
|
||||
// string subVer (currently "")
|
||||
subVer = readStr();
|
||||
// int bestHeight (size of known block chain).
|
||||
//
|
||||
// However, we don't care about these fields right now.
|
||||
bestHeight = readUint32();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bitcoinSerializeToStream(OutputStream buf) throws IOException {
|
||||
Utils.uint32ToByteStreamLE(clientVersion, buf);
|
||||
Utils.uint32ToByteStreamLE(localServices, buf);
|
||||
long ltime = time.longValue();
|
||||
Utils.uint32ToByteStreamLE(ltime >> 32, buf);
|
||||
Utils.uint32ToByteStreamLE(ltime, buf);
|
||||
Utils.uint32ToByteStreamLE(time >> 32, buf);
|
||||
Utils.uint32ToByteStreamLE(time, buf);
|
||||
try {
|
||||
// Now there are two address structures. Note that the official client doesn't do anything with these, and
|
||||
// finding out your own external IP address is kind of tricky anyway, so we just serialize nonsense here.
|
||||
|
||||
// My address.
|
||||
new PeerAddress(InetAddress.getLocalHost(), params.port).bitcoinSerializeToStream(buf);
|
||||
myAddr.bitcoinSerializeToStream(buf);
|
||||
// Their address.
|
||||
new PeerAddress(InetAddress.getLocalHost(), params.port).bitcoinSerializeToStream(buf);
|
||||
theirAddr.bitcoinSerializeToStream(buf);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e); // Can't happen.
|
||||
} catch (IOException e) {
|
||||
@ -95,10 +109,12 @@ public class VersionMessage extends Message {
|
||||
// connections.
|
||||
Utils.uint32ToByteStreamLE(0, buf);
|
||||
Utils.uint32ToByteStreamLE(0, buf);
|
||||
// Now comes an empty string.
|
||||
buf.write(0);
|
||||
// Size of known block chain. Claim we never saw any blocks.
|
||||
Utils.uint32ToByteStreamLE(0, buf);
|
||||
// Now comes subVer.
|
||||
byte[] subVerBytes = subVer.getBytes("UTF-8");
|
||||
buf.write(new VarInt(subVerBytes.length).encode());
|
||||
buf.write(subVerBytes);
|
||||
// Size of known block chain.
|
||||
Utils.uint32ToByteStreamLE(bestHeight, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* PingService demonstrates basic usage of the library. It sits on the network and when it receives coins, simply
|
||||
@ -29,7 +31,7 @@ import java.net.InetAddress;
|
||||
*/
|
||||
public class PingService {
|
||||
public static void main(String[] args) throws Exception {
|
||||
final NetworkParameters params = NetworkParameters.prodNet();
|
||||
final NetworkParameters params = NetworkParameters.testNet();
|
||||
|
||||
// Try to read the wallet from storage, create a new one if not possible.
|
||||
Wallet wallet;
|
||||
@ -51,7 +53,6 @@ public class PingService {
|
||||
BlockChain chain = new BlockChain(params, wallet);
|
||||
final Peer peer = new Peer(params, conn, chain);
|
||||
peer.start();
|
||||
peer.startBlockChainDownload().await();
|
||||
|
||||
// We want to know when the balance changes.
|
||||
wallet.addEventListener(new WalletEventListener() {
|
||||
@ -82,6 +83,15 @@ public class PingService {
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch progress = peer.startBlockChainDownload();
|
||||
long max = progress.getCount(); // Racy but no big deal.
|
||||
long current = max;
|
||||
while (current > 0) {
|
||||
double pct = 100.0 - (100.0 * (current / (double)max));
|
||||
System.out.println(String.format("Chain download %d%% done", (int)pct));
|
||||
progress.await(1, TimeUnit.SECONDS);
|
||||
current = progress.getCount();
|
||||
}
|
||||
System.out.println("Send coins to: " + key.toAddress(params).toString());
|
||||
System.out.println("Waiting for coins to arrive. Press Ctrl-C to quit.");
|
||||
// The peer thread keeps us alive until something kills the process.
|
||||
|
Loading…
Reference in New Issue
Block a user