/** * 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.common.primitives.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; /** * <p>A block is a group of transactions, and is one of the fundamental data structures of the Bitcoin system. * It records a set of {@link com.google.devcoin.core.Transaction}s together with some data that links it into a place in the global block * chain, and proves that a difficult calculation was done over its contents. See * <a href="http://www.bitcoin.org/bitcoin.pdf">the Bitcoin technical paper</a> for * more detail on blocks. <p/> * * To get a block, you can either build one from the raw bytes you can get from another implementation, or request one * specifically using {@link com.google.devcoin.core.Peer#getBlock(com.google.devcoin.core.Sha256Hash)}, or grab one from a downloaded {@link com.google.devcoin.core.BlockChain}. */ public class BlockMergeMined { private static final Logger log = LoggerFactory.getLogger(BlockMergeMined.class); public static final long DEVCOIN_MERGED_MINE_START_TIME = 1325974424L; public static final long BLOCK_VERSION_AUXPOW = (1 << 8); public static final long BLOCK_VERSION_CHAIN_START = (1 << 16); public static final long DEVCOIN_MERGED_MINE_CHAIN_ID = 0x0004; public static final byte pchMergedMiningHeader[] = { (byte)0xfa, (byte)0xbe, 'm', 'm' } ; // Fields defined as part of the protocol format. // modifiers private transient NetworkParameters params; public transient Block block; public transient BlockMergeMinedPayload payload; /** Constructs a block object from the Bitcoin wire format. */ public BlockMergeMined(NetworkParameters netparams, byte[] payloadBytes, int cursor, Block block) throws ProtocolException { params = netparams; payload = new BlockMergeMinedPayload(this.params, payloadBytes, cursor, block); setBlock(block); } private void setBlock(Block block) { this.block = block; if(this.payload != null && this.payload.block == null) this.payload.block = block; } public long GetChainID(long ver) { return ver / BLOCK_VERSION_CHAIN_START; } public long GetChainID() { return block.getVersion() / BLOCK_VERSION_CHAIN_START; } public boolean IsValid() { return payload != null && payload.IsValid(); } public Sha256Hash getParentBlockHash() { return payload.hashOfParentBlockHeader; } /** Returns the version of the block data structure as defined by the Bitcoin protocol. */ public long getVersion() { return block.getVersion(); } /** * Returns the nonce, an arbitrary value that exists only to make the hash of the block header fall below the * difficulty target. */ public long getNonce() { return block.getNonce(); } /** * Returns a multi-line string containing a description of the contents of * the block. Use for debugging purposes only. */ public String toString() { StringBuilder s = new StringBuilder(""); s.append(" version: v"); s.append(block.getVersion()); s.append("\n"); s.append(" time: ["); s.append(block.getTime()); s.append("] "); s.append(new Date(block.getTimeSeconds() * 1000)); s.append("\n"); s.append(" difficulty target (nBits): "); s.append(block.getDifficultyTarget()); s.append("\n"); s.append(" nonce: "); s.append(block.getNonce()); s.append("\n"); if(payload != null) { s.append("\n"); s.append(payload.toString()); } return s.toString(); } public int getCursor() { if(payload != null) return payload.cursor; else return 0; } public int getMessageSize() { if(payload != null) return payload.length; else return 0; } /** Returns true if the hash of the block is OK (lower than difficulty target). */ protected boolean checkProofOfWork(boolean throwException) throws VerificationException { if(GetChainID() != DEVCOIN_MERGED_MINE_CHAIN_ID) { throw new VerificationException("Merged-mine block does not have the correct chain ID required for Devcoin blocks, Current ID: " + GetChainID() + " Expected: " + DEVCOIN_MERGED_MINE_CHAIN_ID); } if(GetChainID(payload.parentBlockHeader.getVersion()) == DEVCOIN_MERGED_MINE_CHAIN_ID) { throw new VerificationException("Merged-mine block Aux POW parent has our chain ID: " + DEVCOIN_MERGED_MINE_CHAIN_ID); } TransactionInput coinbaseInput = payload.parentBlockCoinBaseTx.getInput(0); byte[] scriptBytes = coinbaseInput.getScriptBytes(); int headerIndex = Bytes.indexOf(scriptBytes, this.pchMergedMiningHeader); if(headerIndex > 0) { byte[] remainingBytes = java.util.Arrays.copyOfRange(scriptBytes, headerIndex, scriptBytes.length - headerIndex); headerIndex = Bytes.indexOf(remainingBytes, this.pchMergedMiningHeader); if(headerIndex > 0) { throw new VerificationException("Multiple merged mining headers in coinbase"); } if(!coinbaseInput.isCoinBase()) { throw new VerificationException("Parent coinbase transaction not an actual coinbase transaction!"); } } return true; } public boolean equals(Object o) { if (!(o instanceof BlockMergeMined)) return false; BlockMergeMined other = (BlockMergeMined) o; return block.getTimeSeconds() == other.block.getTimeSeconds(); } /** * Returns the time at which the block was solved and broadcast, according to the clock of the solving node. This * is measured in seconds since the UNIX epoch (midnight Jan 1st 1970). */ public long getTimeSeconds() { return block.getTimeSeconds(); } /** * Returns the time at which the block was solved and broadcast, according to the clock of the solving node. */ public Date getTime() { return new Date(getTimeSeconds()*1000); } }