Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_cltv.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2015-2016 The Bitcoin Core developers | # Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
"""Test BIP65 (CHECKLOCKTIMEVERIFY). | """Test BIP65 (CHECKLOCKTIMEVERIFY). | ||||
Test that the CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height | Test that the CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height | ||||
1351. | 1351. | ||||
""" | """ | ||||
from test_framework.blocktools import create_block, create_coinbase | from test_framework.blocktools import create_block, create_coinbase, make_conform_to_ctor | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
CTransaction, | CTransaction, | ||||
FromHex, | FromHex, | ||||
msg_block, | msg_block, | ||||
msg_tx, | msg_tx, | ||||
ToHex, | ToHex, | ||||
) | ) | ||||
from test_framework.mininode import ( | from test_framework.mininode import ( | ||||
Show All 16 Lines | |||||
CLTV_HEIGHT = 1351 | CLTV_HEIGHT = 1351 | ||||
# Reject codes that we might receive in this test | # Reject codes that we might receive in this test | ||||
REJECT_INVALID = 16 | REJECT_INVALID = 16 | ||||
REJECT_OBSOLETE = 17 | REJECT_OBSOLETE = 17 | ||||
REJECT_NONSTANDARD = 64 | REJECT_NONSTANDARD = 64 | ||||
def cltv_lock_to_height(node, tx, height=-1): | def cltv_lock_to_height(node, tx, to_address, amount, height=-1): | ||||
'''Modify the scriptPubKey to add an OP_CHECKLOCKTIMEVERIFY | '''Modify the scriptPubKey to add an OP_CHECKLOCKTIMEVERIFY, and make | ||||
a transaction that spends it. | |||||
This transforms the script to anyone can spend (OP_TRUE) if the lock time | This transforms the output script to anyone can spend (OP_TRUE) if the | ||||
condition is valid. | lock time condition is valid. | ||||
Default height is -1 which leads CLTV to fail | Default height is -1 which leads CLTV to fail | ||||
TODO: test more ways that transactions using CLTV could be invalid (eg | TODO: test more ways that transactions using CLTV could be invalid (eg | ||||
locktime requirements fail, sequence time requirements fail, etc). | locktime requirements fail, sequence time requirements fail, etc). | ||||
''' | ''' | ||||
height_op = OP_1NEGATE | height_op = OP_1NEGATE | ||||
if(height > 0): | if(height > 0): | ||||
tx.vin[0].nSequence = 0 | tx.vin[0].nSequence = 0 | ||||
tx.nLockTime = height | tx.nLockTime = height | ||||
height_op = CScriptNum(height) | height_op = CScriptNum(height) | ||||
tx.vout[0].scriptPubKey = CScript( | tx.vout[0].scriptPubKey = CScript( | ||||
[height_op, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE]) | [height_op, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE]) | ||||
tx.rehash() | |||||
signed_result = node.signrawtransactionwithwallet(ToHex(tx)) | pad_tx(tx) | ||||
fundtx_raw = node.signrawtransactionwithwallet(ToHex(tx))['hex'] | |||||
new_tx = FromHex(CTransaction(), signed_result['hex']) | fundtx = FromHex(CTransaction(), fundtx_raw) | ||||
pad_tx(new_tx) | fundtx.rehash() | ||||
new_tx.rehash() | |||||
return new_tx | # make spending tx | ||||
from_txid = fundtx.hash | |||||
inputs = [{ | |||||
"txid": fundtx.hash, | |||||
"vout": 0 | |||||
}] | |||||
output = {to_address: amount} | |||||
spendtx_raw = node.createrawtransaction(inputs, output) | |||||
spendtx = FromHex(CTransaction(), spendtx_raw) | |||||
pad_tx(spendtx) | |||||
return fundtx, spendtx | |||||
def spend_from_coinbase(node, coinbase, to_address, amount): | def spend_from_coinbase(node, coinbase, to_address, amount): | ||||
from_txid = node.getblock(coinbase)['tx'][0] | from_txid = node.getblock(coinbase)['tx'][0] | ||||
inputs = [{"txid": from_txid, "vout": 0}] | inputs = [{"txid": from_txid, "vout": 0}] | ||||
outputs = {to_address: amount} | outputs = {to_address: amount} | ||||
rawtx = node.createrawtransaction(inputs, outputs) | rawtx = node.createrawtransaction(inputs, outputs) | ||||
signresult = node.signrawtransactionwithwallet(rawtx) | signresult = node.signrawtransactionwithwallet(rawtx) | ||||
Show All 18 Lines | def run_test(self): | ||||
self.log.info("Mining {} blocks".format(CLTV_HEIGHT - 2)) | self.log.info("Mining {} blocks".format(CLTV_HEIGHT - 2)) | ||||
self.coinbase_blocks = self.nodes[0].generate(CLTV_HEIGHT - 2) | self.coinbase_blocks = self.nodes[0].generate(CLTV_HEIGHT - 2) | ||||
self.nodeaddress = self.nodes[0].getnewaddress() | self.nodeaddress = self.nodes[0].getnewaddress() | ||||
self.log.info( | self.log.info( | ||||
"Test that an invalid-according-to-CLTV transaction can still appear in a block") | "Test that an invalid-according-to-CLTV transaction can still appear in a block") | ||||
spendtx = spend_from_coinbase(self.nodes[0], self.coinbase_blocks[0], | fundtx = spend_from_coinbase(self.nodes[0], self.coinbase_blocks[0], | ||||
self.nodeaddress, 50.0) | self.nodeaddress, 49.99) | ||||
spendtx = cltv_lock_to_height(self.nodes[0], spendtx) | fundtx, spendtx = cltv_lock_to_height( | ||||
self.nodes[0], fundtx, self.nodeaddress, 49.98) | |||||
# Make sure the tx is valid | |||||
self.nodes[0].sendrawtransaction(ToHex(spendtx)) | |||||
tip = self.nodes[0].getbestblockhash() | tip = self.nodes[0].getbestblockhash() | ||||
block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 | block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 | ||||
block = create_block(int(tip, 16), create_coinbase( | block = create_block(int(tip, 16), create_coinbase( | ||||
CLTV_HEIGHT - 1), block_time) | CLTV_HEIGHT - 1), block_time) | ||||
block.nVersion = 3 | block.nVersion = 3 | ||||
block.vtx.append(fundtx) | |||||
# include the -1 CLTV in block | |||||
block.vtx.append(spendtx) | block.vtx.append(spendtx) | ||||
make_conform_to_ctor(block) | |||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
self.nodes[0].p2p.send_and_ping(msg_block(block)) | self.nodes[0].p2p.send_and_ping(msg_block(block)) | ||||
# This block is valid | |||||
assert_equal(self.nodes[0].getbestblockhash(), block.hash) | assert_equal(self.nodes[0].getbestblockhash(), block.hash) | ||||
self.log.info("Test that blocks must now be at least version 4") | self.log.info("Test that blocks must now be at least version 4") | ||||
tip = block.sha256 | tip = block.sha256 | ||||
block_time += 1 | block_time += 1 | ||||
block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time) | block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time) | ||||
block.nVersion = 3 | block.nVersion = 3 | ||||
block.solve() | block.solve() | ||||
Show All 10 Lines | def run_test(self): | ||||
assert_equal( | assert_equal( | ||||
self.nodes[0].p2p.last_message["reject"].data, block.sha256) | self.nodes[0].p2p.last_message["reject"].data, block.sha256) | ||||
del self.nodes[0].p2p.last_message["reject"] | del self.nodes[0].p2p.last_message["reject"] | ||||
self.log.info( | self.log.info( | ||||
"Test that invalid-according-to-cltv transactions cannot appear in a block") | "Test that invalid-according-to-cltv transactions cannot appear in a block") | ||||
block.nVersion = 4 | block.nVersion = 4 | ||||
spendtx = spend_from_coinbase(self.nodes[0], self.coinbase_blocks[1], | fundtx = spend_from_coinbase(self.nodes[0], self.coinbase_blocks[1], | ||||
self.nodeaddress, 49.99) | self.nodeaddress, 49.99) | ||||
spendtx = cltv_lock_to_height(self.nodes[0], spendtx) | fundtx, spendtx = cltv_lock_to_height( | ||||
self.nodes[0], fundtx, self.nodeaddress, 49.98) | |||||
# First we show that this tx is valid except for CLTV by getting it | # The funding tx only has unexecuted bad CLTV, in scriptpubkey; this is valid. | ||||
# accepted to the mempool (which we can achieve with | self.nodes[0].p2p.send_and_ping(msg_tx(fundtx)) | ||||
# -promiscuousmempoolflags). | assert fundtx.hash in self.nodes[0].getrawmempool() | ||||
self.nodes[0].p2p.send_and_ping(msg_tx(spendtx)) | |||||
assert spendtx.hash in self.nodes[0].getrawmempool() | |||||
# Mine a block containing the funding transaction | # Mine a block containing the funding transaction | ||||
block.vtx.append(spendtx) | block.vtx.append(fundtx) | ||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
self.nodes[0].p2p.send_and_ping(msg_block(block)) | self.nodes[0].p2p.send_and_ping(msg_block(block)) | ||||
# This block is valid | # This block is valid | ||||
assert_equal(self.nodes[0].getbestblockhash(), block.hash) | assert_equal(self.nodes[0].getbestblockhash(), block.hash) | ||||
# But a block containing a transaction spending this utxo is not | # We show that this tx is valid except for CLTV by getting it | ||||
rawspendtx = self.nodes[0].decoderawtransaction(ToHex(spendtx)) | # accepted to the mempool (which we can achieve with | ||||
inputs = [{ | # -promiscuousmempoolflags). | ||||
"txid": rawspendtx['txid'], | self.nodes[0].p2p.send_and_ping(msg_tx(spendtx)) | ||||
"vout": rawspendtx['vout'][0]['n'] | assert spendtx.hash in self.nodes[0].getrawmempool() | ||||
}] | |||||
output = {self.nodeaddress: 49.98} | |||||
rejectedtx_raw = self.nodes[0].createrawtransaction(inputs, output) | |||||
rejectedtx_signed = self.nodes[0].signrawtransactionwithwallet( | rejectedtx_signed = self.nodes[0].signrawtransactionwithwallet( | ||||
rejectedtx_raw) | ToHex(spendtx)) | ||||
#!Couldn't complete signature due to CLTV | # Couldn't complete signature due to CLTV | ||||
assert(rejectedtx_signed['errors'][0]['error'] == 'Negative locktime') | assert(rejectedtx_signed['errors'][0]['error'] == 'Negative locktime') | ||||
rejectedtx = FromHex(CTransaction(), rejectedtx_signed['hex']) | |||||
pad_tx(rejectedtx) | |||||
rejectedtx.rehash() | |||||
tip = block.hash | tip = block.hash | ||||
block_time += 1 | block_time += 1 | ||||
block = create_block( | block = create_block( | ||||
block.sha256, create_coinbase(CLTV_HEIGHT+1), block_time) | block.sha256, create_coinbase(CLTV_HEIGHT+1), block_time) | ||||
block.nVersion = 4 | block.nVersion = 4 | ||||
block.vtx.append(rejectedtx) | block.vtx.append(spendtx) | ||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
self.nodes[0].p2p.send_and_ping(msg_block(block)) | self.nodes[0].p2p.send_and_ping(msg_block(block)) | ||||
# This block is invalid | # This block is invalid | ||||
assert_equal(self.nodes[0].getbestblockhash(), tip) | assert_equal(self.nodes[0].getbestblockhash(), tip) | ||||
wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), | wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), | ||||
lock=mininode_lock) | lock=mininode_lock) | ||||
with mininode_lock: | with mininode_lock: | ||||
assert self.nodes[0].p2p.last_message["reject"].code in [ | assert self.nodes[0].p2p.last_message["reject"].code in [ | ||||
REJECT_INVALID, REJECT_NONSTANDARD] | REJECT_INVALID, REJECT_NONSTANDARD] | ||||
assert_equal( | assert_equal( | ||||
self.nodes[0].p2p.last_message["reject"].data, block.sha256) | self.nodes[0].p2p.last_message["reject"].data, block.sha256) | ||||
if self.nodes[0].p2p.last_message["reject"].code == REJECT_INVALID: | if self.nodes[0].p2p.last_message["reject"].code == REJECT_INVALID: | ||||
# Generic rejection when a block is invalid | # Generic rejection when a block is invalid | ||||
assert_equal( | assert_equal( | ||||
self.nodes[0].p2p.last_message["reject"].reason, b'blk-bad-inputs') | self.nodes[0].p2p.last_message["reject"].reason, b'blk-bad-inputs') | ||||
else: | else: | ||||
assert b'Negative locktime' in self.nodes[0].p2p.last_message["reject"].reason | assert b'Negative locktime' in self.nodes[0].p2p.last_message["reject"].reason | ||||
self.log.info( | self.log.info( | ||||
"Test that a version 4 block with a valid-according-to-CLTV transaction is accepted") | "Test that a version 4 block with a valid-according-to-CLTV transaction is accepted") | ||||
spendtx = spend_from_coinbase(self.nodes[0], self.coinbase_blocks[2], | fundtx = spend_from_coinbase(self.nodes[0], self.coinbase_blocks[2], | ||||
self.nodeaddress, 49.99) | self.nodeaddress, 49.99) | ||||
spendtx = cltv_lock_to_height(self.nodes[0], spendtx, CLTV_HEIGHT - 1) | fundtx, spendtx = cltv_lock_to_height( | ||||
self.nodes[0], fundtx, self.nodeaddress, 49.98, CLTV_HEIGHT) | |||||
# make sure sequence is nonfinal and locktime is good | |||||
spendtx.vin[0].nSequence = 0xfffffffe | |||||
spendtx.nLockTime = CLTV_HEIGHT | |||||
# Modify the transaction in the block to be valid against CLTV | # both transactions are fully valid | ||||
self.nodes[0].sendrawtransaction(ToHex(fundtx)) | |||||
self.nodes[0].sendrawtransaction(ToHex(spendtx)) | |||||
# Modify the transactions in the block to be valid against CLTV | |||||
block.vtx.pop(1) | block.vtx.pop(1) | ||||
block.vtx.append(fundtx) | |||||
block.vtx.append(spendtx) | block.vtx.append(spendtx) | ||||
make_conform_to_ctor(block) | |||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
self.nodes[0].p2p.send_and_ping(msg_block(block)) | self.nodes[0].p2p.send_and_ping(msg_block(block)) | ||||
# This block is now valid | # This block is now valid | ||||
assert_equal(self.nodes[0].getbestblockhash(), block.hash) | assert_equal(self.nodes[0].getbestblockhash(), block.hash) | ||||
# A block containing a transaction spending this utxo is also valid | |||||
# Build this transaction | |||||
rawspendtx = self.nodes[0].decoderawtransaction(ToHex(spendtx)) | |||||
inputs = [{ | |||||
"txid": rawspendtx['txid'], | |||||
"vout": rawspendtx['vout'][0]['n'], | |||||
"sequence": 0 | |||||
}] | |||||
output = {self.nodeaddress: 49.98} | |||||
validtx_raw = self.nodes[0].createrawtransaction( | |||||
inputs, output, CLTV_HEIGHT) | |||||
validtx = FromHex(CTransaction(), validtx_raw) | |||||
# Signrawtransaction won't sign a non standard tx. | |||||
# But the prevout being anyone can spend, scriptsig can be left empty | |||||
validtx.vin[0].scriptSig = CScript() | |||||
pad_tx(validtx) | |||||
validtx.rehash() | |||||
tip = block.sha256 | |||||
block_time += 1 | |||||
block = create_block(tip, create_coinbase(CLTV_HEIGHT+3), block_time) | |||||
block.nVersion = 4 | |||||
block.vtx.append(validtx) | |||||
block.hashMerkleRoot = block.calc_merkle_root() | |||||
block.solve() | |||||
self.nodes[0].p2p.send_and_ping(msg_block(block)) | |||||
# This block is valid | |||||
assert_equal(self.nodes[0].getbestblockhash(), block.hash) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
BIP65Test().main() | BIP65Test().main() |