package org.ripple.power.txns.btc; import java.io.EOFException; import java.math.BigInteger; import java.util.List; import org.ripple.power.Helper; /** * BlockHeader contains the block header. It is used to validate transactions sent to us * and to determine if a transaction has become unconfirmed because the block containing it * is no longer on the block chain. * * <p>The block header has the following format:</p> * <pre> * Size Field Description * ==== ===== =========== * 4 bytes Version The block version number * 32 bytes PrevBlockHash The hash of the preceding block in the chain * 32 byte MerkleRoot The Merkle root for the transactions in the block * 4 bytes Time The time the block was mined * 4 bytes Difficulty The target difficulty * 4 bytes Nonce The nonce used to generate the required hash *</pre> */ public class BlockHeader implements ByteSerializable { /** Block header length */ public static final int HEADER_SIZE = 80; /** The number that is one greater than the largest representable SHA-256 hash */ public static final BigInteger LARGEST_HASH = BigInteger.ONE.shiftLeft(256); /** Block version */ private final int version; /** Block hash */ private final Sha256Hash blockHash; /** Previous block hash */ private final Sha256Hash prevHash; /** Time block was mined */ private final long blockTime; /** Merkle root */ private final Sha256Hash merkleRoot; /** Target difficulty */ private final long targetDifficulty; /** Nonce */ private final int nonce; /** Matched transactions */ private List<Sha256Hash> matches; /** * Create a new BlockHeader * * @param version Block version * @param blockHash Block hash * @param prevHash Previous block hash * @param blockTime Time block was mined (seconds since Unix epoch) * @param targetDifficulty Target difficulty * @param merkleRoot Merkle root * @param nonce Block nonce */ public BlockHeader(int version, Sha256Hash blockHash, Sha256Hash prevHash, long blockTime, long targetDifficulty, Sha256Hash merkleRoot, int nonce) { this.version = version; this.blockHash = blockHash; this.prevHash = prevHash; this.blockTime = blockTime; this.targetDifficulty = targetDifficulty; this.merkleRoot = merkleRoot; this.nonce = nonce; } /** * Create a new BlockHeader * * @param version Block version * @param blockHash Block hash * @param prevHash Previous block hash * @param blockTime Time block was mined (seconds since Unix epoch) * @param targetDifficulty Target difficulty * @param merkleRoot Merkle root * @param nonce Block nonce * @param matches Matching transactions */ public BlockHeader(int version, Sha256Hash blockHash, Sha256Hash prevHash, long blockTime, long targetDifficulty, Sha256Hash merkleRoot, int nonce, List<Sha256Hash> matches) { this.version = version; this.blockHash = blockHash; this.prevHash = prevHash; this.blockTime = blockTime; this.targetDifficulty = targetDifficulty; this.merkleRoot = merkleRoot; this.nonce = nonce; this.matches = matches; } /** * Create a BlockHeader from the serialized block header * * @param bytes Serialized data * @param doVerify TRUE to verify the block header structure * @throws EOFException Serialized data is too short * @throws VerificationException Block verification failed */ public BlockHeader(byte[] bytes, boolean doVerify) throws EOFException, VerificationException { this(new SerializedBuffer(bytes), doVerify); } /** * Create a BlockHeader from the serialized block header * * @param inBuffer Input buffer * @param doVerify TRUE to verify the block header structure * @throws EOFException Serialized data is too short * @throws VerificationException Block verification failed */ public BlockHeader(SerializedBuffer inBuffer, boolean doVerify) throws EOFException, VerificationException { if (inBuffer.available() < HEADER_SIZE) throw new EOFException("Header is too short"); // // Compute the block hash from the serialized block header // int startPosition = inBuffer.getPosition(); blockHash = new Sha256Hash(Helper.reverseBytes(Helper.doubleDigest(inBuffer.getBytes(HEADER_SIZE)))); inBuffer.setPosition(startPosition); // // Parse the block header // version = inBuffer.getInt(); prevHash = new Sha256Hash(Helper.reverseBytes(inBuffer.getBytes(32))); merkleRoot = new Sha256Hash(Helper.reverseBytes(inBuffer.getBytes(32))); blockTime = inBuffer.getUnsignedInt(); targetDifficulty = inBuffer.getUnsignedInt(); nonce = inBuffer.getInt(); // // Ensure this block does in fact represent real work done. If the difficulty is high enough, // we can be fairly certain the work was done by the network. // // The block hash must be less than or equal to the target difficulty (the difficulty increases // by requiring an increasing number of leading zeroes in the block hash) // if (doVerify) { BigInteger target = Helper.decodeCompactBits(targetDifficulty); if (target.signum() <= 0 || target.compareTo(NetParams.PROOF_OF_WORK_LIMIT) > 0) throw new VerificationException("Target difficulty is not valid", RejectMessage.REJECT_INVALID, blockHash); BigInteger hash = blockHash.toBigInteger(); if (hash.compareTo(target) > 0) throw new VerificationException("Block hash is higher than target difficulty", RejectMessage.REJECT_INVALID, blockHash); // // Verify the block timestamp // long currentTime = System.currentTimeMillis()/1000; if (blockTime > currentTime+NetParams.ALLOWED_TIME_DRIFT) throw new VerificationException("Block timestamp is too far in the future", RejectMessage.REJECT_INVALID, blockHash); } } /** * Write the serialized block header to the output buffer * * @param outBuffer Output buffer * @return Output buffer */ @Override public SerializedBuffer getBytes(SerializedBuffer outBuffer) { outBuffer.putInt(version) .putBytes(Helper.reverseBytes(prevHash.getBytes())) .putBytes(Helper.reverseBytes(merkleRoot.getBytes())) .putUnsignedInt(blockTime) .putUnsignedInt(targetDifficulty) .putInt(nonce); return outBuffer; } /** * Return the serialized bytes * * @return Byte array */ @Override public byte[] getBytes() { return getBytes(new SerializedBuffer(HEADER_SIZE)).toByteArray(); } /** * Return the block version * * @return Block version */ public int getVersion() { return version; } /** * Returns the block hash * * @return Block hash */ public Sha256Hash getHash() { return blockHash; } /** * Returns the previous block hash * * @return Previous block hash */ public Sha256Hash getPrevHash() { return prevHash; } /** * Returns the block time * * @return Block time */ public long getBlockTime() { return blockTime; } /** * Returns the Merkle root * * @return Merkle root */ public Sha256Hash getMerkleRoot() { return merkleRoot; } /** * Returns the target difficulty * * @return Target difficulty */ public long getTargetDifficulty() { return targetDifficulty; } /** * Returns the block work * * @return Block work */ public BigInteger getBlockWork() { BigInteger target = Helper.decodeCompactBits(targetDifficulty); return LARGEST_HASH.divide(target.add(BigInteger.ONE)); } /** * Returns the block nonce * * @return Block nonce */ public int getNonce() { return nonce; } /** * Returns the list of matched transactions or null if there are no matched transactions * * @return List of matched transactions */ public List<Sha256Hash> getMatches() { return matches; } /** * Sets the list of matched transactions * * @param matches List of matched transactions or null if there are no matched transactions */ public void setMatches(List<Sha256Hash> matches) { this.matches = matches; } }