mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-11 17:55:53 +00:00
Add unit tests for AuxPoW header validation.
This commit is contained in:
parent
36753e5112
commit
258792c803
@ -313,12 +313,11 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
||||
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
|
||||
// Check that the coinbase transaction is in the merkle tree of the
|
||||
// parent block header
|
||||
if (!getCoinbaseBranch().calculateMerkleRoot(getCoinbase().getHash()).equals(parentBlockHeader.getMerkleRoot())) {
|
||||
if (throwException) {
|
||||
throw new VerificationException("Aux POW merkle root incorrect");
|
||||
@ -326,31 +325,35 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
||||
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();
|
||||
|
||||
// Check that the same work is not submitted twice to our chain.
|
||||
//
|
||||
int pcHead = -1;
|
||||
// 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 pcHeader = -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 (pcHeader >= 0) {
|
||||
if (throwException) {
|
||||
throw new VerificationException("Multiple merged mining headers in coinbase");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pcHead = scriptIdx;
|
||||
}
|
||||
if (arrayMatch(script, scriptIdx, vchRootHash)) {
|
||||
pcHeader = scriptIdx;
|
||||
} else if (arrayMatch(script, scriptIdx, vchRootHash)) {
|
||||
pc = scriptIdx;
|
||||
}
|
||||
}
|
||||
|
||||
if (-1 == pcHead) {
|
||||
if (-1 == pcHeader) {
|
||||
if (throwException) {
|
||||
throw new VerificationException("MergedMiningHeader missing from parent coinbase");
|
||||
}
|
||||
@ -364,7 +367,7 @@ public class AuxPoW extends ChildMessage implements Serializable {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pcHead + MERGED_MINING_HEADER.length != pc) {
|
||||
if (pcHeader + MERGED_MINING_HEADER.length != pc) {
|
||||
if (throwException) {
|
||||
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.
|
||||
*/
|
||||
protected long getChainID(final Block blockHeader) {
|
||||
public static long getChainID(final Block blockHeader) {
|
||||
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
|
||||
private transient int optimalEncodingMessageSize;
|
||||
|
||||
private List<Sha256Hash> branchHashes;
|
||||
private List<Sha256Hash> hashes;
|
||||
private long index;
|
||||
|
||||
public MerkleBranch(NetworkParameters params, @Nullable ChildMessage parent) {
|
||||
super(params);
|
||||
setParent(parent);
|
||||
|
||||
this.branchHashes = new ArrayList<Sha256Hash>();
|
||||
this.index = 0;
|
||||
this.hashes = new ArrayList<Sha256Hash>();
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +86,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
||||
super(params);
|
||||
setParent(parent);
|
||||
|
||||
this.branchHashes = hashes;
|
||||
this.hashes = hashes;
|
||||
this.index = branchSideMask;
|
||||
}
|
||||
|
||||
@ -111,19 +111,19 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
||||
|
||||
final int hashCount = (int) readVarInt();
|
||||
optimalEncodingMessageSize += VarInt.sizeOf(hashCount);
|
||||
branchHashes = new ArrayList<Sha256Hash>(hashCount);
|
||||
hashes = new ArrayList<Sha256Hash>(hashCount);
|
||||
for (int hashIdx = 0; hashIdx < hashCount; hashIdx++) {
|
||||
branchHashes.add(readHash());
|
||||
hashes.add(readHash());
|
||||
}
|
||||
optimalEncodingMessageSize += 32 * hashCount;
|
||||
index = readUint32();
|
||||
setIndex(readUint32());
|
||||
optimalEncodingMessageSize += 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
|
||||
stream.write(new VarInt(branchHashes.size()).encode());
|
||||
for (Sha256Hash hash: branchHashes) {
|
||||
stream.write(new VarInt(hashes.size()).encode());
|
||||
for (Sha256Hash hash: hashes) {
|
||||
stream.write(Utils.reverseBytes(hash.getBytes()));
|
||||
}
|
||||
Utils.uint32ToByteStreamLE(index, stream);
|
||||
@ -137,7 +137,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
||||
byte[] target = reverseBytes(leaf.getBytes());
|
||||
long mask = index;
|
||||
|
||||
for (Sha256Hash hash: branchHashes) {
|
||||
for (Sha256Hash hash: hashes) {
|
||||
target = (mask & 1) == 0
|
||||
? doubleDigestTwoBuffers(target, 0, 32, reverseBytes(hash.getBytes()), 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
public int size() {
|
||||
return branchHashes.size();
|
||||
return hashes.size();
|
||||
}
|
||||
|
||||
public int getOptimalEncodingMessageSize() {
|
||||
@ -214,7 +229,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
||||
|
||||
MerkleBranch input = (MerkleBranch) o;
|
||||
|
||||
if (!branchHashes.equals(input.branchHashes)) return false;
|
||||
if (!hashes.equals(input.hashes)) return false;
|
||||
if (index != input.index) return false;
|
||||
|
||||
return true;
|
||||
@ -223,7 +238,7 @@ public class MerkleBranch extends ChildMessage implements Serializable {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 1;
|
||||
result = 31 * result + branchHashes.hashCode();
|
||||
result = 31 * result + hashes.hashCode();
|
||||
result = 31 * result + (int) index;
|
||||
return result;
|
||||
}
|
||||
|
@ -1,19 +1,32 @@
|
||||
package org.bitcoinj.core;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import org.altcoinj.params.DogecoinTestNet3Params;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import org.altcoinj.core.AltcoinSerializer;
|
||||
import org.altcoinj.params.DogecoinMainNetParams;
|
||||
import org.junit.Test;
|
||||
|
||||
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.assertEquals;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
* AuxPoW header parsing/serialization and validation
|
||||
*/
|
||||
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.
|
||||
@ -56,4 +69,290 @@ public class AuxPoWTest {
|
||||
auxpow.checkProofOfWork(new Sha256Hash("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"),
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user