2016-03-19 20:58:06 +01:00
#!/usr/bin/env python3
2020-04-16 13:14:08 -04:00
# Copyright (c) 2015-2020 The Bitcoin Core developers
2016-03-19 20:58:06 +01:00
# Distributed under the MIT software license, see the accompanying
2015-06-28 14:42:17 -04:00
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-01-17 18:34:40 -05:00
""" Test BIP65 (CHECKLOCKTIMEVERIFY).
2017-06-28 14:52:24 -04:00
Test that the CHECKLOCKTIMEVERIFY soft - fork activates at ( regtest ) block height
1351.
2017-01-17 18:34:40 -05:00
"""
2015-06-28 14:42:17 -04:00
2020-08-25 09:11:25 +02:00
from test_framework . blocktools import (
create_block ,
create_coinbase ,
create_transaction ,
)
from test_framework . messages import (
CTransaction ,
ToHex ,
msg_block ,
)
2020-07-19 14:47:05 +07:00
from test_framework . p2p import P2PInterface
2020-08-25 09:11:25 +02:00
from test_framework . script import (
CScript ,
CScriptNum ,
OP_1NEGATE ,
OP_CHECKLOCKTIMEVERIFY ,
OP_DROP ,
)
2018-07-07 00:10:35 +02:00
from test_framework . test_framework import BitcoinTestFramework
2018-08-24 15:26:42 -04:00
from test_framework . util import (
assert_equal ,
hex_str_to_bytes ,
)
2018-07-07 00:10:35 +02:00
2016-03-19 21:36:32 +01:00
from io import BytesIO
2017-06-28 14:52:24 -04:00
CLTV_HEIGHT = 1351
2015-06-28 14:42:17 -04:00
2020-08-25 09:11:25 +02:00
# Helper function to modify a transaction by
# 1) prepending a given script to the scriptSig of vin 0 and
# 2) (optionally) modify the nSequence of vin 0 and the tx's nLockTime
def cltv_modify_tx ( node , tx , prepend_scriptsig , nsequence = None , nlocktime = None ) :
if nsequence is not None :
tx . vin [ 0 ] . nSequence = nsequence
tx . nLockTime = nlocktime
# Need to re-sign, since nSequence and nLockTime changed
signed_result = node . signrawtransactionwithwallet ( ToHex ( tx ) )
new_tx = CTransaction ( )
new_tx . deserialize ( BytesIO ( hex_str_to_bytes ( signed_result [ ' hex ' ] ) ) )
else :
new_tx = tx
new_tx . vin [ 0 ] . scriptSig = CScript ( prepend_scriptsig + list ( CScript ( new_tx . vin [ 0 ] . scriptSig ) ) )
return new_tx
2020-08-25 09:33:04 +02:00
def cltv_invalidate ( node , tx , failure_reason ) :
# Modify the signature in vin 0 and nSequence/nLockTime of the tx to fail CLTV
#
# According to BIP65, OP_CHECKLOCKTIMEVERIFY can fail due the following reasons:
# 1) the stack is empty
# 2) the top item on the stack is less than 0
# 3) the lock-time type (height vs. timestamp) of the top stack item and the
# nLockTime field are not the same
# 4) the top stack item is greater than the transaction's nLockTime field
# 5) the nSequence field of the txin is 0xffffffff
assert failure_reason in range ( 5 )
scheme = [
# | Script to prepend to scriptSig | nSequence | nLockTime |
# +-------------------------------------------------+------------+--------------+
[ [ OP_CHECKLOCKTIMEVERIFY ] , None , None ] ,
[ [ OP_1NEGATE , OP_CHECKLOCKTIMEVERIFY , OP_DROP ] , None , None ] ,
[ [ CScriptNum ( 1000 ) , OP_CHECKLOCKTIMEVERIFY , OP_DROP ] , 0 , 1296688602 ] , # timestamp of genesis block
[ [ CScriptNum ( 1000 ) , OP_CHECKLOCKTIMEVERIFY , OP_DROP ] , 0 , 500 ] ,
[ [ CScriptNum ( 500 ) , OP_CHECKLOCKTIMEVERIFY , OP_DROP ] , 0xffffffff , 500 ] ,
] [ failure_reason ]
return cltv_modify_tx ( node , tx , prepend_scriptsig = scheme [ 0 ] , nsequence = scheme [ 1 ] , nlocktime = scheme [ 2 ] )
2020-08-25 09:11:25 +02:00
2015-06-28 14:42:17 -04:00
2017-06-28 14:52:24 -04:00
def cltv_validate ( node , tx , height ) :
2020-08-25 09:33:04 +02:00
# Modify the signature in vin 0 and nSequence/nLockTime of the tx to pass CLTV
scheme = [ [ CScriptNum ( height ) , OP_CHECKLOCKTIMEVERIFY , OP_DROP ] , 0 , height ]
return cltv_modify_tx ( node , tx , prepend_scriptsig = scheme [ 0 ] , nsequence = scheme [ 1 ] , nlocktime = scheme [ 2 ] )
2017-06-28 14:52:24 -04:00
2018-08-24 15:26:42 -04:00
2017-06-28 14:52:24 -04:00
class BIP65Test ( BitcoinTestFramework ) :
2017-06-09 18:21:21 -04:00
def set_test_params ( self ) :
2015-06-28 14:42:17 -04:00
self . num_nodes = 1
2019-04-24 17:55:58 -04:00
self . extra_args = [ [
2020-02-25 23:38:56 +07:00
' -whitelist=noban@127.0.0.1 ' ,
2019-04-24 17:55:58 -04:00
' -par=1 ' , # Use only one script thread to get the exact reject reason for testing
' -acceptnonstdtxn=1 ' , # cltv_invalidate is nonstandard
] ]
2017-06-28 14:52:24 -04:00
self . setup_clean_chain = True
2020-03-09 14:39:10 -04:00
self . rpc_timeout = 480
2015-06-28 14:42:17 -04:00
2018-09-09 13:32:37 -04:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2019-07-02 14:23:04 -04:00
def test_cltv_info ( self , * , is_active ) :
2020-08-25 09:11:25 +02:00
assert_equal ( self . nodes [ 0 ] . getblockchaininfo ( ) [ ' softforks ' ] [ ' bip65 ' ] , {
2019-05-20 14:58:09 -04:00
" active " : is_active ,
" height " : CLTV_HEIGHT ,
" type " : " buried " ,
2019-07-02 14:23:04 -04:00
} ,
)
2015-06-28 14:42:17 -04:00
def run_test ( self ) :
2020-09-03 18:05:26 -07:00
peer = self . nodes [ 0 ] . add_p2p_connection ( P2PInterface ( ) )
2017-06-28 14:52:24 -04:00
2019-07-02 14:23:04 -04:00
self . test_cltv_info ( is_active = False )
2017-06-28 14:52:24 -04:00
self . log . info ( " Mining %d blocks " , CLTV_HEIGHT - 2 )
2018-07-30 10:16:40 +02:00
self . coinbase_txids = [ self . nodes [ 0 ] . getblock ( b ) [ ' tx ' ] [ 0 ] for b in self . nodes [ 0 ] . generate ( CLTV_HEIGHT - 2 ) ]
2015-06-28 14:42:17 -04:00
self . nodeaddress = self . nodes [ 0 ] . getnewaddress ( )
2017-06-28 14:52:24 -04:00
2020-08-25 09:46:05 +02:00
self . log . info ( " Test that invalid-according-to-CLTV transactions can still appear in a block " )
2017-06-28 14:52:24 -04:00
2020-08-25 09:46:05 +02:00
# create one invalid tx per CLTV failure reason (5 in total) and collect them
invalid_ctlv_txs = [ ]
for i in range ( 5 ) :
spendtx = create_transaction ( self . nodes [ 0 ] , self . coinbase_txids [ i ] ,
self . nodeaddress , amount = 1.0 )
spendtx = cltv_invalidate ( self . nodes [ 0 ] , spendtx , i )
spendtx . rehash ( )
invalid_ctlv_txs . append ( spendtx )
2015-06-28 14:42:17 -04:00
2017-06-28 14:52:24 -04:00
tip = self . nodes [ 0 ] . getbestblockhash ( )
block_time = self . nodes [ 0 ] . getblockheader ( tip ) [ ' mediantime ' ] + 1
block = create_block ( int ( tip , 16 ) , create_coinbase ( CLTV_HEIGHT - 1 ) , block_time )
block . nVersion = 3
2020-08-25 09:46:05 +02:00
block . vtx . extend ( invalid_ctlv_txs )
2015-06-28 14:42:17 -04:00
block . hashMerkleRoot = block . calc_merkle_root ( )
block . solve ( )
2019-05-20 14:58:09 -04:00
self . test_cltv_info ( is_active = False ) # Not active as of current tip and next block does not need to obey rules
2020-09-03 18:05:26 -07:00
peer . send_and_ping ( msg_block ( block ) )
2019-05-20 14:58:09 -04:00
self . test_cltv_info ( is_active = True ) # Not active as of current tip, but next block must obey rules
2017-06-28 14:52:24 -04:00
assert_equal ( self . nodes [ 0 ] . getbestblockhash ( ) , block . hash )
self . log . info ( " Test that blocks must now be at least version 4 " )
tip = block . sha256
block_time + = 1
block = create_block ( tip , create_coinbase ( CLTV_HEIGHT ) , block_time )
2015-06-28 14:42:17 -04:00
block . nVersion = 3
block . solve ( )
2018-08-24 15:26:42 -04:00
with self . nodes [ 0 ] . assert_debug_log ( expected_msgs = [ ' {} , bad-version(0x00000003) ' . format ( block . hash ) ] ) :
2020-09-03 18:05:26 -07:00
peer . send_and_ping ( msg_block ( block ) )
2018-08-24 15:26:42 -04:00
assert_equal ( int ( self . nodes [ 0 ] . getbestblockhash ( ) , 16 ) , tip )
2020-09-03 18:05:26 -07:00
peer . sync_with_ping ( )
2017-06-28 14:52:24 -04:00
self . log . info ( " Test that invalid-according-to-cltv transactions cannot appear in a block " )
2015-06-28 14:42:17 -04:00
block . nVersion = 4
2017-06-28 14:52:24 -04:00
2020-08-25 09:46:05 +02:00
spendtx = create_transaction ( self . nodes [ 0 ] , self . coinbase_txids [ 10 ] ,
2020-08-25 09:11:25 +02:00
self . nodeaddress , amount = 1.0 )
2020-08-25 09:33:04 +02:00
spendtx = cltv_invalidate ( self . nodes [ 0 ] , spendtx , 1 )
2016-07-22 08:27:55 +09:00
spendtx . rehash ( )
2017-06-28 14:52:24 -04:00
# First we show that this tx is valid except for CLTV by getting it
2018-06-23 16:16:54 -04:00
# rejected from the mempool for exactly that reason.
assert_equal (
2021-01-07 16:47:35 +01:00
[ {
' txid ' : spendtx . hash ,
' wtxid ' : spendtx . getwtxid ( ) ,
' allowed ' : False ,
' reject-reason ' : ' non-mandatory-script-verify-flag (Negative locktime) ' ,
} ] ,
self . nodes [ 0 ] . testmempoolaccept ( rawtxs = [ spendtx . serialize ( ) . hex ( ) ] , maxfeerate = 0 ) ,
2018-06-23 16:16:54 -04:00
)
2017-06-28 14:52:24 -04:00
2018-06-23 16:16:54 -04:00
# Now we verify that a block with this transaction is also invalid.
2016-07-22 08:27:55 +09:00
block . vtx . append ( spendtx )
block . hashMerkleRoot = block . calc_merkle_root ( )
block . solve ( )
2019-08-19 11:55:38 -04:00
with self . nodes [ 0 ] . assert_debug_log ( expected_msgs = [ ' CheckInputScripts on {} failed with non-mandatory-script-verify-flag (Negative locktime) ' . format ( block . vtx [ - 1 ] . hash ) ] ) :
2020-09-03 18:05:26 -07:00
peer . send_and_ping ( msg_block ( block ) )
2018-08-24 15:26:42 -04:00
assert_equal ( int ( self . nodes [ 0 ] . getbestblockhash ( ) , 16 ) , tip )
2020-09-03 18:05:26 -07:00
peer . sync_with_ping ( )
2017-06-28 14:52:24 -04:00
self . log . info ( " Test that a version 4 block with a valid-according-to-CLTV transaction is accepted " )
spendtx = cltv_validate ( self . nodes [ 0 ] , spendtx , CLTV_HEIGHT - 1 )
spendtx . rehash ( )
block . vtx . pop ( 1 )
block . vtx . append ( spendtx )
block . hashMerkleRoot = block . calc_merkle_root ( )
2015-06-28 14:42:17 -04:00
block . solve ( )
2017-06-28 14:52:24 -04:00
2019-05-20 14:58:09 -04:00
self . test_cltv_info ( is_active = True ) # Not active as of current tip, but next block must obey rules
2020-09-03 18:05:26 -07:00
peer . send_and_ping ( msg_block ( block ) )
2019-07-02 14:23:04 -04:00
self . test_cltv_info ( is_active = True ) # Active as of current tip
2017-06-28 14:52:24 -04:00
assert_equal ( int ( self . nodes [ 0 ] . getbestblockhash ( ) , 16 ) , block . sha256 )
2015-06-28 14:42:17 -04:00
if __name__ == ' __main__ ' :
BIP65Test ( ) . main ( )