Add BlockLocator class for representing block locators as used in GetBlocksMessage and GetHeadersMessage.

This commit is contained in:
BigAdam2005 2018-07-05 19:51:35 +02:00 committed by Andreas Schildbach
parent 2ec193f847
commit 4a316089fa
6 changed files with 126 additions and 45 deletions

View File

@ -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<Sha256Hash> hashes;
public BlockLocator() {
hashes = ImmutableList.of();
}
/**
* Creates a Block locator with defined list of hashes.
*/
public BlockLocator(ImmutableList<Sha256Hash> 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<Sha256Hash>().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<Sha256Hash> 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);
}
}

View File

@ -19,8 +19,6 @@ package org.bitcoinj.core;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* <p>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<Sha256Hash> locator;
protected BlockLocator locator;
protected Sha256Hash stopHash;
public GetBlocksMessage(NetworkParameters params, List<Sha256Hash> 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<Sha256Hash> 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();
}
}

View File

@ -16,8 +16,6 @@
package org.bitcoinj.core;
import java.util.List;
/**
* <p>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;
* <p>Instances of this class are not safe for use by multiple threads.</p>
*/
public class GetHeadersMessage extends GetBlocksMessage {
public GetHeadersMessage(NetworkParameters params, List<Sha256Hash> 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();
}
}

View File

@ -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<Sha256Hash> 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.

View File

@ -162,7 +162,7 @@ public class BitcoindComparisonTool {
}
LinkedList<Block> 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<Sha256Hash> 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)) {

View File

@ -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<Sha256Hash> 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<Sha256Hash> 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<Sha256Hash> 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());