3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-11 17:55:53 +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 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

View File

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

View File

@ -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;
}
/**

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 java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
/**
* <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 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;
}
}

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.
* 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<Sha256Hash> branchHashes;
private long branchSideMask;
private List<Sha256Hash> branchHashes;
private long index;
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) {
super(params);
setParent(parent);
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 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;
}
}

View File

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

View File

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

View File

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