From 3b8b0833c57628369d66b9d17e1d946374767eac Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 30 Jun 2011 20:33:41 +0000 Subject: [PATCH] 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. --- src/com/google/bitcoin/core/Block.java | 61 +++++++++---------- .../google/bitcoin/core/GetBlocksMessage.java | 16 ++--- .../google/bitcoin/core/InventoryItem.java | 6 +- src/com/google/bitcoin/core/ListMessage.java | 4 +- src/com/google/bitcoin/core/Message.java | 4 +- src/com/google/bitcoin/core/Peer.java | 16 ++--- src/com/google/bitcoin/core/Sha256Hash.java | 43 +++++++++---- src/com/google/bitcoin/core/Transaction.java | 2 +- .../google/bitcoin/core/TransactionInput.java | 12 ++-- .../bitcoin/core/TransactionOutPoint.java | 9 ++- src/com/google/bitcoin/store/BlockStore.java | 3 +- .../store/BoundedOverheadBlockStore.java | 18 +++--- .../google/bitcoin/store/DiskBlockStore.java | 21 +++---- .../bitcoin/store/MemoryBlockStore.java | 37 ++--------- .../google/bitcoin/core/BlockChainTest.java | 10 +-- 15 files changed, 124 insertions(+), 138 deletions(-) diff --git a/src/com/google/bitcoin/core/Block.java b/src/com/google/bitcoin/core/Block.java index 0ce293c2..d5fae2f8 100644 --- a/src/com/google/bitcoin/core/Block.java +++ b/src/com/google/bitcoin/core/Block.java @@ -50,8 +50,8 @@ public class Block extends Message { // For unit testing. If not zero, use this instead of the current time. static long fakeClock = 0; private long version; - private byte[] prevBlockHash; - private byte[] merkleRoot; + private Sha256Hash prevBlockHash; + private Sha256Hash merkleRoot; private long time; private long difficultyTarget; // "nBits" @@ -60,7 +60,7 @@ public class Block extends Message { /** If null, it means this object holds only the headers. */ List transactions; /** 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. */ Block(NetworkParameters params) { @@ -69,7 +69,7 @@ public class Block extends Message { version = 1; difficultyTarget = 0x1d07fff8L; time = System.currentTimeMillis() / 1000; - prevBlockHash = new byte[32]; // All zeros. + prevBlockHash = Sha256Hash.ZERO_HASH; } /** Constructs a block object from the BitCoin wire format. */ @@ -85,7 +85,7 @@ public class Block extends Message { difficultyTarget = 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) { // 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 { Utils.uint32ToByteStreamLE(version, stream); - stream.write(Utils.reverseBytes(prevBlockHash)); - stream.write(Utils.reverseBytes(getMerkleRoot())); + stream.write(Utils.reverseBytes(prevBlockHash.getBytes())); + stream.write(Utils.reverseBytes(getMerkleRoot().getBytes())); Utils.uint32ToByteStreamLE(time, stream); Utils.uint32ToByteStreamLE(difficultyTarget, 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. */ - private byte[] calculateHash() { + private Sha256Hash calculateHash() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); writeHeader(bos); - return Utils.reverseBytes(doubleDigest(bos.toByteArray())); + return new Sha256Hash(Utils.reverseBytes(doubleDigest(bos.toByteArray()))); } catch (IOException e) { throw new RuntimeException(e); // Cannot happen. } @@ -140,13 +140,13 @@ public class Block extends Message { * "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048". */ 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. */ - public byte[] getHash() { + public Sha256Hash getHash() { if (hash == null) hash = calculateHash(); return hash; @@ -186,8 +186,8 @@ public class Block extends Message { @Override public String toString() { StringBuffer s = new StringBuffer("v" + version + " block: \n" + - " previous block: " + bytesToHexString(prevBlockHash) + "\n" + - " merkle root: " + bytesToHexString(getMerkleRoot()) + "\n" + + " previous block: " + prevBlockHash.toString() + "\n" + + " merkle root: " + getMerkleRoot().toString() + "\n" + " time: [" + time + "] " + new Date(time * 1000).toString() + "\n" + " difficulty target (nBits): " + difficultyTarget + "\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. BigInteger target = getDifficultyTargetAsInteger(); - BigInteger h = new BigInteger(1, getHash()); + BigInteger h = getHash().toBigInteger(); if (h.compareTo(target) > 0) { // Proof of work check failed! if (throwException) @@ -263,21 +263,18 @@ public class Block extends Message { throw new VerificationException("Block too far in future"); } - private void checkMerkleHash() throws VerificationException { - List tree = buildMerkleTree(); - byte[] calculatedRoot = tree.get(tree.size() - 1); - if (!Arrays.equals(calculatedRoot, merkleRoot)) { - log.error("Merkle tree did not verify: "); - for (byte[] b : tree) log.error(Utils.bytesToHexString(b)); - + private void checkMerkleRoot() throws VerificationException { + Sha256Hash calculatedRoot = calculateMerkleRoot(); + if (!calculatedRoot.equals(merkleRoot)) { + log.error("Merkle tree did not verify"); throw new VerificationException("Merkle hashes do not match: " + - bytesToHexString(calculatedRoot) + " vs " + bytesToHexString(merkleRoot)); + calculatedRoot + " vs " + merkleRoot); } } - private byte[] calculateMerkleRoot() { + private Sha256Hash calculateMerkleRoot() { List tree = buildMerkleTree(); - return tree.get(tree.size() - 1); + return new Sha256Hash(tree.get(tree.size() - 1)); } private List buildMerkleTree() { @@ -315,7 +312,7 @@ public class Block extends Message { ArrayList tree = new ArrayList(); // Start by adding all the hashes of the transactions as leaves of the tree. 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. // Step through each level, stopping when we reach the root (levelSize == 1). @@ -369,7 +366,7 @@ public class Block extends Message { if (transactions != null) { assert transactions.size() > 0; checkTransactions(); - checkMerkleHash(); + checkMerkleRoot(); } } @@ -377,23 +374,23 @@ public class Block extends Message { public boolean equals(Object o) { if (!(o instanceof Block)) return false; Block other = (Block) o; - return Arrays.equals(getHash(), other.getHash()); + return getHash().equals(other.getHash()); } @Override public int hashCode() { - return Arrays.hashCode(getHash()); + return getHash().hashCode(); } /** Returns the merkle root in big endian form, calculating it from transactions if necessary. */ - public byte[] getMerkleRoot() { + public Sha256Hash getMerkleRoot() { if (merkleRoot == null) merkleRoot = calculateMerkleRoot(); return merkleRoot; } /** Exists only for unit testing. */ - void setMerkleRoot(byte[] value) { + void setMerkleRoot(Sha256Hash value) { merkleRoot = value; 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. */ - public byte[] getPrevBlockHash() { + public Sha256Hash getPrevBlockHash() { return prevBlockHash; } - void setPrevBlockHash(byte[] prevBlockHash) { + void setPrevBlockHash(Sha256Hash prevBlockHash) { this.prevBlockHash = prevBlockHash; this.hash = null; } diff --git a/src/com/google/bitcoin/core/GetBlocksMessage.java b/src/com/google/bitcoin/core/GetBlocksMessage.java index 0866cc7c..654b4fa4 100644 --- a/src/com/google/bitcoin/core/GetBlocksMessage.java +++ b/src/com/google/bitcoin/core/GetBlocksMessage.java @@ -22,10 +22,10 @@ import java.util.List; public class GetBlocksMessage extends Message { private static final long serialVersionUID = 3479412877853645644L; - private final List locator; - private final byte[] stopHash; + private final List locator; + private final Sha256Hash stopHash; - public GetBlocksMessage(NetworkParameters params, List locator, byte[] stopHash) { + public GetBlocksMessage(NetworkParameters params, List locator, Sha256Hash stopHash) { super(params); this.locator = locator; this.stopHash = stopHash; @@ -37,8 +37,8 @@ public class GetBlocksMessage extends Message { public String toString() { StringBuffer b = new StringBuffer(); b.append("getblocks: "); - for (byte[] hash : locator) { - b.append(Utils.bytesToHexString(hash)); + for (Sha256Hash hash : locator) { + b.append(hash.toString()); b.append(" "); } return b.toString(); @@ -53,12 +53,12 @@ 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() buf.write(new VarInt(locator.size()).encode()); - for (byte[] hash : locator) { + for (Sha256Hash hash : locator) { // 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. - buf.write(stopHash); + buf.write(stopHash.getBytes()); return buf.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); // Cannot happen. diff --git a/src/com/google/bitcoin/core/InventoryItem.java b/src/com/google/bitcoin/core/InventoryItem.java index 532fdee7..1b78f66a 100644 --- a/src/com/google/bitcoin/core/InventoryItem.java +++ b/src/com/google/bitcoin/core/InventoryItem.java @@ -24,15 +24,15 @@ public class InventoryItem { } 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.hash = hash; } public String toString() { - return type.toString() + ": " + Utils.bytesToHexString(hash); + return type.toString() + ": " + hash; } } diff --git a/src/com/google/bitcoin/core/ListMessage.java b/src/com/google/bitcoin/core/ListMessage.java index 1e0295af..ac02ef58 100644 --- a/src/com/google/bitcoin/core/ListMessage.java +++ b/src/com/google/bitcoin/core/ListMessage.java @@ -81,14 +81,14 @@ public abstract class ListMessage extends Message @Override - public void bitcoinSerializeToStream( OutputStream stream) throws IOException + 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)); + stream.write(Utils.reverseBytes(i.hash.getBytes())); } } } diff --git a/src/com/google/bitcoin/core/Message.java b/src/com/google/bitcoin/core/Message.java index 41401f99..c2cb49d0 100644 --- a/src/com/google/bitcoin/core/Message.java +++ b/src/com/google/bitcoin/core/Message.java @@ -113,14 +113,14 @@ public abstract class Message implements Serializable { return u; } - byte[] readHash() { + Sha256Hash readHash() { byte[] hash = new byte[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. // Not the most efficient way to do this but the clearest. hash = Utils.reverseBytes(hash); cursor += 32; - return hash; + return new Sha256Hash(hash); } diff --git a/src/com/google/bitcoin/core/Peer.java b/src/com/google/bitcoin/core/Peer.java index faa57c03..a8a44417 100644 --- a/src/com/google/bitcoin/core/Peer.java +++ b/src/com/google/bitcoin/core/Peer.java @@ -115,7 +115,7 @@ public class Peer { synchronized (pendingGetBlockFutures) { for (int i = 0; i < pendingGetBlockFutures.size(); i++) { GetDataFuture 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. f.setResult(m); // 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 // enough to be a problem. Block topBlock = blockChain.getUnconnectedBlock(); - byte[] topHash = (topBlock != null ? topBlock.getHash() : null); + Sha256Hash topHash = (topBlock != null ? topBlock.getHash() : null); List items = inv.getItems(); 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, // 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 @@ -196,7 +196,7 @@ public class Peer { * @param blockHash Hash of the block you wareare requesting. * @throws IOException */ - public Future getBlock(byte[] blockHash) throws IOException { + public Future getBlock(Sha256Hash blockHash) throws IOException { InventoryMessage getdata = new InventoryMessage(params); InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Block, blockHash); getdata.addItem(inventoryItem); @@ -273,7 +273,7 @@ public class Peer { conn.writeMessage(tx); } - private void blockChainDownload(byte[] toHash) throws IOException { + private void blockChainDownload(Sha256Hash toHash) throws IOException { // 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 @@ -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 // 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. - List blockLocator = new LinkedList(); + List blockLocator = new LinkedList(); // 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. blockLocator.add(params.genesisBlock.getHash()); @@ -333,7 +333,7 @@ public class Peer { chainCompletionLatch = new CountDownLatch(blocksToGet); if (blocksToGet > 0) { // 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; } diff --git a/src/com/google/bitcoin/core/Sha256Hash.java b/src/com/google/bitcoin/core/Sha256Hash.java index 243d529d..41d2b97b 100644 --- a/src/com/google/bitcoin/core/Sha256Hash.java +++ b/src/com/google/bitcoin/core/Sha256Hash.java @@ -16,42 +16,61 @@ package com.google.bitcoin.core; -import java.io.Serializable; -import java.util.Arrays; +import com.google.bitcoin.bouncycastle.util.encoders.Hex; -// 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 * map. It also checks that the length is correct and provides a bit more type safety. */ public class Sha256Hash implements Serializable { - public byte[] hash; + private byte[] bytes; - public Sha256Hash(byte[] hash) { - assert hash.length == 32; - this.hash = hash; + public static Sha256Hash ZERO_HASH = new Sha256Hash(new byte[32]); + + /** 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. */ @Override public boolean equals(Object other) { 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 - * secure hash and the type of quick/dirty hash used by the Java hashCode method which is designed for use in - * hash tables. + * secure bytes and the type of quick/dirty bytes used by the Java hashCode method which is designed for use in + * bytes tables. */ @Override public int hashCode() { - return Arrays.hashCode(hash); + return Arrays.hashCode(bytes); } @Override 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; } } diff --git a/src/com/google/bitcoin/core/Transaction.java b/src/com/google/bitcoin/core/Transaction.java index a5093157..38e33ef5 100644 --- a/src/com/google/bitcoin/core/Transaction.java +++ b/src/com/google/bitcoin/core/Transaction.java @@ -448,6 +448,6 @@ public class Transaction extends Message implements Serializable { @Override public int hashCode() { - return Arrays.hashCode(getHash().hash); + return getHash().hashCode(); } } diff --git a/src/com/google/bitcoin/core/TransactionInput.java b/src/com/google/bitcoin/core/TransactionInput.java index 046a3a09..b07d2bf5 100644 --- a/src/com/google/bitcoin/core/TransactionInput.java +++ b/src/com/google/bitcoin/core/TransactionInput.java @@ -16,6 +16,8 @@ package com.google.bitcoin.core; +import com.sun.tools.internal.ws.wsdl.document.Output; + import java.io.IOException; import java.io.OutputStream; 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. */ public boolean isCoinBase() { - for (int i = 0; i < outpoint.hash.length; i++) - if (outpoint.hash[i] != 0) return false; - return true; + return outpoint.hash.equals(Sha256Hash.ZERO_HASH); } /** @@ -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. */ TransactionOutput getConnectedOutput(Map transactions) { - Sha256Hash h = new Sha256Hash(outpoint.hash); - Transaction tx = transactions.get(h); + Transaction tx = transactions.get(outpoint.hash); if (tx == null) return null; 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. */ ConnectionResult connect(Map transactions, boolean disconnect) { - Sha256Hash h = new Sha256Hash(outpoint.hash); - Transaction tx = transactions.get(h); + Transaction tx = transactions.get(outpoint.hash); if (tx == null) return TransactionInput.ConnectionResult.NO_SUCH_TX; TransactionOutput out = tx.outputs.get((int)outpoint.index); diff --git a/src/com/google/bitcoin/core/TransactionOutPoint.java b/src/com/google/bitcoin/core/TransactionOutPoint.java index ac745179..a34313c3 100644 --- a/src/com/google/bitcoin/core/TransactionOutPoint.java +++ b/src/com/google/bitcoin/core/TransactionOutPoint.java @@ -29,7 +29,7 @@ public class TransactionOutPoint extends Message implements Serializable { private static final long serialVersionUID = -6320880638344662579L; /** Hash of the transaction to which we refer. */ - byte[] hash; + Sha256Hash hash; /** Which output of that transaction we are talking about. */ long index; @@ -41,11 +41,11 @@ public class TransactionOutPoint extends Message implements Serializable { super(params); this.index = index; if (fromTx != null) { - this.hash = fromTx.getHash().hash; + this.hash = fromTx.getHash(); this.fromTx = fromTx; } else { // 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 public void bitcoinSerializeToStream(OutputStream stream) throws IOException { - assert hash.length == 32; - stream.write(Utils.reverseBytes(hash)); + stream.write(Utils.reverseBytes(hash.getBytes())); Utils.uint32ToByteStreamLE(index, stream); } diff --git a/src/com/google/bitcoin/store/BlockStore.java b/src/com/google/bitcoin/store/BlockStore.java index 67829335..5bc2c166 100644 --- a/src/com/google/bitcoin/store/BlockStore.java +++ b/src/com/google/bitcoin/store/BlockStore.java @@ -16,6 +16,7 @@ package com.google.bitcoin.store; +import com.google.bitcoin.core.Sha256Hash; 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 * 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. diff --git a/src/com/google/bitcoin/store/BoundedOverheadBlockStore.java b/src/com/google/bitcoin/store/BoundedOverheadBlockStore.java index bc9f8a33..4650ec6b 100644 --- a/src/com/google/bitcoin/store/BoundedOverheadBlockStore.java +++ b/src/com/google/bitcoin/store/BoundedOverheadBlockStore.java @@ -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. Block genesis = params.genesisBlock.cloneAsHeader(); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); - this.chainHead = new Sha256Hash(storedGenesis.getHeader().getHash()); - this.file.write(this.chainHead.hash); + this.chainHead = storedGenesis.getHeader().getHash(); + this.file.write(this.chainHead.getBytes()); put(storedGenesis); } catch (VerificationException e1) { throw new RuntimeException(e1); // Cannot happen. @@ -202,7 +202,7 @@ public class BoundedOverheadBlockStore implements BlockStore { public synchronized void put(StoredBlock block) throws BlockStoreException { try { - Sha256Hash hash = new Sha256Hash(block.getHeader().getHash()); + Sha256Hash hash = block.getHeader().getHash(); // Append to the end of the file. dummyRecord.write(channel, 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. - Sha256Hash hash = new Sha256Hash(hashBytes); StoredBlock fromMem = blockCache.get(hash); if (fromMem != null) { return fromMem; @@ -248,7 +247,7 @@ public class BoundedOverheadBlockStore implements BlockStore { do { if (!record.read(channel, pos, buf)) 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. channel.position(pos); return record; @@ -269,15 +268,14 @@ public class BoundedOverheadBlockStore implements BlockStore { } public synchronized StoredBlock getChainHead() throws BlockStoreException { - return get(chainHead.hash); + return get(chainHead); } public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException { try { - byte[] hash = chainHead.getHeader().getHash(); - this.chainHead = new Sha256Hash(hash); + this.chainHead = chainHead.getHeader().getHash(); // 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) { throw new BlockStoreException(e); } diff --git a/src/com/google/bitcoin/store/DiskBlockStore.java b/src/com/google/bitcoin/store/DiskBlockStore.java index c6bf63d5..c3fd9db1 100644 --- a/src/com/google/bitcoin/store/DiskBlockStore.java +++ b/src/com/google/bitcoin/store/DiskBlockStore.java @@ -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. Block genesis = params.genesisBlock.cloneAsHeader(); StoredBlock storedGenesis = new StoredBlock(genesis, genesis.getWork(), 0); - this.chainHead = new Sha256Hash(storedGenesis.getHeader().getHash()); - stream.write(this.chainHead.hash); + this.chainHead = storedGenesis.getHeader().getHash(); + stream.write(this.chainHead.getBytes()); put(storedGenesis); } catch (VerificationException e1) { throw new RuntimeException(e1); // Cannot happen. @@ -110,8 +110,8 @@ public class DiskBlockStore implements BlockStore { if (b.equals(params.genesisBlock)) { s = new StoredBlock(params.genesisBlock.cloneAsHeader(), params.genesisBlock.getWork(), 0); } else { - throw new BlockStoreException("Could not connect " + Utils.bytesToHexString(b.getHash()) + " to " - + Utils.bytesToHexString(b.getPrevBlockHash())); + throw new BlockStoreException("Could not connect " + b.getHash().toString() + " to " + + b.getPrevBlockHash().toString()); } } else { // 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); } // Save in memory. - blockMap.put(new Sha256Hash(b.getHash()), s); + blockMap.put(b.getHash(), s); } } catch (ProtocolException e) { // Corrupted file. @@ -135,7 +135,7 @@ public class DiskBlockStore implements BlockStore { public synchronized void put(StoredBlock block) throws BlockStoreException { try { - Sha256Hash hash = new Sha256Hash(block.getHeader().getHash()); + Sha256Hash hash = block.getHeader().getHash(); 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. byte[] bytes = block.getHeader().bitcoinSerialize(); @@ -147,8 +147,8 @@ public class DiskBlockStore implements BlockStore { } } - public synchronized StoredBlock get(byte[] hash) throws BlockStoreException { - return blockMap.get(new Sha256Hash(hash)); + public synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException { + return blockMap.get(hash); } public synchronized StoredBlock getChainHead() throws BlockStoreException { @@ -157,10 +157,9 @@ public class DiskBlockStore implements BlockStore { public synchronized void setChainHead(StoredBlock chainHead) throws BlockStoreException { try { - byte[] hash = chainHead.getHeader().getHash(); - this.chainHead = new Sha256Hash(hash); + this.chainHead = chainHead.getHeader().getHash(); // 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) { throw new BlockStoreException(e); } diff --git a/src/com/google/bitcoin/store/MemoryBlockStore.java b/src/com/google/bitcoin/store/MemoryBlockStore.java index 4e034d47..daf63608 100644 --- a/src/com/google/bitcoin/store/MemoryBlockStore.java +++ b/src/com/google/bitcoin/store/MemoryBlockStore.java @@ -27,17 +27,11 @@ import java.util.Map; * Keeps {@link com.google.bitcoin.core.StoredBlock}s in memory. Used primarily for unit testing. */ public class MemoryBlockStore implements BlockStore { - // We use a ByteBuffer to hold hashes here because the Java array equals()/hashcode() methods do not operate on - // 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 blockMap; + private Map blockMap; private StoredBlock chainHead; public MemoryBlockStore(NetworkParameters params) { - blockMap = new HashMap(); + blockMap = new HashMap(); // Insert the genesis block. try { Block genesisHeader = params.genesisBlock.cloneAsHeader(); @@ -52,31 +46,12 @@ public class MemoryBlockStore implements BlockStore { } public synchronized void put(StoredBlock block) throws BlockStoreException { - byte[] hash = block.getHeader().getHash(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - 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); - } + Sha256Hash hash = block.getHeader().getHash(); + blockMap.put(hash, block); } - public synchronized StoredBlock get(byte[] hash) throws BlockStoreException { - try { - 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 synchronized StoredBlock get(Sha256Hash hash) throws BlockStoreException { + return blockMap.get(hash); } public StoredBlock getChainHead() { diff --git a/tests/com/google/bitcoin/core/BlockChainTest.java b/tests/com/google/bitcoin/core/BlockChainTest.java index a98cc566..1f0bc5c8 100644 --- a/tests/com/google/bitcoin/core/BlockChainTest.java +++ b/tests/com/google/bitcoin/core/BlockChainTest.java @@ -123,7 +123,7 @@ public class BlockChainTest { NetworkParameters params2 = NetworkParameters.testNet(); Block bad = new Block(params2); // 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. bad.setNonce(140548933); bad.setTime(1279242649); @@ -158,10 +158,10 @@ public class BlockChainTest { // Some blocks from the test net. private Block getBlock2() throws Exception { Block b2 = new Block(testNet); - b2.setMerkleRoot(Hex.decode("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b")); + b2.setMerkleRoot(new Sha256Hash("addc858a17e21e68350f968ccd384d6439b64aafa6c193c8b9dd66320470838b")); b2.setNonce(2642058077L); b2.setTime(1296734343L); - b2.setPrevBlockHash(Hex.decode("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604")); + b2.setPrevBlockHash(new Sha256Hash("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604")); assertEquals("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f", b2.getHashAsString()); b2.verify(); return b2; @@ -169,10 +169,10 @@ public class BlockChainTest { private Block getBlock1() throws Exception { Block b1 = new Block(testNet); - b1.setMerkleRoot(Hex.decode("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10")); + b1.setMerkleRoot(new Sha256Hash("0e8e58ecdacaa7b3c6304a35ae4ffff964816d2b80b62b58558866ce4e648c10")); b1.setNonce(236038445); b1.setTime(1296734340); - b1.setPrevBlockHash(Hex.decode("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")); + b1.setPrevBlockHash(new Sha256Hash("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")); assertEquals("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604", b1.getHashAsString()); b1.verify(); return b1;