Skip to content

Commit 44fef99

Browse files
committed
net: Fix sent reject messages for blocks and transactions
Ever since we #5913 have been sending invalid reject messages for transactions and blocks. test: Add basic test for `reject` code Extend P2P test framework to make it possible to expect reject codes for transactions and blocks. Github-Pull: #7179 Rebased-From: 9fc6ed6 2041190
1 parent 96e8d12 commit 44fef99

File tree

5 files changed

+123
-6
lines changed

5 files changed

+123
-6
lines changed

qa/pull-tester/rpc-tests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@
100100
'sendheaders.py',
101101
'keypool.py',
102102
'prioritise_transaction.py',
103+
'invalidblockrequest.py',
104+
'invalidtxrequest.py',
103105
]
104106
testScriptsExt = [
105107
'bip65-cltv.py',
@@ -116,7 +118,6 @@
116118
# 'rpcbind_test.py', #temporary, bug in libevent, see #6655
117119
'smartfees.py',
118120
'maxblocksinflight.py',
119-
'invalidblockrequest.py',
120121
'p2p-acceptblock.py',
121122
'mempool_packages.py',
122123
'maxuploadtarget.py',

qa/rpc-tests/invalidblockrequest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from test_framework.test_framework import ComparisonTestFramework
88
from test_framework.util import *
9-
from test_framework.comptool import TestManager, TestInstance
9+
from test_framework.comptool import TestManager, TestInstance, RejectResult
1010
from test_framework.mininode import *
1111
from test_framework.blocktools import *
1212
import logging
@@ -97,7 +97,7 @@ def get_tests(self):
9797
assert(block2_orig.vtx != block2.vtx)
9898

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

103103
'''
@@ -112,7 +112,7 @@ def get_tests(self):
112112
block3.rehash()
113113
block3.solve()
114114

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

117117

118118
if __name__ == '__main__':

qa/rpc-tests/invalidtxrequest.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.test_framework import ComparisonTestFramework
8+
from test_framework.util import *
9+
from test_framework.comptool import TestManager, TestInstance, RejectResult
10+
from test_framework.mininode import *
11+
from test_framework.blocktools import *
12+
import logging
13+
import copy
14+
import time
15+
16+
17+
'''
18+
In this test we connect to one node over p2p, and test tx requests.
19+
'''
20+
21+
# Use the ComparisonTestFramework with 1 node: only use --testbinary.
22+
class InvalidTxRequestTest(ComparisonTestFramework):
23+
24+
''' Can either run this test as 1 node with expected answers, or two and compare them.
25+
Change the "outcome" variable from each TestInstance object to only do the comparison. '''
26+
def __init__(self):
27+
self.num_nodes = 1
28+
29+
def run_test(self):
30+
test = TestManager(self, self.options.tmpdir)
31+
test.add_all_connections(self.nodes)
32+
self.tip = None
33+
self.block_time = None
34+
NetworkThread().start() # Start up network handling in another thread
35+
test.run()
36+
37+
def get_tests(self):
38+
if self.tip is None:
39+
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
40+
self.block_time = int(time.time())+1
41+
42+
'''
43+
Create a new block with an anyone-can-spend coinbase
44+
'''
45+
height = 1
46+
block = create_block(self.tip, create_coinbase(height), self.block_time)
47+
self.block_time += 1
48+
block.solve()
49+
# Save the coinbase for later
50+
self.block1 = block
51+
self.tip = block.sha256
52+
height += 1
53+
yield TestInstance([[block, True]])
54+
55+
'''
56+
Now we need that block to mature so we can spend the coinbase.
57+
'''
58+
test = TestInstance(sync_every_block=False)
59+
for i in xrange(100):
60+
block = create_block(self.tip, create_coinbase(height), self.block_time)
61+
block.solve()
62+
self.tip = block.sha256
63+
self.block_time += 1
64+
test.blocks_and_transactions.append([block, True])
65+
height += 1
66+
yield test
67+
68+
# chr(100) is OP_NOTIF
69+
# Transaction will be rejected with code 16 (REJECT_INVALID)
70+
tx1 = create_transaction(self.block1.vtx[0], 0, chr(100), 50*100000000)
71+
yield TestInstance([[tx1, RejectResult(16, 'mandatory-script-verify-flag-failed')]])
72+
73+
# TODO: test further transactions...
74+
75+
if __name__ == '__main__':
76+
InvalidTxRequestTest().main()

qa/rpc-tests/test_framework/comptool.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ def wait_until(predicate, attempts=float('inf'), timeout=float('inf')):
4141

4242
return False
4343

44+
class RejectResult(object):
45+
'''
46+
Outcome that expects rejection of a transaction or block.
47+
'''
48+
def __init__(self, code, reason=''):
49+
self.code = code
50+
self.reason = reason
51+
def match(self, other):
52+
if self.code != other.code:
53+
return False
54+
return other.reason.startswith(self.reason)
55+
def __repr__(self):
56+
return '%i:%s' % (self.code,self.reason or '*')
57+
4458
class TestNode(NodeConnCB):
4559

4660
def __init__(self, block_store, tx_store):
@@ -51,6 +65,8 @@ def __init__(self, block_store, tx_store):
5165
self.block_request_map = {}
5266
self.tx_store = tx_store
5367
self.tx_request_map = {}
68+
self.block_reject_map = {}
69+
self.tx_reject_map = {}
5470

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

113+
def on_reject(self, conn, message):
114+
if message.message == 'tx':
115+
self.tx_reject_map[message.data] = RejectResult(message.code, message.reason)
116+
if message.message == 'block':
117+
self.block_reject_map[message.data] = RejectResult(message.code, message.reason)
118+
97119
def send_inv(self, obj):
98120
mtype = 2 if isinstance(obj, CBlock) else 1
99121
self.conn.send_message(msg_inv([CInv(mtype, obj.sha256)]))
@@ -243,6 +265,15 @@ def check_results(self, blockhash, outcome):
243265
if outcome is None:
244266
if c.cb.bestblockhash != self.connections[0].cb.bestblockhash:
245267
return False
268+
elif isinstance(outcome, RejectResult): # Check that block was rejected w/ code
269+
if c.cb.bestblockhash == blockhash:
270+
return False
271+
if blockhash not in c.cb.block_reject_map:
272+
print 'Block not in reject map: %064x' % (blockhash)
273+
return False
274+
if not outcome.match(c.cb.block_reject_map[blockhash]):
275+
print 'Block rejected with %s instead of expected %s: %064x' % (c.cb.block_reject_map[blockhash], outcome, blockhash)
276+
return False
246277
elif ((c.cb.bestblockhash == blockhash) != outcome):
247278
# print c.cb.bestblockhash, blockhash, outcome
248279
return False
@@ -262,6 +293,15 @@ def check_mempool(self, txhash, outcome):
262293
if c.cb.lastInv != self.connections[0].cb.lastInv:
263294
# print c.rpc.getrawmempool()
264295
return False
296+
elif isinstance(outcome, RejectResult): # Check that tx was rejected w/ code
297+
if txhash in c.cb.lastInv:
298+
return False
299+
if txhash not in c.cb.tx_reject_map:
300+
print 'Tx not in reject map: %064x' % (txhash)
301+
return False
302+
if not outcome.match(c.cb.tx_reject_map[txhash]):
303+
print 'Tx rejected with %s instead of expected %s: %064x' % (c.cb.tx_reject_map[txhash], outcome, txhash)
304+
return False
265305
elif ((txhash in c.cb.lastInv) != outcome):
266306
# print c.rpc.getrawmempool(), c.cb.lastInv
267307
return False

src/main.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4824,7 +4824,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
48244824
pfrom->id,
48254825
FormatStateMessage(state));
48264826
if (state.GetRejectCode() < REJECT_INTERNAL) // Never send AcceptToMemoryPool's internal codes over P2P
4827-
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
4827+
pfrom->PushMessage("reject", strCommand, (unsigned char)state.GetRejectCode(),
48284828
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
48294829
if (nDoS > 0)
48304830
Misbehaving(pfrom->GetId(), nDoS);
@@ -4954,7 +4954,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
49544954
int nDoS;
49554955
if (state.IsInvalid(nDoS)) {
49564956
assert (state.GetRejectCode() < REJECT_INTERNAL); // Blocks are never rejected with internal reject codes
4957-
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
4957+
pfrom->PushMessage("reject", strCommand, (unsigned char)state.GetRejectCode(),
49584958
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash);
49594959
if (nDoS > 0) {
49604960
LOCK(cs_main);

0 commit comments

Comments
 (0)