Skip to content

Commit fa8489a

Browse files
author
MarcoFalke
committed
test: Add test for BIP30 duplicate tx
1 parent 77770d9 commit fa8489a

File tree

1 file changed

+67
-32
lines changed

1 file changed

+67
-32
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])
@@ -750,7 +761,7 @@ def run_test(self):
750761

751762
# Test a few invalid tx types
752763
#
753-
# -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
764+
# -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 ()
754765
# \-> ??? (17)
755766
#
756767

@@ -776,35 +787,59 @@ def run_test(self):
776787

777788
# reset to good chain
778789
self.move_tip(57)
779-
b60 = self.next_block(60, spend=out[17])
790+
b60 = self.next_block(60)
780791
self.send_blocks([b60], True)
781792
self.save_spendable_output()
782793

783-
# Test BIP30
794+
# Test BIP30 (reject duplicate)
784795
#
785-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
786-
# \-> b61 (18)
796+
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 ()
797+
# \-> b61 ()
787798
#
788799
# Blocks are not allowed to contain a transaction whose id matches that of an earlier,
789800
# not-fully-spent transaction in the same chain. To test, make identical coinbases;
790801
# the second one should be rejected.
791802
#
792803
self.log.info("Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)")
793804
self.move_tip(60)
794-
b61 = self.next_block(61, spend=out[18])
795-
b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig # Equalize the coinbases
805+
b61 = self.next_block(61)
806+
b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG
796807
b61.vtx[0].rehash()
797808
b61 = self.update_block(61, [])
798-
assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize())
809+
assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize())
799810
self.send_blocks([b61], success=False, reject_reason='bad-txns-BIP30', reconnect=True)
800811

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

818853
# Test a non-final coinbase is also rejected
819854
#
820-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
821-
# \-> b63 (-)
855+
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 ()
856+
# \-> b63 (-)
822857
#
823858
self.log.info("Reject a block with a coinbase transaction with a nonfinal locktime")
824-
self.move_tip(60)
859+
self.move_tip('dup_2')
825860
b63 = self.next_block(63)
826861
b63.vtx[0].nLockTime = 0xffffffff
827862
b63.vtx[0].vin[0].nSequence = 0xDEADBEEF
@@ -837,14 +872,14 @@ def run_test(self):
837872
# What matters is that the receiving node should not reject the bloated block, and then reject the canonical
838873
# block on the basis that it's the same as an already-rejected block (which would be a consensus failure.)
839874
#
840-
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18)
841-
# \
842-
# b64a (18)
875+
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18)
876+
# \
877+
# b64a (18)
843878
# b64a is a bloated block (non-canonical varint)
844879
# b64 is a good block (same as b64 but w/ canonical varint)
845880
#
846881
self.log.info("Accept a valid block even if a bloated version of the block has previously been sent")
847-
self.move_tip(60)
882+
self.move_tip('dup_2')
848883
regular_block = self.next_block("64a", spend=out[18])
849884

850885
# make it a "broken_block," with non-canonical serialization
@@ -870,7 +905,7 @@ def run_test(self):
870905
node.disconnect_p2ps()
871906
self.reconnect_p2p()
872907

873-
self.move_tip(60)
908+
self.move_tip('dup_2')
874909
b64 = CBlock(b64a)
875910
b64.vtx = copy.deepcopy(b64a.vtx)
876911
assert_equal(b64.hash, b64a.hash)
@@ -882,7 +917,7 @@ def run_test(self):
882917

883918
# Spend an output created in the block itself
884919
#
885-
# -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
920+
# -> b_dup_2 () -> b64 (18) -> b65 (19)
886921
#
887922
self.log.info("Accept a block with a transaction spending an output created in the same block")
888923
self.move_tip(64)
@@ -895,8 +930,8 @@ def run_test(self):
895930

896931
# Attempt to spend an output created later in the same block
897932
#
898-
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
899-
# \-> b66 (20)
933+
# -> b64 (18) -> b65 (19)
934+
# \-> b66 (20)
900935
self.log.info("Reject a block with a transaction spending an output created later in the same block")
901936
self.move_tip(65)
902937
b66 = self.next_block(66)
@@ -907,8 +942,8 @@ def run_test(self):
907942

908943
# Attempt to double-spend a transaction created in a block
909944
#
910-
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19)
911-
# \-> b67 (20)
945+
# -> b64 (18) -> b65 (19)
946+
# \-> b67 (20)
912947
#
913948
#
914949
self.log.info("Reject a block with a transaction double spending a transaction created in the same block")
@@ -922,8 +957,8 @@ def run_test(self):
922957

923958
# More tests of block subsidy
924959
#
925-
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
926-
# \-> b68 (20)
960+
# -> b64 (18) -> b65 (19) -> b69 (20)
961+
# \-> b68 (20)
927962
#
928963
# b68 - coinbase with an extra 10 satoshis,
929964
# creates a tx that has 9 satoshis from out[20] go to fees
@@ -949,8 +984,8 @@ def run_test(self):
949984

950985
# Test spending the outpoint of a non-existent transaction
951986
#
952-
# -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
953-
# \-> b70 (21)
987+
# -> b65 (19) -> b69 (20)
988+
# \-> b70 (21)
954989
#
955990
self.log.info("Reject a block containing a transaction spending from a non-existent input")
956991
self.move_tip(69)
@@ -965,8 +1000,8 @@ def run_test(self):
9651000

9661001
# Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks)
9671002
#
968-
# -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21)
969-
# \-> b71 (21)
1003+
# -> b65 (19) -> b69 (20) -> b72 (21)
1004+
# \-> b71 (21)
9701005
#
9711006
# b72 is a good block.
9721007
# b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72.
@@ -994,8 +1029,8 @@ def run_test(self):
9941029

9951030
# Test some invalid scripts and MAX_BLOCK_SIGOPS
9961031
#
997-
# -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21)
998-
# \-> b** (22)
1032+
# -> b69 (20) -> b72 (21)
1033+
# \-> b** (22)
9991034
#
10001035

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

0 commit comments

Comments
 (0)