/** * Copyright 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devcoin.core; import com.google.devcoin.store.BlockStore; import com.google.devcoin.store.BlockStoreException; 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 * calculate. By storing it alongside the block header we reduce the amount of work required significantly. * Recalculation is slow because the fields are cumulative - to find the chainWork you have to iterate over every * block in the chain back to the genesis block, which involves lots of seeking/loading etc. So we just keep a * running total: it's a disk space vs cpu/io tradeoff.<p> * * StoredBlocks are put inside a {@link BlockStore} which saves them to memory or disk. */ 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; public StoredBlock(Block header, BigInteger chainWork, int height) { this.header = header; this.chainWork = chainWork; this.height = height; } /** * The block header this object wraps. The referenced block object must not have any transactions in it. */ public Block getHeader() { return header; } /** * The total sum of work done in this block, and all the blocks below it in the chain. Work is a measure of how * many tries are needed to solve a block. If the target is set to cover 10% of the total hash value space, * then the work represented by a block is 10. */ public BigInteger getChainWork() { return chainWork; } /** * Position in the chain for this block. The genesis block has a height of zero. */ public int getHeight() { return height; } /** Returns true if this objects chainWork is higher than the others. */ public boolean moreWorkThan(StoredBlock other) { return chainWork.compareTo(other.chainWork) > 0; } @Override public boolean equals(Object other) { if (!(other instanceof StoredBlock)) return false; StoredBlock o = (StoredBlock) other; return o.header.equals(header) && o.chainWork.equals(chainWork) && o.height == height; } @Override public int hashCode() { // A better hashCode is possible, but this works for now. return header.hashCode() ^ chainWork.hashCode() ^ height; } /** * Creates a new StoredBlock, calculating the additional fields by adding to the values in this block. */ public StoredBlock build(Block block) throws VerificationException { // Stored blocks track total work done in this chain, because the canonical chain is the one that represents // the largest amount of work done not the tallest. BigInteger chainWork = this.chainWork.add(block.getWork()); int height = this.height + 1; return new StoredBlock(block, chainWork, height); } /** * Given a block store, looks up the previous block in this chain. Convenience method for doing * <tt>store.get(this.getHeader().getPrevBlockHash())</tt>. * * @return the previous block in the chain or null if it was not found in the store. */ public StoredBlock getPrev(BlockStore store) throws BlockStoreException { 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", getHeader().getHashAsString(), getHeight(), getHeader().toString()); } }