mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-02 13:37:24 +00:00
Add unit tests for AuxPoW header validation.
This commit is contained in:
@@ -313,12 +313,11 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the chain merkle root is in the coinbase
|
|
||||||
Sha256Hash nRootHash = getChainMerkleBranch().calculateMerkleRoot(hashAuxBlock);
|
Sha256Hash nRootHash = getChainMerkleBranch().calculateMerkleRoot(hashAuxBlock);
|
||||||
final byte[] vchRootHash = nRootHash.getBytes();
|
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
|
// Check that the coinbase transaction is in the merkle tree of the
|
||||||
|
// parent block header
|
||||||
if (!getCoinbaseBranch().calculateMerkleRoot(getCoinbase().getHash()).equals(parentBlockHeader.getMerkleRoot())) {
|
if (!getCoinbaseBranch().calculateMerkleRoot(getCoinbase().getHash()).equals(parentBlockHeader.getMerkleRoot())) {
|
||||||
if (throwException) {
|
if (throwException) {
|
||||||
throw new VerificationException("Aux POW merkle root incorrect");
|
throw new VerificationException("Aux POW merkle root incorrect");
|
||||||
@@ -326,31 +325,35 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.getCoinbase().getInputs().isEmpty()) {
|
||||||
|
throw new VerificationException("Coinbase transaction has no inputs");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the chain merkle root is in the coinbase
|
||||||
final byte[] script = this.getCoinbase().getInput(0).getScriptBytes();
|
final byte[] script = this.getCoinbase().getInput(0).getScriptBytes();
|
||||||
|
|
||||||
// Check that the same work is not submitted twice to our chain.
|
// Check that the same work is not submitted twice to our chain, by
|
||||||
//
|
// confirming that the child block hash is in the coinbase merkle tree
|
||||||
int pcHead = -1;
|
int pcHeader = -1;
|
||||||
int pc = -1;
|
int pc = -1;
|
||||||
|
|
||||||
for (int scriptIdx = 0; scriptIdx < script.length; scriptIdx++) {
|
for (int scriptIdx = 0; scriptIdx < script.length; scriptIdx++) {
|
||||||
if (arrayMatch(script, scriptIdx, MERGED_MINING_HEADER)) {
|
if (arrayMatch(script, scriptIdx, MERGED_MINING_HEADER)) {
|
||||||
// Enforce only one chain merkle root by checking that a single instance of the merged
|
// Enforce only one chain merkle root by checking that a single instance of the merged
|
||||||
// mining header exists just before.
|
// mining header exists just before.
|
||||||
if (pcHead >= 0) {
|
if (pcHeader >= 0) {
|
||||||
if (throwException) {
|
if (throwException) {
|
||||||
throw new VerificationException("Multiple merged mining headers in coinbase");
|
throw new VerificationException("Multiple merged mining headers in coinbase");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pcHead = scriptIdx;
|
pcHeader = scriptIdx;
|
||||||
}
|
} else if (arrayMatch(script, scriptIdx, vchRootHash)) {
|
||||||
if (arrayMatch(script, scriptIdx, vchRootHash)) {
|
|
||||||
pc = scriptIdx;
|
pc = scriptIdx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-1 == pcHead) {
|
if (-1 == pcHeader) {
|
||||||
if (throwException) {
|
if (throwException) {
|
||||||
throw new VerificationException("MergedMiningHeader missing from parent coinbase");
|
throw new VerificationException("MergedMiningHeader missing from parent coinbase");
|
||||||
}
|
}
|
||||||
@@ -364,7 +367,7 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pcHead + MERGED_MINING_HEADER.length != pc) {
|
if (pcHeader + MERGED_MINING_HEADER.length != pc) {
|
||||||
if (throwException) {
|
if (throwException) {
|
||||||
throw new VerificationException("Merged mining header is not just before chain merkle root");
|
throw new VerificationException("Merged mining header is not just before chain merkle root");
|
||||||
}
|
}
|
||||||
@@ -449,7 +452,22 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* Get the chain ID from a block header.
|
* Get the chain ID from a block header.
|
||||||
*/
|
*/
|
||||||
protected long getChainID(final Block blockHeader) {
|
public static long getChainID(final Block blockHeader) {
|
||||||
return blockHeader.getVersion() / AltcoinBlock.BLOCK_VERSION_CHAIN_START;
|
return blockHeader.getVersion() / AltcoinBlock.BLOCK_VERSION_CHAIN_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the merkle branch used to connect the coinbase transaction to the
|
||||||
|
* parent block header.
|
||||||
|
*/
|
||||||
|
public void setCoinbaseBranch(final MerkleBranch merkleBranch) {
|
||||||
|
this.coinbaseBranch = merkleBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the parent chain block header.
|
||||||
|
*/
|
||||||
|
public void setParentBlockHeader(final AltcoinBlock header) {
|
||||||
|
this.parentBlockHeader = header;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,15 +48,15 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
// can properly keep track of optimal encoded size
|
// can properly keep track of optimal encoded size
|
||||||
private transient int optimalEncodingMessageSize;
|
private transient int optimalEncodingMessageSize;
|
||||||
|
|
||||||
private List<Sha256Hash> branchHashes;
|
private List<Sha256Hash> hashes;
|
||||||
private long index;
|
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.hashes = new ArrayList<Sha256Hash>();
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,7 +86,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
super(params);
|
super(params);
|
||||||
setParent(parent);
|
setParent(parent);
|
||||||
|
|
||||||
this.branchHashes = hashes;
|
this.hashes = hashes;
|
||||||
this.index = branchSideMask;
|
this.index = branchSideMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,19 +111,19 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
|
|
||||||
final int hashCount = (int) readVarInt();
|
final int hashCount = (int) readVarInt();
|
||||||
optimalEncodingMessageSize += VarInt.sizeOf(hashCount);
|
optimalEncodingMessageSize += VarInt.sizeOf(hashCount);
|
||||||
branchHashes = new ArrayList<Sha256Hash>(hashCount);
|
hashes = new ArrayList<Sha256Hash>(hashCount);
|
||||||
for (int hashIdx = 0; hashIdx < hashCount; hashIdx++) {
|
for (int hashIdx = 0; hashIdx < hashCount; hashIdx++) {
|
||||||
branchHashes.add(readHash());
|
hashes.add(readHash());
|
||||||
}
|
}
|
||||||
optimalEncodingMessageSize += 32 * hashCount;
|
optimalEncodingMessageSize += 32 * hashCount;
|
||||||
index = readUint32();
|
setIndex(readUint32());
|
||||||
optimalEncodingMessageSize += 4;
|
optimalEncodingMessageSize += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||||
stream.write(new VarInt(branchHashes.size()).encode());
|
stream.write(new VarInt(hashes.size()).encode());
|
||||||
for (Sha256Hash hash: branchHashes) {
|
for (Sha256Hash hash: hashes) {
|
||||||
stream.write(Utils.reverseBytes(hash.getBytes()));
|
stream.write(Utils.reverseBytes(hash.getBytes()));
|
||||||
}
|
}
|
||||||
Utils.uint32ToByteStreamLE(index, stream);
|
Utils.uint32ToByteStreamLE(index, stream);
|
||||||
@@ -137,7 +137,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
byte[] target = reverseBytes(leaf.getBytes());
|
byte[] target = reverseBytes(leaf.getBytes());
|
||||||
long mask = index;
|
long mask = index;
|
||||||
|
|
||||||
for (Sha256Hash hash: branchHashes) {
|
for (Sha256Hash hash: hashes) {
|
||||||
target = (mask & 1) == 0
|
target = (mask & 1) == 0
|
||||||
? doubleDigestTwoBuffers(target, 0, 32, reverseBytes(hash.getBytes()), 0, 32)
|
? doubleDigestTwoBuffers(target, 0, 32, reverseBytes(hash.getBytes()), 0, 32)
|
||||||
: doubleDigestTwoBuffers(reverseBytes(hash.getBytes()), 0, 32, target, 0, 32);
|
: doubleDigestTwoBuffers(reverseBytes(hash.getBytes()), 0, 32, target, 0, 32);
|
||||||
@@ -150,7 +150,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
* Get the hashes which make up this branch.
|
* Get the hashes which make up this branch.
|
||||||
*/
|
*/
|
||||||
public List<Sha256Hash> getHashes() {
|
public List<Sha256Hash> getHashes() {
|
||||||
return Collections.unmodifiableList(this.branchHashes);
|
return Collections.unmodifiableList(this.hashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,11 +162,26 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param hashes the hashes to set
|
||||||
|
*/
|
||||||
|
public void setHashes(List<Sha256Hash> hashes) {
|
||||||
|
this.hashes = hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the mask used to determine the sides in which hashes are applied.
|
||||||
|
*/
|
||||||
|
public void setIndex(final long newIndex) {
|
||||||
|
assert newIndex >= 0;
|
||||||
|
this.index = newIndex;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of hashes in this branch.
|
* Get the number of hashes in this branch.
|
||||||
*/
|
*/
|
||||||
public int size() {
|
public int size() {
|
||||||
return branchHashes.size();
|
return hashes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOptimalEncodingMessageSize() {
|
public int getOptimalEncodingMessageSize() {
|
||||||
@@ -214,7 +229,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
|
|
||||||
MerkleBranch input = (MerkleBranch) o;
|
MerkleBranch input = (MerkleBranch) o;
|
||||||
|
|
||||||
if (!branchHashes.equals(input.branchHashes)) return false;
|
if (!hashes.equals(input.hashes)) return false;
|
||||||
if (index != input.index) return false;
|
if (index != input.index) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -223,7 +238,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = 31 * result + branchHashes.hashCode();
|
result = 31 * result + hashes.hashCode();
|
||||||
result = 31 * result + (int) index;
|
result = 31 * result + (int) index;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
package org.bitcoinj.core;
|
package org.bitcoinj.core;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.util.Arrays;
|
||||||
import org.altcoinj.params.DogecoinTestNet3Params;
|
import java.util.Collections;
|
||||||
|
import org.altcoinj.core.AltcoinSerializer;
|
||||||
|
import org.altcoinj.params.DogecoinMainNetParams;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.bitcoinj.core.Util.getBytes;
|
import static org.bitcoinj.core.Util.getBytes;
|
||||||
|
import static org.bitcoinj.core.Utils.doubleDigestTwoBuffers;
|
||||||
|
import static org.bitcoinj.core.Utils.reverseBytes;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AuxPoW header parsing/serialization and validation
|
* AuxPoW header parsing/serialization and validation
|
||||||
*/
|
*/
|
||||||
public class AuxPoWTest {
|
public class AuxPoWTest {
|
||||||
static final NetworkParameters params = DogecoinTestNet3Params.get();
|
static final NetworkParameters params = DogecoinMainNetParams.get();
|
||||||
|
private static final int MERKLE_ROOT_COINBASE_INDEX = 0;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
Context context = new Context(params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the AuxPoW header from Dogecoin block #403,931.
|
* Parse the AuxPoW header from Dogecoin block #403,931.
|
||||||
@@ -56,4 +69,290 @@ public class AuxPoWTest {
|
|||||||
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
Utils.decodeCompactBits(0x1b06f8f0), true);
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException expectedEx = ExpectedException.none();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a non-generate AuxPoW transaction is rejected.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectNonGenerateAuxPoW() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
auxpow.getCoinbaseBranch().setIndex(0x01);
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("AuxPow is not a generate");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that block headers from the child chain are rejected as parent
|
||||||
|
* chain for AuxPoW, via checking of the chain IDs.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectOwnChainID() throws Exception {
|
||||||
|
byte[] payload = Util.getBytes(getClass().getResourceAsStream("dogecoin_block371337.bin"));
|
||||||
|
AltcoinSerializer serializer = (AltcoinSerializer)params.getDefaultSerializer();
|
||||||
|
final AltcoinBlock block = (AltcoinBlock)serializer.makeBlock(payload);
|
||||||
|
final AuxPoW auxpow = block.getAuxPoW();
|
||||||
|
auxpow.setParentBlockHeader((AltcoinBlock)block.cloneAsHeader());
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW parent has our chain ID");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that where the merkle branch is far too long to use, it's rejected.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectVeryLongMerkleBranch() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
auxpow.getChainMerkleBranch().setHashes(Arrays.asList(new Sha256Hash[32]));
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW chain merkle branch too long");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Later steps in AuxPoW validation depend on the contents of the coinbase
|
||||||
|
* transaction. Obviously that's useless if we don't check the coinbase
|
||||||
|
* transaction is actually part of the parent chain block, so first we test
|
||||||
|
* that the transaction hash is part of the merkle tree. This test modifies
|
||||||
|
* the transaction, invalidating the hash, to confirm that it's rejected.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfCoinbaseTransactionNotInMerkleBranch() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
auxpow.getCoinbase().clearOutputs();
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW merkle root incorrect");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that in case of a malformed coinbase transaction (no inputs) it's
|
||||||
|
* caught and processed neatly.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfCoinbaseTransactionHasNoInputs() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
auxpow.getCoinbase().clearInputs();
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Coinbase transaction has no inputs");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch the case that the merged mine header is missing from the coinbase
|
||||||
|
* transaction.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfMergedMineHeaderMissing() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
in.getScriptBytes()[4] = 0; // Break the first byte of the header
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("MergedMiningHeader missing from parent coinbase");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch the case that more than one merged mine header is present in the
|
||||||
|
* coinbase transaction (this is considered an attempt to confuse the parser).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfMergedMineHeaderDuplicated() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
final byte[] newBytes = Arrays.copyOf(in.getScriptBytes(), in.getScriptBytes().length + 4);
|
||||||
|
for (int byteIdx = 0; byteIdx < AuxPoW.MERGED_MINING_HEADER.length; byteIdx++) {
|
||||||
|
newBytes[newBytes.length - 4 + byteIdx] = AuxPoW.MERGED_MINING_HEADER[byteIdx];
|
||||||
|
}
|
||||||
|
in.setScriptBytes(newBytes);
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Multiple merged mining headers in coinbase");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch the case that the chain merkle branch is missing from the coinbase
|
||||||
|
* transaction. The chain merkle branch is used to prove that the block was
|
||||||
|
* mined for chain or chains including this one (i.e. random proof of work
|
||||||
|
* cannot be taken from any merged-mined blockchain and reused).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfCoinbaseMissingChainMerkleRoot() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
in.getScriptBytes()[8] = 0; // Break the first byte of the chain merkle root
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW missing chain merkle root in parent coinbase");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch the case that the chain merkle branch is not immediately after the
|
||||||
|
* merged mine header in the coinbase transaction (this is considered an
|
||||||
|
* attempt to confuse the parser).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfChainMerkleRootNotAfterHeader() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
final byte[] newBytes = Arrays.copyOf(in.getScriptBytes(), in.getScriptBytes().length + 1);
|
||||||
|
// Copy every byte after the merged-mine header forward one byte. We
|
||||||
|
// have to do this from the end of the array backwards to avoid overwriting
|
||||||
|
// the next byte to copy.
|
||||||
|
for (int byteIdx = newBytes.length - 1; byteIdx > 8; byteIdx--) {
|
||||||
|
newBytes[byteIdx] = newBytes[byteIdx - 1];
|
||||||
|
}
|
||||||
|
newBytes[8] = (byte) 0xff;
|
||||||
|
in.setScriptBytes(newBytes);
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Merged mining header is not just before chain merkle root");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch the case that the chain merkle branch is not immediately after the
|
||||||
|
* merged mine header in the coinbase transaction (this is considered an
|
||||||
|
* attempt to confuse the parser).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfScriptBytesTooShort() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
final byte[] newBytes = Arrays.copyOf(in.getScriptBytes(), in.getScriptBytes().length - 12);
|
||||||
|
in.setScriptBytes(newBytes);
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW missing chain merkle tree size and nonce in parent coinbase");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch the case that the chain merkle branch size in the coinbase transaction
|
||||||
|
* does not match the size of the merkle brach in the AuxPoW header.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfCoinbaseMerkleBranchSizeMismatch() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
in.getScriptBytes()[40] = 3; // Break the merkle branch length
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW merkle branch size does not match parent coinbase");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to ensure that the same work is not submitted more than once,
|
||||||
|
* confirm that the merkle branch index is correct for our chain ID.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectIfNonceIncorrect() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
// This will also break the difficulty check, but as that doesn't occur
|
||||||
|
// until the end, we can get away with it.
|
||||||
|
final TransactionInput in = auxpow.getCoinbase().getInput(0);
|
||||||
|
in.getScriptBytes()[44] = (byte) 0xff; // Break the nonce value
|
||||||
|
updateMerkleRootToMatchCoinbase(auxpow);
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Aux POW wrong index");
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x1b06f8f0), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Having validated the AuxPoW header, the last check is that the block hash
|
||||||
|
* meets the target difficulty.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldRejectHashAboveTarget() throws Exception {
|
||||||
|
final byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header.bin"));
|
||||||
|
final AuxPoW auxpow = new AuxPoW(params, auxpowAsBytes, (ChildMessage) null, params.getDefaultSerializer());
|
||||||
|
|
||||||
|
expectedEx.expect(org.bitcoinj.core.VerificationException.class);
|
||||||
|
expectedEx.expectMessage("Hash is higher than target: a22a9b01671d639fa6389f62ecf8ce69204c8ed41d5f1a745e0c5ba7116d5b4c vs 0");
|
||||||
|
|
||||||
|
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||||
|
Utils.decodeCompactBits(0x00), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix up the merkle root of the parent block header to match the
|
||||||
|
* coinbase transaction.
|
||||||
|
*/
|
||||||
|
private void updateMerkleRootToMatchCoinbase(final AuxPoW auxpow) {
|
||||||
|
final Transaction coinbase = auxpow.getCoinbase();
|
||||||
|
|
||||||
|
final Sha256Hash revisedCoinbaseHash = coinbase.getHash();
|
||||||
|
// The coinbase hash is the single leaf node in the merkle tree,
|
||||||
|
// so to get the root we need to hash it with itself.
|
||||||
|
// Note that bytes are reversed for hashing
|
||||||
|
final byte[] revisedMerkleRootBytes = doubleDigestTwoBuffers(
|
||||||
|
reverseBytes(revisedCoinbaseHash.getBytes()), 0, 32,
|
||||||
|
reverseBytes(revisedCoinbaseHash.getBytes()), 0, 32);
|
||||||
|
final Sha256Hash revisedMerkleRoot = new Sha256Hash(reverseBytes(revisedMerkleRootBytes));
|
||||||
|
auxpow.getParentBlockHeader().setMerkleRoot(revisedMerkleRoot);
|
||||||
|
auxpow.setCoinbaseBranch(new MerkleBranch(params, auxpow,
|
||||||
|
Collections.singletonList(revisedCoinbaseHash), MERKLE_ROOT_COINBASE_INDEX));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user