Merge #14305: Tests: enforce critical class instance attributes in functional tests, fix segwit test specificity

e460232876 Document fixed attribute behavior in critical test framework classes. (Justin Turner Arthur)
17b42f4122 Check for specific tx acceptance failures based on script signature (Justin Turner Arthur)
3a4449e9ad Strictly enforce instance attrs in critical functional test classes. (Justin Turner Arthur)
1d0ce94a54 Fix for incorrect version attr set on functional test segwit block. (Justin Turner Arthur)
ba923e32a0 test: Fix broken segwit test (practicalswift)

Pull request description:

  No extra attributes will be able to be added to instances of the C++ class ports or of other critical classes without causing an exception. Helps prevent adding or depending on attributes that aren't in the intended object structure. It may prevent issues such as the one fixed in bitcoin/bitcoin#14300.

  This request fixes the erroneous version attribute used in the p2p_segwit.py functional tests. This pull includes the commit from bitcoin/bitcoin#14300.

Tree-SHA512: 1b8c58e7aa0f71075ed5ff3e5be0a5182599108d8cd9bce682feac3b1842508124288e9335432b16a43f40f159c9710899e6d84af1b5868f48c947bc6f3e07ec
This commit is contained in:
MarcoFalke 2018-09-27 11:06:40 -04:00
commit edcf29c9da
No known key found for this signature in database
GPG key ID: D2EA4850E7528B25
4 changed files with 153 additions and 50 deletions

View file

@ -60,6 +60,11 @@ don't have test cases for.
- When calling RPCs with lots of arguments, consider using named keyword
arguments instead of positional arguments to make the intent of the call
clear to readers.
- Many of the core test framework classes such as `CBlock` and `CTransaction`
don't allow new attributes to be added to their objects at runtime like
typical Python objects allow. This helps prevent unpredictable side effects
from typographical errors or usage of the objects outside of their intended
purpose.
#### RPC and P2P definitions
@ -72,7 +77,7 @@ P2P messages. These can be found in the following source files:
#### Using the P2P interface
- `mininode.py` contains all the definitions for objects that pass
- `messages.py` contains all the definitions for objects that pass
over the network (`CBlock`, `CTransaction`, etc, along with the network-level
wrappers for them, `msg_block`, `msg_tx`, etc).

View file

@ -205,7 +205,7 @@ class SegWitTest(BitcoinTestFramework):
height = self.nodes[0].getblockcount() + 1
block_time = self.nodes[0].getblockheader(tip)["mediantime"] + 1
block = create_block(int(tip, 16), create_coinbase(height), block_time)
block.version = version
block.nVersion = version
block.rehash()
return block
@ -769,12 +769,16 @@ class SegWitTest(BitcoinTestFramework):
# will require a witness to spend a witness program regardless of
# segwit activation. Note that older bitcoind's that are not
# segwit-aware would also reject this for failing CLEANSTACK.
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
with self.nodes[0].assert_debug_log(
expected_msgs=(spend_tx.hash, 'was not accepted: non-mandatory-script-verify-flag (Witness program was passed an empty witness)')):
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
# Try to put the witness script in the script_sig, should also fail.
spend_tx.vin[0].script_sig = CScript([p2wsh_pubkey, b'a'])
# Try to put the witness script in the scriptSig, should also fail.
spend_tx.vin[0].scriptSig = CScript([p2wsh_pubkey, b'a'])
spend_tx.rehash()
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
with self.nodes[0].assert_debug_log(
expected_msgs=('Not relaying invalid transaction {}'.format(spend_tx.hash), 'was not accepted: mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)')):
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
# Now put the witness script in the witness, should succeed after
# segwit activates.

View file

