diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py index 5c4e74a5a..d71a257db 100755 --- a/test/functional/p2p_feefilter.py +++ b/test/functional/p2p_feefilter.py @@ -1,140 +1,142 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test processing of feefilter messages.""" from decimal import Decimal from test_framework.messages import MSG_TX, msg_feefilter from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal - - -def hashToHex(hash): - return format(hash, '064x') +from test_framework.wallet import MiniWallet class FeefilterConn(P2PInterface): feefilter_received = False def on_feefilter(self, message): self.feefilter_received = True def assert_feefilter_received(self, recv: bool): with p2p_lock: assert_equal(self.feefilter_received, recv) class TestP2PConn(P2PInterface): def __init__(self): super().__init__() self.txinvs = [] def on_inv(self, message): for i in message.inv: if (i.type == MSG_TX): - self.txinvs.append(hashToHex(i.hash)) + self.txinvs.append('{:064x}'.format(i.hash)) def wait_for_invs_to_match(self, invs_expected): invs_expected.sort() self.wait_until(lambda: invs_expected == sorted(self.txinvs)) def clear_invs(self): with p2p_lock: self.txinvs = [] class FeeFilterTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 # We lower the various required feerates for this test # to catch a corner-case where feefilter used to slightly undercut # mempool and wallet feerate calculation based on GetFee # rounding down 3 places, leading to stranded transactions. # See issue #16499 # grant noban permission to all peers to speed up tx relay / mempool # sync self.extra_args = [[ "-minrelaytxfee=1", "-mintxfee=1", "-whitelist=noban@127.0.0.1", ]] * self.num_nodes - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def run_test(self): self.test_feefilter_forcerelay() self.test_feefilter() def test_feefilter_forcerelay(self): self.log.info( 'Check that peers without forcerelay permission (default) get a feefilter message') self.nodes[0].add_p2p_connection( FeefilterConn()).assert_feefilter_received(True) self.log.info( 'Check that peers with forcerelay permission do not get a feefilter message') self.restart_node(0, extra_args=['-whitelist=forcerelay@127.0.0.1']) self.nodes[0].add_p2p_connection( FeefilterConn()).assert_feefilter_received(False) # Restart to disconnect peers and load default extra_args self.restart_node(0) self.connect_nodes(1, 0) def test_feefilter(self): node1 = self.nodes[1] node0 = self.nodes[0] + miniwallet = MiniWallet(node1) + # Add enough mature utxos to the wallet, so that all txs spend + # confirmed coins + miniwallet.generate(5) + node1.generate(100) conn = self.nodes[0].add_p2p_connection(TestP2PConn()) self.log.info( "Test txs paying 0.2 sat/byte are received by test connection") - node1.settxfee(Decimal("2")) - txids = [node1.sendtoaddress(node1.getnewaddress(), 1000000) + txids = [miniwallet.send_self_transfer(fee_rate=Decimal('2.00'), + from_node=node1)['txid'] for _ in range(3)] conn.wait_for_invs_to_match(txids) conn.clear_invs() # Set a fee filter of 0.15 sat/byte on test connection conn.send_and_ping(msg_feefilter(150)) self.log.info( "Test txs paying 0.15 sat/byte are received by test connection") - node1.settxfee(Decimal("1.5")) - txids = [node1.sendtoaddress(node1.getnewaddress(), 1000000) + txids = [miniwallet.send_self_transfer(fee_rate=Decimal('1.50'), + from_node=node1)['txid'] for _ in range(3)] conn.wait_for_invs_to_match(txids) conn.clear_invs() self.log.info( "Test txs paying 0.1 sat/byte are no longer received by test connection") - node1.settxfee(Decimal("1")) - [node1.sendtoaddress(node1.getnewaddress(), 1000000) for _ in range(3)] + txids = [miniwallet.send_self_transfer(fee_rate=Decimal('1.00'), + from_node=node1)['txid'] + for _ in range(3)] self.sync_mempools() # must be sure node 0 has received all txs # Send one transaction from node0 that should be received, so that we # we can sync the test on receipt (if node1's txs were relayed, they'd # be received by the time this node0 tx is received). This is # unfortunately reliant on the current relay behavior where we batch up # to 35 entries in an inv, which means that when this next transaction # is eligible for relay, the prior transactions from node1 are eligible # as well. - node0.settxfee(Decimal("200.00")) - txids = [node0.sendtoaddress(node0.getnewaddress(), 1000000)] + txids = [miniwallet.send_self_transfer(fee_rate=Decimal('200.00'), + from_node=node0)['txid'] + for _ in range(3)] conn.wait_for_invs_to_match(txids) conn.clear_invs() self.log.info("Remove fee filter and check txs are received again") conn.send_and_ping(msg_feefilter(0)) - txids = [node1.sendtoaddress(node1.getnewaddress(), 1000000) + txids = [miniwallet.send_self_transfer(fee_rate=Decimal('200.00'), + from_node=node1)['txid'] for _ in range(3)] conn.wait_for_invs_to_match(txids) conn.clear_invs() if __name__ == '__main__': FeeFilterTest().main() diff --git a/test/functional/p2p_permissions.py b/test/functional/p2p_permissions.py index a525f707b..66794f0b1 100755 --- a/test/functional/p2p_permissions.py +++ b/test/functional/p2p_permissions.py @@ -1,189 +1,191 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test p2p permission message. Test that permissions are correctly calculated and applied """ -from test_framework.address import ADDRESS_ECREG_P2SH_OP_TRUE +from test_framework.address import ( + ADDRESS_ECREG_P2SH_OP_TRUE, + SCRIPTSIG_OP_TRUE, +) from test_framework.messages import CTransaction, FromHex from test_framework.p2p import P2PDataStore -from test_framework.script import OP_TRUE, CScript, CScriptOp from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch from test_framework.txtools import pad_tx from test_framework.util import assert_equal, connect_nodes, p2p_port class P2PPermissionsTests(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True def run_test(self): self.check_tx_relay() self.checkpermission( # default permissions (no specific permissions) ["-whitelist=127.0.0.1"], # Make sure the default values in the command line documentation # match the ones here ["relay", "noban", "mempool", "download"], True) self.checkpermission( # no permission (even with forcerelay) ["-whitelist=@127.0.0.1", "-whitelistforcerelay=1"], [], False) self.checkpermission( # relay permission removed (no specific permissions) ["-whitelist=127.0.0.1", "-whitelistrelay=0"], ["noban", "mempool", "download"], True) self.checkpermission( # forcerelay and relay permission added # Legacy parameter interaction which set whitelistrelay to true # if whitelistforcerelay is true ["-whitelist=127.0.0.1", "-whitelistforcerelay"], ["forcerelay", "relay", "noban", "mempool", "download"], True) # Let's make sure permissions are merged correctly # For this, we need to use whitebind instead of bind # by modifying the configuration file. ip_port = "127.0.0.1:{}".format(p2p_port(1)) self.replaceinconfig( 1, "bind=127.0.0.1", "whitebind=bloomfilter,forcerelay@" + ip_port) self.checkpermission( ["-whitelist=noban@127.0.0.1"], # Check parameter interaction forcerelay should activate relay ["noban", "bloomfilter", "forcerelay", "relay", "download"], False) self.replaceinconfig( 1, "whitebind=bloomfilter,forcerelay@" + ip_port, "bind=127.0.0.1") self.checkpermission( # legacy whitelistrelay should be ignored ["-whitelist=noban,mempool@127.0.0.1", "-whitelistrelay"], ["noban", "mempool", "download"], False) self.checkpermission( # legacy whitelistforcerelay should be ignored ["-whitelist=noban,mempool@127.0.0.1", "-whitelistforcerelay"], ["noban", "mempool", "download"], False) self.checkpermission( # missing mempool permission to be considered legacy whitelisted ["-whitelist=noban@127.0.0.1"], ["noban", "download"], False) self.checkpermission( # all permission added ["-whitelist=all@127.0.0.1"], ["forcerelay", "noban", "mempool", "bloomfilter", "relay", "download", "bypass_proof_request_limits", "addr"], False) self.checkpermission( # bypass_proof_request_limits permission ["-whitelist=bypass_proof_request_limits@127.0.0.1"], ["bypass_proof_request_limits"], False) self.stop_node(1) self.nodes[1].assert_start_raises_init_error( ["-whitelist=oopsie@127.0.0.1"], "Invalid P2P permission", match=ErrorMatch.PARTIAL_REGEX) self.nodes[1].assert_start_raises_init_error( ["-whitelist=noban@127.0.0.1:230"], "Invalid netmask specified in", match=ErrorMatch.PARTIAL_REGEX) self.nodes[1].assert_start_raises_init_error( ["-whitebind=noban@127.0.0.1/10"], "Cannot resolve -whitebind address", match=ErrorMatch.PARTIAL_REGEX) def check_tx_relay(self): block_op_true = self.nodes[0].getblock( self.nodes[0].generatetoaddress(100, ADDRESS_ECREG_P2SH_OP_TRUE)[0]) self.sync_all() self.log.debug( "Create a connection from a forcerelay peer that rebroadcasts raw txs") # A python mininode is needed to send the raw transaction directly. # If a full node was used, it could only rebroadcast via the inv-getdata # mechanism. However, even for forcerelay connections, a full node would # currently not request a txid that is already in the mempool. self.restart_node(1, extra_args=["-whitelist=forcerelay@127.0.0.1"]) p2p_rebroadcast_wallet = self.nodes[1].add_p2p_connection( P2PDataStore()) self.log.debug("Send a tx from the wallet initially") tx = FromHex(CTransaction(), self.nodes[0].createrawtransaction( inputs=[{'txid': block_op_true['tx'][0], 'vout': 0}], outputs=[{ADDRESS_ECREG_P2SH_OP_TRUE: 50}])) # push the one byte script to the stack - tx.vin[0].scriptSig = CScriptOp.encode_op_pushdata(CScript([OP_TRUE])) + tx.vin[0].scriptSig = SCRIPTSIG_OP_TRUE pad_tx(tx) txid = tx.rehash() self.log.debug("Wait until tx is in node[1]'s mempool") p2p_rebroadcast_wallet.send_txs_and_test([tx], self.nodes[1]) self.log.debug( "Check that node[1] will send the tx to node[0] even though it" " is already in the mempool") connect_nodes(self.nodes[1], self.nodes[0]) with self.nodes[1].assert_debug_log( ["Force relaying tx {} from peer=0".format(txid)]): p2p_rebroadcast_wallet.send_txs_and_test([tx], self.nodes[1]) self.wait_until(lambda: txid in self.nodes[0].getrawmempool()) self.log.debug( "Check that node[1] will not send an invalid tx to node[0]") tx.vout[0].nValue += 1 txid = tx.rehash() p2p_rebroadcast_wallet.send_txs_and_test( [tx], self.nodes[1], success=False, reject_reason='Not relaying non-mempool transaction ' '{} from forcerelay peer=0'.format(txid), ) def checkpermission(self, args, expectedPermissions, whitelisted): self.restart_node(1, args) connect_nodes(self.nodes[0], self.nodes[1]) peerinfo = self.nodes[1].getpeerinfo()[0] assert_equal(peerinfo['whitelisted'], whitelisted) assert_equal(len(expectedPermissions), len(peerinfo['permissions'])) for p in expectedPermissions: if p not in peerinfo['permissions']: raise AssertionError( "Expected permissions {!r} is not granted.".format(p)) def replaceinconfig(self, nodeid, old, new): with open(self.nodes[nodeid].bitcoinconf, encoding="utf8") as f: newText = f.read().replace(old, new) with open(self.nodes[nodeid].bitcoinconf, 'w', encoding="utf8") as f: f.write(newText) if __name__ == '__main__': P2PPermissionsTests().main() diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 75cfd01ec..d702f92cc 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -1,135 +1,136 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Encode and decode BASE58, P2PKH and P2SH addresses.""" import unittest -from .script import CScript, hash160, hash256 +from .script import OP_TRUE, CScript, CScriptOp, hash160, hash256 from .util import assert_equal, hex_str_to_bytes ADDRESS_ECREG_UNSPENDABLE = 'ecregtest:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcrl5mqkt' ADDRESS_ECREG_UNSPENDABLE_DESCRIPTOR = 'addr(ecregtest:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqcrl5mqkt)#u6xx93xc' # Coins sent to this address can be spent with a scriptSig of just OP_TRUE ADDRESS_ECREG_P2SH_OP_TRUE = 'ecregtest:prdpw30fk4ym6zl6rftfjuw806arpn26fvkgfu97xt' +SCRIPTSIG_OP_TRUE = CScriptOp.encode_op_pushdata(CScript([OP_TRUE])) chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' def byte_to_base58(b, version): result = '' str = b.hex() str = chr(version).encode('latin-1').hex() + str checksum = hash256(hex_str_to_bytes(str)).hex() str += checksum[:8] value = int('0x' + str, 0) while value > 0: result = chars[value % 58] + result value //= 58 while (str[:2] == '00'): result = chars[0] + result str = str[2:] return result def base58_to_byte(s, verify_checksum=True): if not s: return b'' n = 0 for c in s: n *= 58 assert c in chars digit = chars.index(c) n += digit h = '{:x}'.format(n) if len(h) % 2: h = '0' + h res = n.to_bytes((n.bit_length() + 7) // 8, 'big') pad = 0 for c in s: if c == chars[0]: pad += 1 else: break res = b'\x00' * pad + res if verify_checksum: assert_equal(hash256(res[:-4])[:4], res[-4:]) return res[1:-4], int(res[0]) def keyhash_to_p2pkh(hash, main=False): assert (len(hash) == 20) version = 0 if main else 111 return byte_to_base58(hash, version) def scripthash_to_p2sh(hash, main=False): assert (len(hash) == 20) version = 5 if main else 196 return byte_to_base58(hash, version) def key_to_p2pkh(key, main=False): key = check_key(key) return keyhash_to_p2pkh(hash160(key), main) def script_to_p2sh(script, main=False): script = check_script(script) return scripthash_to_p2sh(hash160(script), main) def check_key(key): if (isinstance(key, str)): key = hex_str_to_bytes(key) # Assuming this is hex string if (isinstance(key, bytes) and (len(key) == 33 or len(key) == 65)): return key assert False def check_script(script): if (isinstance(script, str)): script = hex_str_to_bytes(script) # Assuming this is hex string if (isinstance(script, bytes) or isinstance(script, CScript)): return script assert False class TestFrameworkScript(unittest.TestCase): def test_base58encodedecode(self): def check_base58(data, version): self.assertEqual( base58_to_byte(byte_to_base58(data, version)), (data, version)) check_base58( b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 111) check_base58( b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 111) check_base58( b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) check_base58( b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) check_base58( b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) check_base58( b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 111) check_base58( b'\x1f\x8e\xa1p*{\xd4\x94\x1b\xca\tA\xb8R\xc4\xbb\xfe\xdb.\x05', 0) check_base58( b':\x0b\x05\xf4\xd7\xf6l;\xa7\x00\x9fE50)l\x84\\\xc9\xcf', 0) check_base58( b'A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) check_base58( b'\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) check_base58( b'\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) check_base58( b'\0\0\0A\xc1\xea\xf1\x11\x80%Y\xba\xd6\x1b`\xd6+\x1f\x89|c\x92\x8a', 0) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py new file mode 100644 index 000000000..14c047f71 --- /dev/null +++ b/test/functional/test_framework/wallet.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""A limited-functionality wallet, which may replace a real wallet in tests""" + +from decimal import Decimal + +from test_framework.address import ( + ADDRESS_ECREG_P2SH_OP_TRUE, + SCRIPTSIG_OP_TRUE, +) +from test_framework.messages import XEC, COutPoint, CTransaction, CTxIn, CTxOut +from test_framework.txtools import pad_tx +from test_framework.util import assert_equal, hex_str_to_bytes, satoshi_round + + +class MiniWallet: + def __init__(self, test_node): + self._test_node = test_node + self._utxos = [] + self._address = ADDRESS_ECREG_P2SH_OP_TRUE + self._scriptPubKey = hex_str_to_bytes( + self._test_node.validateaddress( + self._address)['scriptPubKey']) + + def generate(self, num_blocks): + """Generate blocks with coinbase outputs to the internal address, + and append the outputs to the internal list""" + blocks = self._test_node.generatetoaddress(num_blocks, self._address) + for b in blocks: + cb_tx = self._test_node.getblock(blockhash=b, verbosity=2)['tx'][0] + self._utxos.append( + {'txid': cb_tx['txid'], 'vout': 0, 'value': cb_tx['vout'][0]['value']}) + return blocks + + def send_self_transfer(self, *, fee_rate, from_node): + """Create and send a tx with the specified fee_rate. Fee may be exact + or at most one satoshi higher than needed.""" + self._utxos = sorted(self._utxos, key=lambda k: k['value']) + # Pick the largest utxo and hope it covers the fee + largest_utxo = self._utxos.pop() + + # The size will be enforced by pad_tx() + size = 100 + send_value = satoshi_round( + largest_utxo['value'] - fee_rate * (Decimal(size) / 1000)) + fee = largest_utxo['value'] - send_value + assert send_value > 0 + + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(largest_utxo['txid'], 16), + largest_utxo['vout']))] + tx.vout = [CTxOut(int(send_value * XEC), self._scriptPubKey)] + tx.vin[0].scriptSig = SCRIPTSIG_OP_TRUE + pad_tx(tx, size) + tx_hex = tx.serialize().hex() + + txid = from_node.sendrawtransaction(tx_hex) + self._utxos.append({'txid': txid, 'vout': 0, 'value': send_value}) + tx_info = from_node.getmempoolentry(txid) + assert_equal(tx_info['size'], size) + assert_equal(tx_info['fee'], fee) + return {'txid': txid, 'hex': tx_hex}