From 89cf3c6b1778632198c90335157fa7a9fe858623 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Sun, 28 Feb 2016 11:03:08 +0000 Subject: [PATCH] Handle legacy AuxPoW blocks Adds handling of legacy AuxPoW blocks before there was a merged mining header. --- src/main/java/org/bitcoinj/core/AuxPoW.java | 49 +++++++++++------- .../java/org/bitcoinj/core/AuxPoWTest.java | 40 ++++++++++---- .../core/auxpow_header_no_tx_header.bin | Bin 0 -> 2168 bytes 3 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 src/test/resources/org/bitcoinj/core/auxpow_header_no_tx_header.bin diff --git a/src/main/java/org/bitcoinj/core/AuxPoW.java b/src/main/java/org/bitcoinj/core/AuxPoW.java index 00eaf5e8..0d1ac04e 100644 --- a/src/main/java/org/bitcoinj/core/AuxPoW.java +++ b/src/main/java/org/bitcoinj/core/AuxPoW.java @@ -41,6 +41,12 @@ public class AuxPoW extends ChildMessage { (byte) 0xfa, (byte) 0xbe, "m".getBytes()[0], "m".getBytes()[0] }; + /** + * Maximum index of the merkle root hash in the coinbase transaction script, + * where no merged mining header is present. + */ + protected static final int MAX_INDEX_PC_BACKWARDS_COMPATIBILITY = 20; + private static final Logger log = LoggerFactory.getLogger(AuxPoW.class); private static final long serialVersionUID = -8567546957352643140L; @@ -290,44 +296,49 @@ public class AuxPoW extends ChildMessage { // 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 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 (pcHeader >= 0) { + if (pcHead >= 0) { if (throwException) { throw new VerificationException("Multiple merged mining headers in coinbase"); } return false; } - pcHeader = scriptIdx; + pcHead = scriptIdx; } else if (arrayMatch(script, scriptIdx, vchRootHash)) { pc = scriptIdx; } } - if (-1 == pcHeader) { - if (throwException) { - throw new VerificationException("MergedMiningHeader missing from parent coinbase"); - } - return false; - } - - if (-1 == pc) { + if (pc == -1) { if (throwException) { throw new VerificationException("Aux POW missing chain merkle root in parent coinbase"); } return false; } - if (pcHeader + MERGED_MINING_HEADER.length != pc) { - if (throwException) { - throw new VerificationException("Merged mining header is not just before chain merkle root"); + if (pcHead != -1) { + if (pcHead + MERGED_MINING_HEADER.length != pc) { + if (throwException) { + throw new VerificationException("Merged mining header is not just before chain merkle root"); + } + return false; + } + } else { + // For backward compatibility. + // Enforce only one chain merkle root by checking that it starts early in the coinbase. + // 8-12 bytes are enough to encode extraNonce and nBits. + if (pc > MAX_INDEX_PC_BACKWARDS_COMPATIBILITY) { + if (throwException) { + throw new VerificationException("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase"); + } + return false; } - return false; } // Ensure we are at a deterministic point in the merkle leaves by hashing @@ -361,12 +372,12 @@ public class AuxPoW extends ChildMessage { return false; } - BigInteger h = altcoinParams.getBlockDifficultyHash(getParentBlockHeader()) - .toBigInteger(); - if (h.compareTo(target) > 0) { + Sha256Hash hash = altcoinParams.getBlockDifficultyHash(getParentBlockHeader()); + BigInteger hashVal = hash.toBigInteger(); + if (hashVal.compareTo(target) > 0) { // Proof of work check failed! if (throwException) { - throw new VerificationException("Hash is higher than target: " + getParentBlockHeader().getHashAsString() + " vs " + throw new VerificationException("Hash is higher than target: " + hash.toString() + " vs " + target.toString(16)); } return false; diff --git a/src/test/java/org/bitcoinj/core/AuxPoWTest.java b/src/test/java/org/bitcoinj/core/AuxPoWTest.java index ac4b9208..9508ab8e 100644 --- a/src/test/java/org/bitcoinj/core/AuxPoWTest.java +++ b/src/test/java/org/bitcoinj/core/AuxPoWTest.java @@ -1,17 +1,13 @@ package org.bitcoinj.core; -import java.io.OutputStream; -import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; -import static org.bitcoinj.core.AuxPoW.MERGED_MINING_HEADER; import org.libdohj.core.AltcoinSerializer; -import org.libdohj.core.AuxPoWNetworkParameters; import org.libdohj.params.DogecoinMainNetParams; +import org.libdohj.params.DogecoinTestNet3Params; import static org.bitcoinj.core.Util.getBytes; -import static org.bitcoinj.core.Utils.reverseBytes; import static org.junit.Assert.*; import org.junit.Before; @@ -74,6 +70,26 @@ public class AuxPoWTest { auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), Utils.decodeCompactBits(0x1b06f8f0), true); } + + /** + * Validate the AuxPoW header with no explicit data header in the coinbase + * transaction. Namecoin block #19,414 + */ + @Test + public void checkAuxPoWHeaderNoTxHeader() throws Exception { + // Emulate Namecoin block hashing for this test + final NetworkParameters namecoinLikeParams = new DogecoinTestNet3Params() { + @Override + public Sha256Hash getBlockDifficultyHash(Block block) { + // Namecoin uses SHA256 hashes + return block.getHash(); + } + }; + byte[] auxpowAsBytes = getBytes(getClass().getResourceAsStream("auxpow_header_no_tx_header.bin")); + AuxPoW auxpow = new AuxPoW(namecoinLikeParams, auxpowAsBytes, (ChildMessage) null, namecoinLikeParams.getDefaultSerializer()); + auxpow.checkProofOfWork(Sha256Hash.wrap("5fb89c3b18c27bc38d351d516177cbd3504c95ca0494cbbbbd52f2fb5f2ff1ec"), + Utils.decodeCompactBits(0x1b00b269), true); + } @Rule public ExpectedException expectedEx = ExpectedException.none(); @@ -164,8 +180,9 @@ public class AuxPoWTest { } /** - * Catch the case that the merged mine header is missing from the coinbase - * transaction. + * Catch the case that the coinbase transaction does not contain details of + * the merged block. In this case we make the transaction script too short + * for it to do so. */ @Test public void shouldRejectIfMergedMineHeaderMissing() throws Exception { @@ -175,11 +192,14 @@ public class AuxPoWTest { // 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 + final byte[] paddedScriptBytes = new byte[in.getScriptBytes().length + (AuxPoW.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4)]; + Arrays.fill(paddedScriptBytes, (byte) 0); + System.arraycopy(in.getScriptBytes(), 8, paddedScriptBytes, (AuxPoW.MAX_INDEX_PC_BACKWARDS_COMPATIBILITY + 4), in.getScriptBytes().length - 8); + in.setScriptBytes(paddedScriptBytes); updateMerkleRootToMatchCoinbase(auxpow); expectedEx.expect(org.bitcoinj.core.VerificationException.class); - expectedEx.expectMessage("MergedMiningHeader missing from parent coinbase"); + expectedEx.expectMessage("Aux POW chain merkle root must start in the first 20 bytes of the parent coinbase"); auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), Utils.decodeCompactBits(0x1b06f8f0), true); } @@ -337,7 +357,7 @@ public class AuxPoWTest { 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"); + expectedEx.expectMessage("Hash is higher than target: 000000000003178bb23160cdbc81af53f47cae9f479acf1e69849da42fd5bfca vs 0"); auxpow.checkProofOfWork(Sha256Hash.wrap("0c836b86991631d34a8a68054e2f62db919b39d1ee43c27ab3344d6aa82fa609"), Utils.decodeCompactBits(0x00), true); diff --git a/src/test/resources/org/bitcoinj/core/auxpow_header_no_tx_header.bin b/src/test/resources/org/bitcoinj/core/auxpow_header_no_tx_header.bin new file mode 100644 index 0000000000000000000000000000000000000000..fd189bf6384266c8fe5c6af62efddcfb9371980e GIT binary patch literal 2168 zcmaKtc{r5o8^=dkhLAPOAbZ7RW-@auDeK5`LX>11+t@RMh>*P=Woa2kSU6<(^5XKlUxhcyar>D@MV z<7?vfgQ+q1a#&DV!gbbh!f5}w{CENHp8H~SK3o91iN*KO+W$iZ@$HQ4o#kTcr%RaR zzdok@SP5mmBx^8*3~;chlmB)y9HkC#rYjR!D&dE_!)TMQJ7GbW>{fKBlxNno4tAW%g(_>5_WhqS~i zUK~duTFbx9I8Rc{d<2FbKf$wQ5|~ApPu8n;X03?gb5AP31E7zF2Z;shfl9YRgVCJ08{F7mHsY9 zdk2730YV#yt$OfXOWA=phP)=<4O;s#S+f?ok^r_&Dje~ z)kFb^pFW1vBVqV?vY(HCy0}zArmNRl9s7~j41kkY`Vd8+-TySgUGCuRZlZ^-?&lSC zctC9D!stQX?Cc|5X66llazCC8q&?)e22kXeJxHbkuc?Mku0t(frCbVC zQWwRuNs*i=BdiU=R!tY2GIX$*&+}7s)O{>%7Woge* z^Ty^We6H_*09+lfLiQr6H`2?(Tb9tWraVWjLnZ=rQaQcFvGou}w*`HyRu(4b9IXTv7nuo1I4{in11_j{RQfF3RU+ z-tyxF)zOLJ2_QN`A7RK>(qod`+6wTAGn7w6a_^OcD}Ml(wdszn&QVrwD%fJ4=6%;G zUTlwiCgYsR)CVwDITGp1zVkWxW*$d?2Cq%Pp~!|^8mBa`aTIPRkSOS$G1VEvA#q8C zMisLG*~T}%!f^o3j^9OG_>J&&+GySRuD0!{G^TNlh{HWq0q`Bc8gbz}O;vcoy9vl`30+zFzlz~hRi~~5P|C6ER)mq=|TP1uhjk@peuoT*Sy!l}Oz0r~gLq7D* zRn2d*D=GWTqOHsY9JH>B%>#JwA_-CKbErrE-f-*s$OG?#j^yYoY(+!PyyzdCLKF*T z+9DQ#OsgHQ_7Khp`9wLpn{e_Ft8zpXeQG;}#bPE?5BbGbL=;J;Oe&RdCaS7n)5x2r zW$8(lg>>C_JyJ$X+O6(DreQ;GDJM$g&yzoyi*Bu!i}%&89PL!LbSHbLE3^REvT_QE zqSPmy!;s<*JI0_-w^#RJe>^Wz1#qJ$s}&1W8F+Ne>Va&UX5xzS;NQi+O;wbhe2U+A zi$Zb7+xIS9a_Il!%y0}|@ba=LSbXi3G@=-1P~P!TtG;Yn=$?s;)2#htLUi_@<+Ca? zl6LFI-^&fEl)TQ+>C-*^`+EmV<(ZaW)ywXNG^Qvg_n_=^>Tr6WzId)xmZW5%^TxBM zS7X~Q4eAs!q0t%jA(r6PH8NSVlQ>=Cxs?XzzVZKVO9DoxS8E1hO`^wNkgEaV z{>6DEztBg#TjfFpt%X&6FbE@2PuIUamB1>#uX8z~*!47eP*I%{^SDy1L+hrE+k1Z5 J2_+Yve*o<~R&oFU literal 0 HcmV?d00001