Skip to content

Commit cbd345a

Browse files
committed
test: test OP_CSV empty stack fail in feature_csv_activation.py
With BIP112 activated, the operation OP_CHECKSEQUENCEVERIFY (former OP_NOP3) leads to script interpreter termination with an error if one of the following conditions is true: -> stack is empty -> top item on stack is negative (< 0) -> top item on stack has disable flag unset and at least one of four other conditions is true (contains the core CSV logic) This commits adds the missing empty stack failure test to the functional test by prepending a valid scriptSig with just OP_CHECKSEQUENCEVERIFY. If BIP112 is inactive, the operator just behaves as a NOP (for both tx versions 1 and 2) and the transaction remains valid -- if it is active, the tx is invalid due to an empty stack (for both tx versions 1 and 2, as well).
1 parent eae48ec commit cbd345a

File tree

1 file changed

+25
-10
lines changed

1 file changed

+25
-10
lines changed

test/functional/feature_csv_activation.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
bip112txs_vary_OP_CSV - 16 txs with nSequence = 10 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP
3636
bip112txs_vary_OP_CSV_9 - 16 txs with nSequence = 9 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP
3737
bip112tx_special - test negative argument to OP_CSV
38+
bip112tx_emptystack - test empty stack (= no argument) OP_CSV
3839
"""
3940
from decimal import Decimal
4041
from itertools import product
@@ -95,6 +96,13 @@ def create_bip112special(node, input, txversion, address):
9596
signtx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(signtx.vin[0].scriptSig)))
9697
return signtx
9798

99+
def create_bip112emptystack(node, input, txversion, address):
100+
tx = create_transaction(node, input, address, amount=Decimal("49.98"))
101+
tx.nVersion = txversion
102+
signtx = sign_transaction(node, tx)
103+
signtx.vin[0].scriptSig = CScript([OP_CHECKSEQUENCEVERIFY] + list(CScript(signtx.vin[0].scriptSig)))
104+
return signtx
105+
98106
def send_generic_input_tx(node, coinbases, address):
99107
return node.sendrawtransaction(ToHex(sign_transaction(node, create_transaction(node, node.getblock(coinbases.pop())['tx'][0], address, amount=Decimal("49.99")))))
100108

@@ -179,15 +187,15 @@ def run_test(self):
179187
self.log.info("Generate blocks in the past for coinbase outputs.")
180188
long_past_time = int(time.time()) - 600 * 1000 # enough to build up to 1000 blocks 10 minutes apart without worrying about getting into the future
181189
self.nodes[0].setmocktime(long_past_time - 100) # enough so that the generated blocks will still all be before long_past_time
182-
self.coinbase_blocks = self.nodes[0].generate(1 + 16 + 2 * 32 + 1) # 82 blocks generated for inputs
190+
self.coinbase_blocks = self.nodes[0].generate(1 + 16 + 2 * 32 + 2) # 83 blocks generated for inputs
183191
self.nodes[0].setmocktime(0) # set time back to present so yielded blocks aren't in the future as we advance last_block_time
184-
self.tipheight = 82 # height of the next block to build
192+
self.tipheight = 83 # height of the next block to build
185193
self.last_block_time = long_past_time
186194
self.tip = int(self.nodes[0].getbestblockhash(), 16)
187195
self.nodeaddress = self.nodes[0].getnewaddress()
188196

189197
# Activation height is hardcoded
190-
test_blocks = self.generate_blocks(345)
198+
test_blocks = self.generate_blocks(344)
191199
self.send_blocks(test_blocks)
192200
assert not softfork_active(self.nodes[0], 'csv')
193201

@@ -218,6 +226,8 @@ def run_test(self):
218226

219227
# 1 special input with -1 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
220228
bip112specialinput = send_generic_input_tx(self.nodes[0], self.coinbase_blocks, self.nodeaddress)
229+
# 1 special input with (empty stack) OP_CSV (actually will be prepended to spending scriptSig)
230+
bip112emptystackinput = send_generic_input_tx(self.nodes[0],self.coinbase_blocks, self.nodeaddress)
221231

222232
# 1 normal input
223233
bip113input = send_generic_input_tx(self.nodes[0], self.coinbase_blocks, self.nodeaddress)
@@ -228,7 +238,7 @@ def run_test(self):
228238
self.tip = int(inputblockhash, 16)
229239
self.tipheight += 1
230240
self.last_block_time += 600
231-
assert_equal(len(self.nodes[0].getblock(inputblockhash, True)["tx"]), 82 + 1)
241+
assert_equal(len(self.nodes[0].getblock(inputblockhash, True)["tx"]), 83 + 1)
232242

233243
# 2 more version 4 blocks
234244
test_blocks = self.generate_blocks(2)
@@ -267,18 +277,22 @@ def run_test(self):
267277
# -1 OP_CSV OP_DROP input
268278
bip112tx_special_v1 = create_bip112special(self.nodes[0], bip112specialinput, 1, self.nodeaddress)
269279
bip112tx_special_v2 = create_bip112special(self.nodes[0], bip112specialinput, 2, self.nodeaddress)
280+
# (empty stack) OP_CSV input
281+
bip112tx_emptystack_v1 = create_bip112emptystack(self.nodes[0], bip112emptystackinput, 1, self.nodeaddress)
282+
bip112tx_emptystack_v2 = create_bip112emptystack(self.nodes[0], bip112emptystackinput, 2, self.nodeaddress)
270283

271284
self.log.info("TESTING")
272285

273286
self.log.info("Pre-Soft Fork Tests. All txs should pass.")
274287
self.log.info("Test version 1 txs")
275288

276289
success_txs = []
277-
# add BIP113 tx and -1 CSV tx
290+
# BIP113 tx, -1 CSV tx and empty stack CSV tx should succeed
278291
bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
279292
bip113signed1 = sign_transaction(self.nodes[0], bip113tx_v1)
280293
success_txs.append(bip113signed1)
281294
success_txs.append(bip112tx_special_v1)
295+
success_txs.append(bip112tx_emptystack_v1)
282296
# add BIP 68 txs
283297
success_txs.extend(all_rlt_txs(bip68txs_v1))
284298
# add BIP 112 with seq=10 txs
@@ -293,11 +307,12 @@ def run_test(self):
293307
self.log.info("Test version 2 txs")
294308

295309
success_txs = []
296-
# add BIP113 tx and -1 CSV tx
310+
# BIP113 tx, -1 CSV tx and empty stack CSV tx should succeed
297311
bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
298312
bip113signed2 = sign_transaction(self.nodes[0], bip113tx_v2)
299313
success_txs.append(bip113signed2)
300314
success_txs.append(bip112tx_special_v2)
315+
success_txs.append(bip112tx_emptystack_v2)
301316
# add BIP 68 txs
302317
success_txs.extend(all_rlt_txs(bip68txs_v2))
303318
# add BIP 112 with seq=10 txs
@@ -385,8 +400,9 @@ def run_test(self):
385400
self.log.info("BIP 112 tests")
386401
self.log.info("Test version 1 txs")
387402

388-
# -1 OP_CSV tx should fail
403+
# -1 OP_CSV tx and (empty stack) OP_CSV tx should fail
389404
self.send_blocks([self.create_test_block([bip112tx_special_v1])], success=False)
405+
self.send_blocks([self.create_test_block([bip112tx_emptystack_v1])], success=False)
390406
# If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, version 1 txs should still pass
391407

392408
success_txs = [tx['tx'] for tx in bip112txs_vary_OP_CSV_v1 if tx['sdf']]
@@ -404,8 +420,9 @@ def run_test(self):
404420

405421
self.log.info("Test version 2 txs")
406422

407-
# -1 OP_CSV tx should fail
423+
# -1 OP_CSV tx and (empty stack) OP_CSV tx should fail
408424
self.send_blocks([self.create_test_block([bip112tx_special_v2])], success=False)
425+
self.send_blocks([self.create_test_block([bip112tx_emptystack_v2])], success=False)
409426

410427
# If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, version 2 txs should pass (all sequence locks are met)
411428
success_txs = [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if tx['sdf']]
@@ -449,7 +466,5 @@ def run_test(self):
449466
self.send_blocks([self.create_test_block(time_txs)])
450467
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
451468

452-
# TODO: Test empty stack fails
453-
454469
if __name__ == '__main__':
455470
BIP68_112_113Test().main()

0 commit comments

Comments
 (0)