diff --git a/AUTHORS b/AUTHORS index 210fc0dd..63688822 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,4 +9,5 @@ John Sample Jan Møller Wolfgang Nagele Jonny Heggheim -Steve Coughlan \ No newline at end of file +Steve Coughlan +Roman Mandeleil diff --git a/src/com/google/bitcoin/core/BitcoinSerializer.java b/src/com/google/bitcoin/core/BitcoinSerializer.java index 46a1e3e7..627881a8 100644 --- a/src/com/google/bitcoin/core/BitcoinSerializer.java +++ b/src/com/google/bitcoin/core/BitcoinSerializer.java @@ -68,6 +68,8 @@ public class BitcoinSerializer { names.put(VersionAck.class, "verack"); names.put(GetBlocksMessage.class, "getblocks"); names.put(GetAddrMessage.class, "getaddr"); + names.put(HeadersMessage.class, "headers"); + } /** @@ -330,6 +332,8 @@ public class BitcoinSerializer { return new Ping(); } else if (command.equals("verack")) { return new VersionAck(params, payloadBytes); + } else if (command.equals("headers")) { + return new HeadersMessage(params, payloadBytes); } else { throw new ProtocolException("No support for deserializing message with name " + command); } diff --git a/src/com/google/bitcoin/core/HeadersMessage.java b/src/com/google/bitcoin/core/HeadersMessage.java new file mode 100644 index 00000000..cb4862c5 --- /dev/null +++ b/src/com/google/bitcoin/core/HeadersMessage.java @@ -0,0 +1,82 @@ +/* + * 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.util.ArrayList; +import java.util.List; + +/** + * A protocol message that contains a repeated series of block headers, sent in response to the "getheaders" command. + * This is useful when you want to traverse the chain but know you don't care about the block contents, for example, + * because you have a freshly created wallet with no keys. + */ +public class HeadersMessage extends Message { + private static final Logger log = LoggerFactory.getLogger(HeadersMessage.class); + + // The main client will never send us more than this number of headers. + final static int MAX_HEADERS = 2000; + + private List blockHeaders; + + public HeadersMessage(NetworkParameters params, byte[] payload) throws ProtocolException { + super(params, payload, 0); + } + + @Override + protected void parseLite() throws ProtocolException { + if (length == UNKNOWN_LENGTH) { + long numHeaders = readVarInt(); + + // Each header has 80 bytes and one more byte for transactions number which is 00. + length = 81 * (int)numHeaders; + } + } + + @Override + void parse() throws ProtocolException { + long numHeaders = readVarInt(); + if (numHeaders > MAX_HEADERS) + throw new ProtocolException("Too many headers: got " + numHeaders + " which is larger than " + + MAX_HEADERS); + + blockHeaders = new ArrayList(); + + for (int i = 0; i < numHeaders; ++i) { + // Read 80 bytes of the header and one more byte for the transaction list, which is always a 00 because the + // transaction list is empty. + byte[] blockHeader = readBytes(81); + if (blockHeader[80] != 00) + throw new ProtocolException("Block header does not end with a null byte"); + Block newBlockHeader = new Block(this.params, blockHeader); + blockHeaders.add(newBlockHeader); + } + + if (log.isDebugEnabled()) { + for (int i = 0; i < numHeaders; ++i) { + log.debug(this.blockHeaders.get(i).toString()); + } + } + } + + + public List getBlockHeaders() { + return blockHeaders; + } +} diff --git a/tests/com/google/bitcoin/core/BitcoinSerializerTest.java b/tests/com/google/bitcoin/core/BitcoinSerializerTest.java index 7125edad..88624adc 100644 --- a/tests/com/google/bitcoin/core/BitcoinSerializerTest.java +++ b/tests/com/google/bitcoin/core/BitcoinSerializerTest.java @@ -199,5 +199,84 @@ public class BitcoinSerializerTest { assertEquals(true, Arrays.equals(txMessage, bos.toByteArray())); } - + + + /** + * Get 1 header of the block number 1 (the first one is 0) in the chain + */ + @Test + public void testHeaders1() throws Exception { + + BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true, + null); + + ByteArrayInputStream bais = new ByteArrayInputStream(Hex.decode("f9beb4d9686561" + + "646572730000000000520000005d4fab8101010000006fe28c0ab6f1b372c1a6a246ae6" + + "3f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677b" + + "a1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e3629900")); + + HeadersMessage hm = (HeadersMessage) bs.deserialize(bais); + + // The first block after the genesis + // http://blockexplorer.com/b/1 + Block block = hm.getBlockHeaders().get(0); + String hash = block.getHashAsString(); + assertEquals(hash, "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"); + + assertEquals(block.transactions.size(), 0); + + assertEquals(Utils.bytesToHexString(block.getMerkleRoot().getBytes()), + "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); + } + + + @Test + /** + * Get 6 headers of blocks 1-6 in the chain + */ + public void testHeaders2() throws Exception { + BitcoinSerializer bs = new BitcoinSerializer(NetworkParameters.prodNet(), true, + null); + + ByteArrayInputStream bais = new ByteArrayInputStream(Hex.decode("f9beb4d96865616465" + + "72730000000000e701000085acd4ea06010000006fe28c0ab6f1b372c1a6a246ae63f74f931e" + + "8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1c" + + "db606e857233e0e61bc6649ffff001d01e3629900010000004860eb18bf1b1620e37e9490fc8a" + + "427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36" + + "ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610001000000bddd99ccfda39da1b108ce1" + + "a5d70038d0a967bacb68b6b63065f626a0000000044f672226090d85db9a9f2fbfe5f0f9609b387" + + "af7be5b7fbb7a1767c831c9e995dbe6649ffff001d05e0ed6d00010000004944469562ae1c2c74" + + "d9a535e00b6f3e40ffbad4f2fda3895501b582000000007a06ea98cd40ba2e3288262b28638cec" + + "5337c1456aaf5eedc8e9e5a20f062bdf8cc16649ffff001d2bfee0a9000100000085144a84488e" + + "a88d221c8bd6c059da090e88f8a2c99690ee55dbba4e00000000e11c48fecdd9e72510ca84f023" + + "370c9a38bf91ac5cae88019bee94d24528526344c36649ffff001d1d03e4770001000000fc33f5" + + "96f822a0a1951ffdbf2a897b095636ad871707bf5d3162729b00000000379dfb96a5ea8c81700ea4" + + "ac6b97ae9a9312b2d4301a29580e924ee6761a2520adc46649ffff001d189c4c9700")); + + HeadersMessage hm = (HeadersMessage) bs.deserialize(bais); + + int nBlocks = hm.getBlockHeaders().size(); + assertEquals(nBlocks, 6); + + // index 0 block is the number 1 block in the block chain + // http://blockexplorer.com/b/1 + Block zeroBlock = hm.getBlockHeaders().get(0); + String zeroBlockHash = zeroBlock.getHashAsString(); + + assertEquals("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048", + zeroBlockHash); + assertEquals(zeroBlock.getNonce(), 2573394689L); + + + Block thirdBlock = hm.getBlockHeaders().get(3); + String thirdBlockHash = thirdBlock.getHashAsString(); + + // index 3 block is the number 4 block in the block chain + // http://blockexplorer.com/b/4 + assertEquals("000000004ebadb55ee9096c9a2f8880e09da59c0d68b1c228da88e48844a1485", + thirdBlockHash); + assertEquals(thirdBlock.getNonce(), 2850094635L); + } + + }