diff --git a/core/src/main/java/org/bitcoinj/core/BlockLocator.java b/core/src/main/java/org/bitcoinj/core/BlockLocator.java new file mode 100644 index 000000000..74a33a4eb --- /dev/null +++ b/core/src/main/java/org/bitcoinj/core/BlockLocator.java @@ -0,0 +1,88 @@ +/* + * Copyright by the original author or authors. + * + * 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 org.bitcoinj.core; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * Represents Block Locator in GetBlocks and GetHeaders messages + **/ +public final class BlockLocator { + private final ImmutableList hashes; + + public BlockLocator() { + hashes = ImmutableList.of(); + } + + /** + * Creates a Block locator with defined list of hashes. + */ + public BlockLocator(ImmutableList hashes) { + this.hashes = hashes; + } + + /** + * Add a {@link Sha256Hash} to a newly created block locator. + */ + public BlockLocator add(Sha256Hash hash) { + return new BlockLocator(new ImmutableList.Builder().addAll(this.hashes).add(hash).build()); + } + + /** + * Returns the number of hashes in this block locator. + */ + public int size() { + return hashes.size(); + } + + /** + * Returns List of Block locator hashes. + */ + public List getHashes() { + return hashes; + } + + /** + * Get hash by index from this block locator. + */ + public Sha256Hash get(int i) { + return hashes.get(i); + } + + @Override + public String toString() { + return "Block locator with " + size() + " blocks\n " + Utils.SPACE_JOINER.join(hashes); + } + + @Override + public int hashCode() { + int hashCode = 0; + for (Sha256Hash i : hashes) { + hashCode ^= i.hashCode(); + } + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return ((BlockLocator) o).getHashes().equals(hashes); + } +} diff --git a/core/src/main/java/org/bitcoinj/core/GetBlocksMessage.java b/core/src/main/java/org/bitcoinj/core/GetBlocksMessage.java index 732080627..a18fb26a6 100644 --- a/core/src/main/java/org/bitcoinj/core/GetBlocksMessage.java +++ b/core/src/main/java/org/bitcoinj/core/GetBlocksMessage.java @@ -19,8 +19,6 @@ package org.bitcoinj.core; import java.io.IOException; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; /** *

Represents the "getblocks" P2P network message, which requests the hashes of the parts of the block chain we're @@ -31,10 +29,10 @@ import java.util.List; public class GetBlocksMessage extends Message { protected long version; - protected List locator; + protected BlockLocator locator; protected Sha256Hash stopHash; - public GetBlocksMessage(NetworkParameters params, List locator, Sha256Hash stopHash) { + public GetBlocksMessage(NetworkParameters params, BlockLocator locator, Sha256Hash stopHash) { super(params); this.version = protocolVersion; this.locator = locator; @@ -53,14 +51,14 @@ public class GetBlocksMessage extends Message { if (startCount > 500) throw new ProtocolException("Number of locators cannot be > 500, received: " + startCount); length = cursor - offset + ((startCount + 1) * 32); - locator = new ArrayList<>(startCount); + locator = new BlockLocator(); for (int i = 0; i < startCount; i++) { - locator.add(readHash()); + locator = locator.add(readHash()); } stopHash = readHash(); } - public List getLocator() { + public BlockLocator getLocator() { return locator; } @@ -70,7 +68,7 @@ public class GetBlocksMessage extends Message { @Override public String toString() { - return "getblocks: " + Utils.SPACE_JOINER.join(locator); + return "getblocks: " + locator.toString(); } @Override @@ -81,7 +79,7 @@ public class GetBlocksMessage extends Message { // identifiers that spans the entire chain with exponentially increasing gaps between // them, until we end up at the genesis block. See CBlockLocator::Set() stream.write(new VarInt(locator.size()).encode()); - for (Sha256Hash hash : locator) { + for (Sha256Hash hash : locator.getHashes()) { // Have to reverse as wire format is little endian. stream.write(hash.getReversedBytes()); } @@ -95,13 +93,12 @@ public class GetBlocksMessage extends Message { if (o == null || getClass() != o.getClass()) return false; GetBlocksMessage other = (GetBlocksMessage) o; return version == other.version && stopHash.equals(other.stopHash) && - locator.size() == other.locator.size() && locator.containsAll(other.locator); // ignores locator ordering + locator.size() == other.locator.size() && locator.equals(other.locator); // ignores locator ordering } @Override public int hashCode() { - int hashCode = (int)version ^ "getblocks".hashCode() ^ stopHash.hashCode(); - for (Sha256Hash aLocator : locator) hashCode ^= aLocator.hashCode(); // ignores locator ordering - return hashCode; + int hashCode = (int) version ^ "getblocks".hashCode() ^ stopHash.hashCode(); + return hashCode ^= locator.hashCode(); } } diff --git a/core/src/main/java/org/bitcoinj/core/GetHeadersMessage.java b/core/src/main/java/org/bitcoinj/core/GetHeadersMessage.java index ec3403172..35f8ab59e 100644 --- a/core/src/main/java/org/bitcoinj/core/GetHeadersMessage.java +++ b/core/src/main/java/org/bitcoinj/core/GetHeadersMessage.java @@ -16,8 +16,6 @@ package org.bitcoinj.core; -import java.util.List; - /** *

The "getheaders" command is structurally identical to "getblocks", but has different meaning. On receiving this * message a Bitcoin node returns matching blocks up to the limit, but without the bodies. It is useful as an @@ -27,7 +25,7 @@ import java.util.List; *

Instances of this class are not safe for use by multiple threads.

*/ public class GetHeadersMessage extends GetBlocksMessage { - public GetHeadersMessage(NetworkParameters params, List locator, Sha256Hash stopHash) { + public GetHeadersMessage(NetworkParameters params, BlockLocator locator, Sha256Hash stopHash) { super(params, locator, stopHash); } @@ -37,7 +35,7 @@ public class GetHeadersMessage extends GetBlocksMessage { @Override public String toString() { - return "getheaders: " + Utils.SPACE_JOINER.join(locator); + return "getheaders: " + locator.toString(); } /** @@ -50,13 +48,12 @@ public class GetHeadersMessage extends GetBlocksMessage { if (o == null || getClass() != o.getClass()) return false; GetHeadersMessage other = (GetHeadersMessage) o; return version == other.version && stopHash.equals(other.stopHash) && - locator.size() == other.locator.size() && locator.containsAll(other.locator); // ignores locator ordering + locator.size() == other.locator.size() && locator.equals(other.locator); // ignores locator ordering } @Override public int hashCode() { - int hashCode = (int)version ^ "getheaders".hashCode() ^ stopHash.hashCode(); - for (Sha256Hash aLocator : locator) hashCode ^= aLocator.hashCode(); // ignores locator ordering - return hashCode; + int hashCode = (int) version ^ "getheaders".hashCode() ^ stopHash.hashCode(); + return hashCode ^= locator.hashCode(); } } diff --git a/core/src/main/java/org/bitcoinj/core/Peer.java b/core/src/main/java/org/bitcoinj/core/Peer.java index b88db81e3..25dec20aa 100644 --- a/core/src/main/java/org/bitcoinj/core/Peer.java +++ b/core/src/main/java/org/bitcoinj/core/Peer.java @@ -1437,8 +1437,7 @@ public class Peer extends PeerSocketHandler { // headers and then request the blocks from that point onwards. "getheaders" does not send us an inv, it just // sends us the data we requested in a "headers" message. - // TODO: Block locators should be abstracted out rather than special cased here. - List blockLocator = new ArrayList<>(51); + BlockLocator blockLocator = new BlockLocator(); // For now we don't do the exponential thinning as suggested here: // // https://en.bitcoin.it/wiki/Protocol_specification#getblocks @@ -1462,7 +1461,7 @@ public class Peer extends PeerSocketHandler { this, toHash, chainHead.getHeader().getHashAsString()); StoredBlock cursor = chainHead; for (int i = 100; cursor != null && i > 0; i--) { - blockLocator.add(cursor.getHeader().getHash()); + blockLocator = blockLocator.add(cursor.getHeader().getHash()); try { cursor = cursor.getPrev(store); } catch (BlockStoreException e) { @@ -1472,7 +1471,7 @@ public class Peer extends PeerSocketHandler { } // Only add the locator if we didn't already do so. If the chain is < 50 blocks we already reached it. if (cursor != null) - blockLocator.add(params.getGenesisBlock().getHash()); + blockLocator = blockLocator.add(params.getGenesisBlock().getHash()); // Record that we requested this range of blocks so we can filter out duplicate requests in the event of a // block being solved during chain download. diff --git a/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java b/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java index 9498ca234..8decd80fc 100644 --- a/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java +++ b/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java @@ -162,7 +162,7 @@ public class BitcoindComparisonTool { } LinkedList sendHeaders = new LinkedList<>(); boolean found = false; - for (Sha256Hash hash : ((GetHeadersMessage) m).getLocator()) { + for (Sha256Hash hash : ((GetHeadersMessage) m).getLocator().getHashes()) { for (Block b : headers) { if (found) { sendHeaders.addLast(b); @@ -206,8 +206,8 @@ public class BitcoindComparisonTool { connectedFuture.get(); - ArrayList locator = new ArrayList<>(1); - locator.add(PARAMS.getGenesisBlock().getHash()); + BlockLocator locator = new BlockLocator(); + locator = locator.add(PARAMS.getGenesisBlock().getHash()); Sha256Hash hashTo = Sha256Hash.wrap("0000000000000000000000000000000000000000000000000000000000000000"); int rulesSinceFirstFail = 0; @@ -295,8 +295,8 @@ public class BitcoindComparisonTool { if (block.throwsException) blocksRequested.remove(nextBlock.getHash()); //bitcoind.sendMessage(nextBlock); - locator.clear(); - locator.add(bitcoindChainHead); + locator = new BlockLocator(); + locator = locator.add(bitcoindChainHead); bitcoind.sendMessage(new GetHeadersMessage(PARAMS, locator, hashTo)); bitcoind.ping().get(); if (!chain.getChainHead().getHeader().getHash().equals(bitcoindChainHead)) { diff --git a/core/src/test/java/org/bitcoinj/core/PeerTest.java b/core/src/test/java/org/bitcoinj/core/PeerTest.java index 6374f5f18..b94f5b07f 100644 --- a/core/src/test/java/org/bitcoinj/core/PeerTest.java +++ b/core/src/test/java/org/bitcoinj/core/PeerTest.java @@ -177,7 +177,7 @@ public class PeerTest extends TestWithNetworkConnections { inbound(writeTarget, b5); getblocks = (GetBlocksMessage)outbound(writeTarget); assertEquals(b5.getHash(), getblocks.getStopHash()); - assertEquals(b3.getHash(), getblocks.getLocator().get(0)); + assertEquals(b3.getHash(), getblocks.getLocator().getHashes().get(0)); // At this point another block is solved and broadcast. The inv triggers a getdata but we do NOT send another // getblocks afterwards, because that would result in us receiving the same set of blocks twice which is a // timewaste. The getblocks message that would have been generated is set to be the same as the previous @@ -225,9 +225,9 @@ public class PeerTest extends TestWithNetworkConnections { inbound(writeTarget, inv); GetBlocksMessage getblocks = (GetBlocksMessage)outbound(writeTarget); - List expectedLocator = new ArrayList<>(); - expectedLocator.add(b1.getHash()); - expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); + BlockLocator expectedLocator = new BlockLocator(); + expectedLocator = expectedLocator.add(b1.getHash()); + expectedLocator = expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); assertEquals(getblocks.getLocator(), expectedLocator); assertEquals(getblocks.getStopHash(), b3.getHash()); @@ -396,10 +396,10 @@ public class PeerTest extends TestWithNetworkConnections { }); peer.startBlockChainDownload(); - List expectedLocator = new ArrayList<>(); - expectedLocator.add(b2.getHash()); - expectedLocator.add(b1.getHash()); - expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); + BlockLocator expectedLocator = new BlockLocator(); + expectedLocator = expectedLocator.add(b2.getHash()); + expectedLocator = expectedLocator.add(b1.getHash()); + expectedLocator = expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); GetBlocksMessage message = (GetBlocksMessage) outbound(writeTarget); assertEquals(message.getLocator(), expectedLocator); @@ -478,19 +478,19 @@ public class PeerTest extends TestWithNetworkConnections { peer.setDownloadParameters(Utils.currentTimeSeconds() - (600*2) + 1, false); peer.startBlockChainDownload(); GetHeadersMessage getheaders = (GetHeadersMessage) outbound(writeTarget); - List expectedLocator = new ArrayList<>(); - expectedLocator.add(b1.getHash()); - expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); + BlockLocator expectedLocator = new BlockLocator(); + expectedLocator = expectedLocator.add(b1.getHash()); + expectedLocator = expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); assertEquals(getheaders.getLocator(), expectedLocator); assertEquals(getheaders.getStopHash(), Sha256Hash.ZERO_HASH); // Now send all the headers. HeadersMessage headers = new HeadersMessage(UNITTEST, b2.cloneAsHeader(), b3.cloneAsHeader(), b4.cloneAsHeader()); // We expect to be asked for b3 and b4 again, but this time, with a body. - expectedLocator.clear(); - expectedLocator.add(b2.getHash()); - expectedLocator.add(b1.getHash()); - expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); + expectedLocator = new BlockLocator(); + expectedLocator = expectedLocator.add(b2.getHash()); + expectedLocator = expectedLocator.add(b1.getHash()); + expectedLocator = expectedLocator.add(UNITTEST.getGenesisBlock().getHash()); inbound(writeTarget, headers); GetBlocksMessage getblocks = (GetBlocksMessage) outbound(writeTarget); assertEquals(expectedLocator, getblocks.getLocator());