Skip to content

Commit 561f8af

Browse files
committed
Merge pull request #6523
0ce7398 Add p2p-fullblocktest.py (Casey Rodarmor)
2 parents 49793fb + 0ce7398 commit 561f8af

12 files changed

+702
-53
lines changed

Diff for: qa/pull-tester/rpc-tests.sh

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ testScripts=(
3636
'nodehandling.py'
3737
'reindex.py'
3838
'decodescript.py'
39+
'p2p-fullblocktest.py'
3940
);
4041
testScriptsExt=(
4142
'bipdersig-p2p.py'

Diff for: qa/rpc-tests/README.md

+103-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Regression tests of RPC interface
2-
=================================
1+
Regression tests
2+
================
33

44
### [python-bitcoinrpc](https://github.com/jgarzik/python-bitcoinrpc)
55
Git subtree of [https://github.com/jgarzik/python-bitcoinrpc](https://github.com/jgarzik/python-bitcoinrpc).
@@ -12,6 +12,28 @@ Base class for new regression tests.
1212
### [test_framework/util.py](test_framework/util.py)
1313
Generally useful functions.
1414

15+
### [test_framework/mininode.py](test_framework/mininode.py)
16+
Basic code to support p2p connectivity to a bitcoind.
17+
18+
### [test_framework/comptool.py](test_framework/comptool.py)
19+
Framework for comparison-tool style, p2p tests.
20+
21+
### [test_framework/script.py](test_framework/script.py)
22+
Utilities for manipulating transaction scripts (originally from python-bitcoinlib)
23+
24+
### [test_framework/blockstore.py](test_framework/blockstore.py)
25+
Implements disk-backed block and tx storage.
26+
27+
### [test_framework/key.py](test_framework/key.py)
28+
Wrapper around OpenSSL EC_Key (originally from python-bitcoinlib)
29+
30+
### [test_framework/bignum.py](test_framework/bignum.py)
31+
Helpers for script.py
32+
33+
### [test_framework/blocktools.py](test_framework/blocktools.py)
34+
Helper functions for creating blocks and transactions.
35+
36+
1537
Notes
1638
=====
1739

@@ -49,3 +71,82 @@ to recover with:
4971
rm -rf cache
5072
killall bitcoind
5173
```
74+
75+
P2P test design notes
76+
---------------------
77+
78+
## Mininode
79+
80+
* ```mininode.py``` contains all the definitions for objects that pass
81+
over the network (```CBlock```, ```CTransaction```, etc, along with the network-level
82+
wrappers for them, ```msg_block```, ```msg_tx```, etc).
83+
84+
* P2P tests have two threads. One thread handles all network communication
85+
with the bitcoind(s) being tested (using python's asyncore package); the other
86+
implements the test logic.
87+
88+
* ```NodeConn``` is the class used to connect to a bitcoind. If you implement
89+
a callback class that derives from ```NodeConnCB``` and pass that to the
90+
```NodeConn``` object, your code will receive the appropriate callbacks when
91+
events of interest arrive. NOTE: be sure to call
92+
```self.create_callback_map()``` in your derived classes' ```__init__```
93+
function, so that the correct mappings are set up between p2p messages and your
94+
callback functions.
95+
96+
* You can pass the same handler to multiple ```NodeConn```'s if you like, or pass
97+
different ones to each -- whatever makes the most sense for your test.
98+
99+
* Call ```NetworkThread.start()``` after all ```NodeConn``` objects are created to
100+
start the networking thread. (Continue with the test logic in your existing
101+
thread.)
102+
103+
* RPC calls are available in p2p tests.
104+
105+
* Can be used to write free-form tests, where specific p2p-protocol behavior
106+
is tested. Examples: ```p2p-accept-block.py```, ```maxblocksinflight.py```.
107+
108+
## Comptool
109+
110+
* Testing framework for writing tests that compare the block/tx acceptance
111+
behavior of a bitcoind against 1 or more other bitcoind instances, or against
112+
known outcomes, or both.
113+
114+
* Set the ```num_nodes``` variable (defined in ```ComparisonTestFramework```) to start up
115+
1 or more nodes. If using 1 node, then ```--testbinary``` can be used as a command line
116+
option to change the bitcoind binary used by the test. If using 2 or more nodes,
117+
then ```--refbinary``` can be optionally used to change the bitcoind that will be used
118+
on nodes 2 and up.
119+
120+
* Implement a (generator) function called ```get_tests()``` which yields ```TestInstance```s.
121+
Each ```TestInstance``` consists of:
122+
- a list of ```[object, outcome, hash]``` entries
123+
* ```object``` is a ```CBlock```, ```CTransaction```, or
124+
```CBlockHeader```. ```CBlock```'s and ```CTransaction```'s are tested for
125+
acceptance. ```CBlockHeader```s can be used so that the test runner can deliver
126+
complete headers-chains when requested from the bitcoind, to allow writing
127+
tests where blocks can be delivered out of order but still processed by
128+
headers-first bitcoind's.
129+
* ```outcome``` is ```True```, ```False```, or ```None```. If ```True```
130+
or ```False```, the tip is compared with the expected tip -- either the
131+
block passed in, or the hash specified as the optional 3rd entry. If
132+
```None``` is specified, then the test will compare all the bitcoind's
133+
being tested to see if they all agree on what the best tip is.
134+
* ```hash``` is the block hash of the tip to compare against. Optional to
135+
specify; if left out then the hash of the block passed in will be used as
136+
the expected tip. This allows for specifying an expected tip while testing
137+
the handling of either invalid blocks or blocks delivered out of order,
138+
which complete a longer chain.
139+
- ```sync_every_block```: ```True/False```. If ```False```, then all blocks
140+
are inv'ed together, and the test runner waits until the node receives the
141+
last one, and tests only the last block for tip acceptance using the
142+
outcome and specified tip. If ```True```, then each block is tested in
143+
sequence and synced (this is slower when processing many blocks).
144+
- ```sync_every_transaction```: ```True/False```. Analogous to
145+
```sync_every_block```, except if the outcome on the last tx is "None",
146+
then the contents of the entire mempool are compared across all bitcoind
147+
connections. If ```True``` or ```False```, then only the last tx's
148+
acceptance is tested against the given outcome.
149+
150+
* For examples of tests written in this framework, see
151+
```invalidblockrequest.py``` and ```p2p-fullblocktest.py```.
152+

Diff for: qa/rpc-tests/bipdersig-p2p.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -75,32 +75,35 @@ def create_transaction(self, node, coinbase, to_address, amount):
7575
def get_tests(self):
7676

7777
self.coinbase_blocks = self.nodes[0].generate(2)
78+
height = 3 # height of the next block to build
7879
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
7980
self.nodeaddress = self.nodes[0].getnewaddress()
8081
self.last_block_time = time.time()
8182

8283
''' 98 more version 2 blocks '''
8384
test_blocks = []
8485
for i in xrange(98):
85-
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
86+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
8687
block.nVersion = 2
8788
block.rehash()
8889
block.solve()
8990
test_blocks.append([block, True])
9091
self.last_block_time += 1
9192
self.tip = block.sha256
93+
height += 1
9294
yield TestInstance(test_blocks, sync_every_block=False)
9395

9496
''' Mine 749 version 3 blocks '''
9597
test_blocks = []
9698
for i in xrange(749):
97-
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
99+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
98100
block.nVersion = 3
99101
block.rehash()
100102
block.solve()
101103
test_blocks.append([block, True])
102104
self.last_block_time += 1
103105
self.tip = block.sha256
106+
height += 1
104107
yield TestInstance(test_blocks, sync_every_block=False)
105108

106109
'''
@@ -112,7 +115,7 @@ def get_tests(self):
112115
unDERify(spendtx)
113116
spendtx.rehash()
114117

115-
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
118+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
116119
block.nVersion = 3
117120
block.vtx.append(spendtx)
118121
block.hashMerkleRoot = block.calc_merkle_root()
@@ -121,6 +124,7 @@ def get_tests(self):
121124

122125
self.last_block_time += 1
123126
self.tip = block.sha256
127+
height += 1
124128
yield TestInstance([[block, True]])
125129

126130
'''
@@ -132,7 +136,7 @@ def get_tests(self):
132136
unDERify(spendtx)
133137
spendtx.rehash()
134138

135-
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
139+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
136140
block.nVersion = 3
137141
block.vtx.append(spendtx)
138142
block.hashMerkleRoot = block.calc_merkle_root()
@@ -144,35 +148,38 @@ def get_tests(self):
144148
''' Mine 199 new version blocks on last valid tip '''
145149
test_blocks = []
146150
for i in xrange(199):
147-
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
151+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
148152
block.nVersion = 3
149153
block.rehash()
150154
block.solve()
151155
test_blocks.append([block, True])
152156
self.last_block_time += 1
153157
self.tip = block.sha256
158+
height += 1
154159
yield TestInstance(test_blocks, sync_every_block=False)
155160

156161
''' Mine 1 old version block '''
157-
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
162+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
158163
block.nVersion = 2
159164
block.rehash()
160165
block.solve()
161166
self.last_block_time += 1
162167
self.tip = block.sha256
168+
height += 1
163169
yield TestInstance([[block, True]])
164170

165171
''' Mine 1 new version block '''
166-
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
172+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
167173
block.nVersion = 3
168174
block.rehash()
169175
block.solve()
170176
self.last_block_time += 1
171177
self.tip = block.sha256
178+
height += 1
172179
yield TestInstance([[block, True]])
173180

174181
''' Mine 1 old version block, should be invalid '''
175-
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
182+
block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1)
176183
block.nVersion = 2
177184
block.rehash()
178185
block.solve()

Diff for: qa/rpc-tests/invalidblockrequest.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,27 @@ def get_tests(self):
4646
'''
4747
Create a new block with an anyone-can-spend coinbase
4848
'''
49-
block = create_block(self.tip, create_coinbase(), self.block_time)
49+
height = 1
50+
block = create_block(self.tip, create_coinbase(height), self.block_time)
5051
self.block_time += 1
5152
block.solve()
5253
# Save the coinbase for later
5354
self.block1 = block
5455
self.tip = block.sha256
56+
height += 1
5557
yield TestInstance([[block, True]])
5658

5759
'''
5860
Now we need that block to mature so we can spend the coinbase.
5961
'''
6062
test = TestInstance(sync_every_block=False)
6163
for i in xrange(100):
62-
block = create_block(self.tip, create_coinbase(), self.block_time)
64+
block = create_block(self.tip, create_coinbase(height), self.block_time)
6365
block.solve()
6466
self.tip = block.sha256
6567
self.block_time += 1
6668
test.blocks_and_transactions.append([block, True])
69+
height += 1
6770
yield test
6871

6972
'''
@@ -73,7 +76,7 @@ def get_tests(self):
7376
coinbase, spend of that spend). Duplicate the 3rd transaction to
7477
leave merkle root and blockheader unchanged but invalidate the block.
7578
'''
76-
block2 = create_block(self.tip, create_coinbase(), self.block_time)
79+
block2 = create_block(self.tip, create_coinbase(height), self.block_time)
7780
self.block_time += 1
7881

7982
# chr(81) is OP_TRUE
@@ -95,11 +98,12 @@ def get_tests(self):
9598

9699
self.tip = block2.sha256
97100
yield TestInstance([[block2, False], [block2_orig, True]])
101+
height += 1
98102

99103
'''
100104
Make sure that a totally screwed up block is not valid.
101105
'''
102-
block3 = create_block(self.tip, create_coinbase(), self.block_time)
106+
block3 = create_block(self.tip, create_coinbase(height), self.block_time)
103107
self.block_time += 1
104108
block3.vtx[0].vout[0].nValue = 100*100000000 # Too high!
105109
block3.vtx[0].sha256=None

Diff for: qa/rpc-tests/p2p-acceptblock.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def run_test(self):
153153
blocks_h2 = [] # the height 2 blocks on each node's chain
154154
block_time = time.time() + 1
155155
for i in xrange(2):
156-
blocks_h2.append(create_block(tips[i], create_coinbase(), block_time))
156+
blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time))
157157
blocks_h2[i].solve()
158158
block_time += 1
159159
test_node.send_message(msg_block(blocks_h2[0]))
@@ -167,7 +167,7 @@ def run_test(self):
167167
# 3. Send another block that builds on the original tip.
168168
blocks_h2f = [] # Blocks at height 2 that fork off the main chain
169169
for i in xrange(2):
170-
blocks_h2f.append(create_block(tips[i], create_coinbase(), blocks_h2[i].nTime+1))
170+
blocks_h2f.append(create_block(tips[i], create_coinbase(2), blocks_h2[i].nTime+1))
171171
blocks_h2f[i].solve()
172172
test_node.send_message(msg_block(blocks_h2f[0]))
173173
white_node.send_message(msg_block(blocks_h2f[1]))
@@ -186,7 +186,7 @@ def run_test(self):
186186
# 4. Now send another block that builds on the forking chain.
187187
blocks_h3 = []
188188
for i in xrange(2):
189-
blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(), blocks_h2f[i].nTime+1))
189+
blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(3), blocks_h2f[i].nTime+1))
190190
blocks_h3[i].solve()
191191
test_node.send_message(msg_block(blocks_h3[0]))
192192
white_node.send_message(msg_block(blocks_h3[1]))
@@ -217,7 +217,7 @@ def run_test(self):
217217
all_blocks = [] # node0's blocks
218218
for j in xrange(2):
219219
for i in xrange(288):
220-
next_block = create_block(tips[j].sha256, create_coinbase(), tips[j].nTime+1)
220+
next_block = create_block(tips[j].sha256, create_coinbase(i + 4), tips[j].nTime+1)
221221
next_block.solve()
222222
if j==0:
223223
test_node.send_message(msg_block(next_block))

0 commit comments

Comments
 (0)