diff --git a/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java b/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java index 21a33aa2..aefad16e 100644 --- a/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java +++ b/src/main/java/org/altcoinj/params/AbstractDogecoinParams.java @@ -20,12 +20,11 @@ import java.io.ByteArrayOutputStream; import java.math.BigInteger; import org.bitcoinj.core.AltcoinBlock; -import org.bitcoinj.core.AuxPoWNetworkParameters; +import org.bitcoinj.core.AltcoinNetworkParameters; import org.bitcoinj.core.Block; import org.bitcoinj.core.Coin; import static org.bitcoinj.core.Coin.COIN; import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.VerificationException; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptOpCodes; @@ -36,7 +35,8 @@ import org.bitcoinj.utils.MonetaryFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static com.google.common.base.Preconditions.checkState; +import org.bitcoinj.core.AltcoinSerializer; +import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.StoredBlock; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionInput; @@ -46,7 +46,7 @@ import org.bitcoinj.core.Utils; /** * Parameters for the main Dogecoin production network on which people trade goods and services. */ -public abstract class AbstractDogecoinParams extends NetworkParameters implements AuxPoWNetworkParameters { +public abstract class AbstractDogecoinParams extends NetworkParameters implements AltcoinNetworkParameters { /** Standard format for the DOGE denomination. */ public static final MonetaryFormat DOGE; /** Standard format for the mDOGE denomination. */ @@ -55,6 +55,7 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement public static final MonetaryFormat KOINU; public static final int DIGISHIELD_BLOCK_HEIGHT = 145000; // Block height to use Digishield from + public static final int AUXPOW_CHAIN_ID = 0x0062; // 98 public static final int DOGE_TARGET_TIMESPAN = 4 * 60 * 60; // 4 hours per difficulty cycle, on average. public static final int DOGE_TARGET_TIMESPAN_NEW = 60; // 60s per difficulty cycle, on average. Kicks in after block 145k. public static final int DOGE_TARGET_SPACING = 1 * 60; // 1 minute per block. @@ -290,6 +291,24 @@ public abstract class AbstractDogecoinParams extends NetworkParameters implement return DIGISHIELD_BLOCK_HEIGHT; } + @Override + public int getChainID() { + return AUXPOW_CHAIN_ID; + } + + /** + * Get the hash to use for a block. + */ + @Override + public Sha256Hash getBlockDifficultyHash(Block block) { + return ((AltcoinBlock) block).getScryptHash(); + } + + @Override + public AltcoinSerializer getSerializer(boolean parseLazy, boolean parseRetain) { + return new AltcoinSerializer(this, parseLazy, parseRetain); + } + @Override public boolean isAuxPoWBlockVersion(long version) { return version >= BLOCK_VERSION_AUXPOW diff --git a/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java b/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java index e7dc446e..28e99fa6 100644 --- a/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java +++ b/src/main/java/org/altcoinj/params/DogecoinMainNetParams.java @@ -17,7 +17,6 @@ package org.altcoinj.params; import org.altcoinj.core.ScryptHash; -import org.bitcoinj.core.AltcoinBlock; import static com.google.common.base.Preconditions.checkState; diff --git a/src/main/java/org/bitcoinj/core/AltcoinBlock.java b/src/main/java/org/bitcoinj/core/AltcoinBlock.java index 8d2ffcf6..262c304a 100644 --- a/src/main/java/org/bitcoinj/core/AltcoinBlock.java +++ b/src/main/java/org/bitcoinj/core/AltcoinBlock.java @@ -59,50 +59,30 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { super(params); } - /** Constructs a block object from the Bitcoin wire format. */ - public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { - super(params, payloadBytes); + /** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */ + public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) { + this(params, payloadBytes, params.getDefaultSerializer(), payloadBytes.length); } /** - * Contruct a block object from the Bitcoin wire format. + * Construct a block object from the Bitcoin wire format. * @param params NetworkParameters object. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. + * @param serializer the serializer to use for this message. * @param length The length of message if known. Usually this is provided when deserializing of the wire * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH * @throws ProtocolException */ - public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, boolean parseLazy, boolean parseRetain, int length) + public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, MessageSerializer serializer, int length) throws ProtocolException { - super(params, payloadBytes, parseLazy, parseRetain, length); + super(params, payloadBytes, serializer, length); } - /** - * Contruct a block object from the Bitcoin wire format. Used in the case of a block - * contained within another message (i.e. for AuxPoW header). - * - * @param params NetworkParameters object. - * @param payloadBytes Bitcoin protocol formatted byte array containing message content. - * @param offset The location of the first payload byte within the array. - * @param parent The message element which contains this block, maybe null for no parent. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. - * @param length The length of message if known. Usually this is provided when deserializing of the wire - * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH - * @throws ProtocolException - */ - public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset, @Nullable Message parent, boolean parseLazy, boolean parseRetain, int length) - throws ProtocolException { - // TODO: Keep the parent - super(params, payloadBytes, offset, parent, parseLazy, parseRetain, length); + public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset, + Message parent, MessageSerializer serializer, int length) + throws ProtocolException { + super(params, payloadBytes, serializer, length); } - /** * Construct a block initialized with all the given fields. * @param params Which network the block is for. @@ -158,18 +138,18 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { return; this.auxpow = null; - if (this.params instanceof AuxPoWNetworkParameters) { - final AuxPoWNetworkParameters altcoinParams = (AuxPoWNetworkParameters)this.params; + if (this.params instanceof AltcoinNetworkParameters) { + final AltcoinNetworkParameters altcoinParams = (AltcoinNetworkParameters)this.params; if (altcoinParams.isAuxPoWBlockVersion(this.getVersion())) { // The following is used in dogecoinj, but I don't think we necessarily need it // payload.length >= 160) { // We have at least 2 headers in an Aux block. Workaround for StoredBlocks - this.auxpow = new AuxPoW(params, payload, cursor, this, parseLazy, parseRetain); + this.auxpow = new AuxPoW(params, payload, cursor, this, serializer); optimalEncodingMessageSize += auxpow.getOptimalEncodingMessageSize(); } } this.auxpowParsed = true; - this.auxpowBytesValid = parseRetain; + this.auxpowBytesValid = serializer.isParseRetainMode(); } @Override @@ -194,22 +174,24 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { // Ignore the header since it has fixed length. If length is not provided we will have to // invoke a light parse of transactions to calculate the length. if (length == UNKNOWN_LENGTH) { - Preconditions.checkState(parseLazy, + Preconditions.checkState(serializer.isParseLazyMode(), "Performing lite parse of block transaction as block was initialised from byte array " + "without providing length. This should never need to happen."); parseAuxPoW(); parseTransactions(); length = cursor - offset; } else { - transactionBytesValid = !transactionsParsed || parseRetain && length > HEADER_SIZE; + transactionBytesValid = !transactionsParsed || serializer.isParseRetainMode() && length > HEADER_SIZE; } - headerBytesValid = !headerParsed || parseRetain && length >= HEADER_SIZE; + headerBytesValid = !headerParsed || serializer.isParseRetainMode() && length >= HEADER_SIZE; } @Override void writeHeader(OutputStream stream) throws IOException { super.writeHeader(stream); - // TODO: Write the AuxPoW header + if (null != this.auxpow) { + this.auxpow.bitcoinSerialize(stream); + } } /** Returns a copy of the block, but without any transactions. */ @@ -223,28 +205,27 @@ public class AltcoinBlock extends org.bitcoinj.core.Block { /** Returns true if the hash of the block is OK (lower than difficulty target). */ protected boolean checkProofOfWork(boolean throwException) throws VerificationException { - // TODO: Add AuxPoW support + if (params instanceof AltcoinNetworkParameters) { + BigInteger target = getDifficultyTargetAsInteger(); + + final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters)auxpow; + if (altParams.isAuxPoWBlockVersion(getVersion()) && null != auxpow) { + return auxpow.checkProofOfWork(this.getHash(), target, throwException); + } - // This part is key - it is what proves the block was as difficult to make as it claims - // to be. Note however that in the context of this function, the block can claim to be - // as difficult as it wants to be .... if somebody was able to take control of our network - // connection and fork us onto a different chain, they could send us valid blocks with - // ridiculously easy difficulty and this function would accept them. - // - // To prevent this attack from being possible, elsewhere we check that the difficultyTarget - // field is of the right value. This requires us to have the preceeding blocks. - BigInteger target = getDifficultyTargetAsInteger(); - - BigInteger h = getHash().toBigInteger(); - if (h.compareTo(target) > 0) { - // Proof of work check failed! - if (throwException) - throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs " - + target.toString(16)); - else - return false; + BigInteger h = altParams.getBlockDifficultyHash(this).toBigInteger(); + if (h.compareTo(target) > 0) { + // Proof of work check failed! + if (throwException) + throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs " + + target.toString(16)); + else + return false; + } + return true; + } else { + return super.checkProofOfWork(throwException); } - return true; } /** diff --git a/src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java b/src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java new file mode 100644 index 00000000..4a67f933 --- /dev/null +++ b/src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Ross Nicoll + * + * 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 org.bitcoinj.core; + +/** + * + * @author jrn + */ +public interface AltcoinNetworkParameters { + + boolean isAuxPoWBlockVersion(long version); + + int getChainID(); + + /** + * Get the hash for the given block, for comparing against target difficulty. + * This provides an extension hook for networks which use a hash other than + * SHA256 twice (Bitcoin standard) for proof of work. + */ + Sha256Hash getBlockDifficultyHash(Block block); +} diff --git a/src/main/java/org/bitcoinj/core/AltcoinSerializer.java b/src/main/java/org/bitcoinj/core/AltcoinSerializer.java new file mode 100644 index 00000000..dec3ab19 --- /dev/null +++ b/src/main/java/org/bitcoinj/core/AltcoinSerializer.java @@ -0,0 +1,27 @@ + /* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.bitcoinj.core; + +/** + * + * @author jrn + */ +public class AltcoinSerializer extends BitcoinSerializer { + + public AltcoinSerializer(NetworkParameters params, boolean parseLazy, boolean parseRetain) { + super(params, parseLazy, parseRetain); + } + + @Override + public Block makeBlock(byte[] payloadBytes) throws ProtocolException { + return new AltcoinBlock(getParameters(), payloadBytes, this, payloadBytes.length); + } + + @Override + public Block makeBlock(byte[] payloadBytes, int length) throws ProtocolException { + return new AltcoinBlock(getParameters(), payloadBytes, this, length); + } +} diff --git a/src/main/java/org/bitcoinj/core/AuxPoW.java b/src/main/java/org/bitcoinj/core/AuxPoW.java index 90327df1..6085c920 100644 --- a/src/main/java/org/bitcoinj/core/AuxPoW.java +++ b/src/main/java/org/bitcoinj/core/AuxPoW.java @@ -23,23 +23,31 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.*; +import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.*; /** *
An AuxPoW header wraps a block header from another coin, enabling the foreign - * chain's proof of work to be used for this chain as well.
+ * chain's proof of work to be used for this chain as well. Note: + * NetworkParameters for AuxPoW networks must implement AltcoinNetworkParameters + * in order for AuxPoW to work. */ public class AuxPoW extends ChildMessage implements Serializable { - + + public static final byte[] MERGED_MINING_HEADER = new byte[] { + (byte) 0xfa, (byte) 0xbe, "m".getBytes()[0], "m".getBytes()[0] + }; + private static final Logger log = LoggerFactory.getLogger(AuxPoW.class); private static final long serialVersionUID = -8567546957352643140L; - private Transaction transaction; + private Transaction transaction; private Sha256Hash hashBlock; private MerkleBranch coinbaseBranch; - private MerkleBranch blockchainBranch; - private Block parentBlockHeader; + private MerkleBranch chainMerkleBranch; + private AltcoinBlock parentBlockHeader; // Transactions can be encoded in a way that will use more bytes than is optimal // (due to VarInts having multiple encodings) @@ -50,36 +58,38 @@ public class AuxPoW extends ChildMessage implements Serializable { public AuxPoW(NetworkParameters params, @Nullable Message parent) { super(params); - transaction = new Transaction(params); - hashBlock = Sha256Hash.ZERO_HASH; - coinbaseBranch = new MerkleBranch(params, this); - blockchainBranch = new MerkleBranch(params, this); - parentBlockHeader = null; + transaction = new Transaction(params); + hashBlock = Sha256Hash.ZERO_HASH; + coinbaseBranch = new MerkleBranch(params, this); + chainMerkleBranch = new MerkleBranch(params, this); + parentBlockHeader = null; } /** * Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed. - * @param params NetworkParameters object.1 + * @param params NetworkParameters object. * @param payload Bitcoin protocol formatted byte array containing message content. * @param offset The location of the first payload byte within the array. * @param parent The message element which contains this header. - * @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. - * @param parseRetain Whether to retain the backing byte array for quick reserialization. - * If true and the backing byte array is invalidated due to modification of a field then - * the cached bytes may be repopulated and retained if the message is serialized again in the future. + * @param serializer the serializer to use for this message. * @throws ProtocolException */ - public AuxPoW(NetworkParameters params, byte[] payload, int offset, Message parent, boolean parseLazy, boolean parseRetain) + public AuxPoW(NetworkParameters params, byte[] payload, int offset, Message parent, MessageSerializer serializer) throws ProtocolException { - super(params, payload, offset, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); + super(params, payload, offset, parent, serializer, Message.UNKNOWN_LENGTH); } /** * Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed. + * + * @param params NetworkParameters object. + * @param payload Bitcoin protocol formatted byte array containing message content. + * @param parent The message element which contains this header. + * @param serializer the serializer to use for this message. */ - public AuxPoW(NetworkParameters params, byte[] payload, @Nullable Message parent, boolean parseLazy, boolean parseRetain) + public AuxPoW(NetworkParameters params, byte[] payload, @Nullable Message parent, MessageSerializer serializer) throws ProtocolException { - super(params, payload, 0, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); + super(params, payload, 0, parent, serializer, Message.UNKNOWN_LENGTH); } @Override @@ -96,11 +106,11 @@ public class AuxPoW extends ChildMessage implements Serializable { // jump past header hash cursor += 4; - // Coin base branch - cursor += MerkleBranch.calcLength(buf, offset); + // Coin base branch + cursor += MerkleBranch.calcLength(buf, offset); - // Block chain branch - cursor += MerkleBranch.calcLength(buf, offset); + // Block chain branch + cursor += MerkleBranch.calcLength(buf, offset); // Block header cursor += Block.HEADER_SIZE; @@ -115,26 +125,26 @@ public class AuxPoW extends ChildMessage implements Serializable { return; cursor = offset; - transaction = new Transaction(params, payload, cursor, this, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); + transaction = new Transaction(params, payload, cursor, this, serializer, Message.UNKNOWN_LENGTH); cursor += transaction.getOptimalEncodingMessageSize(); optimalEncodingMessageSize = transaction.getOptimalEncodingMessageSize(); hashBlock = readHash(); - optimalEncodingMessageSize += 32; // Add the hash size to the optimal encoding + optimalEncodingMessageSize += 32; // Add the hash size to the optimal encoding - coinbaseBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain); - cursor += coinbaseBranch.getOptimalEncodingMessageSize(); - optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize(); + coinbaseBranch = new MerkleBranch(params, this, payload, cursor, serializer); + cursor += coinbaseBranch.getOptimalEncodingMessageSize(); + optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize(); - blockchainBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain); - cursor += blockchainBranch.getOptimalEncodingMessageSize(); - optimalEncodingMessageSize += blockchainBranch.getOptimalEncodingMessageSize(); + chainMerkleBranch = new MerkleBranch(params, this, payload, cursor, serializer); + cursor += chainMerkleBranch.getOptimalEncodingMessageSize(); + optimalEncodingMessageSize += chainMerkleBranch.getOptimalEncodingMessageSize(); // Make a copy of JUST the contained block header, so the block parser doesn't try reading // transactions past the end byte[] blockBytes = Arrays.copyOfRange(payload, cursor, cursor + Block.HEADER_SIZE); cursor += Block.HEADER_SIZE; - parentBlockHeader = new AltcoinBlock(params, blockBytes, 0, this, parseLazy, parseRetain, Block.HEADER_SIZE); + parentBlockHeader = new AltcoinBlock(params, blockBytes, 0, this, serializer, Block.HEADER_SIZE); length = cursor - offset; } @@ -164,13 +174,13 @@ public class AuxPoW extends ChildMessage implements Serializable { @Override protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { - transaction.bitcoinSerialize(stream); + transaction.bitcoinSerialize(stream); stream.write(Utils.reverseBytes(hashBlock.getBytes())); - coinbaseBranch.bitcoinSerialize(stream); - blockchainBranch.bitcoinSerialize(stream); + coinbaseBranch.bitcoinSerialize(stream); + chainMerkleBranch.bitcoinSerialize(stream); - parentBlockHeader.bitcoinSerializeToStream(stream); + parentBlockHeader.bitcoinSerializeToStream(stream); } @Override @@ -178,11 +188,11 @@ public class AuxPoW extends ChildMessage implements Serializable { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AuxPoW input = (AuxPoW) o; - if (!transaction.equals(input.transaction)) return false; - if (!hashBlock.equals(input.hashBlock)) return false; - if (!coinbaseBranch.equals(input.hashBlock)) return false; - if (!blockchainBranch.equals(input.hashBlock)) return false; - if (!parentBlockHeader.equals(input.hashBlock)) return false; + if (!transaction.equals(input.transaction)) return false; + if (!hashBlock.equals(input.hashBlock)) return false; + if (!coinbaseBranch.equals(input.hashBlock)) return false; + if (!chainMerkleBranch.equals(input.hashBlock)) return false; + if (!parentBlockHeader.equals(input.hashBlock)) return false; return getHash().equals(input.getHash()); } @@ -192,7 +202,7 @@ public class AuxPoW extends ChildMessage implements Serializable { result = 31 * result + transaction.hashCode(); result = 31 * result + hashBlock.hashCode(); result = 31 * result + coinbaseBranch.hashCode(); - result = 31 * result + blockchainBranch.hashCode(); + result = 31 * result + chainMerkleBranch.hashCode(); result = 31 * result + parentBlockHeader.hashCode(); return result; } @@ -213,7 +223,7 @@ public class AuxPoW extends ChildMessage implements Serializable { * not necessarily part of the parent blockchain, they simply must be valid * blocks at the difficulty of the child blockchain. */ - public Block getParentBlockHeader() { + public AltcoinBlock getParentBlockHeader() { return parentBlockHeader; } @@ -229,8 +239,8 @@ public class AuxPoW extends ChildMessage implements Serializable { /** * Get the Merkle branch used to connect the AuXPow header with this blockchain. */ - public MerkleBranch getBlockchainBranch() { - return blockchainBranch; + public MerkleBranch getChainMerkleBranch() { + return chainMerkleBranch; } /** @@ -260,4 +270,170 @@ public class AuxPoW extends ChildMessage implements Serializable { maybeParse(); // TODO: Verify the AuxPoW data } + + /** + * Check the proof of work for this AuxPoW header meets the target + * difficulty. + * + * @param hashAuxBlock hash of the block the AuxPoW header is attached to. + * @param target the difficulty target after decoding from compact bits. + */ + protected boolean checkProofOfWork(Sha256Hash hashAuxBlock, + BigInteger target, boolean throwException) throws VerificationException { + if (0 != this.getCoinbaseBranch().getIndex()) { + if (throwException) { + // I don't like the message, but it correlates with what's in the reference client. + throw new VerificationException("AuxPow is not a generate"); + } + return false; + } + + /* if (!TestNet() + parentBlockHeader.getChainID() == ((AuxPoWNetworkParameters) params).getChainID()) { + if (throwException) { + throw new VerificationException("Aux POW parent has our chain ID"); + } + return false; + } */ + + if (this.getChainMerkleBranch().size() > 30) { + if (throwException) { + throw new VerificationException("Aux POW chain merkle branch too long"); + } + return false; + } + + // Check that the chain merkle root is in the coinbase + Sha256Hash nRootHash = getChainMerkleBranch().calculateMerkleRoot(hashAuxBlock); + final byte[] vchRootHash = nRootHash.getBytes(); + //std::reverse(vchRootHash.begin(), vchRootHash.end()); // correct endian// correct endian + + // Check that we are in the parent block merkle tree + if (!getCoinbaseBranch().calculateMerkleRoot(getCoinbase().getHash()).equals(parentBlockHeader.getMerkleRoot())) { + if (throwException) { + throw new VerificationException("Aux POW merkle root incorrect"); + } + return false; + } + + final byte[] script = this.getCoinbase().getInput(0).getScriptBytes(); + + // Check that the same work is not submitted twice to our chain. + // + int pcHead = -1; + int pc = -1; + + for (int scriptIdx = 0; scriptIdx < script.length; scriptIdx++) { + if (arrayMatch(script, scriptIdx, MERGED_MINING_HEADER)) { + // Enforce only one chain merkle root by checking that a single instance of the merged + // mining header exists just before. + if (pcHead >= 0) { + if (throwException) { + throw new VerificationException("Multiple merged mining headers in coinbase"); + } + return false; + } + pcHead = scriptIdx; + } + if (arrayMatch(script, scriptIdx, vchRootHash)) { + pc = scriptIdx; + } + } + + if (-1 == pcHead) { + if (throwException) { + throw new VerificationException("MergedMiningHeader missing from parent coinbase"); + } + return false; + } + + if (-1 == pc) { + if (throwException) { + throw new VerificationException("Aux POW missing chain merkle root in parent coinbase"); + } + return false; + } + + if (pcHead + MERGED_MINING_HEADER.length != pc) { + if (throwException) { + throw new VerificationException("Merged mining header is not just before chain merkle root"); + } + return false; + } + + // Ensure we are at a deterministic point in the merkle leaves by hashing + // a nonce and our chain ID and comparing to the index. + pc += vchRootHash.length; + if ((script.length - pc) < 8) { + if (throwException) { + throw new VerificationException("Aux POW missing chain merkle tree size and nonce in parent coinbase"); + } + return false; + } + + byte[] sizeBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc, pc + 4)); + int branchSize = ByteBuffer.wrap(sizeBytes).getInt(); + if (branchSize != (1 << getChainMerkleBranch().size())) { + if (throwException) { + throw new VerificationException("Aux POW merkle branch size does not match parent coinbase"); + } + return false; + } + + byte[] nonceBytes = Utils.reverseBytes(Arrays.copyOfRange(script, pc + 4, pc + 8)); + int nonce = ByteBuffer.wrap(nonceBytes).getInt(); + + // Choose a pseudo-random slot in the chain merkle tree + // but have it be fixed for a size/nonce/chain combination. + // + // This prevents the same work from being used twice for the + // same chain while reducing the chance that two chains clash + // for the same slot. + long rand = nonce; + rand = rand * 1103515245 + 12345; + rand += ((AltcoinNetworkParameters) params).getChainID(); + rand = rand * 1103515245 + 12345; + + if (getChainMerkleBranch().getIndex() != (rand % branchSize)) { + if (throwException) { + throw new VerificationException("Aux POW wrong index"); + } + return false; + } + + BigInteger h = ((AltcoinNetworkParameters) params) + .getBlockDifficultyHash(getParentBlockHeader()) + .toBigInteger(); + if (h.compareTo(target) > 0) { + // Proof of work check failed! + if (throwException) { + throw new VerificationException("Hash is higher than target: " + getParentBlockHeader().getHashAsString() + " vs " + + target.toString(16)); + } + return false; + } + + return true; + } + + public Transaction getTransaction() { + return transaction; + } + + /** + * Test whether one array is at a specific offset within the other. + * + * @param script the longer array to test for containing another array. + * @param offset the offset to start at within the larger array. + * @param subArray the shorter array to test for presence in the longer array. + * @return true if the shorter array is present at the offset, false otherwise. + */ + private boolean arrayMatch(byte[] script, int offset, byte[] subArray) { + for (int matchIdx = 0; matchIdx + offset < script.length && matchIdx < subArray.length; matchIdx++) { + if (script[offset + matchIdx] != subArray[matchIdx]) { + return false; + } + } + return true; + } } diff --git a/src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java b/src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java deleted file mode 100644 index 00f36788..00000000 --- a/src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.bitcoinj.core; - -/** - * - * @author jrn - */ -public interface AuxPoWNetworkParameters { - - public boolean isAuxPoWBlockVersion(long version); - -} diff --git a/src/main/java/org/bitcoinj/core/MerkleBranch.java b/src/main/java/org/bitcoinj/core/MerkleBranch.java index 3acd33a6..faa38844 100644 --- a/src/main/java/org/bitcoinj/core/MerkleBranch.java +++ b/src/main/java/org/bitcoinj/core/MerkleBranch.java @@ -35,6 +35,9 @@ import java.util.List; * up to its root, plus a bitset used to define how the hashes are applied. * Given the hash of the leaf, this can be used to calculate the tree * root. This is useful for proving that a leaf belongs to a given tree. + * + * TODO: Has a lot of similarity to PartialMerkleTree, should attempt to merge + * the two. */ public class MerkleBranch extends ChildMessage implements Serializable { private static final long serialVersionUID = 2; @@ -46,15 +49,15 @@ public class MerkleBranch extends ChildMessage implements Serializable { // can properly keep track of optimal encoded size private transient int optimalEncodingMessageSize; - private List