@ -13,7 +13,11 @@ CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....:
msg_block, msg_tx, msg_headers, etc.:
data structures that represent network messages
ser_*, deser_*: functions that handle serialization/deserialization."""
ser_*, deser_*: functions that handle serialization/deserialization.
Classes use __slots__ to ensure extraneous attributes aren't accidentally added
by tests, compromising their intended effect.
"""
from codecs import encode
import copy
import hashlib
@ -185,7 +189,10 @@ def ToHex(obj):
# Objects that map to bitcoind objects, which can be serialized/deserialized
class CAddress():
class CAddress:
__slots__ = ("ip", "nServices", "pchReserved", "port", "time")
def __init__(self):
self.time = 0
self.nServices = 1
@ -215,7 +222,10 @@ class CAddress():
return "CAddress(nServices=%i ip=%s port=%i)" % (self.nServices,
self.ip, self.port)
class CInv():
class CInv:
__slots__ = ("hash", "type")
typemap = {
0: "Error",
1: "TX",
@ -244,7 +254,9 @@ class CInv():
% (self.typemap[self.type], self.hash)
class CBlockLocator():
class CBlockLocator:
__slots__ = ("nVersion", "vHave")
def __init__(self):
self.nVersion = MY_VERSION
self.vHave = []
@ -264,7 +276,9 @@ class CBlockLocator():
% (self.nVersion, repr(self.vHave))
class COutPoint():
class COutPoint:
__slots__ = ("hash", "n")
def __init__(self, hash=0, n=0):
self.hash = hash
self.n = n
@ -283,7 +297,9 @@ class COutPoint():
return "COutPoint(hash=%064x n=%i)" % (self.hash, self.n)
class CTxIn():
class CTxIn:
__slots__ = ("nSequence", "prevout", "scriptSig")
def __init__(self, outpoint=None, scriptSig=b"", nSequence=0):
if outpoint is None:
self.prevout = COutPoint()
@ -311,7 +327,9 @@ class CTxIn():
self.nSequence)
class CTxOut():
class CTxOut:
__slots__ = ("nValue", "scriptPubKey")
def __init__(self, nValue=0, scriptPubKey=b""):
self.nValue = nValue
self.scriptPubKey = scriptPubKey
@ -332,7 +350,9 @@ class CTxOut():
bytes_to_hex_str(self.scriptPubKey))
class CScriptWitness():
class CScriptWitness:
__slots__ = ("stack",)
def __init__(self):
# stack is a vector of strings
self.stack = []
@ -347,7 +367,9 @@ class CScriptWitness():
return True
class CTxInWitness():
class CTxInWitness:
__slots__ = ("scriptWitness",)
def __init__(self):
self.scriptWitness = CScriptWitness()
@ -364,7 +386,9 @@ class CTxInWitness():
return self.scriptWitness.is_null()
class CTxWitness():
class CTxWitness:
__slots__ = ("vtxinwit",)
def __init__(self):
self.vtxinwit = []
@ -392,7 +416,10 @@ class CTxWitness():
return True
class CTransaction():
class CTransaction:
__slots__ = ("hash", "nLockTime", "nVersion", "sha256", "vin", "vout",
"wit")
def __init__(self, tx=None):
if tx is None:
self.nVersion = 1
@ -496,7 +523,10 @@ class CTransaction():
% (self.nVersion, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime)
class CBlockHeader():
class CBlockHeader:
__slots__ = ("hash", "hashMerkleRoot", "hashPrevBlock", "nBits", "nNonce",
"nTime", "nVersion", "sha256")
def __init__(self, header=None):
if header is None:
self.set_null()
@ -565,6 +595,8 @@ class CBlockHeader():
class CBlock(CBlockHeader):
__slots__ = ("vtx",)
def __init__(self, header=None):
super(CBlock, self).__init__(header)
self.vtx = []
@ -636,7 +668,9 @@ class CBlock(CBlockHeader):
time.ctime(self.nTime), self.nBits, self.nNonce, repr(self.vtx))
class PrefilledTransaction():
class PrefilledTransaction:
__slots__ = ("index", "tx")
def __init__(self, index=0, tx = None):
self.index = index
self.tx = tx
@ -664,8 +698,12 @@ class PrefilledTransaction():
def __repr__(self):
return "PrefilledTransaction(index=%d, tx=%s)" % (self.index, repr(self.tx))
# This is what we send on the wire, in a cmpctblock message.
class P2PHeaderAndShortIDs():
class P2PHeaderAndShortIDs:
__slots__ = ("header", "nonce", "prefilled_txn", "prefilled_txn_length",
"shortids", "shortids_length")
def __init__(self):
self.header = CBlockHeader()
self.nonce = 0
@ -703,9 +741,11 @@ class P2PHeaderAndShortIDs():
def __repr__(self):
return "P2PHeaderAndShortIDs(header=%s, nonce=%d, shortids_length=%d, shortids=%s, prefilled_txn_length=%d, prefilledtxn=%s" % (repr(self.header), self.nonce, self.shortids_length, repr(self.shortids), self.prefilled_txn_length, repr(self.prefilled_txn))
# P2P version of the above that will use witness serialization (for compact
# block version 2)
class P2PHeaderAndShortWitnessIDs(P2PHeaderAndShortIDs):
__slots__ = ()
def serialize(self):
return super(P2PHeaderAndShortWitnessIDs, self).serialize(with_witness=True)
@ -715,9 +755,12 @@ def calculate_shortid(k0, k1, tx_hash):
expected_shortid &= 0x0000ffffffffffff
return expected_shortid
# This version gets rid of the array lengths, and reinterprets the differential
# encoding into indices that can be used for lookup.
class HeaderAndShortIDs():
class HeaderAndShortIDs:
__slots__ = ("header", "nonce", "prefilled_txn", "shortids", "use_witness")
def __init__(self, p2pheaders_and_shortids = None):
self.header = CBlockHeader()
self.nonce = 0
@ -778,7 +821,8 @@ class HeaderAndShortIDs():
return "HeaderAndShortIDs(header=%s, nonce=%d, shortids=%s, prefilledtxn=%s" % (repr(self.header), self.nonce, repr(self.shortids), repr(self.prefilled_txn))
class BlockTransactionsRequest():
class BlockTransactionsRequest:
__slots__ = ("blockhash", "indexes")
def __init__(self, blockhash=0, indexes = None):
self.blockhash = blockhash
@ -818,7 +862,8 @@ class BlockTransactionsRequest():
return "BlockTransactionsRequest(hash=%064x indexes=%s)" % (self.blockhash, repr(self.indexes))
class BlockTransactions():
class BlockTransactions:
__slots__ = ("blockhash", "transactions")
def __init__(self, blockhash=0, transactions = None):
self.blockhash = blockhash
@ -840,7 +885,10 @@ class BlockTransactions():
def __repr__(self):
return "BlockTransactions(hash=%064x transactions=%s)" % (self.blockhash, repr(self.transactions))
class CPartialMerkleTree():
class CPartialMerkleTree:
__slots__ = ("fBad", "nTransactions", "vBits", "vHash")
def __init__(self):
self.nTransactions = 0
self.vHash = []
@ -868,7 +916,10 @@ class CPartialMerkleTree():
def __repr__(self):
return "CPartialMerkleTree(nTransactions=%d, vHash=%s, vBits=%s)" % (self.nTransactions, repr(self.vHash), repr(self.vBits))
class CMerkleBlock():
class CMerkleBlock:
__slots__ = ("header", "txn")
def __init__(self):
self.header = CBlockHeader()
self.txn = CPartialMerkleTree()
@ -888,7 +939,9 @@ class CMerkleBlock():
# Objects that correspond to messages on the wire
class msg_version():
class msg_version:
__slots__ = ("addrFrom", "addrTo", "nNonce", "nRelay", "nServices",
"nStartingHeight", "nTime", "nVersion", "strSubVer")
command = b"version"
def __init__(self):
@ -945,7 +998,8 @@ class msg_version():
self.strSubVer, self.nStartingHeight, self.nRelay)
class msg_verack():
class msg_verack:
__slots__ = ()
command = b"verack"
def __init__(self):
@ -961,7 +1015,8 @@ class msg_verack():
return "msg_verack()"
class msg_addr():
class msg_addr:
__slots__ = ("addrs",)
command = b"addr"
def __init__(self):
@ -977,7 +1032,8 @@ class msg_addr():
return "msg_addr(addrs=%s)" % (repr(self.addrs))
class msg_inv():
class msg_inv:
__slots__ = ("inv",)
command = b"inv"
def __init__(self, inv=None):
@ -996,7 +1052,8 @@ class msg_inv():
return "msg_inv(inv=%s)" % (repr(self.inv))
class msg_getdata():
class msg_getdata:
__slots__ = ("inv",)
command = b"getdata"
def __init__(self, inv=None):
@ -1012,7 +1069,8 @@ class msg_getdata():
return "msg_getdata(inv=%s)" % (repr(self.inv))
class msg_getblocks():
class msg_getblocks:
__slots__ = ("locator", "hashstop")
command = b"getblocks"
def __init__(self):
@ -1035,7 +1093,8 @@ class msg_getblocks():
% (repr(self.locator), self.hashstop)
class msg_tx():
class msg_tx:
__slots__ = ("tx",)
command = b"tx"
def __init__(self, tx=CTransaction()):
@ -1050,13 +1109,16 @@ class msg_tx():
def __repr__(self):
return "msg_tx(tx=%s)" % (repr(self.tx))
class msg_witness_tx(msg_tx):
__slots__ = ()
def serialize(self):
return self.tx.serialize_with_witness()
class msg_block():
class msg_block:
__slots__ = ("block",)
command = b"block"
def __init__(self, block=None):
@ -1074,9 +1136,12 @@ class msg_block():
def __repr__(self):
return "msg_block(block=%s)" % (repr(self.block))
# for cases where a user needs tighter control over what is sent over the wire
# note that the user must supply the name of the command, and the data
class msg_generic():
class msg_generic:
__slots__ = ("command", "data")
def __init__(self, command, data=None):
self.command = command
self.data = data
@ -1087,13 +1152,16 @@ class msg_generic():
def __repr__(self):
return "msg_generic()"
class msg_witness_block(msg_block):
class msg_witness_block(msg_block):
__slots__ = ()
def serialize(self):
r = self.block.serialize(with_witness=True)
return r
class msg_getaddr():
class msg_getaddr:
__slots__ = ()
command = b"getaddr"
def __init__(self):
@ -1109,7 +1177,8 @@ class msg_getaddr():
return "msg_getaddr()"
class msg_ping():
class msg_ping:
__slots__ = ("nonce",)
command = b"ping"
def __init__(self, nonce=0):
@ -1127,7 +1196,8 @@ class msg_ping():
return "msg_ping(nonce=%08x)" % self.nonce
class msg_pong():
class msg_pong:
__slots__ = ("nonce",)
command = b"pong"
def __init__(self, nonce=0):
@ -1145,7 +1215,8 @@ class msg_pong():
return "msg_pong(nonce=%08x)" % self.nonce
class msg_mempool():
class msg_mempool:
__slots__ = ()
command = b"mempool"
def __init__(self):
@ -1160,7 +1231,9 @@ class msg_mempool():
def __repr__(self):
return "msg_mempool()"
class msg_sendheaders():
class msg_sendheaders:
__slots__ = ()
command = b"sendheaders"
def __init__(self):
@ -1180,7 +1253,8 @@ class msg_sendheaders():
# number of entries
# vector of hashes
# hash_stop (hash of last desired block header, 0 to get as many as possible)
class msg_getheaders():
class msg_getheaders:
__slots__ = ("hashstop", "locator",)
command = b"getheaders"
def __init__(self):
@ -1205,7 +1279,8 @@ class msg_getheaders():
# headers message has
# <count> <vector of block headers>
class msg_headers():
class msg_headers:
__slots__ = ("headers",)
command = b"headers"
def __init__(self, headers=None):
@ -1225,7 +1300,8 @@ class msg_headers():
return "msg_headers(headers=%s)" % repr(self.headers)
class msg_reject():
class msg_reject:
__slots__ = ("code", "data", "message", "reason")
command = b"reject"
REJECT_MALFORMED = 1
@ -1256,7 +1332,9 @@ class msg_reject():
return "msg_reject: %s %d %s [%064x]" \
% (self.message, self.code, self.reason, self.data)
class msg_feefilter():
class msg_feefilter:
__slots__ = ("feerate",)
command = b"feefilter"
def __init__(self, feerate=0):
@ -1273,7 +1351,9 @@ class msg_feefilter():
def __repr__(self):
return "msg_feefilter(feerate=%08x)" % self.feerate
class msg_sendcmpct():
class msg_sendcmpct:
__slots__ = ("announce", "version")
command = b"sendcmpct"
def __init__(self):
@ -1293,7 +1373,9 @@ class msg_sendcmpct():
def __repr__(self):
return "msg_sendcmpct(announce=%s, version=%lu)" % (self.announce, self.version)
class msg_cmpctblock():
class msg_cmpctblock:
__slots__ = ("header_and_shortids",)
command = b"cmpctblock"
def __init__(self, header_and_shortids = None):
@ -1311,7 +1393,9 @@ class msg_cmpctblock():
def __repr__(self):
return "msg_cmpctblock(HeaderAndShortIDs=%s)" % repr(self.header_and_shortids)
class msg_getblocktxn():
class msg_getblocktxn:
__slots__ = ("block_txn_request",)
command = b"getblocktxn"
def __init__(self):
@ -1329,7 +1413,9 @@ class msg_getblocktxn():
def __repr__(self):
return "msg_getblocktxn(block_txn_request=%s)" % (repr(self.block_txn_request))
class msg_blocktxn():
class msg_blocktxn:
__slots__ = ("block_transactions",)
command = b"blocktxn"
def __init__(self):
@ -1346,7 +1432,10 @@ class msg_blocktxn():
def __repr__(self):
return "msg_blocktxn(block_transactions=%s)" % (repr(self.block_transactions))
class msg_witness_blocktxn(msg_blocktxn):
__slots__ = ()
def serialize(self):
r = b""
r += self.block_transactions.serialize(with_witness=True)

View file

@ -26,7 +26,7 @@ def hash160(s):
_opcode_instances = []
class CScriptOp(int):
"""A single script opcode"""
__slots__ = []
__slots__ = ()
@staticmethod
def encode_op_pushdata(d):
@ -361,8 +361,11 @@ class CScriptTruncatedPushDataError(CScriptInvalidError):
self.data = data
super(CScriptTruncatedPushDataError, self).__init__(msg)
# This is used, eg, for blockchain heights in coinbase scripts (bip34)
class CScriptNum():
class CScriptNum:
__slots__ = ("value",)
def __init__(self, d=0):
self.value = d
@ -393,6 +396,8 @@ class CScript(bytes):
iter(script) however does iterate by opcode.
"""
__slots__ = ()
@classmethod
def __coerce_instance(cls, other):
# Coerce other into bytes