From 68907880cbddb4f997696591298c1d20e27c43f1 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 4 Mar 2013 17:18:39 +0100 Subject: [PATCH] Refactor compact serialization out of SPVBlockStore and into StoredBlock. --- .../com/google/bitcoin/core/StoredBlock.java | 39 ++++++++++++++++++- .../google/bitcoin/store/SPVBlockStore.java | 31 ++------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/StoredBlock.java b/core/src/main/java/com/google/bitcoin/core/StoredBlock.java index 0a46d1cc..67f0b8e6 100644 --- a/core/src/main/java/com/google/bitcoin/core/StoredBlock.java +++ b/core/src/main/java/com/google/bitcoin/core/StoredBlock.java @@ -19,8 +19,11 @@ package com.google.bitcoin.core; import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.BlockStoreException; -import java.io.Serializable; +import java.io.*; import java.math.BigInteger; +import java.nio.ByteBuffer; + +import static com.google.common.base.Preconditions.checkState; /** * Wraps a {@link Block} object with extra data that can be derived from the block chain but is slow or inconvenient to @@ -34,6 +37,12 @@ import java.math.BigInteger; public class StoredBlock implements Serializable { private static final long serialVersionUID = -6097565241243701771L; + // A BigInteger representing the total amount of work done so far on this chain. As of May 2011 it takes 8 + // bytes to represent this field, so 12 bytes should be plenty for now. + public static final int CHAIN_WORK_BYTES = 12; + public static final byte[] EMPTY_BYTES = new byte[CHAIN_WORK_BYTES]; + public static final int COMPACT_SERIALIZED_SIZE = Block.HEADER_SIZE + CHAIN_WORK_BYTES + 4; // for height + private Block header; private BigInteger chainWork; private int height; @@ -44,7 +53,6 @@ public class StoredBlock implements Serializable { this.height = height; } - /** * The block header this object wraps. The referenced block object must not have any transactions in it. */ @@ -108,6 +116,33 @@ public class StoredBlock implements Serializable { return store.get(getHeader().getPrevBlockHash()); } + /** Serializes the stored block to a custom packed format. Used by {@link CheckpointManager}. */ + public void serializeCompact(ByteBuffer buffer) { + byte[] chainWorkBytes = getChainWork().toByteArray(); + checkState(chainWorkBytes.length <= CHAIN_WORK_BYTES, "Ran out of space to store chain work!"); + if (chainWorkBytes.length < CHAIN_WORK_BYTES) { + // Pad to the right size. + buffer.put(EMPTY_BYTES, 0, CHAIN_WORK_BYTES - chainWorkBytes.length); + } + buffer.put(chainWorkBytes); + buffer.putInt(getHeight()); + // Using unsafeBitcoinSerialize here can give us direct access to the same bytes we read off the wire, + // avoiding serialization round-trips. + byte[] bytes = getHeader().unsafeBitcoinSerialize(); + buffer.put(bytes, 0, Block.HEADER_SIZE); // Trim the trailing 00 byte (zero transactions). + } + + /** De-serializes the stored block from a custom packed format. Used by {@link CheckpointManager}. */ + public static StoredBlock deserializeCompact(NetworkParameters params, ByteBuffer buffer) throws ProtocolException { + byte[] chainWorkBytes = new byte[StoredBlock.CHAIN_WORK_BYTES]; + buffer.get(chainWorkBytes); + BigInteger chainWork = new BigInteger(1, chainWorkBytes); + int height = buffer.getInt(); // +4 bytes + byte[] header = new byte[Block.HEADER_SIZE + 1]; // Extra byte for the 00 transactions length. + buffer.get(header, 0, Block.HEADER_SIZE); + return new StoredBlock(new Block(params, header), chainWork, height); + } + @Override public String toString() { return String.format("Block %s at height %d: %s", diff --git a/core/src/main/java/com/google/bitcoin/store/SPVBlockStore.java b/core/src/main/java/com/google/bitcoin/store/SPVBlockStore.java index 09762371..e314d7ae 100644 --- a/core/src/main/java/com/google/bitcoin/store/SPVBlockStore.java +++ b/core/src/main/java/com/google/bitcoin/store/SPVBlockStore.java @@ -173,18 +173,7 @@ public class SPVBlockStore implements BlockStore { Sha256Hash hash = block.getHeader().getHash(); notFoundCache.remove(hash); buffer.put(hash.getBytes()); - byte[] chainWorkBytes = block.getChainWork().toByteArray(); - checkState(chainWorkBytes.length <= CHAIN_WORK_BYTES, "Ran out of space to store chain work!"); - if (chainWorkBytes.length < CHAIN_WORK_BYTES) { - // Pad to the right size. - buffer.put(EMPTY_BYTES, 0, CHAIN_WORK_BYTES - chainWorkBytes.length); - } - buffer.put(chainWorkBytes); - buffer.putInt(block.getHeight()); - // Using unsafeBitcoinSerialize here can give us direct access to the same bytes we read off the wire, - // avoiding serialization round-trips. - byte[] bytes = block.getHeader().unsafeBitcoinSerialize(); - buffer.put(bytes, 0, Block.HEADER_SIZE); // Trim the trailing 00 byte (zero transactions). + block.serializeCompact(buffer); setRingCursor(buffer, buffer.position()); blockCache.put(hash, block); } finally { lock.unlock(); } @@ -220,13 +209,7 @@ public class SPVBlockStore implements BlockStore { buffer.get(scratch); if (Arrays.equals(scratch, targetHashBytes)) { // Found the target. - byte[] chainWorkBytes = new byte[CHAIN_WORK_BYTES]; - buffer.get(chainWorkBytes); - BigInteger chainWork = new BigInteger(1, chainWorkBytes); - int height = buffer.getInt(); // +4 bytes - byte[] header = new byte[Block.HEADER_SIZE + 1]; // Extra byte for the 00 transactions length. - buffer.get(header, 0, Block.HEADER_SIZE); - StoredBlock storedBlock = new StoredBlock(new Block(params, header), chainWork, height); + StoredBlock storedBlock = StoredBlock.deserializeCompact(params, buffer); blockCache.put(hash, storedBlock); return storedBlock; } @@ -281,15 +264,7 @@ public class SPVBlockStore implements BlockStore { } } - // A BigInteger representing the total amount of work done so far on this chain. As of May 2011 it takes 8 - // bytes to represent this field, so 12 bytes should be plenty for now. - protected static final int CHAIN_WORK_BYTES = 12; - protected static final byte[] EMPTY_BYTES = new byte[CHAIN_WORK_BYTES]; - - protected static final int RECORD_SIZE = Block.HEADER_SIZE + - 32 + // for a SHA256 hash - 4 + // for a height - CHAIN_WORK_BYTES; // == 128 all together. + protected static final int RECORD_SIZE = 32 /* hash */ + StoredBlock.COMPACT_SERIALIZED_SIZE; // File format: // 4 header bytes = "SPVB"