Skip to content

Commit 62117f9

Browse files
author
MarcoFalke
committed
Merge #16363: test: Add test for BIP30 duplicate tx
fa8489a test: Add test for BIP30 duplicate tx (MarcoFalke) 77770d9 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 bitcoin/bitcoin#16333 (comment)) 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 bitcoin/bitcoin#14633 (comment) ACKs for top commit: laanwj: Code review ACK fa8489a Tree-SHA512: c707d0bdc93937263876b603425b53322a2a9f9ec3f50716ae2fa9de8ddc644beb22b26c1bfde7f4aab102633e096b354ef380db919176bd2cb44a2828f884aa
2 parents d5ea8f4 + fa8489a commit 62117f9

File tree

2 files changed

+78
-49
lines changed

2 files changed

+78
-49
lines changed

test/functional/feature_block.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def serialize(self, with_witness=False):
7474
def normal_serialize(self):
7575
return super().serialize()
7676

77+
78+
DUPLICATE_COINBASE_SCRIPT_SIG = b'\x01\x78' # Valid for block at height 120
79+
80+
7781
class FullBlockTest(BitcoinTestFramework):
7882
def set_test_params(self):
7983
self.num_nodes = 1
@@ -96,6 +100,13 @@ def run_test(self):
96100
self.spendable_outputs = []
97101

98102
# Create a new block
103+
b_dup_cb = self.next_block('dup_cb')
104+
b_dup_cb.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG
105+
b_dup_cb.vtx[0].rehash()
106+
duplicate_tx = b_dup_cb.vtx[0]
107+
b_dup_cb = self.update_block('dup_cb', [])
108+
self.send_blocks([b_dup_cb])
109+
99110
b0 = self.next_block(0)
100111
self.save_spendable_output()
101112
self.send_blocks([b0])
@@ -758,7 +769,7 @@ def run_test(self):
758769

759770
# Test a few invalid tx types
760771
#
761-
# -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
772+
# -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 ()
762773
# \-> ??? (17)
763774
#
764775

@@ -784,35 +795,59 @@ def run_test(self):
784795

785796
# reset to good chain
786797
self.move_tip(57)
787-
b60 = self.next_block(60, spend=out[17])
798+
b60 = self.next_block(60)
788799
self.send_blocks([b60], True)
789800
self.save_spendable_output()
790801

791-
# Test BIP30
802+
# Test BIP30 (reject duplicate)
792803
#
793-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
794-
# \-> b61 (18)
804+
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 ()
805+
# \-> b61 ()
795806
#
796807
# Blocks are not allowed to contain a transaction whose id matches that of an earlier,
797808
# not-fully-spent transaction in the same chain. To test, make identical coinbases;
798809
# the second one should be rejected.
799810
#
800811
self.log.info("Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)")
801812
self.move_tip(60)
802-
b61 = self.next_block(61, spend=out[18])
803-
b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig # Equalize the coinbases
813+
b61 = self.next_block(61)
814+
b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG
804815
b61.vtx[0].rehash()
805816
b61 = self.update_block(61, [])
806-
assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize())
817+
assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize())
807818
self.send_blocks([b61], success=False, reject_reason='bad-txns-BIP30', reconnect=True)
808819

820+
# Test BIP30 (allow duplicate if spent)
821+
#
822+
# -> b57 (16) -> b60 ()
823+
# \-> b_spend_dup_cb (b_dup_cb) -> b_dup_2 ()
824+
#
825+
self.move_tip(57)
826+
b_spend_dup_cb = self.next_block('spend_dup_cb')
827+
tx = CTransaction()
828+
tx.vin.append(CTxIn(COutPoint(duplicate_tx.sha256, 0)))
829+
tx.vout.append(CTxOut(0, CScript([OP_TRUE])))
830+
self.sign_tx(tx, duplicate_tx)
831+
tx.rehash()
832+
b_spend_dup_cb = self.update_block('spend_dup_cb', [tx])
833+
834+
b_dup_2 = self.next_block('dup_2')
835+
b_dup_2.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG
836+
b_dup_2.vtx[0].rehash()
837+
b_dup_2 = self.update_block('dup_2', [])
838+
assert_equal(duplicate_tx.serialize(), b_dup_2.vtx[0].serialize())
839+
assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 119)
840+
self.send_blocks([b_spend_dup_cb, b_dup_2], success=True)
841+
# The duplicate has less confirmations
842+
assert_equal(self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)['confirmations'], 1)
843+
809844
# Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests)
810845
#
811-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
812-
# \-> b62 (18)
846+
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 ()
847+
# \-> b62 (18)
813848
#
814849
self.log.info("Reject a block with a transaction with a nonfinal locktime")
815-
self.move_tip(60)
850+
self.move_tip('dup_2')
816851
b62 = self.next_block(62)
817852
tx = CTransaction()
818853
tx.nLockTime = 0xffffffff # this locktime is non-final
@@ -825,11 +860,11 @@ def run_test(self):
825860

826861
# Test a non-final coinbase is also rejected
827862
#
828-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
829-
# \-> b63 (-)
863+
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 ()
864+
# \-> b63 (-)
830865
#
831866
self.log.info("Reject a block with a coinbase transaction with a nonfinal locktime")
832-
self.move_tip(60)
867+
self.move_tip('dup_2')
833868
b63 = self.next_block(63)
834869
b63.vtx[0].nLockTime = 0xffffffff
835870
b63.vtx[0].vin[0].nSequence = 0xDEADBEEF
@@ -845,14 +880,14 @@ def run_test(self):
845880
# What matters is that the receiving node should not reject the bloated block, and then reject the canonical
846881
# block on the basis that it's the same as an already-rejected block (which would be a consensus failure.)
847882
#
848-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18)
849-
# \
850-
# b64a (18)
883+
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18)
884+
# \
885+
# b64a (18)
851886
# b64a is a bloated block (non-canonical varint)
852887
# b64 is a good block (same as b64 but w/ canonical varint)
853888
#
854889
self.log.info("Accept a valid block even if a bloated version of the block has previously been sent")
855-
self.move_tip(60)
890+
self.move_tip('dup_2')
856891
regular_block = self.next_block("64a", spend=out[18])
857892

