3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 11:15:51 +00:00

Completed AuxPoW header validation code.

This commit is contained in:
Ross Nicoll 2015-05-31 22:54:46 +01:00
parent 453819ccb2
commit c6659bad12
11 changed files with 401 additions and 157 deletions

View File

@ -20,12 +20,11 @@ import java.io.ByteArrayOutputStream;
import java.math.BigInteger; import java.math.BigInteger;
import org.bitcoinj.core.AltcoinBlock; import org.bitcoinj.core.AltcoinBlock;
import org.bitcoinj.core.AuxPoWNetworkParameters; import org.bitcoinj.core.AltcoinNetworkParameters;
import org.bitcoinj.core.Block; import org.bitcoinj.core.Block;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import static org.bitcoinj.core.Coin.COIN; import static org.bitcoinj.core.Coin.COIN;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.VerificationException; import org.bitcoinj.core.VerificationException;
import org.bitcoinj.script.Script; import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptOpCodes; import org.bitcoinj.script.ScriptOpCodes;
@ -36,7 +35,8 @@ import org.bitcoinj.utils.MonetaryFormat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.StoredBlock;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput; 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. * 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. */ /** Standard format for the DOGE denomination. */
public static final MonetaryFormat DOGE; public static final MonetaryFormat DOGE;
/** Standard format for the mDOGE denomination. */ /** 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 MonetaryFormat KOINU;
public static final int DIGISHIELD_BLOCK_HEIGHT = 145000; // Block height to use Digishield from 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 = 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_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. 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; 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 @Override
public boolean isAuxPoWBlockVersion(long version) { public boolean isAuxPoWBlockVersion(long version) {
return version >= BLOCK_VERSION_AUXPOW return version >= BLOCK_VERSION_AUXPOW

View File

@ -17,7 +17,6 @@
package org.altcoinj.params; package org.altcoinj.params;
import org.altcoinj.core.ScryptHash; import org.altcoinj.core.ScryptHash;
import org.bitcoinj.core.AltcoinBlock;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;

View File

@ -59,50 +59,30 @@ public class AltcoinBlock extends org.bitcoinj.core.Block {
super(params); super(params);
} }
/** Constructs a block object from the Bitcoin wire format. */ /** Special case constructor, used for the genesis node, cloneAsHeader and unit tests. */
public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) throws ProtocolException { public AltcoinBlock(NetworkParameters params, byte[] payloadBytes) {
super(params, 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 params NetworkParameters object.
* @param parseLazy Whether to perform a full parse immediately or delay until a read is requested. * @param serializer the serializer to use for this message.
* @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 * @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 * as the length will be provided as part of the header. If unknown then set to Message.UNKNOWN_LENGTH
* @throws ProtocolException * @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 { throws ProtocolException {
super(params, payloadBytes, parseLazy, parseRetain, length); super(params, payloadBytes, serializer, length);
} }
/** public AltcoinBlock(NetworkParameters params, byte[] payloadBytes, int offset,
* Contruct a block object from the Bitcoin wire format. Used in the case of a block Message parent, MessageSerializer serializer, int length)
* 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 { throws ProtocolException {
// TODO: Keep the parent super(params, payloadBytes, serializer, length);
super(params, payloadBytes, offset, parent, parseLazy, parseRetain, length);
} }
/** /**
* Construct a block initialized with all the given fields. * Construct a block initialized with all the given fields.
* @param params Which network the block is for. * @param params Which network the block is for.
@ -158,18 +138,18 @@ public class AltcoinBlock extends org.bitcoinj.core.Block {
return; return;
this.auxpow = null; this.auxpow = null;
if (this.params instanceof AuxPoWNetworkParameters) { if (this.params instanceof AltcoinNetworkParameters) {
final AuxPoWNetworkParameters altcoinParams = (AuxPoWNetworkParameters)this.params; final AltcoinNetworkParameters altcoinParams = (AltcoinNetworkParameters)this.params;
if (altcoinParams.isAuxPoWBlockVersion(this.getVersion())) { if (altcoinParams.isAuxPoWBlockVersion(this.getVersion())) {
// The following is used in dogecoinj, but I don't think we necessarily need it // 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 // 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(); optimalEncodingMessageSize += auxpow.getOptimalEncodingMessageSize();
} }
} }
this.auxpowParsed = true; this.auxpowParsed = true;
this.auxpowBytesValid = parseRetain; this.auxpowBytesValid = serializer.isParseRetainMode();
} }
@Override @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 // 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. // invoke a light parse of transactions to calculate the length.
if (length == UNKNOWN_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 " + "Performing lite parse of block transaction as block was initialised from byte array " +
"without providing length. This should never need to happen."); "without providing length. This should never need to happen.");
parseAuxPoW(); parseAuxPoW();
parseTransactions(); parseTransactions();
length = cursor - offset; length = cursor - offset;
} else { } 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 @Override
void writeHeader(OutputStream stream) throws IOException { void writeHeader(OutputStream stream) throws IOException {
super.writeHeader(stream); 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. */ /** Returns a copy of the block, but without any transactions. */
@ -223,19 +205,15 @@ public class AltcoinBlock extends org.bitcoinj.core.Block {
/** Returns true if the hash of the block is OK (lower than difficulty target). */ /** Returns true if the hash of the block is OK (lower than difficulty target). */
protected boolean checkProofOfWork(boolean throwException) throws VerificationException { protected boolean checkProofOfWork(boolean throwException) throws VerificationException {
// TODO: Add AuxPoW support if (params instanceof AltcoinNetworkParameters) {
// 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 target = getDifficultyTargetAsInteger();
BigInteger h = getHash().toBigInteger(); final AltcoinNetworkParameters altParams = (AltcoinNetworkParameters)auxpow;
if (altParams.isAuxPoWBlockVersion(getVersion()) && null != auxpow) {
return auxpow.checkProofOfWork(this.getHash(), target, throwException);
}
BigInteger h = altParams.getBlockDifficultyHash(this).toBigInteger();
if (h.compareTo(target) > 0) { if (h.compareTo(target) > 0) {
// Proof of work check failed! // Proof of work check failed!
if (throwException) if (throwException)
@ -245,6 +223,9 @@ public class AltcoinBlock extends org.bitcoinj.core.Block {
return false; return false;
} }
return true; return true;
} else {
return super.checkProofOfWork(throwException);
}
} }
/** /**

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -23,23 +23,31 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.*; import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
/** /**
* <p>An AuxPoW header wraps a block header from another coin, enabling the foreign * <p>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.</p> * chain's proof of work to be used for this chain as well. <b>Note: </b>
* NetworkParameters for AuxPoW networks <b>must</b> implement AltcoinNetworkParameters
* in order for AuxPoW to work.</p>
*/ */
public class AuxPoW extends ChildMessage implements Serializable { 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 Logger log = LoggerFactory.getLogger(AuxPoW.class);
private static final long serialVersionUID = -8567546957352643140L; private static final long serialVersionUID = -8567546957352643140L;
private Transaction transaction; private Transaction transaction;
private Sha256Hash hashBlock; private Sha256Hash hashBlock;
private MerkleBranch coinbaseBranch; private MerkleBranch coinbaseBranch;
private MerkleBranch blockchainBranch; private MerkleBranch chainMerkleBranch;
private Block parentBlockHeader; private AltcoinBlock parentBlockHeader;
// Transactions can be encoded in a way that will use more bytes than is optimal // Transactions can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings) // (due to VarInts having multiple encodings)
@ -53,33 +61,35 @@ public class AuxPoW extends ChildMessage implements Serializable {
transaction = new Transaction(params); transaction = new Transaction(params);
hashBlock = Sha256Hash.ZERO_HASH; hashBlock = Sha256Hash.ZERO_HASH;
coinbaseBranch = new MerkleBranch(params, this); coinbaseBranch = new MerkleBranch(params, this);
blockchainBranch = new MerkleBranch(params, this); chainMerkleBranch = new MerkleBranch(params, this);
parentBlockHeader = null; parentBlockHeader = null;
} }
/** /**
* Creates an AuxPoW header by reading payload starting from offset bytes in. Length of header is fixed. * 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 payload Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array. * @param offset The location of the first payload byte within the array.
* @param parent The message element which contains this header. * @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 serializer the serializer to use for this message.
* @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.
* @throws ProtocolException * @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 { 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. * 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 { throws ProtocolException {
super(params, payload, 0, parent, parseLazy, parseRetain, Message.UNKNOWN_LENGTH); super(params, payload, 0, parent, serializer, Message.UNKNOWN_LENGTH);
} }
@Override @Override
@ -115,26 +125,26 @@ public class AuxPoW extends ChildMessage implements Serializable {
return; return;
cursor = offset; 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(); cursor += transaction.getOptimalEncodingMessageSize();
optimalEncodingMessageSize = transaction.getOptimalEncodingMessageSize(); optimalEncodingMessageSize = transaction.getOptimalEncodingMessageSize();
hashBlock = readHash(); 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); coinbaseBranch = new MerkleBranch(params, this, payload, cursor, serializer);
cursor += coinbaseBranch.getOptimalEncodingMessageSize(); cursor += coinbaseBranch.getOptimalEncodingMessageSize();
optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize(); optimalEncodingMessageSize += coinbaseBranch.getOptimalEncodingMessageSize();
blockchainBranch = new MerkleBranch(params, this, payload, cursor, parseLazy, parseRetain); chainMerkleBranch = new MerkleBranch(params, this, payload, cursor, serializer);
cursor += blockchainBranch.getOptimalEncodingMessageSize(); cursor += chainMerkleBranch.getOptimalEncodingMessageSize();
optimalEncodingMessageSize += blockchainBranch.getOptimalEncodingMessageSize(); optimalEncodingMessageSize += chainMerkleBranch.getOptimalEncodingMessageSize();
// Make a copy of JUST the contained block header, so the block parser doesn't try reading // Make a copy of JUST the contained block header, so the block parser doesn't try reading
// transactions past the end // transactions past the end
byte[] blockBytes = Arrays.copyOfRange(payload, cursor, cursor + Block.HEADER_SIZE); byte[] blockBytes = Arrays.copyOfRange(payload, cursor, cursor + Block.HEADER_SIZE);
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; length = cursor - offset;
} }
@ -168,7 +178,7 @@ public class AuxPoW extends ChildMessage implements Serializable {
stream.write(Utils.reverseBytes(hashBlock.getBytes())); stream.write(Utils.reverseBytes(hashBlock.getBytes()));
coinbaseBranch.bitcoinSerialize(stream); coinbaseBranch.bitcoinSerialize(stream);
blockchainBranch.bitcoinSerialize(stream); chainMerkleBranch.bitcoinSerialize(stream);
parentBlockHeader.bitcoinSerializeToStream(stream); parentBlockHeader.bitcoinSerializeToStream(stream);
} }
@ -181,7 +191,7 @@ public class AuxPoW extends ChildMessage implements Serializable {
if (!transaction.equals(input.transaction)) return false; if (!transaction.equals(input.transaction)) return false;
if (!hashBlock.equals(input.hashBlock)) return false; if (!hashBlock.equals(input.hashBlock)) return false;
if (!coinbaseBranch.equals(input.hashBlock)) return false; if (!coinbaseBranch.equals(input.hashBlock)) return false;
if (!blockchainBranch.equals(input.hashBlock)) return false; if (!chainMerkleBranch.equals(input.hashBlock)) return false;
if (!parentBlockHeader.equals(input.hashBlock)) return false; if (!parentBlockHeader.equals(input.hashBlock)) return false;
return getHash().equals(input.getHash()); return getHash().equals(input.getHash());
} }
@ -192,7 +202,7 @@ public class AuxPoW extends ChildMessage implements Serializable {
result = 31 * result + transaction.hashCode(); result = 31 * result + transaction.hashCode();
result = 31 * result + hashBlock.hashCode(); result = 31 * result + hashBlock.hashCode();
result = 31 * result + coinbaseBranch.hashCode(); result = 31 * result + coinbaseBranch.hashCode();
result = 31 * result + blockchainBranch.hashCode(); result = 31 * result + chainMerkleBranch.hashCode();
result = 31 * result + parentBlockHeader.hashCode(); result = 31 * result + parentBlockHeader.hashCode();
return result; 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 * not necessarily part of the parent blockchain, they simply must be valid
* blocks at the difficulty of the child blockchain. * blocks at the difficulty of the child blockchain.
*/ */
public Block getParentBlockHeader() { public AltcoinBlock getParentBlockHeader() {
return parentBlockHeader; 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. * Get the Merkle branch used to connect the AuXPow header with this blockchain.
*/ */
public MerkleBranch getBlockchainBranch() { public MerkleBranch getChainMerkleBranch() {
return blockchainBranch; return chainMerkleBranch;
} }
/** /**
@ -260,4 +270,170 @@ public class AuxPoW extends ChildMessage implements Serializable {
maybeParse(); maybeParse();
// TODO: Verify the AuxPoW data // 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;
}
} }

View File

@ -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);
}

View File

@ -35,6 +35,9 @@ import java.util.List;
* up to its root, plus a bitset used to define how the hashes are applied. * 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 * 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. * 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 { public class MerkleBranch extends ChildMessage implements Serializable {
private static final long serialVersionUID = 2; private static final long serialVersionUID = 2;
@ -47,14 +50,14 @@ public class MerkleBranch extends ChildMessage implements Serializable {
private transient int optimalEncodingMessageSize; private transient int optimalEncodingMessageSize;
private List<Sha256Hash> branchHashes; private List<Sha256Hash> branchHashes;
private long branchSideMask; private long index;
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) { public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) {
super(params); super(params);
setParent(parent); setParent(parent);
this.branchHashes = new ArrayList<Sha256Hash>(); this.branchHashes = new ArrayList<Sha256Hash>();
this.branchSideMask = 0; this.index = 0;
} }
/** /**
@ -70,17 +73,13 @@ public class MerkleBranch extends ChildMessage implements Serializable {
* @param params NetworkParameters object. * @param params NetworkParameters object.
* @param payload Bitcoin protocol formatted byte array containing message content. * @param payload Bitcoin protocol formatted byte array containing message content.
* @param offset The location of the first payload byte within the array. * @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 serializer the serializer to use for this message.
* @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
* @throws ProtocolException * @throws ProtocolException
*/ */
public MerkleBranch(NetworkParameters params, ChildMessage parent, byte[] payload, int offset, public MerkleBranch(NetworkParameters params, ChildMessage parent, byte[] payload, int offset,
boolean parseLazy, boolean parseRetain) MessageSerializer serializer)
throws ProtocolException { 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, public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent,
@ -89,7 +88,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
setParent(parent); setParent(parent);
this.branchHashes = hashes; this.branchHashes = hashes;
this.branchSideMask = branchSideMask; this.index = branchSideMask;
} }
@Override @Override
@ -118,7 +117,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
branchHashes.add(readHash()); branchHashes.add(readHash());
} }
optimalEncodingMessageSize += 32 * hashCount; optimalEncodingMessageSize += 32 * hashCount;
branchSideMask = readUint32(); index = readUint32();
optimalEncodingMessageSize += 4; optimalEncodingMessageSize += 4;
} }
@ -128,7 +127,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
for (Sha256Hash hash: branchHashes) { for (Sha256Hash hash: branchHashes) {
stream.write(Utils.reverseBytes(hash.getBytes())); 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) { public Sha256Hash calculateMerkleRoot(final Sha256Hash leaf) {
byte[] target = reverseBytes(leaf.getBytes()); byte[] target = reverseBytes(leaf.getBytes());
long mask = branchSideMask; long mask = index;
for (Sha256Hash hash: branchHashes) { for (Sha256Hash hash: branchHashes) {
target = (mask & 1) == 0 target = (mask & 1) == 0
@ -155,10 +154,19 @@ public class MerkleBranch extends ChildMessage implements Serializable {
return Collections.unmodifiableList(this.branchHashes); 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. * Get the number of hashes in this branch.
*/ */
public int getSize() { public int size() {
return branchHashes.size(); return branchHashes.size();
} }
@ -208,7 +216,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
MerkleBranch input = (MerkleBranch) o; MerkleBranch input = (MerkleBranch) o;
if (!branchHashes.equals(input.branchHashes)) return false; if (!branchHashes.equals(input.branchHashes)) return false;
if (branchSideMask != input.branchSideMask) return false; if (index != input.index) return false;
return true; return true;
} }
@ -217,7 +225,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
public int hashCode() { public int hashCode() {
int result = 1; int result = 1;
result = 31 * result + branchHashes.hashCode(); result = 31 * result + branchHashes.hashCode();
result = 31 * result + (int) branchSideMask; result = 31 * result + (int) index;
return result; return result;
} }
} }

View File

@ -1,6 +1,7 @@
package org.bitcoinj.core; package org.bitcoinj.core;
import org.bitcoinj.params.TestNet3Params; import java.math.BigInteger;
import org.altcoinj.params.DogecoinTestNet3Params;
import org.junit.Test; import org.junit.Test;
import static org.bitcoinj.core.Util.getBytes; import static org.bitcoinj.core.Util.getBytes;
@ -12,7 +13,7 @@ import static org.junit.Assert.assertEquals;
* AuxPoW header parsing/serialization and validation * AuxPoW header parsing/serialization and validation
*/ */
public class AuxPoWTest { public class AuxPoWTest {
static final NetworkParameters params = TestNet3Params.get(); static final NetworkParameters params = DogecoinTestNet3Params.get();
/** /**
* Parse the AuxPoW header from Dogecoin block #403,931. * Parse the AuxPoW header from Dogecoin block #403,931.
@ -20,13 +21,13 @@ public class AuxPoWTest {
@Test @Test
public void parseAuxPoWHeader() throws Exception { public void parseAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin")); 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(); MerkleBranch branch = auxpow.getCoinbaseBranch();
Sha256Hash expected = new Sha256Hash("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf"); Sha256Hash expected = new Sha256Hash("089b911f5e471c0e1800f3384281ebec5b372fbb6f358790a92747ade271ccdf");
assertEquals(expected, auxpow.getCoinbase().getHash()); assertEquals(expected, auxpow.getCoinbase().getHash());
assertEquals(3, auxpow.getCoinbaseBranch().getSize()); assertEquals(3, auxpow.getCoinbaseBranch().size());
assertEquals(6, auxpow.getBlockchainBranch().getSize()); assertEquals(6, auxpow.getChainMerkleBranch().size());
expected = new Sha256Hash("a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c"); expected = new Sha256Hash("a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c");
assertEquals(expected, auxpow.getParentBlockHeader().getHash()); assertEquals(expected, auxpow.getParentBlockHeader().getHash());
@ -38,10 +39,21 @@ public class AuxPoWTest {
@Test @Test
public void serializeAuxPoWHeader() throws Exception { public void serializeAuxPoWHeader() throws Exception {
byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin")); 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[] expected = auxpowAsBytes;
byte[] actual = auxpow.bitcoinSerialize(); byte[] actual = auxpow.bitcoinSerialize();
assertArrayEquals(expected, actual); 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);
}
} }

View File

@ -29,7 +29,8 @@ public class DogecoinBlockTest {
@Test @Test
public void shouldParseBlock1() throws IOException { public void shouldParseBlock1() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block1.bin")); 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("82bc68038f6034c0596b6e313729793a887fded6e92a31fbdf70863f89d9bea2", block.getHashAsString());
assertEquals(1, block.getTransactions().size()); assertEquals(1, block.getTransactions().size());
} }
@ -41,7 +42,8 @@ public class DogecoinBlockTest {
@Test @Test
public void shouldParseBlock250000() throws IOException { public void shouldParseBlock250000() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block250000.bin")); 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()); assertEquals(2469341065L, block.getNonce());
final AuxPoW auxpow = block.getAuxPoW(); final AuxPoW auxpow = block.getAuxPoW();
assertNull(auxpow); assertNull(auxpow);
@ -58,7 +60,8 @@ public class DogecoinBlockTest {
@Test @Test
public void shouldParseBlock371337() throws IOException { public void shouldParseBlock371337() throws IOException {
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin")); 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("60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053", block.getHashAsString());
assertEquals(0, block.getNonce()); assertEquals(0, block.getNonce());
final AuxPoW auxpow = block.getAuxPoW(); final AuxPoW auxpow = block.getAuxPoW();
@ -69,14 +72,14 @@ public class DogecoinBlockTest {
assertEquals("45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9", parentBlock.getHashAsString()); assertEquals("45df41e40aba5b2a03d08bd1202a1c02ef3954d8aa22ea6c5ae62fd00f290ea9", parentBlock.getHashAsString());
assertNull(parentBlock.getTransactions()); assertNull(parentBlock.getTransactions());
final MerkleBranch blockchainMerkleBranch = auxpow.getBlockchainBranch(); final MerkleBranch blockchainMerkleBranch = auxpow.getChainMerkleBranch();
Sha256Hash[] expected = new Sha256Hash[] { Sha256Hash[] expected = new Sha256Hash[] {
new Sha256Hash("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"), new Sha256Hash("b541c848bc001d07d2bdf8643abab61d2c6ae50d5b2495815339a4b30703a46f"),
new Sha256Hash("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"), new Sha256Hash("78d6abe48cee514cf3496f4042039acb7e27616dcfc5de926ff0d6c7e5987be7"),
new Sha256Hash("a0469413ce64d67c43902d54ee3a380eff12ded22ca11cbd3842e15d48298103") 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(); final MerkleBranch coinbaseMerkleBranch = auxpow.getCoinbaseBranch();
expected = new Sha256Hash[] { expected = new Sha256Hash[] {
@ -84,7 +87,7 @@ public class DogecoinBlockTest {
new Sha256Hash("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"), new Sha256Hash("48f9e8fef3411944e27f49ec804462c9e124dca0954c71c8560e8a9dd218a452"),
new Sha256Hash("d11293660392e7c51f69477a6130237c72ecee2d0c1d3dc815841734c370331a") 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()); assertEquals(6, block.getTransactions().size());
} }

View File

@ -1,6 +1,7 @@
package org.bitcoinj.core; package org.bitcoinj.core;
import static org.bitcoinj.core.AuxPoWTest.params;
import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.params.TestNet3Params;
import org.junit.Test; import org.junit.Test;
@ -28,7 +29,7 @@ public class MerkleBranchTest {
new Sha256Hash("d8c6fe42ca25076159cd121a5e20c48c1bc53ab90730083e44a334566ea6bbcb") 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 { public void serializeMerkleBranch() throws Exception {
byte[] expected = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin")); byte[] expected = getBytes(getClass().getResourceAsStream("auxpow_merkle_branch.bin"));
MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, expected, 0, MerkleBranch branch = new MerkleBranch(params, (ChildMessage) null, expected, 0,
false, false); params.getDefaultSerializer());
byte[] actual = branch.bitcoinSerialize(); byte[] actual = branch.bitcoinSerialize();
assertArrayEquals(expected, actual); assertArrayEquals(expected, actual);