Use Sha256Hash more consistently, improve the class a bit.

Note that the endianness of the hashes is still very ad-hoc and messy. Next step is to pick an endianness and stick with it, to reduce the number of times reverseBytes is used.
This commit is contained in:
Mike Hearn
2011-06-30 20:33:41 +00:00
parent 46ccc7389d
commit 3b8b0833c5
15 changed files with 124 additions and 138 deletions

View File

@@ -50,8 +50,8 @@ public class Block extends Message {
// For unit testing. If not zero, use this instead of the current time. // For unit testing. If not zero, use this instead of the current time.
static long fakeClock = 0; static long fakeClock = 0;
private long version; private long version;
private byte[] prevBlockHash; private Sha256Hash prevBlockHash;
private byte[] merkleRoot; private Sha256Hash merkleRoot;
private long time; private long time;
private long difficultyTarget; // "nBits" private long difficultyTarget; // "nBits"
@@ -60,7 +60,7 @@ public class Block extends Message {
/** If null, it means this object holds only the headers. */ /** If null, it means this object holds only the headers. */
List<Transaction> transactions; List<Transaction> transactions;
/** Stores the hash of the block. If null, getHash() will recalculate it. */ /** Stores the hash of the block. If null, getHash() will recalculate it. */
private transient byte[] hash; private transient Sha256Hash hash;
/** Special case constructor, used for the genesis node and unit tests. */ /** Special case constructor, used for the genesis node and unit tests. */
Block(NetworkParameters params) { Block(NetworkParameters params) {
@@ -69,7 +69,7 @@ public class Block extends Message {
version = 1; version = 1;
difficultyTarget = 0x1d07fff8L; difficultyTarget = 0x1d07fff8L;
time = System.currentTimeMillis() / 1000; time = System.currentTimeMillis() / 1000;
prevBlockHash = new byte[32]; // All zeros. prevBlockHash = Sha256Hash.ZERO_HASH;
} }
/** Constructs a block object from the BitCoin wire format. */ /** Constructs a block object from the BitCoin wire format. */
@@ -85,7 +85,7 @@ public class Block extends Message {
difficultyTarget = readUint32(); difficultyTarget = readUint32();
nonce = readUint32(); nonce = readUint32();
hash = Utils.reverseBytes(Utils.doubleDigest(bytes, 0, cursor)); hash = new Sha256Hash(Utils.reverseBytes(Utils.doubleDigest(bytes, 0, cursor)));
if (cursor == bytes.length) { if (cursor == bytes.length) {
// This message is just a header, it has no transactions. // This message is just a header, it has no transactions.
@@ -103,8 +103,8 @@ public class Block extends Message {
private void writeHeader(OutputStream stream) throws IOException { private void writeHeader(OutputStream stream) throws IOException {
Utils.uint32ToByteStreamLE(version, stream); Utils.uint32ToByteStreamLE(version, stream);
stream.write(Utils.reverseBytes(prevBlockHash)); stream.write(Utils.reverseBytes(prevBlockHash.getBytes()));
stream.write(Utils.reverseBytes(getMerkleRoot())); stream.write(Utils.reverseBytes(getMerkleRoot().getBytes()));
Utils.uint32ToByteStreamLE(time, stream); Utils.uint32ToByteStreamLE(time, stream);
Utils.uint32ToByteStreamLE(difficultyTarget, stream); Utils.uint32ToByteStreamLE(difficultyTarget, stream);
Utils.uint32ToByteStreamLE(nonce, stream); Utils.uint32ToByteStreamLE(nonce, stream);
@@ -124,11 +124,11 @@ public class Block extends Message {
/** /**
* Calculates the block hash by serializing the block and hashing the resulting bytes. * Calculates the block hash by serializing the block and hashing the resulting bytes.
*/ */
private byte[] calculateHash() { private Sha256Hash calculateHash() {
try { try {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
writeHeader(bos); writeHeader(bos);
return Utils.reverseBytes(doubleDigest(bos.toByteArray())); return new Sha256Hash(Utils.reverseBytes(doubleDigest(bos.toByteArray())));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); // Cannot happen. throw new RuntimeException(e); // Cannot happen.
} }
@@ -140,13 +140,13 @@ public class Block extends Message {
* "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048". * "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048".
*/ */
public String getHashAsString() { public String getHashAsString() {
return Utils.bytesToHexString(getHash()); return getHash().toString();
} }
/** /**
* Returns the hash of the block (which for a valid, solved block should be below the target). Big endian. * Returns the hash of the block (which for a valid, solved block should be below the target). Big endian.
*/ */
public byte[] getHash() { public Sha256Hash getHash() {
if (hash == null) if (hash == null)
hash = calculateHash(); hash = calculateHash();
return hash; return hash;
@@ -186,8 +186,8 @@ public class Block extends Message {
@Override @Override
public String toString() { public String toString() {
StringBuffer s = new StringBuffer("v" + version + " block: \n" + StringBuffer s = new StringBuffer("v" + version + " block: \n" +
" previous block: " + bytesToHexString(prevBlockHash) + "\n" + " previous block: " + prevBlockHash.toString() + "\n" +
" merkle root: " + bytesToHexString(getMerkleRoot()) + "\n" + " merkle root: " + getMerkleRoot().toString() + "\n" +
" time: [" + time + "] " + new Date(time * 1000).toString() + "\n" + " time: [" + time + "] " + new Date(time * 1000).toString() + "\n" +
" difficulty target (nBits): " + difficultyTarget + "\n" + " difficulty target (nBits): " + difficultyTarget + "\n" +
" nonce: " + nonce + "\n"); " nonce: " + nonce + "\n");
@@ -244,7 +244,7 @@ public class Block extends Message {
// field is of the right value. This requires us to have the preceeding blocks. // field is of the right value. This requires us to have the preceeding blocks.
BigInteger target = getDifficultyTargetAsInteger(); BigInteger target = getDifficultyTargetAsInteger();
BigInteger h = new BigInteger(1, getHash()); BigInteger h = getHash().toBigInteger();
if (h.compareTo(target) > 0) { if (h.compareTo(target) > 0) {
// Proof of work check failed! // Proof of work check failed!
if (throwException) if (throwException)
@@ -263,21 +263,18 @@ public class Block extends Message {
throw new VerificationException("Block too far in future"); throw new VerificationException("Block too far in future");
} }
private void checkMerkleHash() throws VerificationException { private void checkMerkleRoot() throws VerificationException {
List<byte[]> tree = buildMerkleTree(); Sha256Hash calculatedRoot = calculateMerkleRoot();
byte[] calculatedRoot = tree.get(tree.size() - 1); if (!calculatedRoot.equals(merkleRoot)) {
if (!Arrays.equals(calculatedRoot, merkleRoot)) { log.error("Merkle tree did not verify");
log.error("Merkle tree did not verify: ");
for (byte[] b : tree) log.error(Utils.bytesToHexString(b));
throw new VerificationException("Merkle hashes do not match: " + throw new VerificationException("Merkle hashes do not match: " +
bytesToHexString(calculatedRoot) + " vs " + bytesToHexString(merkleRoot)); calculatedRoot + " vs " + merkleRoot);
} }
} }
private byte[] calculateMerkleRoot() { private Sha256Hash calculateMerkleRoot() {
List<byte[]> tree = buildMerkleTree(); List<byte[]> tree = buildMerkleTree();
return tree.get(tree.size() - 1); return new Sha256Hash(tree.get(tree.size() - 1));
} }
private List<byte[]> buildMerkleTree() { private List<byte[]> buildMerkleTree() {
@@ -315,7 +312,7 @@ public class Block extends Message {
ArrayList<byte[]> tree = new ArrayList<byte[]>(); ArrayList<byte[]> tree = new ArrayList<byte[]>();
// Start by adding all the hashes of the transactions as leaves of the tree. // Start by adding all the hashes of the transactions as leaves of the tree.
for (Transaction t : transactions) { for (Transaction t : transactions) {
tree.add(t.getHash().hash); tree.add(t.getHash().getBytes());
} }
int levelOffset = 0; // Offset in the list where the currently processed level starts. int levelOffset = 0; // Offset in the list where the currently processed level starts.
// Step through each level, stopping when we reach the root (levelSize == 1). // Step through each level, stopping when we reach the root (levelSize == 1).
@@ -369,7 +366,7 @@ public class Block extends Message {
if (transactions != null) { if (transactions != null) {
assert transactions.size() > 0; assert transactions.size() > 0;
checkTransactions(); checkTransactions();
checkMerkleHash(); checkMerkleRoot();
} }
} }
@@ -377,23 +374,23 @@ public class Block extends Message {
public boolean equals(Object o) { public boolean equals(Object o) {
if (!(o instanceof Block)) return false; if (!(o instanceof Block)) return false;
Block other = (Block) o; Block other = (Block) o;
return Arrays.equals(getHash(), other.getHash()); return getHash().equals(other.getHash());
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(getHash()); return getHash().hashCode();
} }
/** Returns the merkle root in big endian form, calculating it from transactions if necessary. */ /** Returns the merkle root in big endian form, calculating it from transactions if necessary. */
public byte[] getMerkleRoot() { public Sha256Hash getMerkleRoot() {
if (merkleRoot == null) if (merkleRoot == null)
merkleRoot = calculateMerkleRoot(); merkleRoot = calculateMerkleRoot();
return merkleRoot; return merkleRoot;
} }
/** Exists only for unit testing. */ /** Exists only for unit testing. */
void setMerkleRoot(byte[] value) { void setMerkleRoot(Sha256Hash value) {
merkleRoot = value; merkleRoot = value;
hash = null; hash = null;
} }
@@ -415,11 +412,11 @@ public class Block extends Message {
} }
/** Returns the hash of the previous block in the chain, as defined by the block header. */ /** Returns the hash of the previous block in the chain, as defined by the block header. */
public byte[] getPrevBlockHash() { public Sha256Hash getPrevBlockHash() {
return prevBlockHash; return prevBlockHash;
} }
void setPrevBlockHash(byte[] prevBlockHash) { void setPrevBlockHash(Sha256Hash prevBlockHash) {
this.prevBlockHash = prevBlockHash; this.prevBlockHash = prevBlockHash;
this.hash = null; this.hash = null;
} }

View File

@@ -22,10 +22,10 @@ import java.util.List;
public class GetBlocksMessage extends Message { public class GetBlocksMessage extends Message {
private static final long serialVersionUID = 3479412877853645644L; private static final long serialVersionUID = 3479412877853645644L;
private final List<byte[]> locator; private final List<Sha256Hash> locator;
private final byte[] stopHash; private final Sha256Hash stopHash;
public GetBlocksMessage(NetworkParameters params, List<byte[]> locator, byte[] stopHash) { public GetBlocksMessage(NetworkParameters params, List<Sha256Hash> locator, Sha256Hash stopHash) {
super(params); super(params);
this.locator = locator; this.locator = locator;
this.stopHash = stopHash; this.stopHash = stopHash;
@@ -37,8 +37,8 @@ public class GetBlocksMessage extends Message {
public String toString() { public String toString() {
StringBuffer b = new StringBuffer(); StringBuffer b = new StringBuffer();
b.append("getblocks: "); b.append("getblocks: ");
for (byte[] hash : locator) { for (Sha256Hash hash : locator) {
b.append(Utils.bytesToHexString(hash)); b.append(hash.toString());
b.append(" "); b.append(" ");
} }
return b.toString(); return b.toString();
@@ -53,12 +53,12 @@ public class GetBlocksMessage extends Message {
// identifiers that spans the entire chain with exponentially increasing gaps between // identifiers that spans the entire chain with exponentially increasing gaps between
// them, until we end up at the genesis block. See CBlockLocator::Set() // them, until we end up at the genesis block. See CBlockLocator::Set()
buf.write(new VarInt(locator.size()).encode()); buf.write(new VarInt(locator.size()).encode());
for (byte[] hash : locator) { for (Sha256Hash hash : locator) {
// Have to reverse as wire format is little endian. // Have to reverse as wire format is little endian.
buf.write(Utils.reverseBytes(hash)); buf.write(Utils.reverseBytes(hash.getBytes()));
} }
// Next, a block ID to stop at. // Next, a block ID to stop at.
buf.write(stopHash); buf.write(stopHash.getBytes());
return buf.toByteArray(); return buf.toByteArray();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); // Cannot happen. throw new RuntimeException(e); // Cannot happen.

View File

@@ -24,15 +24,15 @@ public class InventoryItem {
} }
public final Type type; public final Type type;
public final byte[] hash; public final Sha256Hash hash;
public InventoryItem(Type type, byte[] hash) { public InventoryItem(Type type, Sha256Hash hash) {
this.type = type; this.type = type;
this.hash = hash; this.hash = hash;
} }
public String toString() { public String toString() {
return type.toString() + ": " + Utils.bytesToHexString(hash); return type.toString() + ": " + hash;
} }
} }

View File

@@ -81,14 +81,14 @@ public abstract class ListMessage extends Message
@Override @Override
public void bitcoinSerializeToStream( OutputStream stream) throws IOException public void bitcoinSerializeToStream(OutputStream stream) throws IOException
{ {
stream.write(new VarInt(items.size()).encode()); stream.write(new VarInt(items.size()).encode());
for (InventoryItem i : items) { for (InventoryItem i : items) {
// Write out the type code. // Write out the type code.
Utils.uint32ToByteStreamLE(i.type.ordinal(), stream); Utils.uint32ToByteStreamLE(i.type.ordinal(), stream);
// And now the hash. // And now the hash.
stream.write(Utils.reverseBytes(i.hash)); stream.write(Utils.reverseBytes(i.hash.getBytes()));
} }
} }
} }

View File

@@ -113,14 +113,14 @@ public abstract class Message implements Serializable {
return u; return u;
} }
byte[] readHash() { Sha256Hash readHash() {
byte[] hash = new byte[32]; byte[] hash = new byte[32];
System.arraycopy(bytes, cursor, hash, 0, 32); System.arraycopy(bytes, cursor, hash, 0, 32);
// We have to flip it around, as it's been read off the wire in little endian. // We have to flip it around, as it's been read off the wire in little endian.
// Not the most efficient way to do this but the clearest. // Not the most efficient way to do this but the clearest.
hash = Utils.reverseBytes(hash); hash = Utils.reverseBytes(hash);
cursor += 32; cursor += 32;
return hash; return new Sha256Hash(hash);
} }

View File

@@ -115,7 +115,7 @@ public class Peer {
synchronized (pendingGetBlockFutures) { synchronized (pendingGetBlockFutures) {
for (int i = 0; i < pendingGetBlockFutures.size(); i++) { for (int i = 0; i < pendingGetBlockFutures.size(); i++) {
GetDataFuture<Block> f = pendingGetBlockFutures.get(i); GetDataFuture<Block> f = pendingGetBlockFutures.get(i);
if (Arrays.equals(f.getItem().hash, m.getHash())) { if (f.getItem().hash.equals(m.getHash())) {
// Yes, it was. So pass it through the future. // Yes, it was. So pass it through the future.
f.setResult(m); f.setResult(m);
// Blocks explicitly requested don't get sent to the block chain. // Blocks explicitly requested don't get sent to the block chain.
@@ -161,10 +161,10 @@ public class Peer {
// chain, we may end up requesting blocks we already requested before. This shouldn't (in theory) happen // chain, we may end up requesting blocks we already requested before. This shouldn't (in theory) happen
// 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); Sha256Hash topHash = (topBlock != null ? topBlock.getHash() : null);
List<InventoryItem> items = inv.getItems(); List<InventoryItem> items = inv.getItems();
if (items.size() == 1 && items.get(0).type == InventoryItem.Type.Block && topHash != null && if (items.size() == 1 && items.get(0).type == InventoryItem.Type.Block && topHash != null &&
Arrays.equals(items.get(0).hash, topHash)) { items.get(0).hash.equals(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
@@ -196,7 +196,7 @@ public class Peer {
* @param blockHash Hash of the block you wareare requesting. * @param blockHash Hash of the block you wareare requesting.
* @throws IOException * @throws IOException
*/ */
public Future<Block> getBlock(byte[] blockHash) throws IOException { public Future<Block> getBlock(Sha256Hash 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.addItem(inventoryItem); getdata.addItem(inventoryItem);
@@ -273,7 +273,7 @@ public class Peer {
conn.writeMessage(tx); conn.writeMessage(tx);
} }
private void blockChainDownload(byte[] toHash) throws IOException { private void blockChainDownload(Sha256Hash toHash) throws IOException {
// This may run in ANY thread. // This may run in ANY thread.
// The block chain download process is a bit complicated. Basically, we start with zero or more blocks in a // The block chain download process is a bit complicated. Basically, we start with zero or more blocks in a
@@ -301,10 +301,10 @@ public class Peer {
// //
// So this is a complicated process but it has the advantage that we can download a chain of enormous length // So this is a complicated process but it has the advantage that we can download a chain of enormous length
// in a relatively stateless manner and with constant/bounded memory usage. // in a relatively stateless manner and with constant/bounded memory usage.
log.info("blockChainDownload({})", Utils.bytesToHexString(toHash)); log.info("blockChainDownload({})", toHash.toString());
// TODO: Block locators should be abstracted out rather than special cased here. // TODO: Block locators should be abstracted out rather than special cased here.
List<byte[]> blockLocator = new LinkedList<byte[]>(); List<Sha256Hash> blockLocator = new LinkedList<Sha256Hash>();
// We don't do the exponential thinning here, so if we get onto a fork of the chain we will end up // We don't do the exponential thinning here, so if we get onto a fork of the chain we will end up
// redownloading the whole thing again. // redownloading the whole thing again.
blockLocator.add(params.genesisBlock.getHash()); blockLocator.add(params.genesisBlock.getHash());
@@ -333,7 +333,7 @@ public class Peer {
chainCompletionLatch = new CountDownLatch(blocksToGet); chainCompletionLatch = new CountDownLatch(blocksToGet);
if (blocksToGet > 0) { if (blocksToGet > 0) {
// When we just want as many blocks as possible, we can set the target hash to zero. // When we just want as many blocks as possible, we can set the target hash to zero.
blockChainDownload(new byte[32]); blockChainDownload(Sha256Hash.ZERO_HASH);
} }
return chainCompletionLatch; return chainCompletionLatch;
} }

View File

@@ -16,42 +16,61 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
import java.io.Serializable; import com.google.bitcoin.bouncycastle.util.encoders.Hex;
import java.util.Arrays;
// TODO: Switch all code/interfaces to using this class. import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;
/** /**
* A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be used as keys in a * A Sha256Hash just wraps a byte[] so that equals and hashcode work correctly, allowing it to be used as keys in a
* map. It also checks that the length is correct and provides a bit more type safety. * map. It also checks that the length is correct and provides a bit more type safety.
*/ */
public class Sha256Hash implements Serializable { public class Sha256Hash implements Serializable {
public byte[] hash; private byte[] bytes;
public Sha256Hash(byte[] hash) { public static Sha256Hash ZERO_HASH = new Sha256Hash(new byte[32]);
assert hash.length == 32;
this.hash = hash; /** Creates a Sha256Hash by wrapping the given byte array. It must be 32 bytes long. */
public Sha256Hash(byte[] bytes) {
assert bytes.length == 32;
this.bytes = bytes;
}
/** Creates a Sha256Hash by decoding the given hex string. It must be 64 characters long. */
public Sha256Hash(String string) {
assert string.length() == 64;
this.bytes = Hex.decode(string);
} }
/** Returns true if the hashes are equal. */ /** Returns true if the hashes are equal. */
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (!(other instanceof Sha256Hash)) return false; if (!(other instanceof Sha256Hash)) return false;
return Arrays.equals(hash, ((Sha256Hash)other).hash); return Arrays.equals(bytes, ((Sha256Hash)other).bytes);
} }
/** /**
* Hash code of the byte array as calculated by {@link Arrays#hashCode()}. Note the difference between a SHA256 * Hash code of the byte array as calculated by {@link Arrays#hashCode()}. Note the difference between a SHA256
* secure hash and the type of quick/dirty hash used by the Java hashCode method which is designed for use in * secure bytes and the type of quick/dirty bytes used by the Java hashCode method which is designed for use in
* hash tables. * bytes tables.
*/ */
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(hash); return Arrays.hashCode(bytes);
} }
@Override @Override
public String toString() { public String toString() {
return Utils.bytesToHexString(hash); return Utils.bytesToHexString(bytes);
}
/** Returns the bytes interpreted as a positive integer. */
public BigInteger toBigInteger() {
return new BigInteger(1, bytes);
}
public byte[] getBytes() {
return bytes;
} }
} }

View File

@@ -448,6 +448,6 @@ public class Transaction extends Message implements Serializable {
@Override @Override
public int hashCode() { public int hashCode() {
return Arrays.hashCode(getHash().hash); return getHash().hashCode();
} }
} }

View File

@@ -16,6 +16,8 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
import com.sun.tools.internal.ws.wsdl.document.Output;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
@@ -93,9 +95,7 @@ public class TransactionInput extends Message implements Serializable {
* Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true. * Coinbase transactions have special inputs with hashes of zero. If this is such an input, returns true.
*/ */
public boolean isCoinBase() { public boolean isCoinBase() {
for (int i = 0; i < outpoint.hash.length; i++) return outpoint.hash.equals(Sha256Hash.ZERO_HASH);
if (outpoint.hash[i] != 0) return false;
return true;
} }
/** /**
@@ -146,8 +146,7 @@ public class TransactionInput extends Message implements Serializable {
* @return The TransactionOutput or null if the transactions map doesn't contain the referenced tx. * @return The TransactionOutput or null if the transactions map doesn't contain the referenced tx.
*/ */
TransactionOutput getConnectedOutput(Map<Sha256Hash, Transaction> transactions) { TransactionOutput getConnectedOutput(Map<Sha256Hash, Transaction> transactions) {
Sha256Hash h = new Sha256Hash(outpoint.hash); Transaction tx = transactions.get(outpoint.hash);
Transaction tx = transactions.get(h);
if (tx == null) if (tx == null)
return null; return null;
TransactionOutput out = tx.outputs.get((int)outpoint.index); TransactionOutput out = tx.outputs.get((int)outpoint.index);
@@ -163,8 +162,7 @@ public class TransactionInput extends Message implements Serializable {
* @return true if connection took place, false if the referenced transaction was not in the list. * @return true if connection took place, false if the referenced transaction was not in the list.
*/ */
ConnectionResult connect(Map<Sha256Hash, Transaction> transactions, boolean disconnect) { ConnectionResult connect(Map<Sha256Hash, Transaction> transactions, boolean disconnect) {
Sha256Hash h = new Sha256Hash(outpoint.hash); Transaction tx = transactions.get(outpoint.hash);
Transaction tx = transactions.get(h);
if (tx == null) if (tx == null)
return TransactionInput.ConnectionResult.NO_SUCH_TX; return TransactionInput.ConnectionResult.NO_SUCH_TX;
TransactionOutput out = tx.outputs.get((int)outpoint.index); TransactionOutput out = tx.outputs.get((int)outpoint.index);

View File

@@ -29,7 +29,7 @@ public class TransactionOutPoint extends Message implements Serializable {
private static final long serialVersionUID = -6320880638344662579L; private static final long serialVersionUID = -6320880638344662579L;
/** Hash of the transaction to which we refer. */ /** Hash of the transaction to which we refer. */
byte[] hash; Sha256Hash hash;
/** Which output of that transaction we are talking about. */ /** Which output of that transaction we are talking about. */
long index; long index;
@@ -41,11 +41,11 @@ public class TransactionOutPoint extends Message implements Serializable {
super(params); super(params);
this.index = index; this.index = index;
if (fromTx != null) { if (fromTx != null) {
this.hash = fromTx.getHash().hash; this.hash = fromTx.getHash();
this.fromTx = fromTx; this.fromTx = fromTx;
} else { } else {
// This happens when constructing the genesis block. // This happens when constructing the genesis block.
hash = new byte[32]; // All zeros. hash = Sha256Hash.ZERO_HASH;
} }
} }
@@ -62,8 +62,7 @@ public class TransactionOutPoint extends Message implements Serializable {
@Override @Override
public void bitcoinSerializeToStream(OutputStream stream) throws IOException { public void bitcoinSerializeToStream(OutputStream stream) throws IOException {
assert hash.length == 32; stream.write(Utils.reverseBytes(hash.getBytes()));
stream.write(Utils.reverseBytes(hash));
Utils.uint32ToByteStreamLE(index, stream); Utils.uint32ToByteStreamLE(index, stream);
} }

View File

@@ -16,6 +16,7 @@
package com.google.bitcoin.store; package com.google.bitcoin.store;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.StoredBlock; import com.google.bitcoin.core.StoredBlock;
/** /**
@@ -40,7 +41,7 @@ public interface BlockStore {
* Returns the StoredBlock given a hash. The returned values block.getHash() method will be equal to the * Returns the StoredBlock given a hash. The returned values block.getHash() method will be equal to the
* parameter. If no such block is found, returns null. * parameter. If no such block is found, returns null.
*/ */
StoredBlock get(byte[] hash) throws BlockStoreException; StoredBlock get(Sha256Hash hash) throws BlockStoreException;
/** /**
* Returns the {@link StoredBlock} that represents the top of the chain of greatest total work. * Returns the {@link StoredBlock} that represents the top of the chain of greatest total work.

View File

@@ -166,8 +166,8 @@ public class BoundedOverheadBlockStore implements BlockStore {
// Set up the genesis block. When we start out fresh, it is by definition the top of the chain. // Set up the genesis block. When we start out fresh, it is by definition the top of the chain.
Block genesis = params.genesisBlock.cloneAsHeader(); Block genesis = params.genesisBlock.cloneAsHeader();
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
this.chainHead = new Sha256Hash(storedGenesis.getHeader().getHash()); this.chainHead = storedGenesis.getHeader().getHash();
this.file.write(this.chainHead.hash); this.file.write(this.chainHead.getBytes());
put(storedGenesis); put(storedGenesis);
} catch (VerificationException e1) { } catch (VerificationException e1) {
throw new RuntimeException(e1); // Cannot happen. throw new RuntimeException(e1); // Cannot happen.
@@ -202,7 +202,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
public synchronized void put(StoredBlock block) throws BlockStoreException { public synchronized void put(StoredBlock block) throws BlockStoreException {
try { try {
Sha256Hash hash = new Sha256Hash(block.getHeader().getHash()); Sha256Hash hash = block.getHeader().getHash();
// Append to the end of the file. // Append to the end of the file.
dummyRecord.write(channel, block); dummyRecord.write(channel, block);
blockCache.put(hash, block); blockCache.put(hash, block);
@@ -211,9 +211,8 @@ public class BoundedOverheadBlockStore implements BlockStore {
} }
} }
public synchronized StoredBlock get(byte[] hashBytes) throws BlockStoreException { public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
// Check the memory cache first. // Check the memory cache first.
Sha256Hash hash = new Sha256Hash(hashBytes);
StoredBlock fromMem = blockCache.get(hash); StoredBlock fromMem = blockCache.get(hash);
if (fromMem != null) { if (fromMem != null) {
return fromMem; return fromMem;
@@ -248,7 +247,7 @@ public class BoundedOverheadBlockStore implements BlockStore {
do { do {
if (!record.read(channel, pos, buf)) if (!record.read(channel, pos, buf))
throw new IOException("Failed to read buffer"); throw new IOException("Failed to read buffer");
if (Arrays.equals(record.getHeader(params).getHash(), hash.hash)) { if (record.getHeader(params).getHash().equals(hash)) {
// Found it. Update file position for next time. // Found it. Update file position for next time.
channel.position(pos); channel.position(pos);
return record; return record;
@@ -269,15 +268,14 @@ public class BoundedOverheadBlockStore implements BlockStore {
} }
public synchronized StoredBlock getChainHead() throws BlockStoreException { public synchronized StoredBlock getChainHead() throws BlockStoreException {
return get(chainHead.hash); return get(chainHead);
} }
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException { public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
try { try {
byte[] hash = chainHead.getHeader().getHash(); this.chainHead = chainHead.getHeader().getHash();
this.chainHead = new Sha256Hash(hash);
// Write out new hash to the first 32 bytes of the file past one (first byte is version number). // Write out new hash to the first 32 bytes of the file past one (first byte is version number).
channel.write(ByteBuffer.wrap(hash), 1); channel.write(ByteBuffer.wrap(this.chainHead.getBytes()), 1);
} catch (IOException e) { } catch (IOException e) {
throw new BlockStoreException(e); throw new BlockStoreException(e);
} }

View File

@@ -63,8 +63,8 @@ public class DiskBlockStore implements BlockStore {
// Set up the genesis block. When we start out fresh, it is by definition the top of the chain. // Set up the genesis block. When we start out fresh, it is by definition the top of the chain.
Block genesis = params.genesisBlock.cloneAsHeader(); Block genesis = params.genesisBlock.cloneAsHeader();
StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0);
this.chainHead = new Sha256Hash(storedGenesis.getHeader().getHash()); this.chainHead = storedGenesis.getHeader().getHash();
stream.write(this.chainHead.hash); stream.write(this.chainHead.getBytes());
put(storedGenesis); put(storedGenesis);
} catch (VerificationException e1) { } catch (VerificationException e1) {
throw new RuntimeException(e1); // Cannot happen. throw new RuntimeException(e1); // Cannot happen.
@@ -110,8 +110,8 @@ public class DiskBlockStore implements BlockStore {
if (b.equals(params.genesisBlock)) { if (b.equals(params.genesisBlock)) {
s = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0); s = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0);
} else { } else {
throw new BlockStoreException("Could not connect " + Utils.bytesToHexString(b.getHash()) + " to " throw new BlockStoreException("Could not connect " + b.getHash().toString() + " to "
+ Utils.bytesToHexString(b.getPrevBlockHash())); + b.getPrevBlockHash().toString());
} }
} else { } else {
// Don't try to verify the genesis block to avoid upsetting the unit tests. // Don't try to verify the genesis block to avoid upsetting the unit tests.
@@ -120,7 +120,7 @@ public class DiskBlockStore implements BlockStore {
s = prev.build(b); s = prev.build(b);
} }
// Save in memory. // Save in memory.
blockMap.put(new Sha256Hash(b.getHash()), s); blockMap.put(b.getHash(), s);
} }
} catch (ProtocolException e) { } catch (ProtocolException e) {
// Corrupted file. // Corrupted file.
@@ -135,7 +135,7 @@ public class DiskBlockStore implements BlockStore {
public synchronized void put(StoredBlock block) throws BlockStoreException { public synchronized void put(StoredBlock block) throws BlockStoreException {
try { try {
Sha256Hash hash = new Sha256Hash(block.getHeader().getHash()); Sha256Hash hash = block.getHeader().getHash();
assert blockMap.get(hash) == null : "Attempt to insert duplicate"; assert blockMap.get(hash) == null : "Attempt to insert duplicate";
// Append to the end of the file. The other fields in StoredBlock will be recalculated when it's reloaded. // Append to the end of the file. The other fields in StoredBlock will be recalculated when it's reloaded.
byte[] bytes = block.getHeader().bitcoinSerialize(); byte[] bytes = block.getHeader().bitcoinSerialize();
@@ -147,8 +147,8 @@ public class DiskBlockStore implements BlockStore {
} }
} }
public synchronized StoredBlock get(byte[] hash) throws BlockStoreException { public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
return blockMap.get(new Sha256Hash(hash)); return blockMap.get(hash);
} }
public synchronized StoredBlock getChainHead() throws BlockStoreException { public synchronized StoredBlock getChainHead() throws BlockStoreException {
@@ -157,10 +157,9 @@ public class DiskBlockStore implements BlockStore {
public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException { public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException {
try { try {
byte[] hash = chainHead.getHeader().getHash(); this.chainHead = chainHead.getHeader().getHash();
this.chainHead = new Sha256Hash(hash);
// Write out new hash to the first 32 bytes of the file past one (first byte is version number). // Write out new hash to the first 32 bytes of the file past one (first byte is version number).
stream.getChannel().write(ByteBuffer.wrap(hash), 1); stream.getChannel().write(ByteBuffer.wrap(this.chainHead.getBytes()), 1);
} catch (IOException e) { } catch (IOException e) {
throw new BlockStoreException(e); throw new BlockStoreException(e);
} }

View File

@@ -27,17 +27,11 @@ import java.util.Map;
* Keeps {@link com.google.bitcoin.core.StoredBlock}s in memory. Used primarily for unit testing. * Keeps {@link com.google.bitcoin.core.StoredBlock}s in memory. Used primarily for unit testing.
*/ */
public class MemoryBlockStore implements BlockStore { public class MemoryBlockStore implements BlockStore {
// We use a ByteBuffer to hold hashes here because the Java array equals()/hashcode() methods do not operate on private Map<Sha256Hash, StoredBlock> blockMap;
// the contents of the array but just inherit the default Object behavior. ByteBuffer provides the functionality
// needed to act as a key in a map.
//
// The StoredBlocks are also stored as serialized objects to ensure we don't have assumptions that would make
// things harder for disk based implementations.
private Map<ByteBuffer, byte[]> blockMap;
private StoredBlock chainHead; private StoredBlock chainHead;
public MemoryBlockStore(NetworkParameters params) { public MemoryBlockStore(NetworkParameters params) {
blockMap = new HashMap<ByteBuffer, byte[]>(); blockMap = new HashMap<Sha256Hash, StoredBlock>();
// Insert the genesis block. // Insert the genesis block.
try { try {
Block genesisHeader = params.genesisBlock.cloneAsHeader(); Block genesisHeader = params.genesisBlock.cloneAsHeader();
@@ -52,31 +46,12 @@ public class MemoryBlockStore implements BlockStore {
} }
public synchronized void put(StoredBlock block) throws BlockStoreException { public synchronized void put(StoredBlock block) throws BlockStoreException {
byte[] hash = block.getHeader().getHash(); Sha256Hash hash = block.getHeader().getHash();
ByteArrayOutputStream bos = new ByteArrayOutputStream(); blockMap.put(hash, block);
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(block);
oos.close();
blockMap.put(ByteBuffer.wrap(hash), bos.toByteArray());
} catch (IOException e) {
throw new BlockStoreException(e);
}
} }
public synchronized StoredBlock get(byte[] hash) throws BlockStoreException { public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException {
try { return blockMap.get(hash);
byte[] serializedBlock = blockMap.get(ByteBuffer.wrap(hash));
if (serializedBlock == null)
return null;
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serializedBlock));
StoredBlock storedBlock = (StoredBlock) ois.readObject();
return storedBlock;
} catch (IOException e) {
throw new BlockStoreException(e);
} catch (ClassNotFoundException e) {
throw new BlockStoreException(e);
}
} }
public StoredBlock getChainHead() { public StoredBlock getChainHead() {

View File

@@ -123,7 +123,7 @@ public class BlockChainTest {
NetworkParameters params2 = NetworkParameters.testNet(); NetworkParameters params2 = NetworkParameters.testNet();
Block bad = new Block(params2); Block bad = new Block(params2);
// Merkle root can be anything here, doesn't matter. // Merkle root can be anything here, doesn't matter.
bad.setMerkleRoot(Hex.decode("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); bad.setMerkleRoot(new Sha256Hash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
// Nonce was just some number that made the hash < difficulty limit set below, it can be anything. // Nonce was just some number that made the hash < difficulty limit set below, it can be anything.
bad.setNonce(140548933); bad.setNonce(140548933);
bad.setTime(1279242649); bad.setTime(1279242649);
@@ -158,10 +158,10 @@ public class BlockChainTest {
// Some blocks from the test net. // Some blocks from the test net.
private Block getBlock2() throws Exception { private Block getBlock2() throws Exception {
Block b2 = new Block(testNet); Block b2 = new Block(testNet);
b2.setMerkleRoot(Hex.decode("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b")); b2.setMerkleRoot(new Sha256Hash("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b"));
b2.setNonce(2642058077L); b2.setNonce(2642058077L);
b2.setTime(1296734343L); b2.setTime(1296734343L);
b2.setPrevBlockHash(Hex.decode("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604")); b2.setPrevBlockHash(new Sha256Hash("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604"));
assertEquals("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f", b2.getHashAsString()); assertEquals("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f", b2.getHashAsString());
b2.verify(); b2.verify();
return b2; return b2;
@@ -169,10 +169,10 @@ public class BlockChainTest {
private Block getBlock1() throws Exception { private Block getBlock1() throws Exception {
Block b1 = new Block(testNet); Block b1 = new Block(testNet);
b1.setMerkleRoot(Hex.decode("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10")); b1.setMerkleRoot(new Sha256Hash("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10"));
b1.setNonce(236038445); b1.setNonce(236038445);
b1.setTime(1296734340); b1.setTime(1296734340);
b1.setPrevBlockHash(Hex.decode("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")); b1.setPrevBlockHash(new Sha256Hash("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"));
assertEquals("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604", b1.getHashAsString()); assertEquals("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604", b1.getHashAsString());
b1.verify(); b1.verify();
return b1; return b1;