mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
Refactor the serialization logic:
- Extract serialization code out of NetworkConnection into a new class - Created classes for Ping and VerAck messages - Use reflection to create new message classes - Add a few test cases to exercise the BitcoinSerializer class Patch by Noa Resare.
This commit is contained in:
parent
3caa419aab
commit
a955187e04
268
src/com/google/bitcoin/core/BitcoinSerializer.java
Normal file
268
src/com/google/bitcoin/core/BitcoinSerializer.java
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.google.bitcoin.core.Utils.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods to serialize and de-serialize messages to the bitcoin network format as defined in the bitcoin protocol
|
||||||
|
* specification at https://en.bitcoin.it/wiki/Protocol_specification
|
||||||
|
*
|
||||||
|
* To be able to serialize and deserialize new Message subclasses the following criteria needs to be met.
|
||||||
|
* <ul>
|
||||||
|
* <li>The proper Class instance needs to be mapped to it's message name in the names variable below</li>
|
||||||
|
* <li>There needs to be a constructor matching: NetworkParameters params, byte[] payload</li>
|
||||||
|
* <li>Message.bitcoinSerializeToStream() needs to be properly subclassed</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class BitcoinSerializer
|
||||||
|
{
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(BitcoinSerializer.class);
|
||||||
|
private static final int COMMAND_LEN = 12;
|
||||||
|
|
||||||
|
private NetworkParameters params;
|
||||||
|
private boolean usesChecksumming;
|
||||||
|
|
||||||
|
private static Map<Class<? extends Message>, String> names = new HashMap<Class<? extends Message>,String>();
|
||||||
|
private static Map<String, Constructor<? extends Message>>
|
||||||
|
messageConstructors = new HashMap<String, Constructor<? extends Message>>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
names.put(VersionMessage.class, "version");
|
||||||
|
names.put(InventoryMessage.class, "inv");
|
||||||
|
names.put(Block.class, "block");
|
||||||
|
names.put(GetDataMessage.class, "getdata");
|
||||||
|
names.put(Transaction.class, "tx");
|
||||||
|
names.put(AddressMessage.class, "addr");
|
||||||
|
names.put(Ping.class, "ping");
|
||||||
|
names.put(VersionAck.class, "verack");
|
||||||
|
names.put(GetBlocksMessage.class, "getblocks");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a BitcoinSerializer with the given behavior.
|
||||||
|
*
|
||||||
|
* @param params networkParams used to create Messages instances and termining packetMagic
|
||||||
|
* @param usesChecksumming set to true if checkums should be included and expected in headers
|
||||||
|
*/
|
||||||
|
public BitcoinSerializer(NetworkParameters params, boolean usesChecksumming) {
|
||||||
|
this.params = params;
|
||||||
|
this.usesChecksumming = usesChecksumming;
|
||||||
|
|
||||||
|
// some Message subclasses can only be sent for now, ignore missing constructors
|
||||||
|
for (Class<? extends Message> c : names.keySet()) {
|
||||||
|
Constructor<? extends Message> ct = makeConstructor(c);
|
||||||
|
if (ct != null) {
|
||||||
|
messageConstructors.put(names.get(c),ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useChecksumming(boolean usesChecksumming) {
|
||||||
|
this.usesChecksumming = usesChecksumming;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes message to to the output stream.
|
||||||
|
*/
|
||||||
|
public void serialize(Message message, OutputStream out) throws IOException {
|
||||||
|
String name = names.get(message.getClass());
|
||||||
|
if (name == null) {
|
||||||
|
throw new Error("BitcoinSerializer doesn't currently know how to serialize "+ message.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] header = new byte[4 + COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)];
|
||||||
|
|
||||||
|
uint32ToByteArrayBE(params.packetMagic, header, 0);
|
||||||
|
|
||||||
|
// The header array is initialized to zero by Java so we don't have to worry about
|
||||||
|
// NULL terminating the string here.
|
||||||
|
for (int i = 0; i < name.length() && i < COMMAND_LEN; i++) {
|
||||||
|
header[4 + i] = (byte) (name.codePointAt(i) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] payload = message.bitcoinSerialize();
|
||||||
|
|
||||||
|
Utils.uint32ToByteArrayLE(payload.length, header, 4 + COMMAND_LEN);
|
||||||
|
|
||||||
|
if (usesChecksumming) {
|
||||||
|
byte[] hash = doubleDigest(payload);
|
||||||
|
System.arraycopy(hash, 0, header, 4 + COMMAND_LEN + 4, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(header);
|
||||||
|
out.write(payload);
|
||||||
|
|
||||||
|
log.debug("Sending {} message: {}", name, bytesToHexString(header) + bytesToHexString(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a message from the given InputStream and returns it.
|
||||||
|
*/
|
||||||
|
public Message deserialize(InputStream in) throws ProtocolException, IOException {
|
||||||
|
// A BitCoin protocol message has the following format.
|
||||||
|
//
|
||||||
|
// - 4 byte magic number: 0xfabfb5da for the testnet or
|
||||||
|
// 0xf9beb4d9 for production
|
||||||
|
// - 12 byte command in ASCII
|
||||||
|
// - 4 byte payload size
|
||||||
|
// - 4 byte checksum
|
||||||
|
// - Payload data
|
||||||
|
//
|
||||||
|
// The checksum is the first 4 bytes of a SHA256 hash of the message payload. It isn't
|
||||||
|
// present for all messages, notably, the first one on a connection.
|
||||||
|
//
|
||||||
|
// Satoshi's implementation ignores garbage before the magic header bytes. We have to do the same because
|
||||||
|
// sometimes it sends us stuff that isn't part of any message.
|
||||||
|
seekPastMagicBytes(in);
|
||||||
|
// Now read in the header.
|
||||||
|
byte[] header = new byte[COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)];
|
||||||
|
int readCursor = 0;
|
||||||
|
while (readCursor < header.length) {
|
||||||
|
int bytesRead = in.read(header, readCursor, header.length - readCursor);
|
||||||
|
if (bytesRead == -1) {
|
||||||
|
// There's no more data to read.
|
||||||
|
throw new IOException("Socket is disconnected");
|
||||||
|
}
|
||||||
|
readCursor += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursor = 0;
|
||||||
|
|
||||||
|
// The command is a NULL terminated string, unless the command fills all twelve bytes
|
||||||
|
// in which case the termination is implicit.
|
||||||
|
String command;
|
||||||
|
int mark = cursor;
|
||||||
|
for (; header[cursor] != 0 && cursor - mark < COMMAND_LEN; cursor++);
|
||||||
|
byte[] commandBytes = new byte[cursor - mark];
|
||||||
|
System.arraycopy(header, mark, commandBytes, 0, cursor - mark);
|
||||||
|
try {
|
||||||
|
command = new String(commandBytes, "US-ASCII");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e); // Cannot happen.
|
||||||
|
}
|
||||||
|
cursor = mark + COMMAND_LEN;
|
||||||
|
|
||||||
|
int size = (int) readUint32(header, cursor);
|
||||||
|
cursor += 4;
|
||||||
|
|
||||||
|
if (size > Message.MAX_SIZE)
|
||||||
|
throw new ProtocolException("Message size too large: " + size);
|
||||||
|
|
||||||
|
// Old clients don't send the checksum.
|
||||||
|
byte[] checksum = new byte[4];
|
||||||
|
if (usesChecksumming) {
|
||||||
|
// Note that the size read above includes the checksum bytes.
|
||||||
|
System.arraycopy(header, cursor, checksum, 0, 4);
|
||||||
|
cursor += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try to read the whole message.
|
||||||
|
readCursor = 0;
|
||||||
|
byte[] payloadBytes = new byte[size];
|
||||||
|
while (readCursor < payloadBytes.length - 1) {
|
||||||
|
int bytesRead = in.read(payloadBytes, readCursor, size - readCursor);
|
||||||
|
if (bytesRead == -1) {
|
||||||
|
throw new IOException("Socket is disconnected");
|
||||||
|
}
|
||||||
|
readCursor += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the checksum.
|
||||||
|
if (usesChecksumming) {
|
||||||
|
byte[] hash = doubleDigest(payloadBytes);
|
||||||
|
if (checksum[0] != hash[0] || checksum[1] != hash[1] ||
|
||||||
|
checksum[2] != hash[2] || checksum[3] != hash[3]) {
|
||||||
|
throw new ProtocolException("Checksum failed to verify, actual " +
|
||||||
|
bytesToHexString(hash) +
|
||||||
|
" vs " + bytesToHexString(checksum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Received {} byte '{}' message: {}", new Object[]{
|
||||||
|
size,
|
||||||
|
command,
|
||||||
|
Utils.bytesToHexString(payloadBytes)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Constructor<? extends Message> c = messageConstructors.get(command);
|
||||||
|
if (c == null) {
|
||||||
|
throw new ProtocolException("No support for deserializing message with name " + command);
|
||||||
|
}
|
||||||
|
return c.newInstance(params, payloadBytes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Constructor<? extends Message> makeConstructor(Class<? extends Message> c) {
|
||||||
|
Class<?> parTypes[] = new Class<?>[2];
|
||||||
|
parTypes[0] = NetworkParameters.class;
|
||||||
|
parTypes[1] = byte[].class;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return c.getDeclaredConstructor(parTypes);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void seekPastMagicBytes(InputStream in) throws IOException {
|
||||||
|
int magicCursor = 3; // Which byte of the magic we're looking for currently.
|
||||||
|
while (true) {
|
||||||
|
int b = in.read(); // Read a byte.
|
||||||
|
if (b == -1) {
|
||||||
|
// There's no more data to read.
|
||||||
|
throw new IOException("Socket is disconnected");
|
||||||
|
}
|
||||||
|
// We're looking for a run of bytes that is the same as the packet magic but we want to ignore partial
|
||||||
|
// magics that aren't complete. So we keep track of where we're up to with magicCursor.
|
||||||
|
int expectedByte = 0xFF & (int)(params.packetMagic >>> (magicCursor * 8));
|
||||||
|
if (b == expectedByte) {
|
||||||
|
magicCursor--;
|
||||||
|
if (magicCursor < 0) {
|
||||||
|
// We found the magic sequence.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// We still have further to go to find the next message.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
magicCursor = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,20 +16,14 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
public class GetDataMessage extends Message {
|
public class GetDataMessage extends ListMessage {
|
||||||
private static final long serialVersionUID = 2754681589501709887L;
|
private static final long serialVersionUID = 2754681589501709887L;
|
||||||
|
|
||||||
public GetDataMessage(NetworkParameters params, byte[] payloadBytes) throws ProtocolException {
|
public GetDataMessage(NetworkParameters params, byte[] payloadBytes) throws ProtocolException {
|
||||||
super(params, payloadBytes, 0);
|
super(params, payloadBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetDataMessage(NetworkParameters params) {
|
||||||
public byte[] bitcoinSerialize() {
|
super(params);
|
||||||
return new byte[] {};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void parse() throws ProtocolException {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,64 +16,15 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
import com.google.bitcoin.core.InventoryItem.Type;
|
public class InventoryMessage extends ListMessage {
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class InventoryMessage extends Message {
|
|
||||||
private static final long serialVersionUID = -7050246551646107066L;
|
private static final long serialVersionUID = -7050246551646107066L;
|
||||||
private static final long MAX_INVENTORY_ITEMS = 50000;
|
|
||||||
|
|
||||||
// For some reason the compiler complains if this is inside InventoryItem
|
|
||||||
public List<InventoryItem> items;
|
|
||||||
|
|
||||||
public InventoryMessage(NetworkParameters params, byte[] bytes) throws ProtocolException {
|
public InventoryMessage(NetworkParameters params, byte[] bytes) throws ProtocolException {
|
||||||
super(params, bytes, 0);
|
super(params, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void parse() throws ProtocolException {
|
|
||||||
// An inv is vector<CInv> where CInv is int+hash. The int is either 1 or 2 for tx or block.
|
|
||||||
long arrayLen = readVarInt();
|
|
||||||
if (arrayLen > MAX_INVENTORY_ITEMS)
|
|
||||||
throw new ProtocolException("Too many items in INV message: " + arrayLen);
|
|
||||||
items = new ArrayList<InventoryItem>((int)arrayLen);
|
|
||||||
for (int i = 0; i < arrayLen; i++) {
|
|
||||||
if (cursor + 4 + 32 > bytes.length) {
|
|
||||||
throw new ProtocolException("Ran off the end of the INV");
|
|
||||||
}
|
|
||||||
int typeCode = (int) readUint32();
|
|
||||||
Type type;
|
|
||||||
// See ppszTypeName in net.h
|
|
||||||
switch (typeCode) {
|
|
||||||
case 0: type = InventoryItem.Type.Error; break;
|
|
||||||
case 1: type = InventoryItem.Type.Transaction; break;
|
|
||||||
case 2: type = InventoryItem.Type.Block; break;
|
|
||||||
default:
|
|
||||||
throw new ProtocolException("Unknown CInv type: " + typeCode);
|
|
||||||
}
|
|
||||||
InventoryItem item = new InventoryItem(type, readHash());
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
bytes = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InventoryMessage(NetworkParameters params) {
|
public InventoryMessage(NetworkParameters params) {
|
||||||
super(params);
|
super(params);
|
||||||
items = new ArrayList<InventoryItem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bitcoinSerializeToStream( OutputStream stream) throws IOException {
|
|
||||||
stream.write(new VarInt(items.size()).encode());
|
|
||||||
for (InventoryItem i : items) {
|
|
||||||
// Write out the type code.
|
|
||||||
Utils.uint32ToByteStreamLE(i.type.ordinal(), stream);
|
|
||||||
// And now the hash.
|
|
||||||
stream.write(Utils.reverseBytes(i.hash));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
94
src/com/google/bitcoin/core/ListMessage.java
Normal file
94
src/com/google/bitcoin/core/ListMessage.java
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract superclass of classes with list based payload, i.e. InventoryMessage and GetDataMessage.
|
||||||
|
*/
|
||||||
|
public abstract class ListMessage extends Message
|
||||||
|
{
|
||||||
|
// For some reason the compiler complains if this is inside InventoryItem
|
||||||
|
private List<InventoryItem> items;
|
||||||
|
|
||||||
|
private static final long MAX_INVENTORY_ITEMS = 50000;
|
||||||
|
|
||||||
|
|
||||||
|
public ListMessage(NetworkParameters params, byte[] bytes) throws ProtocolException {
|
||||||
|
super(params, bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ListMessage(NetworkParameters params) {
|
||||||
|
super(params);
|
||||||
|
items = new ArrayList<InventoryItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InventoryItem> getItems()
|
||||||
|
{
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addItem(InventoryItem item)
|
||||||
|
{
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse() throws ProtocolException {
|
||||||
|
// An inv is vector<CInv> where CInv is int+hash. The int is either 1 or 2 for tx or block.
|
||||||
|
long arrayLen = readVarInt();
|
||||||
|
if (arrayLen > MAX_INVENTORY_ITEMS)
|
||||||
|
throw new ProtocolException("Too many items in INV message: " + arrayLen);
|
||||||
|
items = new ArrayList<InventoryItem>((int)arrayLen);
|
||||||
|
for (int i = 0; i < arrayLen; i++) {
|
||||||
|
if (cursor + 4 + 32 > bytes.length) {
|
||||||
|
throw new ProtocolException("Ran off the end of the INV");
|
||||||
|
}
|
||||||
|
int typeCode = (int) readUint32();
|
||||||
|
InventoryItem.Type type;
|
||||||
|
// See ppszTypeName in net.h
|
||||||
|
switch (typeCode) {
|
||||||
|
case 0: type = InventoryItem.Type.Error; break;
|
||||||
|
case 1: type = InventoryItem.Type.Transaction; break;
|
||||||
|
case 2: type = InventoryItem.Type.Block; break;
|
||||||
|
default:
|
||||||
|
throw new ProtocolException("Unknown CInv type: " + typeCode);
|
||||||
|
}
|
||||||
|
InventoryItem item = new InventoryItem(type, readHash());
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
bytes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bitcoinSerializeToStream( OutputStream stream) throws IOException
|
||||||
|
{
|
||||||
|
stream.write(new VarInt(items.size()).encode());
|
||||||
|
for (InventoryItem i : items) {
|
||||||
|
// Write out the type code.
|
||||||
|
Utils.uint32ToByteStreamLE(i.type.ordinal(), stream);
|
||||||
|
// And now the hash.
|
||||||
|
stream.write(Utils.reverseBytes(i.hash));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,20 +16,17 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import static com.google.bitcoin.core.Utils.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A NetworkConnection handles talking to a remote BitCoin peer at a low level. It understands how to read and write
|
* A NetworkConnection handles talking to a remote BitCoin peer at a low level. It understands how to read and write
|
||||||
* messages off the network, but doesn't asynchronously communicate with the peer or handle the higher level details
|
* messages off the network, but doesn't asynchronously communicate with the peer or handle the higher level details
|
||||||
@ -41,28 +38,17 @@ import static com.google.bitcoin.core.Utils.*;
|
|||||||
public class NetworkConnection {
|
public class NetworkConnection {
|
||||||
private static final Logger log = LoggerFactory.getLogger(NetworkConnection.class);
|
private static final Logger log = LoggerFactory.getLogger(NetworkConnection.class);
|
||||||
|
|
||||||
static final int COMMAND_LEN = 12;
|
|
||||||
|
|
||||||
// Message strings.
|
|
||||||
static final String MSG_VERSION = "version";
|
|
||||||
static final String MSG_INVENTORY = "inv";
|
|
||||||
static final String MSG_BLOCK = "block";
|
|
||||||
static final String MSG_GETBLOCKS = "getblocks";
|
|
||||||
static final String MSG_GETDATA = "getdata";
|
|
||||||
static final String MSG_TX = "tx";
|
|
||||||
static final String MSG_ADDR = "addr";
|
|
||||||
static final String MSG_VERACK = "verack";
|
|
||||||
|
|
||||||
private final Socket socket;
|
private final Socket socket;
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
private final InputStream in;
|
private final InputStream in;
|
||||||
// The IP address to which we are connecting.
|
// The IP address to which we are connecting.
|
||||||
private final InetAddress remoteIp;
|
private final InetAddress remoteIp;
|
||||||
private boolean usesChecksumming;
|
|
||||||
private final NetworkParameters params;
|
private final NetworkParameters params;
|
||||||
private final VersionMessage versionMessage;
|
private final VersionMessage versionMessage;
|
||||||
private static final boolean PROTOCOL_LOG = false;
|
private static final boolean PROTOCOL_LOG = false;
|
||||||
|
|
||||||
|
private BitcoinSerializer serializer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to the given IP address using the port specified as part of the network parameters. Once construction
|
* 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.
|
* is complete a functioning network channel is set up and running.
|
||||||
@ -78,7 +64,8 @@ public class NetworkConnection {
|
|||||||
throws IOException, ProtocolException {
|
throws IOException, ProtocolException {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.remoteIp = remoteIp;
|
this.remoteIp = remoteIp;
|
||||||
|
|
||||||
|
|
||||||
InetSocketAddress address = new InetSocketAddress(remoteIp, params.port);
|
InetSocketAddress address = new InetSocketAddress(remoteIp, params.port);
|
||||||
socket = new Socket();
|
socket = new Socket();
|
||||||
socket.connect(address, connectTimeout);
|
socket.connect(address, connectTimeout);
|
||||||
@ -86,15 +73,18 @@ public class NetworkConnection {
|
|||||||
out = socket.getOutputStream();
|
out = socket.getOutputStream();
|
||||||
in = socket.getInputStream();
|
in = socket.getInputStream();
|
||||||
|
|
||||||
|
// the version message never uses checksumming. Update checkumming property after version is read.
|
||||||
|
this.serializer = new BitcoinSerializer(params, false);
|
||||||
|
|
||||||
// Announce ourselves. This has to come first to connect to clients beyond v0.30.20.2 which wait to hear
|
// 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.
|
// from us until they send their version message back.
|
||||||
writeMessage(MSG_VERSION, new VersionMessage(params, bestHeight));
|
writeMessage(new VersionMessage(params, bestHeight));
|
||||||
// When connecting, the remote peer sends us a version message with various bits of
|
// 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.
|
// useful data in it. We need to know the peer protocol version before we can talk to it.
|
||||||
versionMessage = (VersionMessage) readMessage();
|
versionMessage = (VersionMessage) readMessage();
|
||||||
// Now it's our turn ...
|
// Now it's our turn ...
|
||||||
// Send an ACK message stating we accept the peers protocol version.
|
// Send an ACK message stating we accept the peers protocol version.
|
||||||
writeMessage(MSG_VERACK, new byte[] {});
|
writeMessage(new VersionAck());
|
||||||
// And get one back ...
|
// And get one back ...
|
||||||
readMessage();
|
readMessage();
|
||||||
// Switch to the new protocol version.
|
// Switch to the new protocol version.
|
||||||
@ -110,7 +100,8 @@ public class NetworkConnection {
|
|||||||
// mode nodes because we can't download the data from them we need to find/verify transactions.
|
// mode nodes because we can't download the data from them we need to find/verify transactions.
|
||||||
if (!versionMessage.hasBlockChain())
|
if (!versionMessage.hasBlockChain())
|
||||||
throw new ProtocolException("Peer does not have a copy of the block chain.");
|
throw new ProtocolException("Peer does not have a copy of the block chain.");
|
||||||
usesChecksumming = peerVersion >= 209;
|
// newer clients use checksumming
|
||||||
|
serializer.useChecksumming(peerVersion >= 209);
|
||||||
// Handshake is done!
|
// Handshake is done!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +110,7 @@ public class NetworkConnection {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void ping() throws IOException {
|
public void ping() throws IOException {
|
||||||
writeMessage("ping", new byte[] {});
|
writeMessage(new Ping());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,31 +129,6 @@ public class NetworkConnection {
|
|||||||
"disconnected") + ")";
|
"disconnected") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekPastMagicBytes() throws IOException {
|
|
||||||
int magicCursor = 3; // Which byte of the magic we're looking for currently.
|
|
||||||
while (true) {
|
|
||||||
int b = in.read(); // Read a byte.
|
|
||||||
if (b == -1) {
|
|
||||||
// There's no more data to read.
|
|
||||||
throw new IOException("Socket is disconnected");
|
|
||||||
}
|
|
||||||
// We're looking for a run of bytes that is the same as the packet magic but we want to ignore partial
|
|
||||||
// magics that aren't complete. So we keep track of where we're up to with magicCursor.
|
|
||||||
int expectedByte = 0xFF & (int)(params.packetMagic >>> (magicCursor * 8));
|
|
||||||
if (b == expectedByte) {
|
|
||||||
magicCursor--;
|
|
||||||
if (magicCursor < 0) {
|
|
||||||
// We found the magic sequence.
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// We still have further to go to find the next message.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
magicCursor = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a network message from the wire, blocking until the message is fully received.
|
* Reads a network message from the wire, blocking until the message is fully received.
|
||||||
*
|
*
|
||||||
@ -170,140 +136,7 @@ public class NetworkConnection {
|
|||||||
* @throws ProtocolException if the message is badly formatted, failed checksum or there was a TCP failure.
|
* @throws ProtocolException if the message is badly formatted, failed checksum or there was a TCP failure.
|
||||||
*/
|
*/
|
||||||
public Message readMessage() throws IOException, ProtocolException {
|
public Message readMessage() throws IOException, ProtocolException {
|
||||||
// A BitCoin protocol message has the following format.
|
return serializer.deserialize(in);
|
||||||
//
|
|
||||||
// - 4 byte magic number: 0xfabfb5da for the testnet or
|
|
||||||
// 0xf9beb4d9 for production
|
|
||||||
// - 12 byte command in ASCII
|
|
||||||
// - 4 byte payload size
|
|
||||||
// - 4 byte checksum
|
|
||||||
// - Payload data
|
|
||||||
//
|
|
||||||
// The checksum is the first 4 bytes of a SHA256 hash of the message payload. It isn't
|
|
||||||
// present for all messages, notably, the first one on a connection.
|
|
||||||
//
|
|
||||||
// Satoshis implementation ignores garbage before the magic header bytes. We have to do the same because
|
|
||||||
// sometimes it sends us stuff that isn't part of any message.
|
|
||||||
seekPastMagicBytes();
|
|
||||||
// Now read in the header.
|
|
||||||
byte[] header = new byte[COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)];
|
|
||||||
int readCursor = 0;
|
|
||||||
while (readCursor < header.length) {
|
|
||||||
int bytesRead = in.read(header, readCursor, header.length - readCursor);
|
|
||||||
if (bytesRead == -1) {
|
|
||||||
// There's no more data to read.
|
|
||||||
throw new IOException("Socket is disconnected");
|
|
||||||
}
|
|
||||||
readCursor += bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cursor = 0;
|
|
||||||
|
|
||||||
// The command is a NULL terminated string, unless the command fills all twelve bytes
|
|
||||||
// in which case the termination is implicit.
|
|
||||||
String command;
|
|
||||||
int mark = cursor;
|
|
||||||
for (; header[cursor] != 0 && cursor - mark < COMMAND_LEN; cursor++);
|
|
||||||
byte[] commandBytes = new byte[cursor - mark];
|
|
||||||
System.arraycopy(header, mark, commandBytes, 0, cursor - mark);
|
|
||||||
try {
|
|
||||||
command = new String(commandBytes, "US-ASCII");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e); // Cannot happen.
|
|
||||||
}
|
|
||||||
cursor = mark + COMMAND_LEN;
|
|
||||||
|
|
||||||
int size = (int) readUint32(header, cursor);
|
|
||||||
cursor += 4;
|
|
||||||
|
|
||||||
if (size > Message.MAX_SIZE)
|
|
||||||
throw new ProtocolException("Message size too large: " + size);
|
|
||||||
|
|
||||||
// Old clients don't send the checksum.
|
|
||||||
byte[] checksum = new byte[4];
|
|
||||||
if (usesChecksumming) {
|
|
||||||
// Note that the size read above includes the checksum bytes.
|
|
||||||
System.arraycopy(header, cursor, checksum, 0, 4);
|
|
||||||
cursor += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try to read the whole message.
|
|
||||||
readCursor = 0;
|
|
||||||
byte[] payloadBytes = new byte[size];
|
|
||||||
while (readCursor < payloadBytes.length - 1) {
|
|
||||||
int bytesRead = in.read(payloadBytes, readCursor, size - readCursor);
|
|
||||||
if (bytesRead == -1) {
|
|
||||||
throw new IOException("Socket is disconnected");
|
|
||||||
}
|
|
||||||
readCursor += bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the checksum.
|
|
||||||
if (usesChecksumming) {
|
|
||||||
byte[] hash = doubleDigest(payloadBytes);
|
|
||||||
if (checksum[0] != hash[0] || checksum[1] != hash[1] ||
|
|
||||||
checksum[2] != hash[2] || checksum[3] != hash[3]) {
|
|
||||||
throw new ProtocolException("Checksum failed to verify, actual " +
|
|
||||||
bytesToHexString(hash) +
|
|
||||||
" vs " + bytesToHexString(checksum));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Received {} byte '{}' message: {}", new Object[]{
|
|
||||||
size,
|
|
||||||
command,
|
|
||||||
Utils.bytesToHexString(payloadBytes)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Message message;
|
|
||||||
if (command.equals(MSG_VERSION))
|
|
||||||
message = new VersionMessage(params, payloadBytes);
|
|
||||||
else if (command.equals(MSG_INVENTORY))
|
|
||||||
message = new InventoryMessage(params, payloadBytes);
|
|
||||||
else if (command.equals(MSG_BLOCK))
|
|
||||||
message = new Block(params, payloadBytes);
|
|
||||||
else if (command.equals(MSG_GETDATA))
|
|
||||||
message = new GetDataMessage(params, payloadBytes);
|
|
||||||
else if (command.equals(MSG_TX))
|
|
||||||
message = new Transaction(params, payloadBytes);
|
|
||||||
else if (command.equals(MSG_ADDR))
|
|
||||||
message = new AddressMessage(params, payloadBytes);
|
|
||||||
else
|
|
||||||
message = new UnknownMessage(params, command, payloadBytes);
|
|
||||||
return message;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ProtocolException("Error deserializing message " + Utils.bytesToHexString(payloadBytes) + "\n", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeMessage(String name, byte[] payload) throws IOException {
|
|
||||||
byte[] header = new byte[4 + COMMAND_LEN + 4 + (usesChecksumming ? 4 : 0)];
|
|
||||||
|
|
||||||
uint32ToByteArrayBE(params.packetMagic, header, 0);
|
|
||||||
|
|
||||||
// The header array is initialized to zero by Java so we don't have to worry about
|
|
||||||
// NULL terminating the string here.
|
|
||||||
for (int i = 0; i < name.length() && i < COMMAND_LEN; i++) {
|
|
||||||
header[4 + i] = (byte) (name.codePointAt(i) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.uint32ToByteArrayLE(payload.length, header, 4 + COMMAND_LEN);
|
|
||||||
|
|
||||||
if (usesChecksumming) {
|
|
||||||
byte[] hash = doubleDigest(payload);
|
|
||||||
System.arraycopy(hash, 0, header, 4 + COMMAND_LEN + 4, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Sending {} message: {}", name, bytesToHexString(payload));
|
|
||||||
|
|
||||||
// Another writeMessage call may be running concurrently.
|
|
||||||
synchronized (out) {
|
|
||||||
out.write(header);
|
|
||||||
out.write(payload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -313,9 +146,10 @@ public class NetworkConnection {
|
|||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void writeMessage(String tag, Message message) throws IOException {
|
public void writeMessage(Message message) throws IOException {
|
||||||
// TODO: Requiring "tag" here is redundant, the message object should know its own protocol tag.
|
synchronized (out) {
|
||||||
writeMessage(tag, message.bitcoinSerialize());
|
serializer.serialize(message, out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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. */
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -23,9 +26,6 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Peer handles the high level communication with a BitCoin node. It requires a NetworkConnection to be set up for
|
* A Peer handles the high level communication with a BitCoin node. It requires a NetworkConnection to be set up for
|
||||||
* it. After that it takes ownership of the connection, creates and manages its own thread used for communication
|
* it. After that it takes ownership of the connection, creates and manages its own thread used for communication
|
||||||
@ -162,8 +162,9 @@ public class Peer {
|
|||||||
// enough to be a problem.
|
// enough to be a problem.
|
||||||
Block topBlock = blockChain.getUnconnectedBlock();
|
Block topBlock = blockChain.getUnconnectedBlock();
|
||||||
byte[] topHash = (topBlock != null ? topBlock.getHash() : null);
|
byte[] topHash = (topBlock != null ? topBlock.getHash() : null);
|
||||||
if (inv.items.size() == 1 && inv.items.get(0).type == InventoryItem.Type.Block && topHash != null &&
|
List<InventoryItem> items = inv.getItems();
|
||||||
Arrays.equals(inv.items.get(0).hash, topHash)) {
|
if (items.size() == 1 && items.get(0).type == InventoryItem.Type.Block && topHash != null &&
|
||||||
|
Arrays.equals(items.get(0).hash, topHash)) {
|
||||||
// An inv with a single hash containing our most recent unconnected block is a special inv,
|
// An inv with a single hash containing our most recent unconnected block is a special inv,
|
||||||
// it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to
|
// it's kind of like a tickle from the peer telling us that it's time to download more blocks to catch up to
|
||||||
// the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head
|
// the block chain. We could just ignore this and treat it as a regular inv but then we'd download the head
|
||||||
@ -171,17 +172,19 @@ public class Peer {
|
|||||||
blockChainDownload(topHash);
|
blockChainDownload(topHash);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InventoryMessage getdata = new InventoryMessage(params);
|
GetDataMessage getdata = new GetDataMessage(params);
|
||||||
for (InventoryItem item : inv.items) {
|
boolean dirty = false;
|
||||||
|
for (InventoryItem item : items) {
|
||||||
if (item.type != InventoryItem.Type.Block) continue;
|
if (item.type != InventoryItem.Type.Block) continue;
|
||||||
getdata.items.add(item);
|
getdata.addItem(item);
|
||||||
|
dirty = true;
|
||||||
}
|
}
|
||||||
// No blocks to download. This probably contained transactions instead, but right now we can't prove they are
|
// No blocks to download. This probably contained transactions instead, but right now we can't prove they are
|
||||||
// valid so we don't bother downloading transactions that aren't in blocks yet.
|
// valid so we don't bother downloading transactions that aren't in blocks yet.
|
||||||
if (getdata.items.size() == 0)
|
if (!dirty)
|
||||||
return;
|
return;
|
||||||
// This will cause us to receive a bunch of block messages.
|
// This will cause us to receive a bunch of block messages.
|
||||||
conn.writeMessage(NetworkConnection.MSG_GETDATA, getdata);
|
conn.writeMessage(getdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -196,14 +199,14 @@ public class Peer {
|
|||||||
public Future<Block> getBlock(byte[] blockHash) throws IOException {
|
public Future<Block> getBlock(byte[] blockHash) throws IOException {
|
||||||
InventoryMessage getdata = new InventoryMessage(params);
|
InventoryMessage getdata = new InventoryMessage(params);
|
||||||
InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Block, blockHash);
|
InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Block, blockHash);
|
||||||
getdata.items.add(inventoryItem);
|
getdata.addItem(inventoryItem);
|
||||||
GetDataFuture<Block> future = new GetDataFuture<Block>(inventoryItem);
|
GetDataFuture<Block> future = new GetDataFuture<Block>(inventoryItem);
|
||||||
// Add to the list of things we're waiting for. It's important this come before the network send to avoid
|
// Add to the list of things we're waiting for. It's important this come before the network send to avoid
|
||||||
// race conditions.
|
// race conditions.
|
||||||
synchronized (pendingGetBlockFutures) {
|
synchronized (pendingGetBlockFutures) {
|
||||||
pendingGetBlockFutures.add(future);
|
pendingGetBlockFutures.add(future);
|
||||||
}
|
}
|
||||||
conn.writeMessage(NetworkConnection.MSG_GETDATA, getdata);
|
conn.writeMessage(getdata);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +270,7 @@ public class Peer {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
void broadcastTransaction(Transaction tx) throws IOException {
|
void broadcastTransaction(Transaction tx) throws IOException {
|
||||||
conn.writeMessage(NetworkConnection.MSG_TX, tx);
|
conn.writeMessage(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void blockChainDownload(byte[] toHash) throws IOException {
|
private void blockChainDownload(byte[] toHash) throws IOException {
|
||||||
@ -309,7 +312,7 @@ public class Peer {
|
|||||||
if (!topBlock.equals(params.genesisBlock))
|
if (!topBlock.equals(params.genesisBlock))
|
||||||
blockLocator.add(0, topBlock.getHash());
|
blockLocator.add(0, topBlock.getHash());
|
||||||
GetBlocksMessage message = new GetBlocksMessage(params, blockLocator, toHash);
|
GetBlocksMessage message = new GetBlocksMessage(params, blockLocator, toHash);
|
||||||
conn.writeMessage(NetworkConnection.MSG_GETBLOCKS, message);
|
conn.writeMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
27
src/com/google/bitcoin/core/Ping.java
Normal file
27
src/com/google/bitcoin/core/Ping.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2011 Noa Resare
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
public class Ping
|
||||||
|
extends Message
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
void parse() throws ProtocolException
|
||||||
|
{
|
||||||
|
// nothing to parse
|
||||||
|
}
|
||||||
|
}
|
41
src/com/google/bitcoin/core/VersionAck.java
Normal file
41
src/com/google/bitcoin/core/VersionAck.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2011 Noa Resare.
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The verack message, sent by a client accepting the version message they received from their peer.
|
||||||
|
*/
|
||||||
|
public class VersionAck
|
||||||
|
extends Message
|
||||||
|
{
|
||||||
|
public VersionAck()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is needed by the BitcoinSerializer
|
||||||
|
public VersionAck(NetworkParameters params, byte[] payload) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void parse() throws ProtocolException
|
||||||
|
{
|
||||||
|
// nothing to parse for now
|
||||||
|
}
|
||||||
|
}
|
64
tests/com/google/bitcoin/core/BitcoinSerializerTest.java
Normal file
64
tests/com/google/bitcoin/core/BitcoinSerializerTest.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2011 Noa Resare
|
||||||
|
*
|
||||||
|
* 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 com.google.bitcoin.core;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.bitcoin.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class BitcoinSerializerTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testVersion() throws Exception {
|
||||||
|
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), false);
|
||||||
|
// the actual data from https://en.bitcoin.it/wiki/Protocol_specification#version
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(Hex.decode("f9beb4d976657273696f6e0000000000550000009" +
|
||||||
|
"c7c00000100000000000000e615104d00000000010000000000000000000000000000000000ffff0a000001daf6010000" +
|
||||||
|
"000000000000000000000000000000ffff0a000002208ddd9d202c3ab457130055810100"));
|
||||||
|
VersionMessage vm = (VersionMessage)bs.deserialize(bais);
|
||||||
|
assertEquals(31900, vm.clientVersion);
|
||||||
|
assertEquals(1292899814L, vm.time);
|
||||||
|
assertEquals(98645L, vm.bestHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerack() throws Exception {
|
||||||
|
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), false);
|
||||||
|
// the actual data from https://en.bitcoin.it/wiki/Protocol_specification#verack
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(Hex.decode("f9beb4d976657261636b00000000000000000000"));
|
||||||
|
VersionAck va = (VersionAck)bs.deserialize(bais);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddr() throws Exception {
|
||||||
|
BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true);
|
||||||
|
// the actual data from https://en.bitcoin.it/wiki/Protocol_specification#addr
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(Hex.decode("f9beb4d96164647200000000000000001f000000" +
|
||||||
|
"ed52399b01e215104d010000000000000000000000000000000000ffff0a000001208d"));
|
||||||
|
AddressMessage a = (AddressMessage)bs.deserialize(bais);
|
||||||
|
assertEquals(1, a.addresses.size());
|
||||||
|
PeerAddress pa = a.addresses.get(0);
|
||||||
|
assertEquals(8333, pa.port);
|
||||||
|
assertEquals("10.0.0.1", pa.addr.getHostAddress());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user