Skip to content

Commit

Permalink
test: Add basic test for reject code
Browse files Browse the repository at this point in the history
Extend P2P test framework to make it possible to expect reject
codes for transactions and blocks.
  • Loading branch information
laanwj committed Dec 9, 2015
1 parent 9fc6ed6 commit 2041190
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 4 deletions.
3 changes: 2 additions & 1 deletion qa/pull-tester/rpc-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
'sendheaders.py',
'keypool.py',
'prioritise_transaction.py',
'invalidblockrequest.py',
'invalidtxrequest.py',
]
testScriptsExt = [
'bip65-cltv.py',
Expand All @@ -116,7 +118,6 @@
# 'rpcbind_test.py', #temporary, bug in libevent, see #6655
'smartfees.py',
'maxblocksinflight.py',
'invalidblockrequest.py',
'p2p-acceptblock.py',
'mempool_packages.py',
'maxuploadtarget.py',
Expand Down
6 changes: 3 additions & 3 deletions qa/rpc-tests/invalidblockrequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from test_framework.test_framework import ComparisonTestFramework
from test_framework.util import *
from test_framework.comptool import TestManager, TestInstance
from test_framework.comptool import TestManager, TestInstance, RejectResult
from test_framework.mininode import *
from test_framework.blocktools import *
import logging
Expand Down Expand Up @@ -97,7 +97,7 @@ def get_tests(self):
assert(block2_orig.vtx != block2.vtx)

self.tip = block2.sha256
yield TestInstance([[block2, False], [block2_orig, True]])
yield TestInstance([[block2, RejectResult(16,'bad-txns-duplicate')], [block2_orig, True]])
height += 1

'''
Expand All @@ -112,7 +112,7 @@ def get_tests(self):
block3.rehash()
block3.solve()

yield TestInstance([[block3, False]])
yield TestInstance([[block3, RejectResult(16,'bad-cb-amount')]])


if __name__ == '__main__':
Expand Down
76 changes: 76 additions & 0 deletions qa/rpc-tests/invalidtxrequest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python2
#
# Distributed under the MIT/X11 software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#

from test_framework.test_framework import ComparisonTestFramework
from test_framework.util import *
from test_framework.comptool import TestManager, TestInstance, RejectResult
from test_framework.mininode import *
from test_framework.blocktools import *
import logging
import copy
import time


'''
In this test we connect to one node over p2p, and test tx requests.
'''

# Use the ComparisonTestFramework with 1 node: only use --testbinary.
class InvalidTxRequestTest(ComparisonTestFramework):

''' Can either run this test as 1 node with expected answers, or two and compare them.
Change the "outcome" variable from each TestInstance object to only do the comparison. '''
def __init__(self):
self.num_nodes = 1

def run_test(self):
test = TestManager(self, self.options.tmpdir)
test.add_all_connections(self.nodes)
self.tip = None
self.block_time = None
NetworkThread().start() # Start up network handling in another thread
test.run()

def get_tests(self):
if self.tip is None:
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
self.block_time = int(time.time())+1

'''
Create a new block with an anyone-can-spend coinbase
'''
height = 1
block = create_block(self.tip, create_coinbase(height), self.block_time)
self.block_time += 1
block.solve()
# Save the coinbase for later
self.block1 = block
self.tip = block.sha256
height += 1
yield TestInstance([[block, True]])

'''
Now we need that block to mature so we can spend the coinbase.
'''
test = TestInstance(sync_every_block=False)
for i in xrange(100):
block = create_block(self.tip, create_coinbase(height), self.block_time)
block.solve()
self.tip = block.sha256
self.block_time += 1
test.blocks_and_transactions.append([block, True])
height += 1
yield test

# chr(100) is OP_NOTIF
# Transaction will be rejected with code 16 (REJECT_INVALID)
tx1 = create_transaction(self.block1.vtx[0], 0, chr(100), 50*100000000)
yield TestInstance([[tx1, RejectResult(16, 'mandatory-script-verify-flag-failed')]])

# TODO: test further transactions...

if __name__ == '__main__':
InvalidTxRequestTest().main()
40 changes: 40 additions & 0 deletions qa/rpc-tests/test_framework/comptool.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):

return False

class RejectResult(object):
'''
Outcome that expects rejection of a transaction or block.
'''
def __init__(self, code, reason=''):
self.code = code
self.reason = reason
def match(self, other):
if self.code != other.code:
return False
return other.reason.startswith(self.reason)
def __repr__(self):
return '%i:%s' % (self.code,self.reason or '*')

class TestNode(NodeConnCB):

def __init__(self, block_store, tx_store):
Expand All @@ -51,6 +65,8 @@ def __init__(self, block_store, tx_store):
self.block_request_map = {}
self.tx_store = tx_store
self.tx_request_map = {}
self.block_reject_map = {}
self.tx_reject_map = {}

# When the pingmap is non-empty we're waiting for
# a response
Expand Down Expand Up @@ -94,6 +110,12 @@ def on_pong(self, conn, message):
except KeyError:
raise AssertionError("Got pong for unknown ping [%s]" % repr(message))

def on_reject(self, conn, message):
if message.message == 'tx':
self.tx_reject_map[message.data] = RejectResult(message.code, message.reason)
if message.message == 'block':
self.block_reject_map[message.data] = RejectResult(message.code, message.reason)

def send_inv(self, obj):
mtype = 2 if isinstance(obj, CBlock) else 1
self.conn.send_message(msg_inv([CInv(mtype, obj.sha256)]))
Expand Down Expand Up @@ -243,6 +265,15 @@ def check_results(self, blockhash, outcome):
if outcome is None:
if c.cb.bestblockhash != self.connections[0].cb.bestblockhash:
return False
elif isinstance(outcome, RejectResult): # Check that block was rejected w/ code
if c.cb.bestblockhash == blockhash:
return False
if blockhash not in c.cb.block_reject_map:
print 'Block not in reject map: %064x' % (blockhash)
return False
if not outcome.match(c.cb.block_reject_map[blockhash]):
print 'Block rejected with %s instead of expected %s: %064x' % (c.cb.block_reject_map[blockhash], outcome, blockhash)
return False
elif ((c.cb.bestblockhash == blockhash) != outcome):
# print c.cb.bestblockhash, blockhash, outcome
return False
Expand All @@ -262,6 +293,15 @@ def check_mempool(self, txhash, outcome):
if c.cb.lastInv != self.connections[0].cb.lastInv:
# print c.rpc.getrawmempool()
return False
elif isinstance(outcome, RejectResult): # Check that tx was rejected w/ code
if txhash in c.cb.lastInv:
return False
if txhash not in c.cb.tx_reject_map:
print 'Tx not in reject map: %064x' % (txhash)
return False
if not outcome.match(c.cb.tx_reject_map[txhash]):
print 'Tx rejected with %s instead of expected %s: %064x' % (c.cb.tx_reject_map[txhash], outcome, txhash)
return False
elif ((txhash in c.cb.lastInv) != outcome):
# print c.rpc.getrawmempool(), c.cb.lastInv
return False
Expand Down

0 comments on commit 2041190

Please sign in to comment.