858893
# make it a "broken_block," with non-canonical serialization
@@ -878,7 +913,7 @@ def run_test(self):
878913
node.disconnect_p2ps()
879914
self.reconnect_p2p()
880915

881-
self.move_tip(60)
916+
self.move_tip('dup_2')
882917
b64 = CBlock(b64a)
883918
b64.vtx = copy.deepcopy(b64a.vtx)
884919
assert_equal(b64.hash, b64a.hash)
@@ -890,7 +925,7 @@ def run_test(self):
890925

891926
# Spend an output created in the block itself
892927
#
893-
# -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
928+
# -> b_dup_2 () -> b64 (18) -> b65 (19)
894929
#
895930
self.log.info("Accept a block with a transaction spending an output created in the same block")
896931
self.move_tip(64)
@@ -903,8 +938,8 @@ def run_test(self):
903938

904939
# Attempt to spend an output created later in the same block
905940
#
906-
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
907-
# \-> b66 (20)
941+
# -> b64 (18) -> b65 (19)
942+
# \-> b66 (20)
908943
self.log.info("Reject a block with a transaction spending an output created later in the same block")
909944
self.move_tip(65)
910945
b66 = self.next_block(66)
@@ -915,8 +950,8 @@ def run_test(self):
915950

916951
# Attempt to double-spend a transaction created in a block
917952
#
918-
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
919-
# \-> b67 (20)
953+
# -> b64 (18) -> b65 (19)
954+
# \-> b67 (20)
920955
#
921956
#
922957
self.log.info("Reject a block with a transaction double spending a transaction created in the same block")
@@ -930,8 +965,8 @@ def run_test(self):
930965

931966
# More tests of block subsidy
932967
#
933-
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
934-
# \-> b68 (20)
968+
# -> b64 (18) -> b65 (19) -> b69 (20)
969+
# \-> b68 (20)
935970
#
936971
# b68 - coinbase with an extra 10 satoshis,
937972
# creates a tx that has 9 satoshis from out[20] go to fees
@@ -957,8 +992,8 @@ def run_test(self):
957992

958993
# Test spending the outpoint of a non-existent transaction
959994
#
960-
# -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
961-
# \-> b70 (21)
995+
# -> b65 (19) -> b69 (20)
996+
# \-> b70 (21)
962997
#
963998
self.log.info("Reject a block containing a transaction spending from a non-existent input")
964999
self.move_tip(69)
@@ -973,8 +1008,8 @@ def run_test(self):
9731008

9741009
# Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks)
9751010
#
976-
# -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21)
977-
# \-> b71 (21)
1011+
# -> b65 (19) -> b69 (20) -> b72 (21)
1012+
# \-> b71 (21)
9781013
#
9791014
# b72 is a good block.
9801015
# b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72.
@@ -1002,8 +1037,8 @@ def run_test(self):
10021037

10031038
# Test some invalid scripts and MAX_BLOCK_SIGOPS
10041039
#
1005-
# -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21)
1006-
# \-> b** (22)
1040+
# -> b69 (20) -> b72 (21)
1041+
# \-> b** (22)
10071042
#
10081043

10091044
# b73 - tx with excessive sigops that are placed after an excessively large script element.

test/functional/test_framework/blocktools.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@
2222
ToHex,
2323
hash256,
2424
hex_str_to_bytes,
25-
ser_string,
2625
ser_uint256,
2726
sha256,
2827
uint256_from_str,
2928
)
3029
from .script import (
3130
CScript,
31+
CScriptNum,
32+
CScriptOp,
3233
OP_0,
3334
OP_1,
3435
OP_CHECKMULTISIG,
@@ -89,29 +90,22 @@ def add_witness_commitment(block, nonce=0):
8990
block.hashMerkleRoot = block.calc_merkle_root()
9091
block.rehash()
9192

92-
def serialize_script_num(value):
93-
r = bytearray(0)
94-
if value == 0:
95-
return r
96-
neg = value < 0
97-
absvalue = -value if neg else value
98-
while (absvalue):
99-
r.append(int(absvalue & 0xff))
100-
absvalue >>= 8
101-
if r[-1] & 0x80:
102-
r.append(0x80 if neg else 0)
103-
elif neg:
104-
r[-1] |= 0x80
105-
return r
93+
94+
def script_BIP34_coinbase_height(height):
95+
if height <= 16:
96+
res = CScriptOp.encode_op_n(height)
97+
# Append dummy to increase scriptSig size above 2 (see bad-cb-length consensus rule)
98+
return CScript([res, OP_1])
99+
return CScript([CScriptNum(height)])
100+
106101

107102
def create_coinbase(height, pubkey=None):
108103
"""Create a coinbase transaction, assuming no miner fees.
109104
110105
If pubkey is passed in, the coinbase output will be a P2PK output;
111106
otherwise an anyone-can-spend output."""
112107
coinbase = CTransaction()
113-
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
114-
ser_string(serialize_script_num(height)), 0xffffffff))
108+
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff))
115109
coinbaseoutput = CTxOut()
116110
coinbaseoutput.nValue = 50 * COIN
117111
halvings = int(height / 150) # regtest

0 commit comments

Comments
 (0)