From d0f1663305f8ffb2df949e096ed79d62802af4ef Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Mon, 5 Aug 2019 08:12:17 -0400 Subject: [PATCH] Merge #16363: test: Add test for BIP30 duplicate tx fa8489a15511f61a372473927e73c34692bbec23 test: Add test for BIP30 duplicate tx (MarcoFalke) 77770d95e2838d7665fa8f621e9e83d79f9b3196 test: Properly serialize BIP34 coinbase height (MarcoFalke) Pull request description: This adds a test for BIP30 to check that duplicate txs can exist in the blockchain given the first one was completely spent when the second one is added. (Requested by ajtowns in https://github.com/bitcoin/bitcoin/pull/16333#issuecomment-508604071) We can not add a test that a later duplicate tx overwrites a previous one, because BIP30 is always enforced on regtest. If someone feels strongly about such a test, some Bitcoin Core code would have to be modified, which can be done in a follow up pull request. Also, add a commit to fix the BIP34 test failures reported in https://github.com/bitcoin/bitcoin/pull/14633#issue-227712540 ACKs for top commit: laanwj: Code review ACK fa8489a15511f61a372473927e73c34692bbec23 Tree-SHA512: c707d0bdc93937263876b603425b53322a2a9f9ec3f50716ae2fa9de8ddc644beb22b26c1bfde7f4aab102633e096b354ef380db919176bd2cb44a2828f884aa --- test/functional/feature_block.py | 99 +++++++++++++------- test/functional/test_framework/blocktools.py | 27 ++---- 2 files changed, 76 insertions(+), 50 deletions(-) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index e6d1cf44b9..7232a8d073 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -65,6 +65,10 @@ class CBrokenBlock(CBlock): def normal_serialize(self): return super().serialize() + +DUPLICATE_COINBASE_SCRIPT_SIG = b'\x01\x78' # Valid for block at height 120 + + class FullBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -95,6 +99,13 @@ class FullBlockTest(BitcoinTestFramework): self.spendable_outputs = [] # Create a new block + b_dup_cb = self.next_block('dup_cb') + b_dup_cb.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG + b_dup_cb.vtx[0].rehash() + duplicate_tx = b_dup_cb.vtx[0] + b_dup_cb = self.update_block('dup_cb', []) + self.send_blocks([b_dup_cb]) + b0 = self.next_block(0) self.save_spendable_output() self.send_blocks([b0]) @@ -717,7 +728,7 @@ class FullBlockTest(BitcoinTestFramework): # Test a few invalid tx types # - # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () # \-> ??? (17) # @@ -743,14 +754,14 @@ class FullBlockTest(BitcoinTestFramework): # reset to good chain self.move_tip(57) - b60 = self.next_block(60, spend=out[17]) + b60 = self.next_block(60) self.send_blocks([b60], True) self.save_spendable_output() - # Test BIP30 + # Test BIP30 (reject duplicate) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b61 (18) + # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () + # \-> b61 () # # Blocks are not allowed to contain a transaction whose id matches that of an earlier, # not-fully-spent transaction in the same chain. To test, make identical coinbases; @@ -758,20 +769,44 @@ class FullBlockTest(BitcoinTestFramework): # self.log.info("Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)") self.move_tip(60) - b61 = self.next_block(61, spend=out[18]) - b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig # Equalize the coinbases + b61 = self.next_block(61) + b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG b61.vtx[0].rehash() b61 = self.update_block(61, []) - assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize()) + assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize()) self.send_blocks([b61], success=False, reject_reason='bad-txns-BIP30', reconnect=True) + # Test BIP30 (allow duplicate if spent) + # + # -> b57 (16) -> b60 () + # \-> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # + self.move_tip(57) + b_spend_dup_cb = self.next_block('spend_dup_cb') + tx = CTransaction() + tx.vin.append(CTxIn(COutPoint(duplicate_tx.sha256, 0))) + tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) + self.sign_tx(tx, duplicate_tx) + tx.rehash() + b_spend_dup_cb = self.update_block('spend_dup_cb', [tx]) + + b_dup_2 = self.next_block('dup_2') + b_dup_2.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG + b_dup_2.vtx[0].rehash() + b_dup_2 = self.update_block('dup_2', []) + assert_equal(duplicate_tx.serialize(), b_dup_2.vtx[0].serialize()) + assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 119) + self.send_blocks([b_spend_dup_cb, b_dup_2], success=True) + # The duplicate has less confirmations + assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 1) + # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b62 (18) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # \-> b62 (18) # self.log.info("Reject a block with a transaction with a nonfinal locktime") - self.move_tip(60) + self.move_tip('dup_2') b62 = self.next_block(62) tx = CTransaction() tx.nLockTime = 0xffffffff # this locktime is non-final @@ -784,11 +819,11 @@ class FullBlockTest(BitcoinTestFramework): # Test a non-final coinbase is also rejected # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) - # \-> b63 (-) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () + # \-> b63 (-) # self.log.info("Reject a block with a coinbase transaction with a nonfinal locktime") - self.move_tip(60) + self.move_tip('dup_2') b63 = self.next_block(63) b63.vtx[0].nLockTime = 0xffffffff b63.vtx[0].vin[0].nSequence = 0xDEADBEEF @@ -804,14 +839,14 @@ class FullBlockTest(BitcoinTestFramework): # What matters is that the receiving node should not reject the bloated block, and then reject the canonical # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) # - # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) - # \ - # b64a (18) + # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18) + # \ + # b64a (18) # b64a is a bloated block (non-canonical varint) # b64 is a good block (same as b64 but w/ canonical varint) # self.log.info("Accept a valid block even if a bloated version of the block has previously been sent") - self.move_tip(60) + self.move_tip('dup_2') regular_block = self.next_block("64a", spend=out[18]) # make it a "broken_block," with non-canonical serialization @@ -837,7 +872,7 @@ class FullBlockTest(BitcoinTestFramework): node.disconnect_p2ps() self.reconnect_p2p() - self.move_tip(60) + self.move_tip('dup_2') b64 = CBlock(b64a) b64.vtx = copy.deepcopy(b64a.vtx) assert_equal(b64.hash, b64a.hash) @@ -849,7 +884,7 @@ class FullBlockTest(BitcoinTestFramework): # Spend an output created in the block itself # - # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) + # -> b_dup_2 () -> b64 (18) -> b65 (19) # self.log.info("Accept a block with a transaction spending an output created in the same block") self.move_tip(64) @@ -862,8 +897,8 @@ class FullBlockTest(BitcoinTestFramework): # Attempt to spend an output created later in the same block # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) - # \-> b66 (20) + # -> b64 (18) -> b65 (19) + # \-> b66 (20) self.log.info("Reject a block with a transaction spending an output created later in the same block") self.move_tip(65) b66 = self.next_block(66) @@ -874,8 +909,8 @@ class FullBlockTest(BitcoinTestFramework): # Attempt to double-spend a transaction created in a block # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) - # \-> b67 (20) + # -> b64 (18) -> b65 (19) + # \-> b67 (20) # # self.log.info("Reject a block with a transaction double spending a transaction created in the same block") @@ -889,8 +924,8 @@ class FullBlockTest(BitcoinTestFramework): # More tests of block subsidy # - # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) - # \-> b68 (20) + # -> b64 (18) -> b65 (19) -> b69 (20) + # \-> b68 (20) # # b68 - coinbase with an extra 10 satoshis, # creates a tx that has 9 satoshis from out[20] go to fees @@ -916,8 +951,8 @@ class FullBlockTest(BitcoinTestFramework): # Test spending the outpoint of a non-existent transaction # - # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) - # \-> b70 (21) + # -> b65 (19) -> b69 (20) + # \-> b70 (21) # self.log.info("Reject a block containing a transaction spending from a non-existent input") self.move_tip(69) @@ -932,8 +967,8 @@ class FullBlockTest(BitcoinTestFramework): # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) # - # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) - # \-> b71 (21) + # -> b65 (19) -> b69 (20) -> b72 (21) + # \-> b71 (21) # # b72 is a good block. # b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72. @@ -961,8 +996,8 @@ class FullBlockTest(BitcoinTestFramework): # Test some invalid scripts and MAX_BLOCK_SIGOPS # - # -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) - # \-> b** (22) + # -> b69 (20) -> b72 (21) + # \-> b** (22) # # b73 - tx with excessive sigops that are placed after an excessively large script element. diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 385dfe3383..5714b042ac 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -12,9 +12,8 @@ from .messages import ( CTransaction, CTxIn, CTxOut, - ser_string, ) -from .script import CScript, OP_TRUE, OP_CHECKSIG +from .script import CScript, CScriptNum, CScriptOp, OP_TRUE, OP_CHECKSIG from .util import assert_equal, hex_str_to_bytes from io import BytesIO @@ -33,20 +32,13 @@ def create_block(hashprev, coinbase, ntime=None): block.calc_sha256() return block -def serialize_script_num(value): - r = bytearray(0) - if value == 0: - return r - neg = value < 0 - absvalue = -value if neg else value - while (absvalue): - r.append(int(absvalue & 0xff)) - absvalue >>= 8 - if r[-1] & 0x80: - r.append(0x80 if neg else 0) - elif neg: - r[-1] |= 0x80 - return r +def script_BIP34_coinbase_height(height): + if height <= 16: + res = CScriptOp.encode_op_n(height) + # Append dummy to increase scriptSig size above 2 (see bad-cb-length consensus rule) + return CScript([res, OP_TRUE]) + return CScript([CScriptNum(height)]) + def create_coinbase(height, pubkey=None, dip4_activated=False): """Create a coinbase transaction, assuming no miner fees. @@ -54,8 +46,7 @@ def create_coinbase(height, pubkey=None, dip4_activated=False): If pubkey is passed in, the coinbase output will be a P2PK output; otherwise an anyone-can-spend output.""" coinbase = CTransaction() - coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), - ser_string(serialize_script_num(height)), 0xffffffff)) + coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = 500 * COIN halvings = int(height / 150) # regtest