Skip to content

Commit d76412b

Browse files
committed
Add script manipulation tools for use in mininode testing framework
script.py is modified from the code in python-bitcoinlib, and provides tools for manipulating and creating CScript objects. bignum.py is a dependency for script.py script_test.py is an example test that uses the script tools for running a test that compares the behavior of two nodes, in a comptool- style test, for each of the test cases in the bitcoin unit test script files, script_valid.json and script_invalid.json. (This test is very slow to run, but is a proof of concept for how we can write tests to compare consensus-critical behavior between different versions of bitcoind.) bipdersig-p2p.py is another example test in the comptool framework, which tests deployment of BIP DERSIG for a single node. It uses the script.py tools for manipulating signatures to be non-DER compliant.
1 parent b93974c commit d76412b

File tree

4 files changed

+1434
-0
lines changed

4 files changed

+1434
-0
lines changed

qa/rpc-tests/bignum.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#
2+
#
3+
# bignum.py
4+
#
5+
# This file is copied from python-bitcoinlib.
6+
#
7+
# Distributed under the MIT/X11 software license, see the accompanying
8+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
9+
#
10+
11+
"""Bignum routines"""
12+
13+
from __future__ import absolute_import, division, print_function, unicode_literals
14+
15+
import struct
16+
17+
18+
# generic big endian MPI format
19+
20+
def bn_bytes(v, have_ext=False):
21+
ext = 0
22+
if have_ext:
23+
ext = 1
24+
return ((v.bit_length()+7)//8) + ext
25+
26+
def bn2bin(v):
27+
s = bytearray()
28+
i = bn_bytes(v)
29+
while i > 0:
30+
s.append((v >> ((i-1) * 8)) & 0xff)
31+
i -= 1
32+
return s
33+
34+
def bin2bn(s):
35+
l = 0
36+
for ch in s:
37+
l = (l << 8) | ch
38+
return l
39+
40+
def bn2mpi(v):
41+
have_ext = False
42+
if v.bit_length() > 0:
43+
have_ext = (v.bit_length() & 0x07) == 0
44+
45+
neg = False
46+
if v < 0:
47+
neg = True
48+
v = -v
49+
50+
s = struct.pack(b">I", bn_bytes(v, have_ext))
51+
ext = bytearray()
52+
if have_ext:
53+
ext.append(0)
54+
v_bin = bn2bin(v)
55+
if neg:
56+
if have_ext:
57+
ext[0] |= 0x80
58+
else:
59+
v_bin[0] |= 0x80
60+
return s + ext + v_bin
61+
62+
def mpi2bn(s):
63+
if len(s) < 4:
64+
return None
65+
s_size = bytes(s[:4])
66+
v_len = struct.unpack(b">I", s_size)[0]
67+
if len(s) != (v_len + 4):
68+
return None
69+
if v_len == 0:
70+
return 0
71+
72+
v_str = bytearray(s[4:])
73+
neg = False
74+
i = v_str[0]
75+
if i & 0x80:
76+
neg = True
77+
i &= ~0x80
78+
v_str[0] = i
79+
80+
v = bin2bn(v_str)
81+
82+
if neg:
83+
return -v
84+
return v
85+
86+
# bitcoin-specific little endian format, with implicit size
87+
def mpi2vch(s):
88+
r = s[4:] # strip size
89+
r = r[::-1] # reverse string, converting BE->LE
90+
return r
91+
92+
def bn2vch(v):
93+
return bytes(mpi2vch(bn2mpi(v)))
94+
95+
def vch2mpi(s):
96+
r = struct.pack(b">I", len(s)) # size
97+
r += s[::-1] # reverse string, converting LE->BE
98+
return r
99+
100+
def vch2bn(s):
101+
return mpi2bn(vch2mpi(s))
102+

qa/rpc-tests/bipdersig-p2p.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#!/usr/bin/env python2
2+
#
3+
# Distributed under the MIT/X11 software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
#
6+
7+
from test_framework import ComparisonTestFramework
8+
from util import *
9+
from mininode import CTransaction, NetworkThread
10+
from blocktools import create_coinbase, create_block
11+
from binascii import hexlify, unhexlify
12+
import cStringIO
13+
from comptool import TestInstance, TestManager
14+
from script import CScript
15+
import time
16+
17+
# A canonical signature consists of:
18+
# <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
19+
def unDERify(tx):
20+
'''
21+
Make the signature in vin 0 of a tx non-DER-compliant,
22+
by adding padding after the S-value.
23+
'''
24+
scriptSig = CScript(tx.vin[0].scriptSig)
25+
newscript = []
26+
for i in scriptSig:
27+
if (len(newscript) == 0):
28+
newscript.append(i[0:-1] + '\0' + i[-1])
29+
else:
30+
newscript.append(i)
31+
tx.vin[0].scriptSig = CScript(newscript)
32+
33+
'''
34+
This test is meant to exercise BIP66 (DER SIG).
35+
Connect to a single node.
36+
Mine 2 (version 2) blocks (save the coinbases for later).
37+
Generate 98 more version 2 blocks, verify the node accepts.
38+
Mine 749 version 3 blocks, verify the node accepts.
39+
Check that the new DERSIG rules are not enforced on the 750th version 3 block.
40+
Check that the new DERSIG rules are enforced on the 751st version 3 block.
41+
Mine 199 new version blocks.
42+
Mine 1 old-version block.
43+
Mine 1 new version block.
44+
Mine 1 old version block, see that the node rejects.
45+
'''
46+
47+
class BIP66Test(ComparisonTestFramework):
48+
49+
def __init__(self):
50+
self.num_nodes = 1
51+
52+
def setup_network(self):
53+
# Must set the blockversion for this test
54+
self.nodes = start_nodes(1, self.options.tmpdir,
55+
extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=2']],
56+
binary=[self.options.testbinary])
57+
58+
def run_test(self):
59+
test = TestManager(self, self.options.tmpdir)
60+
test.add_all_connections(self.nodes)
61+
NetworkThread().start() # Start up network handling in another thread
62+
test.run()
63+
64+
def create_transaction(self, node, coinbase, to_address, amount):
65+
from_txid = node.getblock(coinbase)['tx'][0]
66+
inputs = [{ "txid" : from_txid, "vout" : 0}]
67+
outputs = { to_address : amount }
68+
rawtx = node.createrawtransaction(inputs, outputs)
69+
signresult = node.signrawtransaction(rawtx)
70+
tx = CTransaction()
71+
f = cStringIO.StringIO(unhexlify(signresult['hex']))
72+
tx.deserialize(f)
73+
return tx
74+
75+
def get_tests(self):
76+
77+
self.coinbase_blocks = self.nodes[0].generate(2)
78+
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
79+
self.nodeaddress = self.nodes[0].getnewaddress()
80+
self.last_block_time = time.time()
81+
82+
''' 98 more version 2 blocks '''
83+
test_blocks = []
84+
for i in xrange(98):
85+
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
86+
block.nVersion = 2
87+
block.rehash()
88+
block.solve()
89+
test_blocks.append([block, True])
90+
self.last_block_time += 1
91+
self.tip = block.sha256
92+
yield TestInstance(test_blocks, sync_every_block=False)
93+
94+
''' Mine 749 version 3 blocks '''
95+
test_blocks = []
96+
for i in xrange(749):
97+
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
98+
block.nVersion = 3
99+
block.rehash()
100+
block.solve()
101+
test_blocks.append([block, True])
102+
self.last_block_time += 1
103+
self.tip = block.sha256
104+
yield TestInstance(test_blocks, sync_every_block=False)
105+
106+
'''
107+
Check that the new DERSIG rules are not enforced in the 750th
108+
version 3 block.
109+
'''
110+
spendtx = self.create_transaction(self.nodes[0],
111+
self.coinbase_blocks[0], self.nodeaddress, 1.0)
112+
unDERify(spendtx)
113+
spendtx.rehash()
114+
115+
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
116+
block.nVersion = 3
117+
block.vtx.append(spendtx)
118+
block.hashMerkleRoot = block.calc_merkle_root()
119+
block.rehash()
120+
block.solve()
121+
122+
self.last_block_time += 1
123+
self.tip = block.sha256
124+
yield TestInstance([[block, True]])
125+
126+
'''
127+
Check that the new DERSIG rules are enforced in the 751st version 3
128+
block.
129+
'''
130+
spendtx = self.create_transaction(self.nodes[0],
131+
self.coinbase_blocks[1], self.nodeaddress, 1.0)
132+
unDERify(spendtx)
133+
spendtx.rehash()
134+
135+
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
136+
block.nVersion = 3
137+
block.vtx.append(spendtx)
138+
block.hashMerkleRoot = block.calc_merkle_root()
139+
block.rehash()
140+
block.solve()
141+
self.last_block_time += 1
142+
yield TestInstance([[block, False]])
143+
144+
''' Mine 199 new version blocks on last valid tip '''
145+
test_blocks = []
146+
for i in xrange(199):
147+
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
148+
block.nVersion = 3
149+
block.rehash()
150+
block.solve()
151+
test_blocks.append([block, True])
152+
self.last_block_time += 1
153+
self.tip = block.sha256
154+
yield TestInstance(test_blocks, sync_every_block=False)
155+
156+
''' Mine 1 old version block '''
157+
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
158+
block.nVersion = 2
159+
block.rehash()
160+
block.solve()
161+
self.last_block_time += 1
162+
self.tip = block.sha256
163+
yield TestInstance([[block, True]])
164+
165+
''' Mine 1 new version block '''
166+
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
167+
block.nVersion = 3
168+
block.rehash()
169+
block.solve()
170+
self.last_block_time += 1
171+
self.tip = block.sha256
172+
yield TestInstance([[block, True]])
173+
174+
''' Mine 1 old version block, should be invalid '''
175+
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
176+
block.nVersion = 2
177+
block.rehash()
178+
block.solve()
179+
self.last_block_time += 1
180+
yield TestInstance([[block, False]])
181+
182+
if __name__ == '__main__':
183+
BIP66Test().main()

0 commit comments

Comments
 (0)