2016-08-31 13:38:23 +02:00
#!/usr/bin/env python3
2021-07-28 13:57:16 +02:00
# Copyright (c) 2016-2021 The Bitcoin Core developers
2016-08-31 13:38:23 +02:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-01-18 00:34:40 +01:00
""" Test NULLDUMMY softfork.
Connect to a single node .
Generate 2 blocks ( save the coinbases for later ) .
2021-03-05 16:42:25 +01:00
Generate COINBASE_MATURITY ( CB ) more blocks to ensure the coinbases are mature .
[ Policy / Consensus ] Check that NULLDUMMY compliant transactions are accepted in block CB + 3.
2017-01-18 00:34:40 +01:00
[ Policy ] Check that non - NULLDUMMY transactions are rejected before activation .
2021-03-05 16:42:25 +01:00
[ Consensus ] Check that the new NULLDUMMY rules are not enforced on block CB + 4.
[ Policy / Consensus ] Check that the new NULLDUMMY rules are enforced on block CB + 5.
2017-01-18 00:34:40 +01:00
"""
2018-09-23 14:50:47 +02:00
import time
2016-08-31 13:38:23 +02:00
2021-05-17 16:38:02 +02:00
from test_framework . blocktools import (
COINBASE_MATURITY ,
NORMAL_GBT_REQUEST_PARAMS ,
add_witness_commitment ,
create_block ,
)
2022-06-24 14:26:53 +02:00
from test_framework . messages import (
CTransaction ,
tx_from_hex ,
)
2021-11-12 23:32:01 +01:00
from test_framework . script import (
OP_0 ,
OP_TRUE ,
)
2018-07-07 00:10:35 +02:00
from test_framework . test_framework import BitcoinTestFramework
2021-08-18 17:23:26 +02:00
from test_framework . util import (
assert_equal ,
assert_raises_rpc_error ,
)
2022-06-24 14:26:53 +02:00
from test_framework . wallet import getnewdestination
from test_framework . key import ECKey
from test_framework . wallet_util import bytes_to_wif
2018-07-07 00:10:35 +02:00
2019-10-10 17:19:42 +02:00
NULLDUMMY_ERROR = " non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero) "
2016-08-31 13:38:23 +02:00
2021-06-04 15:48:07 +02:00
2021-11-12 23:56:05 +01:00
def invalidate_nulldummy_tx ( tx ) :
2021-11-12 23:32:01 +01:00
""" Transform a NULLDUMMY compliant tx (i.e. scriptSig starts with OP_0)
to be non - NULLDUMMY compliant by replacing the dummy with OP_TRUE """
assert_equal ( tx . vin [ 0 ] . scriptSig [ 0 ] , OP_0 )
tx . vin [ 0 ] . scriptSig = bytes ( [ OP_TRUE ] ) + tx . vin [ 0 ] . scriptSig [ 1 : ]
2016-08-31 13:38:23 +02:00
tx . rehash ( )
2021-06-04 15:48:07 +02:00
class NULLDUMMYTest ( BitcoinTestFramework ) :
2017-06-10 00:21:21 +02:00
def set_test_params ( self ) :
2021-06-04 15:48:07 +02:00
self . num_nodes = 1
2016-09-29 15:34:04 +02:00
self . setup_clean_chain = True
2017-10-12 05:25:18 +02:00
# This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through
# normal segwit activation here (and don't use the default always-on behaviour).
2020-02-25 17:38:56 +01:00
self . extra_args = [ [
2021-08-27 12:54:24 +02:00
f ' -testactivationheight=segwit@ { COINBASE_MATURITY + 5 } ' ,
2020-02-25 17:38:56 +01:00
' -addresstype=legacy ' ,
test: fix failure in feature_nulldummy.py on single-core machines
On single-core machines, executing the test feature_nulldummy.py results in
the following assertion error:
...
2021-08-18T15:37:58.805000Z TestFramework (INFO): Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation
2021-08-18T15:37:58.814000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
File "[...]/test/functional/test_framework/test_framework.py", line 131, in main
self.run_test()
File "[...]/test/functional/feature_nulldummy.py", line 107, in run_test
self.block_submit(self.nodes[0], [test4tx], accept=False)
File "[...]/test/functional/feature_nulldummy.py", line 134, in block_submit
assert_equal(None if accept else 'block-validation-failed', node.submitblock(block.serialize().hex()))
File "[...]/test/functional/test_framework/util.py", line 49, in assert_equal
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
AssertionError: not(block-validation-failed == non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero))
2021-08-18T15:37:58.866000Z TestFramework (INFO): Stopping nodes
...
The behaviour can be reproduced on a multi-core machine by simply changing the
function GetNumCores() (in src/util/system.cpp) to return 1:
int GetNumCores()
{
return 1;
}
2021-08-18 17:27:37 +02:00
' -par=1 ' , # Use only one script thread to get the exact reject reason for testing
2021-06-04 15:48:07 +02:00
] ]
2016-08-31 13:38:23 +02:00
2022-06-24 14:26:53 +02:00
def create_transaction ( self , * , txid , input_details = None , addr , amount , privkey ) :
input = { " txid " : txid , " vout " : 0 }
output = { addr : amount }
rawtx = self . nodes [ 0 ] . createrawtransaction ( [ input ] , output )
# Details only needed for scripthash or witness spends
input = None if not input_details else [ { * * input , * * input_details } ]
signedtx = self . nodes [ 0 ] . signrawtransactionwithkey ( rawtx , [ privkey ] , input )
return tx_from_hex ( signedtx [ " hex " ] )
2018-09-09 19:32:37 +02:00
2016-08-31 13:38:23 +02:00
def run_test ( self ) :
2022-06-24 14:26:53 +02:00
eckey = ECKey ( )
eckey . generate ( )
self . privkey = bytes_to_wif ( eckey . get_bytes ( ) )
self . pubkey = eckey . get_pubkey ( ) . get_bytes ( ) . hex ( )
cms = self . nodes [ 0 ] . createmultisig ( 1 , [ self . pubkey ] )
wms = self . nodes [ 0 ] . createmultisig ( 1 , [ self . pubkey ] , ' p2sh-segwit ' )
self . ms_address = cms [ " address " ]
ms_unlock_details = { " scriptPubKey " : self . nodes [ 0 ] . validateaddress ( self . ms_address ) [ " scriptPubKey " ] ,
" redeemScript " : cms [ " redeemScript " ] }
self . wit_ms_address = wms [ ' address ' ]
2016-08-31 13:38:23 +02:00
2021-08-19 17:10:24 +02:00
self . coinbase_blocks = self . generate ( self . nodes [ 0 ] , 2 ) # block height = 2
2016-08-31 13:38:23 +02:00
coinbase_txid = [ ]
for i in self . coinbase_blocks :
coinbase_txid . append ( self . nodes [ 0 ] . getblock ( i ) [ ' tx ' ] [ 0 ] )
2021-08-19 17:10:24 +02:00
self . generate ( self . nodes [ 0 ] , COINBASE_MATURITY ) # block height = COINBASE_MATURITY + 2
2016-08-31 13:38:23 +02:00
self . lastblockhash = self . nodes [ 0 ] . getbestblockhash ( )
2021-03-05 16:42:25 +01:00
self . lastblockheight = COINBASE_MATURITY + 2
self . lastblocktime = int ( time . time ( ) ) + self . lastblockheight
2016-08-31 13:38:23 +02:00
2021-03-05 16:42:25 +01:00
self . log . info ( f " Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [ { COINBASE_MATURITY + 3 } ] " )
2022-06-24 14:26:53 +02:00
test1txs = [ self . create_transaction ( txid = coinbase_txid [ 0 ] , addr = self . ms_address , amount = 49 ,
privkey = self . nodes [ 0 ] . get_deterministic_priv_key ( ) . key ) ]
2018-06-27 10:21:07 +02:00
txid1 = self . nodes [ 0 ] . sendrawtransaction ( test1txs [ 0 ] . serialize_with_witness ( ) . hex ( ) , 0 )
2022-06-24 14:26:53 +02:00
test1txs . append ( self . create_transaction ( txid = txid1 , input_details = ms_unlock_details ,
addr = self . ms_address , amount = 48 ,
privkey = self . privkey ) )
2018-06-27 10:21:07 +02:00
txid2 = self . nodes [ 0 ] . sendrawtransaction ( test1txs [ 1 ] . serialize_with_witness ( ) . hex ( ) , 0 )
2022-06-24 14:26:53 +02:00
test1txs . append ( self . create_transaction ( txid = coinbase_txid [ 1 ] ,
addr = self . wit_ms_address , amount = 49 ,
privkey = self . nodes [ 0 ] . get_deterministic_priv_key ( ) . key ) )
2018-06-27 10:21:07 +02:00
txid3 = self . nodes [ 0 ] . sendrawtransaction ( test1txs [ 2 ] . serialize_with_witness ( ) . hex ( ) , 0 )
2021-08-18 17:23:26 +02:00
self . block_submit ( self . nodes [ 0 ] , test1txs , accept = True )
2016-08-31 13:38:23 +02:00
2017-03-08 00:46:17 +01:00
self . log . info ( " Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation " )
2022-06-24 14:26:53 +02:00
test2tx = self . create_transaction ( txid = txid2 , input_details = ms_unlock_details ,
addr = self . ms_address , amount = 47 ,
privkey = self . privkey )
2021-11-12 23:56:05 +01:00
invalidate_nulldummy_tx ( test2tx )
2018-06-27 10:21:07 +02:00
assert_raises_rpc_error ( - 26 , NULLDUMMY_ERROR , self . nodes [ 0 ] . sendrawtransaction , test2tx . serialize_with_witness ( ) . hex ( ) , 0 )
2016-08-31 13:38:23 +02:00
2021-03-05 16:42:25 +01:00
self . log . info ( f " Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [ { COINBASE_MATURITY + 4 } ] " )
2021-08-18 17:23:26 +02:00
self . block_submit ( self . nodes [ 0 ] , [ test2tx ] , accept = True )
2016-08-31 13:38:23 +02:00
2017-03-17 23:36:39 +01:00
self . log . info ( " Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation " )
2022-06-24 14:26:53 +02:00
test4tx = self . create_transaction ( txid = test2tx . hash , input_details = ms_unlock_details ,
addr = getnewdestination ( ) [ 2 ] , amount = 46 ,
privkey = self . privkey )
2018-09-23 14:50:47 +02:00
test6txs = [ CTransaction ( test4tx ) ]
2021-11-12 23:56:05 +01:00
invalidate_nulldummy_tx ( test4tx )
2018-06-27 10:21:07 +02:00
assert_raises_rpc_error ( - 26 , NULLDUMMY_ERROR , self . nodes [ 0 ] . sendrawtransaction , test4tx . serialize_with_witness ( ) . hex ( ) , 0 )
2021-08-18 17:23:26 +02:00
self . block_submit ( self . nodes [ 0 ] , [ test4tx ] , accept = False )
2016-08-31 13:38:23 +02:00
2017-03-17 23:36:39 +01:00
self . log . info ( " Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation " )
2022-06-24 14:26:53 +02:00
test5tx = self . create_transaction ( txid = txid3 , input_details = { " scriptPubKey " : test1txs [ 2 ] . vout [ 0 ] . scriptPubKey . hex ( ) ,
" amount " : 49 , " witnessScript " : wms [ " redeemScript " ] } ,
addr = getnewdestination ( address_type = ' p2sh-segwit ' ) [ 2 ] , amount = 48 ,
privkey = self . privkey )
2016-08-31 13:38:23 +02:00
test6txs . append ( CTransaction ( test5tx ) )
test5tx . wit . vtxinwit [ 0 ] . scriptWitness . stack [ 0 ] = b ' \x01 '
2018-06-27 10:21:07 +02:00
assert_raises_rpc_error ( - 26 , NULLDUMMY_ERROR , self . nodes [ 0 ] . sendrawtransaction , test5tx . serialize_with_witness ( ) . hex ( ) , 0 )
2021-08-18 17:23:26 +02:00
self . block_submit ( self . nodes [ 0 ] , [ test5tx ] , with_witness = True , accept = False )
2016-08-31 13:38:23 +02:00
2021-03-05 16:42:25 +01:00
self . log . info ( f " Test 6: NULLDUMMY compliant base/witness transactions should be accepted to mempool and in block after activation [ { COINBASE_MATURITY + 5 } ] " )
2016-08-31 13:38:23 +02:00
for i in test6txs :
2018-06-27 10:21:07 +02:00
self . nodes [ 0 ] . sendrawtransaction ( i . serialize_with_witness ( ) . hex ( ) , 0 )
2021-08-18 17:23:26 +02:00
self . block_submit ( self . nodes [ 0 ] , test6txs , with_witness = True , accept = True )
2016-08-31 13:38:23 +02:00
2021-08-18 17:23:26 +02:00
def block_submit ( self , node , txs , * , with_witness = False , accept ) :
2020-06-27 02:24:13 +02:00
tmpl = node . getblocktemplate ( NORMAL_GBT_REQUEST_PARAMS )
assert_equal ( tmpl [ ' previousblockhash ' ] , self . lastblockhash )
assert_equal ( tmpl [ ' height ' ] , self . lastblockheight + 1 )
2021-11-13 00:11:25 +01:00
block = create_block ( tmpl = tmpl , ntime = self . lastblocktime + 1 , txlist = txs )
2021-08-18 17:23:26 +02:00
if with_witness :
add_witness_commitment ( block )
2016-08-31 13:38:23 +02:00
block . solve ( )
test: fix failure in feature_nulldummy.py on single-core machines
On single-core machines, executing the test feature_nulldummy.py results in
the following assertion error:
...
2021-08-18T15:37:58.805000Z TestFramework (INFO): Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation
2021-08-18T15:37:58.814000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
File "[...]/test/functional/test_framework/test_framework.py", line 131, in main
self.run_test()
File "[...]/test/functional/feature_nulldummy.py", line 107, in run_test
self.block_submit(self.nodes[0], [test4tx], accept=False)
File "[...]/test/functional/feature_nulldummy.py", line 134, in block_submit
assert_equal(None if accept else 'block-validation-failed', node.submitblock(block.serialize().hex()))
File "[...]/test/functional/test_framework/util.py", line 49, in assert_equal
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
AssertionError: not(block-validation-failed == non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero))
2021-08-18T15:37:58.866000Z TestFramework (INFO): Stopping nodes
...
The behaviour can be reproduced on a multi-core machine by simply changing the
function GetNumCores() (in src/util/system.cpp) to return 1:
int GetNumCores()
{
return 1;
}
2021-08-18 17:27:37 +02:00
assert_equal ( None if accept else NULLDUMMY_ERROR , node . submitblock ( block . serialize ( ) . hex ( ) ) )
2021-08-18 17:23:26 +02:00
if accept :
2016-08-31 13:38:23 +02:00
assert_equal ( node . getbestblockhash ( ) , block . hash )
self . lastblockhash = block . hash
self . lastblocktime + = 1
self . lastblockheight + = 1
else :
assert_equal ( node . getbestblockhash ( ) , self . lastblockhash )
2021-03-05 16:42:25 +01:00
2016-08-31 13:38:23 +02:00
if __name__ == ' __main__ ' :
2016-09-29 15:34:04 +02:00
NULLDUMMYTest ( ) . main ( )