From c6659bad128ebc556fef4d4f7e462140f3f4bd9f Mon Sep 17 00:00:00 2001
From: Ross Nicoll
Date: Sun, 31 May 2015 22:54:46 +0100
Subject: [PATCH] Completed AuxPoW header validation code.
---
.../params/AbstractDogecoinParams.java | 27 +-
.../params/DogecoinMainNetParams.java | 1 -
.../java/org/bitcoinj/core/AltcoinBlock.java | 99 +++----
.../core/AltcoinNetworkParameters.java | 34 +++
.../org/bitcoinj/core/AltcoinSerializer.java | 27 ++
src/main/java/org/bitcoinj/core/AuxPoW.java | 266 +++++++++++++++---
.../core/AuxPoWNetworkParameters.java | 16 --
.../java/org/bitcoinj/core/MerkleBranch.java | 44 +--
.../java/org/bitcoinj/core/AuxPoWTest.java | 24 +-
.../org/bitcoinj/core/DogecoinBlockTest.java | 15 +-
.../org/bitcoinj/core/MerkleBranchTest.java | 5 +-
11 files changed, 401 insertions(+), 157 deletions(-)
create mode 100644 src/main/java/org/bitcoinj/core/AltcoinNetworkParameters.java
create mode 100644 src/main/java/org/bitcoinj/core/AltcoinSerializer.java
delete mode 100644 src/main/java/org/bitcoinj/core/AuxPoWNetworkParameters.java
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 branchHashes;
- private long branchSideMask;
+ private List branchHashes;
+ private long index;
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) {
super(params);
setParent(parent);
this.branchHashes = new ArrayList();
- this.branchSideMask = 0;
+ this.index = 0;
}
/**
@@ -70,17 +73,13 @@ public class MerkleBranch extends ChildMessage implements Serializable {
* @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 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.
- * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
+ * @param serializer the serializer to use for this message.
* @throws ProtocolException
*/
public MerkleBranch(NetworkParameters params, ChildMessage parent, byte[] payload, int offset,
- boolean parseLazy, boolean parseRetain)
+ MessageSerializer serializer)
throws ProtocolException {
- super(params, payload, offset, parent, parseLazy, parseRetain, UNKNOWN_LENGTH);
+ super(params, payload, offset, parent, serializer, UNKNOWN_LENGTH);
}
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent,
@@ -89,7 +88,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
setParent(parent);
this.branchHashes = hashes;
- this.branchSideMask = branchSideMask;
+ this.index = branchSideMask;
}
@Override
@@ -118,7 +117,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
branchHashes.add(readHash());
}
optimalEncodingMessageSize += 32 * hashCount;
- branchSideMask = readUint32();
+ index = readUint32();
optimalEncodingMessageSize += 4;
}
@@ -128,7 +127,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
for (Sha256Hash hash: branchHashes) {
stream.write(Utils.reverseBytes(hash.getBytes()));
}
- Utils.uint32ToByteStreamLE(branchSideMask, stream);
+ Utils.uint32ToByteStreamLE(index, stream);
}
/**
@@ -137,7 +136,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
*/
public Sha256Hash calculateMerkleRoot(final Sha256Hash leaf) {
byte[] target = reverseBytes(leaf.getBytes());
- long mask = branchSideMask;
+ long mask = index;
for (Sha256Hash hash: branchHashes) {
target = (mask & 1) == 0
@@ -155,10 +154,19 @@ public class MerkleBranch extends ChildMessage implements Serializable {
return Collections.unmodifiableList(this.branchHashes);
}
+ /**
+ * Return the mask used to determine which side the hashes are applied on.
+ * Each bit represents a hash. Zero means it goes on the right, one means
+ * on the left.
+ */
+ public long getIndex() {
+ return index;
+ }
+
/**
* Get the number of hashes in this branch.
*/
- public int getSize() {
+ public int size() {
return branchHashes.size();
}
@@ -207,8 +215,8 @@ public class MerkleBranch extends ChildMessage implements Serializable {
MerkleBranch input = (MerkleBranch) o;
- if (!branchHashes.equals(input.branchHashes)) return false;
- if (branchSideMask != input.branchSideMask) return false;
+ if (!branchHashes.equals(input.branchHashes)) return false;
+ if (index != input.index) return false;
return true;
}
@@ -217,7 +225,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
public int hashCode() {
int result = 1;
result = 31 * result + branchHashes.hashCode();
- result = 31 * result + (int) branchSideMask;
+ result = 31 * result + (int) index;
return result;
}
}
diff --git a/src/test/java/org/bitcoinj/core/AuxPoWTest.java b/src/test/java/org/bitcoinj/core/AuxPoWTest.java
index ddedd559..3238ce99 100644
--- a/src/test/java/org/bitcoinj/core/AuxPoWTest.java
+++ b/src/test/java/org/bitcoinj/core/AuxPoWTest.java
@@ -1,6 +1,7 @@
package org.bitcoinj.core;
-import org.bitcoinj.params.TestNet3Params;
+import java.math.BigInteger;
+import org.altcoinj.params.DogecoinTestNet3Params;
import org.junit.Test;
import static org.bitcoinj.core.Util.getBytes;
@@ -12,7 +13,7 @@ import static org.junit.Assert.assertEquals;
* AuxPoW header parsing/serialization and validation
*/
public class AuxPoWTest {
- static final NetworkParameters params = TestNet3Params.get();
+ static final NetworkParameters params = DogecoinTestNet3Params.get();
/**
* Parse the AuxPoW header from Dogecoin block #403,931.
@@ -20,13 +21,13 @@ public class AuxPoWTest {
@Test
public void parseAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
- AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, false, false);
+ AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
MerkleBranch branch = auxpow.getCoinbaseBranch();
Sha256Hash expected = new Sha256Hash("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf");
assertEquals(expected, auxpow.getCoinbase().getHash());
- assertEquals(3, auxpow.getCoinbaseBranch().getSize());
- assertEquals(6, auxpow.getBlockchainBranch().getSize());
+ assertEquals(3, auxpow.getCoinbaseBranch().size());
+ assertEquals(6, auxpow.getChainMerkleBranch().size());
expected = new Sha256Hash("a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c");
assertEquals(expected, auxpow.getParentBlockHeader().getHash());
@@ -38,10 +39,21 @@ public class AuxPoWTest {
@Test
public void serializeAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
- AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, false, false);
+ AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
byte[] expected = auxpowAsBytes;
byte[] actual = auxpow.bitcoinSerialize();
assertArrayEquals(expected, actual);
}
+
+ /**
+ * Validate the AuxPoW header from Dogecoin block #403,931.
+ */
+ @Test
+ public void checkAuxPoWHeader() throws Exception {
+ byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
+ AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
+ auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
+ Utils.decodeCompactBits(0x1b06f8f0), true);
+ }
}
diff --git a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java
index 9eb929f3..19f6dd0d 100644
--- a/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java
+++ b/src/test/java/org/bitcoinj/core/DogecoinBlockTest.java
@@ -29,7 +29,8 @@ public class DogecoinBlockTest {
@Test
public void shouldParseBlock1() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block1.bin"));
- final AltcoinBlock block = new AltcoinBlock(params, payload);
+ AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
+ final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("82bc68038f6034c0596b6e313729793a887fded6e92a31fbdf70863f89d9bea2", block.getHashAsString());
assertEquals(1, block.getTransactions().size());
}
@@ -41,7 +42,8 @@ public class DogecoinBlockTest {
@Test
public void shouldParseBlock250000() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block250000.bin"));
- final AltcoinBlock block = new AltcoinBlock(params, payload);
+ AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
+ final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals(2469341065L, block.getNonce());
final AuxPoW auxpow = block.getAuxPoW();
assertNull(auxpow);
@@ -58,7 +60,8 @@ public class DogecoinBlockTest {
@Test
public void shouldParseBlock371337() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin"));
- final AltcoinBlock block = new AltcoinBlock(params, payload);
+ AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
+ final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
assertEquals("60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053", block.getHashAsString());
assertEquals(0, block.getNonce());
final AuxPoW auxpow = block.getAuxPoW();
@@ -69,14 +72,14 @@ public class DogecoinBlockTest {
assertEquals("45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9", parentBlock.getHashAsString());
assertNull(parentBlock.getTransactions());
- final MerkleBranch blockchainMerkleBranch = auxpow.getBlockchainBranch();
+ final MerkleBranch blockchainMerkleBranch = auxpow.getChainMerkleBranch();
Sha256Hash[] expected = new Sha256Hash[] {
new Sha256Hash("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"),
new Sha256Hash("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"),
new Sha256Hash("a0469413ce64d67c43902d54ee3a380eff12ded22ca11cbd3842e15d48298103")
};
- assertArrayEquals(expected, blockchainMerkleBranch.getHashes().toArray(new Sha256Hash[blockchainMerkleBranch.getSize()]));
+ assertArrayEquals(expected, blockchainMerkleBranch.getHashes().toArray(new Sha256Hash[blockchainMerkleBranch.size()]));
final MerkleBranch coinbaseMerkleBranch = auxpow.getCoinbaseBranch();
expected = new Sha256Hash[] {
@@ -84,7 +87,7 @@ public class DogecoinBlockTest {
new Sha256Hash("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"),
new Sha256Hash("d11293660392e7c51f69477a6130237c72ecee2d0c1d3dc815841734c370331a")
};
- assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.getSize()]));
+ assertArrayEquals(expected, coinbaseMerkleBranch.getHashes().toArray(new Sha256Hash[coinbaseMerkleBranch.size()]));
assertEquals(6, block.getTransactions().size());
}
diff --git a/src/test/java/org/bitcoinj/core/MerkleBranchTest.java b/src/test/java/org/bitcoinj/core/MerkleBranchTest.java
index fa1f7473..b968b550 100644
--- a/src/test/java/org/bitcoinj/core/MerkleBranchTest.java
+++ b/src/test/java/org/bitcoinj/core/MerkleBranchTest.java
@@ -1,6 +1,7 @@
package org.bitcoinj.core;
+import static org.bitcoinj.core.AuxPoWTest.params;
import org.bitcoinj.params.TestNet3Params;
import org.junit.Test;
@@ -28,7 +29,7 @@ public class MerkleBranchTest {
new Sha256Hash("d8c6fe42ca25076159cd121a5e20c48c1bc53ab90730083e44a334566ea6bbcb")
};
- assertArrayEquals(expected, branch.getHashes().toArray(new Sha256Hash[branch.getSize()]));
+ assertArrayEquals(expected, branch.getHashes().toArray(new Sha256Hash[branch.size()]));
}
/**
@@ -39,7 +40,7 @@ public class MerkleBranchTest {
public void serializeMerkleBranch() throws Exception {
byte[] expected = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, expected, 0,
- false, false);
+ params.getDefaultSerializer());
byte[] actual = branch.bitcoinSerialize();
assertArrayEquals(expected, actual);