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.
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<Transaction> 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<byte[]> 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<byte[]> tree = buildMerkleTree();
return tree.get(tree.size() - 1);
return new Sha256Hash(tree.get(tree.size() - 1));
}
private List<byte[]> buildMerkleTree() {
@@ -315,7 +312,7 @@ public class Block extends Message {
ArrayList<byte[]> tree = new ArrayList<byte[]>();
// 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;
}

View File

@@ -22,10 +22,10 @@ import java.util.List;
public class GetBlocksMessage extends Message {
private static final long serialVersionUID = 3479412877853645644L;
private final List<byte[]> locator;
private final byte[] stopHash;
private final List<Sha256Hash> locator;
private final Sha256Hash stopHash;
public GetBlocksMessage(NetworkParameters params, List<byte[]> locator, byte[] stopHash) {
public GetBlocksMessage(NetworkParameters params, List<Sha256Hash> 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.

View File

@@ -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;
}
}

View File

@@ -88,7 +88,7 @@ public abstract class ListMessage extends Message
// 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()));
}
}
}

View File

@@ -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);
}

View File

@@ -115,7 +115,7 @@ public class Peer {
synchronized (pendingGetBlockFutures) {
for (int i = 0; i < pendingGetBlockFutures.size(); 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.
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<InventoryItem> 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<Block> getBlock(byte[] blockHash) throws IOException {
public Future<Block> 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<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
// 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;
}

View File

@@ -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;
}
}

View File

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

View File

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

View File

@@ -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);
}

View File

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

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.
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);
}

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.
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);
}

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.
*/
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<ByteBuffer, byte[]> blockMap;
private Map<Sha256Hash, StoredBlock> blockMap;
private StoredBlock chainHead;
public MemoryBlockStore(NetworkParameters params) {
blockMap = new HashMap<ByteBuffer, byte[]>();
blockMap = new HashMap<Sha256Hash, StoredBlock>();
// 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() {

View File

@@ -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;