diff options
Diffstat (limited to 'test/functional')
21 files changed, 760 insertions, 354 deletions
diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 5bb3b5c094..45ecaabe14 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -11,6 +11,7 @@ from collections import defaultdict, namedtuple import heapq import itertools import os +import pathlib import re import sys import tempfile @@ -51,9 +52,23 @@ def main(): if not args.testdir: print("Opening latest test directory: {}".format(testdir), file=sys.stderr) + colors = defaultdict(lambda: '') + if args.color: + colors["test"] = "\033[0;36m" # CYAN + colors["node0"] = "\033[0;34m" # BLUE + colors["node1"] = "\033[0;32m" # GREEN + colors["node2"] = "\033[0;31m" # RED + colors["node3"] = "\033[0;33m" # YELLOW + colors["reset"] = "\033[0m" # Reset font color + log_events = read_logs(testdir) - print_logs(log_events, color=args.color, html=args.html) + if args.html: + print_logs_html(log_events) + else: + print_logs_plain(log_events, colors) + print_node_warnings(testdir, colors) + def read_logs(tmp_dir): """Reads log files. @@ -71,6 +86,26 @@ def read_logs(tmp_dir): return heapq.merge(*[get_log_events(source, f) for source, f in files]) +def print_node_warnings(tmp_dir, colors): + """Print nodes' errors and warnings""" + + warnings = [] + for stream in ['stdout', 'stderr']: + for i in itertools.count(): + folder = "{}/node{}/{}".format(tmp_dir, i, stream) + if not os.path.isdir(folder): + break + for (_, _, fns) in os.walk(folder): + for fn in fns: + warning = pathlib.Path('{}/{}'.format(folder, fn)).read_text().strip() + if warning: + warnings.append(("node{} {}".format(i, stream), warning)) + + print() + for w in warnings: + print("{} {} {} {}".format(colors[w[0].split()[0]], w[0], w[1], colors["reset"])) + + def find_latest_test_dir(): """Returns the latest tmpfile test directory prefix.""" tmpdir = tempfile.gettempdir() @@ -127,18 +162,9 @@ def get_log_events(source, logfile): except FileNotFoundError: print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr) -def print_logs(log_events, color=False, html=False): - """Renders the iterator of log events into text or html.""" - if not html: - colors = defaultdict(lambda: '') - if color: - colors["test"] = "\033[0;36m" # CYAN - colors["node0"] = "\033[0;34m" # BLUE - colors["node1"] = "\033[0;32m" # GREEN - colors["node2"] = "\033[0;31m" # RED - colors["node3"] = "\033[0;33m" # YELLOW - colors["reset"] = "\033[0m" # Reset font color +def print_logs_plain(log_events, colors): + """Renders the iterator of log events into text.""" for event in log_events: lines = event.event.splitlines() print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"])) @@ -146,7 +172,9 @@ def print_logs(log_events, color=False, html=False): for line in lines[1:]: print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"])) - else: + +def print_logs_html(log_events): + """Renders the iterator of log events into html.""" try: import jinja2 except ImportError: diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index f0991a284b..454eb583f7 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -71,9 +71,13 @@ class InputMissing(BadTxTemplate): reject_reason = "bad-txns-vin-empty" expect_disconnect = True + # We use a blank transaction here to make sure + # it is interpreted as a non-witness transaction. + # Otherwise the transaction will fail the + # "surpufluous witness" check during deserialization + # rather than the input count check. def get_tx(self): tx = CTransaction() - tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE] * 100))) tx.calc_sha256() return tx diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index e7e4f84ad9..b7814bf33e 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -32,7 +32,7 @@ Start three nodes: import time from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.key import CECKey +from test_framework.key import ECKey from test_framework.messages import ( CBlockHeader, COutPoint, @@ -104,9 +104,9 @@ class AssumeValidTest(BitcoinTestFramework): self.blocks = [] # Get a pubkey for the coinbase TXO - coinbase_key = CECKey() - coinbase_key.set_secretbytes(b"horsebattery") - coinbase_pubkey = coinbase_key.get_pubkey() + coinbase_key = ECKey() + coinbase_key.generate() + coinbase_pubkey = coinbase_key.get_pubkey().get_bytes() # Create the first block with a coinbase output to our key height = 1 diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 9a9424b82b..3ad83cd2b3 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -14,7 +14,7 @@ from test_framework.blocktools import ( get_legacy_sigopcount_block, MAX_BLOCK_SIGOPS, ) -from test_framework.key import CECKey +from test_framework.key import ECKey from test_framework.messages import ( CBlock, COIN, @@ -86,9 +86,9 @@ class FullBlockTest(BitcoinTestFramework): self.bootstrap_p2p() # Add one p2p connection to the node self.block_heights = {} - self.coinbase_key = CECKey() - self.coinbase_key.set_secretbytes(b"horsebattery") - self.coinbase_pubkey = self.coinbase_key.get_pubkey() + self.coinbase_key = ECKey() + self.coinbase_key.generate() + self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() self.tip = None self.blocks = {} self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) @@ -146,20 +146,6 @@ class FullBlockTest(BitcoinTestFramework): badtx = template.get_tx() if TxTemplate != invalid_txs.InputMissing: self.sign_tx(badtx, attempt_spend_tx) - else: - # Segwit is active in regtest at this point, so to deserialize a - # transaction without any inputs correctly, we set the outputs - # to an empty list. This is a hack, as the serialization of an - # empty list of outputs is deserialized as flags==0 and thus - # deserialization of the outputs is skipped. - # A policy check requires "loose" txs to be of a minimum size, - # so vtx is not set to be empty in the TxTemplate class and we - # only apply the workaround where txs are not "loose", i.e. in - # blocks. - # - # The workaround has the purpose that both sides calculate - # the same tx hash in the merkle tree - badtx.vout = [] badtx.rehash() badblock = self.update_block(blockname, [badtx]) self.send_blocks( @@ -528,7 +514,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b'')) # Note: must pass the redeem_script (not p2sh_script) to the signature hash function (sighash, err) = SignatureHash(redeem_script, tx, 1, SIGHASH_ALL) - sig = self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL])) + sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) scriptSig = CScript([sig, redeem_script]) tx.vin[1].scriptSig = scriptSig @@ -1284,7 +1270,7 @@ class FullBlockTest(BitcoinTestFramework): tx.vin[0].scriptSig = CScript() return (sighash, err) = SignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL) - tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, 0, value, script) diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py index 9fb0d35a68..ba116e41f5 100755 --- a/test/functional/feature_filelock.py +++ b/test/functional/feature_filelock.py @@ -23,7 +23,7 @@ class FilelockTest(BitcoinTestFramework): self.log.info("Using datadir {}".format(datadir)) self.log.info("Check that we can't start a second bitcoind instance using the same datadir") - expected_msg = "Error: Cannot obtain a lock on data directory {}. Bitcoin Core is probably already running.".format(datadir) + expected_msg = "Error: Cannot obtain a lock on data directory {0}. {1} is probably already running.".format(datadir, self.config['environment']['PACKAGE_NAME']) self.nodes[1].assert_start_raises_init_error(extra_args=['-datadir={}'.format(self.nodes[0].datadir), '-noserver'], expected_msg=expected_msg) if self.is_wallet_compiled(): diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 8fb7c49640..e2b3b2d544 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -35,16 +35,17 @@ def mine_large_blocks(node, n): # followed by 950k of OP_NOP. This would be non-standard in a non-coinbase # transaction but is consensus valid. + # Set the nTime if this is the first time this function has been called. + # A static variable ensures that time is monotonicly increasing and is therefore + # different for each block created => blockhash is unique. + if "nTimes" not in mine_large_blocks.__dict__: + mine_large_blocks.nTime = 0 + # Get the block parameters for the first block big_script = CScript([OP_RETURN] + [OP_NOP] * 950000) best_block = node.getblock(node.getbestblockhash()) height = int(best_block["height"]) + 1 - try: - # Static variable ensures that time is monotonicly increasing and is therefore - # different for each block created => blockhash is unique. - mine_large_blocks.nTime = min(mine_large_blocks.nTime, int(best_block["time"])) + 1 - except AttributeError: - mine_large_blocks.nTime = int(best_block["time"]) + 1 + mine_large_blocks.nTime = max(mine_large_blocks.nTime, int(best_block["time"])) + 1 previousblockhash = int(best_block["hash"], 16) for _ in range(n): diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 15b7ba9ae1..7bb7044cc0 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -16,7 +16,7 @@ class TestBitcoinCli(BitcoinTestFramework): """Main test logic""" cli_response = self.nodes[0].cli("-version").send_cli() - assert "Bitcoin Core RPC client version" in cli_response + assert "{} RPC client version".format(self.config['environment']['PACKAGE_NAME']) in cli_response self.log.info("Compare responses from getwalletinfo RPC and `bitcoin-cli getwalletinfo`") if self.is_wallet_compiled(): diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index d74d4eaaf1..bb0169ee52 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -37,7 +37,6 @@ Test is as follows: """ from decimal import Decimal import os -import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, wait_until @@ -83,9 +82,10 @@ class MempoolPersistTest(BitcoinTestFramework): self.start_node(1, extra_args=["-persistmempool=0"]) self.start_node(0) self.start_node(2) - # Give bitcoind a second to reload the mempool - wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5, timeout=1) - wait_until(lambda: len(self.nodes[2].getrawmempool()) == 5, timeout=1) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"], timeout=1) + wait_until(lambda: self.nodes[2].getmempoolinfo()["loaded"], timeout=1) + assert_equal(len(self.nodes[0].getrawmempool()), 5) + assert_equal(len(self.nodes[2].getrawmempool()), 5) # The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now: assert_equal(len(self.nodes[1].getrawmempool()), 0) @@ -100,14 +100,14 @@ class MempoolPersistTest(BitcoinTestFramework): self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") self.stop_nodes() self.start_node(0, extra_args=["-persistmempool=0"]) - # Give bitcoind a second to reload the mempool - time.sleep(1) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"]) assert_equal(len(self.nodes[0].getrawmempool()), 0) self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.") self.stop_nodes() self.start_node(0) - wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5) + wait_until(lambda: self.nodes[0].getmempoolinfo()["loaded"]) + assert_equal(len(self.nodes[0].getrawmempool()), 5) mempooldat0 = os.path.join(self.nodes[0].datadir, 'regtest', 'mempool.dat') mempooldat1 = os.path.join(self.nodes[1].datadir, 'regtest', 'mempool.dat') @@ -120,7 +120,8 @@ class MempoolPersistTest(BitcoinTestFramework): os.rename(mempooldat0, mempooldat1) self.stop_nodes() self.start_node(1, extra_args=[]) - wait_until(lambda: len(self.nodes[1].getrawmempool()) == 5) + wait_until(lambda: self.nodes[1].getmempoolinfo()["loaded"]) + assert_equal(len(self.nodes[1].getrawmempool()), 5) self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") # to test the exception we are creating a tmp folder called mempool.dat.new diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 700fdf6e04..481d697e63 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -3,11 +3,12 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test node responses to invalid network messages.""" +import asyncio import os import struct from test_framework import messages -from test_framework.mininode import P2PDataStore +from test_framework.mininode import P2PDataStore, NetworkThread from test_framework.test_framework import BitcoinTestFramework @@ -143,8 +144,15 @@ class InvalidMessagesTest(BitcoinTestFramework): def test_magic_bytes(self): conn = self.nodes[0].add_p2p_connection(P2PDataStore()) - conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes - conn.magic_bytes = b'\x00\x11\x22\x32' + + def swap_magic_bytes(): + conn._on_data = lambda: None # Need to ignore all incoming messages from now, since they come with "invalid" magic bytes + conn.magic_bytes = b'\x00\x11\x22\x32' + + # Call .result() to block until the atomic swap is complete, otherwise + # we might run into races later on + asyncio.run_coroutine_threadsafe(asyncio.coroutine(swap_magic_bytes)(), NetworkThread.network_event_loop).result() + with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']): conn.send_message(messages.msg_ping(nonce=0xff)) conn.wait_for_disconnect(timeout=1) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index a901e11536..000c30646a 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -9,7 +9,7 @@ import struct import time from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, get_witness_script, WITNESS_COMMITMENT_HEADER -from test_framework.key import CECKey, CPubKey +from test_framework.key import ECKey from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, CBlock, @@ -36,6 +36,7 @@ from test_framework.messages import ( ser_vector, sha256, uint256_from_str, + FromHex, ) from test_framework.mininode import ( P2PInterface, @@ -77,6 +78,7 @@ from test_framework.util import ( disconnect_nodes, get_bip9_status, hex_str_to_bytes, + assert_raises_rpc_error, ) # The versionbit bit used to signal activation of SegWit @@ -100,7 +102,7 @@ def get_p2pkh_script(pubkeyhash): def sign_p2pk_witness_input(script, tx_to, in_idx, hashtype, value, key): """Add signature for a P2PK witness program.""" tx_hash = SegwitVersion1SignatureHash(script, tx_to, in_idx, hashtype, value) - signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1') + signature = key.sign_ecdsa(tx_hash) + chr(hashtype).encode('latin-1') tx_to.wit.vtxinwit[in_idx].scriptWitness.stack = [signature, script] tx_to.rehash() @@ -269,6 +271,7 @@ class SegWitTest(BitcoinTestFramework): self.test_non_standard_witness() self.test_upgrade_after_activation() self.test_witness_sigops() + self.test_superfluous_witness() # Individual tests @@ -1357,7 +1360,8 @@ class SegWitTest(BitcoinTestFramework): def test_segwit_versions(self): """Test validity of future segwit version transactions. - Future segwit version transactions are non-standard, but valid in blocks. + Future segwit versions are non-standard to spend, but valid in blocks. + Sending to future segwit versions is always allowed. Can run this before and after segwit activation.""" NUM_SEGWIT_VERSIONS = 17 # will test OP_0, OP1, ..., OP_16 @@ -1397,7 +1401,7 @@ class SegWitTest(BitcoinTestFramework): assert len(self.nodes[0].getrawmempool()) == 0 # Finally, verify that version 0 -> version 1 transactions - # are non-standard + # are standard script_pubkey = CScript([CScriptOp(OP_1), witness_hash]) tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] @@ -1405,10 +1409,9 @@ class SegWitTest(BitcoinTestFramework): tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program] tx2.rehash() - # Gets accepted to test_node, because standardness of outputs isn't - # checked with fRequireStandard + # Gets accepted to both policy-enforcing nodes and others. test_transaction_acceptance(self.nodes[0], self.test_node, tx2, with_witness=True, accepted=True) - test_transaction_acceptance(self.nodes[1], self.std_node, tx2, with_witness=True, accepted=False) + test_transaction_acceptance(self.nodes[1], self.std_node, tx2, with_witness=True, accepted=True) temp_utxo.pop() # last entry in temp_utxo was the output we just spent temp_utxo.append(UTXO(tx2.sha256, 0, tx2.vout[0].nValue)) @@ -1479,10 +1482,9 @@ class SegWitTest(BitcoinTestFramework): # Segwit transactions using uncompressed pubkeys are not accepted # under default policy, but should still pass consensus. - key = CECKey() - key.set_secretbytes(b"9") - key.set_compressed(False) - pubkey = CPubKey(key.get_pubkey()) + key = ECKey() + key.generate(False) + pubkey = key.get_pubkey().get_bytes() assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey utxo = self.utxo.pop(0) @@ -1512,7 +1514,7 @@ class SegWitTest(BitcoinTestFramework): tx2.vout.append(CTxOut(tx.vout[0].nValue - 1000, script_wsh)) script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[0].scriptWitness.stack = [signature, pubkey] tx2.rehash() @@ -1566,7 +1568,7 @@ class SegWitTest(BitcoinTestFramework): tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE]))) (sig_hash, err) = SignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx5.vin[0].scriptSig = CScript([signature, pubkey]) tx5.rehash() # Should pass policy and consensus. @@ -1579,9 +1581,9 @@ class SegWitTest(BitcoinTestFramework): @subtest def test_signature_version_1(self): - key = CECKey() - key.set_secretbytes(b"9") - pubkey = CPubKey(key.get_pubkey()) + key = ECKey() + key.generate() + pubkey = key.get_pubkey().get_bytes() witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) witness_hash = sha256(witness_program) @@ -1716,7 +1718,7 @@ class SegWitTest(BitcoinTestFramework): script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL # Check that we can't have a scriptSig tx2.vin[0].scriptSig = CScript([signature, pubkey]) @@ -2035,5 +2037,31 @@ class SegWitTest(BitcoinTestFramework): # TODO: test p2sh sigop counting + def test_superfluous_witness(self): + # Serialization of tx that puts witness flag to 1 always + def serialize_with_bogus_witness(tx): + flags = 1 + r = b"" + r += struct.pack("<i", tx.nVersion) + if flags: + dummy = [] + r += ser_vector(dummy) + r += struct.pack("<B", flags) + r += ser_vector(tx.vin) + r += ser_vector(tx.vout) + if flags & 1: + if (len(tx.wit.vtxinwit) != len(tx.vin)): + # vtxinwit must have the same length as vin + tx.wit.vtxinwit = tx.wit.vtxinwit[:len(tx.vin)] + for i in range(len(tx.wit.vtxinwit), len(tx.vin)): + tx.wit.vtxinwit.append(CTxInWitness()) + r += tx.wit.serialize() + r += struct.pack("<I", tx.nLockTime) + return r + + raw = self.nodes[0].createrawtransaction([{"txid":"00"*32, "vout":0}], {self.nodes[0].getnewaddress():1}) + tx = FromHex(CTransaction(), raw) + assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex()) + if __name__ == '__main__': SegWitTest().main() diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 17dbf59a8c..7abcd71bb8 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -1,12 +1,16 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2018 The Bitcoin Core developers +# Copyright (c) 2015-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 transaction signing using the signrawtransaction* RPCs.""" +"""Test multisig RPCs""" from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_raises_rpc_error, +) import decimal + class RpcCreateMultiSigTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -17,29 +21,40 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): def get_keys(self): node0, node1, node2 = self.nodes - self.add = [node1.getnewaddress() for _ in range(self.nkeys)] - self.pub = [node1.getaddressinfo(a)["pubkey"] for a in self.add] - self.priv = [node1.dumpprivkey(a) for a in self.add] + add = [node1.getnewaddress() for _ in range(self.nkeys)] + self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add] + self.priv = [node1.dumpprivkey(a) for a in add] self.final = node2.getnewaddress() def run_test(self): - node0,node1,node2 = self.nodes + node0, node1, node2 = self.nodes - # 50 BTC each, rest will be 25 BTC each + self.check_addmultisigaddress_errors() + + self.log.info('Generating blocks ...') node0.generate(149) self.sync_all() self.moved = 0 - for self.nkeys in [3,5]: - for self.nsigs in [2,3]: + for self.nkeys in [3, 5]: + for self.nsigs in [2, 3]: for self.output_type in ["bech32", "p2sh-segwit", "legacy"]: self.get_keys() self.do_multisig() self.checkbalances() + def check_addmultisigaddress_errors(self): + self.log.info('Check that addmultisigaddress fails when the private keys are missing') + addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)] + assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) + for a in addresses: + # Importing all addresses should not change the result + self.nodes[0].importaddress(a) + assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) + def checkbalances(self): - node0,node1,node2 = self.nodes + node0, node1, node2 = self.nodes node0.generate(100) self.sync_all() @@ -49,13 +64,13 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): height = node0.getblockchaininfo()["blocks"] assert 150 < height < 350 - total = 149*50 + (height-149-100)*25 + total = 149 * 50 + (height - 149 - 100) * 25 assert bal1 == 0 assert bal2 == self.moved - assert bal0+bal1+bal2 == total + assert bal0 + bal1 + bal2 == total def do_multisig(self): - node0,node1,node2 = self.nodes + node0, node1, node2 = self.nodes msig = node2.createmultisig(self.nsigs, self.pub, self.output_type) madd = msig["address"] @@ -74,7 +89,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): txid = node0.sendtoaddress(madd, 40) tx = node0.getrawtransaction(txid, True) - vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses",[])] + vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses", [])] assert len(vout) == 1 vout = vout[0] scriptPubKey = tx["vout"][vout]["scriptPubKey"]["hex"] @@ -86,7 +101,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): outval = value - decimal.Decimal("0.00001000") rawtx = node2.createrawtransaction([{"txid": txid, "vout": vout}], [{self.final: outval}]) - rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs-1], prevtxs) + rawtx2 = node2.signrawtransactionwithkey(rawtx, self.priv[0:self.nsigs - 1], prevtxs) rawtx3 = node2.signrawtransactionwithkey(rawtx2["hex"], [self.priv[-1]], prevtxs) self.moved += outval @@ -97,5 +112,6 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): txinfo = node0.getrawtransaction(tx, True, blk) self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"])) + if __name__ == '__main__': RpcCreateMultiSigTest().main() diff --git a/test/functional/rpc_getblockfilter.py b/test/functional/rpc_getblockfilter.py new file mode 100755 index 0000000000..bd93b6f7a4 --- /dev/null +++ b/test/functional/rpc_getblockfilter.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# Copyright (c) 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 the getblockfilter RPC.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, assert_is_hex_string, assert_raises_rpc_error, + connect_nodes, disconnect_nodes, sync_blocks + ) + +FILTER_TYPES = ["basic"] + +class GetBlockFilterTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [["-blockfilterindex"], []] + + def run_test(self): + # Create two chains by disconnecting nodes 0 & 1, mining, then reconnecting + disconnect_nodes(self.nodes[0], 1) + + self.nodes[0].generate(3) + self.nodes[1].generate(4) + + assert_equal(self.nodes[0].getblockcount(), 3) + chain0_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)] + + # Reorg node 0 to a new chain + connect_nodes(self.nodes[0], 1) + sync_blocks(self.nodes) + + assert_equal(self.nodes[0].getblockcount(), 4) + chain1_hashes = [self.nodes[0].getblockhash(block_height) for block_height in range(4)] + + # Test getblockfilter returns a filter for all blocks and filter types on active chain + for block_hash in chain1_hashes: + for filter_type in FILTER_TYPES: + result = self.nodes[0].getblockfilter(block_hash, filter_type) + assert_is_hex_string(result['filter']) + + # Test getblockfilter returns a filter for all blocks and filter types on stale chain + for block_hash in chain0_hashes: + for filter_type in FILTER_TYPES: + result = self.nodes[0].getblockfilter(block_hash, filter_type) + assert_is_hex_string(result['filter']) + + # Test getblockfilter with unknown block + bad_block_hash = "0123456789abcdef" * 4 + assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockfilter, bad_block_hash, "basic") + + # Test getblockfilter with undefined filter type + genesis_hash = self.nodes[0].getblockhash(0) + assert_raises_rpc_error(-5, "Unknown filtertype", self.nodes[0].getblockfilter, genesis_hash, "unknown") + +if __name__ == '__main__': + GetBlockFilterTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index fb68f79bbd..8a7ea7aa58 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -150,10 +150,11 @@ class PSBTTest(BitcoinTestFramework): new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) self.nodes[0].decodepsbt(new_psbt) - # Make sure that a psbt with signatures cannot be converted + # Make sure that a non-psbt with signatures cannot be converted + # Error could be either "TX decode failed" (segwit inputs causes parsing to fail) or "Inputs must not have scriptSigs and scriptWitnesses" signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) - assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex']) - assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].converttopsbt, signedtx['hex'], False) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) + assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False) # Unless we allow it to convert and strip signatures self.nodes[0].converttopsbt(signedtx['hex'], True) diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 8c82b0ae4d..6e71817dc3 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -446,9 +446,9 @@ class RawTransactionsTest(BitcoinTestFramework): # and sendrawtransaction should throw assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 0.00001000) # And below calls should both succeed - testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate=0.00007000)[0] + testres = self.nodes[2].testmempoolaccept(rawtxs=[rawTxSigned['hex']], maxfeerate='0.00007000')[0] assert_equal(testres['allowed'], True) - self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate=0.00007000) + self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex'], maxfeerate='0.00007000') if __name__ == '__main__': diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 1b3e510dc4..912c0ca978 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -1,226 +1,386 @@ -# Copyright (c) 2011 Sam Rushing -"""ECC secp256k1 OpenSSL wrapper. +# Copyright (c) 2019 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test-only secp256k1 elliptic curve implementation -WARNING: This module does not mlock() secrets; your private keys may end up on -disk in swap! Use with caution! +WARNING: This code is slow, uses bad randomness, does not properly protect +keys, and is trivially vulnerable to side channel attacks. Do not use for +anything but tests.""" +import random -This file is modified from python-bitcoinlib. -""" - -import ctypes -import ctypes.util -import hashlib - -ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library ('ssl') or 'libeay32') - -ssl.BN_new.restype = ctypes.c_void_p -ssl.BN_new.argtypes = [] - -ssl.BN_bin2bn.restype = ctypes.c_void_p -ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p] - -ssl.BN_CTX_free.restype = None -ssl.BN_CTX_free.argtypes = [ctypes.c_void_p] - -ssl.BN_CTX_new.restype = ctypes.c_void_p -ssl.BN_CTX_new.argtypes = [] - -ssl.ECDH_compute_key.restype = ctypes.c_int -ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] - -ssl.ECDSA_sign.restype = ctypes.c_int -ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - -ssl.ECDSA_verify.restype = ctypes.c_int -ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] - -ssl.EC_KEY_free.restype = None -ssl.EC_KEY_free.argtypes = [ctypes.c_void_p] - -ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p -ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int] - -ssl.EC_KEY_get0_group.restype = ctypes.c_void_p -ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p] - -ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p -ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p] - -ssl.EC_KEY_set_private_key.restype = ctypes.c_int -ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - -ssl.EC_KEY_set_conv_form.restype = None -ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int] - -ssl.EC_KEY_set_public_key.restype = ctypes.c_int -ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - -ssl.i2o_ECPublicKey.restype = ctypes.c_void_p -ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - -ssl.EC_POINT_new.restype = ctypes.c_void_p -ssl.EC_POINT_new.argtypes = [ctypes.c_void_p] - -ssl.EC_POINT_free.restype = None -ssl.EC_POINT_free.argtypes = [ctypes.c_void_p] - -ssl.EC_POINT_mul.restype = ctypes.c_int -ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - -# this specifies the curve used with ECDSA. -NID_secp256k1 = 714 # from openssl/obj_mac.h +def modinv(a, n): + """Compute the modular inverse of a modulo n + See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. + """ + t1, t2 = 0, 1 + r1, r2 = n, a + while r2 != 0: + q = r1 // r2 + t1, t2 = t2, t1 - q * t2 + r1, r2 = r2, r1 - q * r2 + if r1 > 1: + return None + if t1 < 0: + t1 += n + return t1 + +def jacobi_symbol(n, k): + """Compute the Jacobi symbol of n modulo k + + See http://en.wikipedia.org/wiki/Jacobi_symbol + + For our application k is always prime, so this is the same as the Legendre symbol.""" + assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k" + n %= k + t = 0 + while n != 0: + while n & 1 == 0: + n >>= 1 + r = k & 7 + t ^= (r == 3 or r == 5) + n, k = k, n + t ^= (n & k & 3 == 3) + n = n % k + if k == 1: + return -1 if t else 1 + return 0 + +def modsqrt(a, p): + """Compute the square root of a modulo p when p % 4 = 3. + + The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm + + Limiting this function to only work for p % 4 = 3 means we don't need to + iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd + is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4) + + secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4. + """ + if p % 4 != 3: + raise NotImplementedError("modsqrt only implemented for p % 4 = 3") + sqrt = pow(a, (p + 1)//4, p) + if pow(sqrt, 2, p) == a % p: + return sqrt + return None + +class EllipticCurve: + def __init__(self, p, a, b): + """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p).""" + self.p = p + self.a = a % p + self.b = b % p + + def affine(self, p1): + """Convert a Jacobian point tuple p1 to affine form, or None if at infinity. + + An affine point is represented as the Jacobian (x, y, 1)""" + x1, y1, z1 = p1 + if z1 == 0: + return None + inv = modinv(z1, self.p) + inv_2 = (inv**2) % self.p + inv_3 = (inv_2 * inv) % self.p + return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1) + + def negate(self, p1): + """Negate a Jacobian point tuple p1.""" + x1, y1, z1 = p1 + return (x1, (self.p - y1) % self.p, z1) + + def on_curve(self, p1): + """Determine whether a Jacobian tuple p is on the curve (and not infinity)""" + x1, y1, z1 = p1 + z2 = pow(z1, 2, self.p) + z4 = pow(z2, 2, self.p) + return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0 + + def is_x_coord(self, x): + """Test whether x is a valid X coordinate on the curve.""" + x_3 = pow(x, 3, self.p) + return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1 + + def lift_x(self, x): + """Given an X coordinate on the curve, return a corresponding affine point.""" + x_3 = pow(x, 3, self.p) + v = x_3 + self.a * x + self.b + y = modsqrt(v, self.p) + if y is None: + return None + return (x, y, 1) + + def double(self, p1): + """Double a Jacobian tuple p1 + + See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling""" + x1, y1, z1 = p1 + if z1 == 0: + return (0, 1, 0) + y1_2 = (y1**2) % self.p + y1_4 = (y1_2**2) % self.p + x1_2 = (x1**2) % self.p + s = (4*x1*y1_2) % self.p + m = 3*x1_2 + if self.a: + m += self.a * pow(z1, 4, self.p) + m = m % self.p + x2 = (m**2 - 2*s) % self.p + y2 = (m*(s - x2) - 8*y1_4) % self.p + z2 = (2*y1*z1) % self.p + return (x2, y2, z2) + + def add_mixed(self, p1, p2): + """Add a Jacobian tuple p1 and an affine tuple p2 + + See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)""" + x1, y1, z1 = p1 + x2, y2, z2 = p2 + assert(z2 == 1) + # Adding to the point at infinity is a no-op + if z1 == 0: + return p2 + z1_2 = (z1**2) % self.p + z1_3 = (z1_2 * z1) % self.p + u2 = (x2 * z1_2) % self.p + s2 = (y2 * z1_3) % self.p + if x1 == u2: + if (y1 != s2): + # p1 and p2 are inverses. Return the point at infinity. + return (0, 1, 0) + # p1 == p2. The formulas below fail when the two points are equal. + return self.double(p1) + h = u2 - x1 + r = s2 - y1 + h_2 = (h**2) % self.p + h_3 = (h_2 * h) % self.p + u1_h_2 = (x1 * h_2) % self.p + x3 = (r**2 - h_3 - 2*u1_h_2) % self.p + y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p + z3 = (h*z1) % self.p + return (x3, y3, z3) + + def add(self, p1, p2): + """Add two Jacobian tuples p1 and p2 + + See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition""" + x1, y1, z1 = p1 + x2, y2, z2 = p2 + # Adding the point at infinity is a no-op + if z1 == 0: + return p2 + if z2 == 0: + return p1 + # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1 + if z1 == 1: + return self.add_mixed(p2, p1) + if z2 == 1: + return self.add_mixed(p1, p2) + z1_2 = (z1**2) % self.p + z1_3 = (z1_2 * z1) % self.p + z2_2 = (z2**2) % self.p + z2_3 = (z2_2 * z2) % self.p + u1 = (x1 * z2_2) % self.p + u2 = (x2 * z1_2) % self.p + s1 = (y1 * z2_3) % self.p + s2 = (y2 * z1_3) % self.p + if u1 == u2: + if (s1 != s2): + # p1 and p2 are inverses. Return the point at infinity. + return (0, 1, 0) + # p1 == p2. The formulas below fail when the two points are equal. + return self.double(p1) + h = u2 - u1 + r = s2 - s1 + h_2 = (h**2) % self.p + h_3 = (h_2 * h) % self.p + u1_h_2 = (u1 * h_2) % self.p + x3 = (r**2 - h_3 - 2*u1_h_2) % self.p + y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p + z3 = (h*z1*z2) % self.p + return (x3, y3, z3) + + def mul(self, ps): + """Compute a (multi) point multiplication + + ps is a list of (Jacobian tuple, scalar) pairs. + """ + r = (0, 1, 0) + for i in range(255, -1, -1): + r = self.double(r) + for (p, n) in ps: + if ((n >> i) & 1): + r = self.add(r, p) + return r + +SECP256K1 = EllipticCurve(2**256 - 2**32 - 977, 0, 7) +SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1) SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 -# Thx to Sam Devlin for the ctypes magic 64-bit fix. -def _check_result(val, func, args): - if val == 0: - raise ValueError - else: - return ctypes.c_void_p (val) - -ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p -ssl.EC_KEY_new_by_curve_name.errcheck = _check_result - -class CECKey(): - """Wrapper around OpenSSL's EC_KEY""" - - POINT_CONVERSION_COMPRESSED = 2 - POINT_CONVERSION_UNCOMPRESSED = 4 +class ECPubKey(): + """A secp256k1 public key""" def __init__(self): - self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1) - - def __del__(self): - if ssl: - ssl.EC_KEY_free(self.k) - self.k = None - - def set_secretbytes(self, secret): - priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new()) - group = ssl.EC_KEY_get0_group(self.k) - pub_key = ssl.EC_POINT_new(group) - ctx = ssl.BN_CTX_new() - if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx): - raise ValueError("Could not derive public key from the supplied secret.") - ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) - ssl.EC_KEY_set_private_key(self.k, priv_key) - ssl.EC_KEY_set_public_key(self.k, pub_key) - ssl.EC_POINT_free(pub_key) - ssl.BN_CTX_free(ctx) - return self.k - - def set_privkey(self, key): - self.mb = ctypes.create_string_buffer(key) - return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) - - def set_pubkey(self, key): - self.mb = ctypes.create_string_buffer(key) - return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) - - def get_privkey(self): - size = ssl.i2d_ECPrivateKey(self.k, 0) - mb_pri = ctypes.create_string_buffer(size) - ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri))) - return mb_pri.raw - - def get_pubkey(self): - size = ssl.i2o_ECPublicKey(self.k, 0) - mb = ctypes.create_string_buffer(size) - ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb))) - return mb.raw - - def get_raw_ecdh_key(self, other_pubkey): - ecdh_keybuffer = ctypes.create_string_buffer(32) - r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32, - ssl.EC_KEY_get0_public_key(other_pubkey.k), - self.k, 0) - if r != 32: - raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed') - return ecdh_keybuffer.raw - - def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()): - # FIXME: be warned it's not clear what the kdf should be as a default - r = self.get_raw_ecdh_key(other_pubkey) - return kdf(r) - - def sign(self, hash, low_s = True): - # FIXME: need unit tests for below cases - if not isinstance(hash, bytes): - raise TypeError('Hash must be bytes instance; got %r' % hash.__class__) - if len(hash) != 32: - raise ValueError('Hash must be exactly 32 bytes long') - - sig_size0 = ctypes.c_uint32() - sig_size0.value = ssl.ECDSA_size(self.k) - mb_sig = ctypes.create_string_buffer(sig_size0.value) - result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k) - assert 1 == result - assert mb_sig.raw[0] == 0x30 - assert mb_sig.raw[1] == sig_size0.value - 2 - total_size = mb_sig.raw[1] - assert mb_sig.raw[2] == 2 - r_size = mb_sig.raw[3] - assert mb_sig.raw[4 + r_size] == 2 - s_size = mb_sig.raw[5 + r_size] - s_value = int.from_bytes(mb_sig.raw[6+r_size:6+r_size+s_size], byteorder='big') - if (not low_s) or s_value <= SECP256K1_ORDER_HALF: - return mb_sig.raw[:sig_size0.value] - else: - low_s_value = SECP256K1_ORDER - s_value - low_s_bytes = (low_s_value).to_bytes(33, byteorder='big') - while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80: - low_s_bytes = low_s_bytes[1:] - new_s_size = len(low_s_bytes) - new_total_size_byte = (total_size + new_s_size - s_size).to_bytes(1,byteorder='big') - new_s_size_byte = (new_s_size).to_bytes(1,byteorder='big') - return b'\x30' + new_total_size_byte + mb_sig.raw[2:5+r_size] + new_s_size_byte + low_s_bytes - - def verify(self, hash, sig): - """Verify a DER signature""" - return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 - - def set_compressed(self, compressed): - if compressed: - form = self.POINT_CONVERSION_COMPRESSED + """Construct an uninitialized public key""" + self.valid = False + + def set(self, data): + """Construct a public key from a serialization in compressed or uncompressed format""" + if (len(data) == 65 and data[0] == 0x04): + p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1) + self.valid = SECP256K1.on_curve(p) + if self.valid: + self.p = p + self.compressed = False + elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)): + x = int.from_bytes(data[1:33], 'big') + if SECP256K1.is_x_coord(x): + p = SECP256K1.lift_x(x) + # if the oddness of the y co-ord isn't correct, find the other + # valid y + if (p[1] & 1) != (data[0] & 1): + p = SECP256K1.negate(p) + self.p = p + self.valid = True + self.compressed = True + else: + self.valid = False else: - form = self.POINT_CONVERSION_UNCOMPRESSED - ssl.EC_KEY_set_conv_form(self.k, form) - + self.valid = False -class CPubKey(bytes): - """An encapsulated public key - - Attributes: + @property + def is_compressed(self): + return self.compressed - is_valid - Corresponds to CPubKey.IsValid() - is_fullyvalid - Corresponds to CPubKey.IsFullyValid() - is_compressed - Corresponds to CPubKey.IsCompressed() - """ + @property + def is_valid(self): + return self.valid + + def get_bytes(self): + assert(self.valid) + p = SECP256K1.affine(self.p) + if p is None: + return None + if self.compressed: + return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') + else: + return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') + + def verify_ecdsa(self, sig, msg, low_s=True): + """Verify a strictly DER-encoded ECDSA signature against this pubkey. + + See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the + ECDSA verifier algorithm""" + assert(self.valid) + + # Extract r and s from the DER formatted signature. Return false for + # any DER encoding errors. + if (sig[1] + 2 != len(sig)): + return False + if (len(sig) < 4): + return False + if (sig[0] != 0x30): + return False + if (sig[2] != 0x02): + return False + rlen = sig[3] + if (len(sig) < 6 + rlen): + return False + if rlen < 1 or rlen > 33: + return False + if sig[4] >= 0x80: + return False + if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)): + return False + r = int.from_bytes(sig[4:4+rlen], 'big') + if (sig[4+rlen] != 0x02): + return False + slen = sig[5+rlen] + if slen < 1 or slen > 33: + return False + if (len(sig) != 6 + rlen + slen): + return False + if sig[6+rlen] >= 0x80: + return False + if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)): + return False + s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big') + + # Verify that r and s are within the group order + if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER: + return False + if low_s and s >= SECP256K1_ORDER_HALF: + return False + z = int.from_bytes(msg, 'big') + + # Run verifier algorithm on r, s + w = modinv(s, SECP256K1_ORDER) + u1 = z*w % SECP256K1_ORDER + u2 = r*w % SECP256K1_ORDER + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)])) + if R is None or R[0] != r: + return False + return True + +class ECKey(): + """A secp256k1 private key""" - def __new__(cls, buf, _cec_key=None): - self = super(CPubKey, cls).__new__(cls, buf) - if _cec_key is None: - _cec_key = CECKey() - self._cec_key = _cec_key - self.is_fullyvalid = _cec_key.set_pubkey(self) != 0 - return self + def __init__(self): + self.valid = False + + def set(self, secret, compressed): + """Construct a private key object with given 32-byte secret and compressed flag.""" + assert(len(secret) == 32) + secret = int.from_bytes(secret, 'big') + self.valid = (secret > 0 and secret < SECP256K1_ORDER) + if self.valid: + self.secret = secret + self.compressed = compressed + + def generate(self, compressed=True): + """Generate a random private key (compressed or uncompressed).""" + self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed) + + def get_bytes(self): + """Retrieve the 32-byte representation of this key.""" + assert(self.valid) + return self.secret.to_bytes(32, 'big') @property def is_valid(self): - return len(self) > 0 + return self.valid @property def is_compressed(self): - return len(self) == 33 - - def verify(self, hash, sig): - return self._cec_key.verify(hash, sig) - - def __str__(self): - return repr(self) - - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) + return self.compressed + def get_pubkey(self): + """Compute an ECPubKey object for this secret key.""" + assert(self.valid) + ret = ECPubKey() + p = SECP256K1.mul([(SECP256K1_G, self.secret)]) + ret.p = p + ret.valid = True + ret.compressed = self.compressed + return ret + + def sign_ecdsa(self, msg, low_s=True): + """Construct a DER-encoded ECDSA signature with this key. + + See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the + ECDSA signer algorithm.""" + assert(self.valid) + z = int.from_bytes(msg, 'big') + # Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation) + k = random.randrange(1, SECP256K1_ORDER) + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) + r = R[0] % SECP256K1_ORDER + s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER + if low_s and s > SECP256K1_ORDER_HALF: + s = SECP256K1_ORDER - s + # Represent in DER format. The byte representations of r and s have + # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33 + # bytes). + rb = r.to_bytes((r.bit_length() + 8) // 8, 'big') + sb = s.to_bytes((s.bit_length() + 8) // 8, 'big') + return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 7a063ac526..11ea968257 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -531,7 +531,7 @@ class P2PDataStore(P2PInterface): for b in blocks: self.send_message(msg_block(block=b)) else: - self.send_message(msg_headers([CBlockHeader(blocks[-1])])) + self.send_message(msg_headers([CBlockHeader(block) for block in blocks])) wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) if expect_disconnect: diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 4aeff24d12..555d55d97f 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -556,21 +556,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def is_cli_compiled(self): """Checks whether bitcoin-cli was compiled.""" - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - - return config["components"].getboolean("ENABLE_CLI") + return self.config["components"].getboolean("ENABLE_CLI") def is_wallet_compiled(self): """Checks whether the wallet module was compiled.""" - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - - return config["components"].getboolean("ENABLE_WALLET") + return self.config["components"].getboolean("ENABLE_WALLET") def is_zmq_compiled(self): """Checks whether the zmq module was compiled.""" - config = configparser.ConfigParser() - config.read_file(open(self.options.configfile)) - - return config["components"].getboolean("ENABLE_ZMQ") + return self.config["components"].getboolean("ENABLE_ZMQ") diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 61c5c9aefb..ec5d6f1715 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -66,6 +66,13 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 +EXTENDED_SCRIPTS = [ + # These tests are not run by the travis build process. + # Longest test should go first, to favor running tests in parallel + 'feature_pruning.py', + 'feature_dbcrash.py', +] + BASE_SCRIPTS = [ # Scripts that are run by the travis build process. # Longest test should go first, to favor running tests in parallel @@ -144,6 +151,7 @@ BASE_SCRIPTS = [ 'wallet_txn_doublespend.py', 'wallet_txn_clone.py --mineblock', 'feature_notifications.py', + 'rpc_getblockfilter.py', 'rpc_invalidateblock.py', 'feature_rbf.py', 'mempool_packages.py', @@ -195,13 +203,6 @@ BASE_SCRIPTS = [ # Put them in a random line within the section that fits their approximate run-time ] -EXTENDED_SCRIPTS = [ - # These tests are not run by the travis build process. - # Longest test should go first, to favor running tests in parallel - 'feature_pruning.py', - 'feature_dbcrash.py', -] - # Place EXTENDED_SCRIPTS first since it has the 3 longest running tests ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index e2a20beec5..c02d7422b9 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -4,20 +4,23 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet balance RPC methods.""" from decimal import Decimal +import struct +from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE as ADDRESS_WATCHONLY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, + connect_nodes_bi, + sync_blocks, ) -RANDOM_COINBASE_ADDRESS = 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ' def create_transactions(node, address, amt, fees): # Create and sign raw transactions from node to address for amt. # Creates a transaction for each fee and returns an array # of the raw transactions. - utxos = node.listunspent(0) + utxos = [u for u in node.listunspent(0) if u['spendable']] # Create transactions inputs = [] @@ -25,7 +28,7 @@ def create_transactions(node, address, amt, fees): for utxo in utxos: inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) ins_total += utxo['amount'] - if ins_total > amt: + if ins_total + max(fees) > amt: break txs = [] @@ -33,6 +36,7 @@ def create_transactions(node, address, amt, fees): outputs = {address: amt, node.getrawchangeaddress(): ins_total - amt - fee} raw_tx = node.createrawtransaction(inputs, outputs, 0, True) raw_tx = node.signrawtransactionwithwallet(raw_tx) + assert_equal(raw_tx['complete'], True) txs.append(raw_tx) return txs @@ -41,21 +45,26 @@ class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True + self.extra_args = [ + ['-limitdescendantcount=3'], # Limit mempool descendants as a hack to have wallet txs rejected from the mempool + [], + ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): + self.nodes[0].importaddress(ADDRESS_WATCHONLY) # Check that nodes don't own any UTXOs assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0) - self.log.info("Mining one block for each node") + self.log.info("Mining blocks ...") self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(1) - self.nodes[1].generatetoaddress(100, RANDOM_COINBASE_ADDRESS) + self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) self.sync_all() assert_equal(self.nodes[0].getbalance(), 50) @@ -64,8 +73,10 @@ class WalletTest(BitcoinTestFramework): self.log.info("Test getbalance with different arguments") assert_equal(self.nodes[0].getbalance("*"), 50) assert_equal(self.nodes[0].getbalance("*", 1), 50) - assert_equal(self.nodes[0].getbalance("*", 1, True), 50) + assert_equal(self.nodes[0].getbalance("*", 1, True), 100) assert_equal(self.nodes[0].getbalance(minconf=1), 50) + assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100) + assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 50) # Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0. txs = create_transactions(self.nodes[0], self.nodes[1].getnewaddress(), 40, [Decimal('0.01')]) @@ -83,32 +94,34 @@ class WalletTest(BitcoinTestFramework): self.log.info("Test getbalance and getunconfirmedbalance with unconfirmed inputs") - # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions - assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send - assert_equal(self.nodes[1].getbalance(), Decimal('29.99')) # change from node 1's send - # Same with minconf=0 - assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99')) - assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('29.99')) - # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago - # TODO: fix getbalance tracking of coin spentness depth - assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) - assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) - # getunconfirmedbalance - assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend - assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + def test_balances(*, fee_node_1=0): + # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions + assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) # change from node 0's send + assert_equal(self.nodes[1].getbalance(), Decimal('30') - fee_node_1) # change from node 1's send + # Same with minconf=0 + assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99')) + assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('30') - fee_node_1) + # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago + # TODO: fix getbalance tracking of coin spentness depth + assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) + assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) + # getunconfirmedbalance + assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) # output of node 1's spend + assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) + assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) # Doesn't include output of node 0's send since it was spent + assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) + + test_balances(fee_node_1=Decimal('0.01')) # Node 1 bumps the transaction fee and resends self.nodes[1].sendrawtransaction(txs[1]['hex']) + self.nodes[0].sendrawtransaction(txs[1]['hex']) # sending on both nodes is faster than waiting for propagation self.sync_all() self.log.info("Test getbalance and getunconfirmedbalance with conflicted unconfirmed inputs") + test_balances(fee_node_1=Decimal('0.02')) - assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], Decimal('60')) # output of node 1's send - assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) - assert_equal(self.nodes[1].getwalletinfo()["unconfirmed_balance"], Decimal('0')) # Doesn't include output of node 0's send since it was spent - assert_equal(self.nodes[1].getunconfirmedbalance(), Decimal('0')) - - self.nodes[1].generatetoaddress(1, RANDOM_COINBASE_ADDRESS) + self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY) self.sync_all() # balances are correct after the transactions are confirmed @@ -118,7 +131,7 @@ class WalletTest(BitcoinTestFramework): # Send total balance away from node 1 txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), Decimal('29.97'), [Decimal('0.01')]) self.nodes[1].sendrawtransaction(txs[0]['hex']) - self.nodes[1].generatetoaddress(2, RANDOM_COINBASE_ADDRESS) + self.nodes[1].generatetoaddress(2, ADDRESS_WATCHONLY) self.sync_all() # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago @@ -140,6 +153,51 @@ class WalletTest(BitcoinTestFramework): after = self.nodes[1].getunconfirmedbalance() assert_equal(before + Decimal('0.1'), after) + # Create 3 more wallet txs, where the last is not accepted to the + # mempool because it is the third descendant of the tx above + for _ in range(3): + # Set amount high enough such that all coins are spent by each tx + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 99) + + self.log.info('Check that wallet txs not in the mempool are untrusted') + assert txid not in self.nodes[0].getrawmempool() + assert_equal(self.nodes[0].gettransaction(txid)['trusted'], False) + assert_equal(self.nodes[0].getbalance(minconf=0), 0) + + self.log.info("Test replacement and reorg of non-mempool tx") + tx_orig = self.nodes[0].gettransaction(txid)['hex'] + # Increase fee by 1 coin + tx_replace = tx_orig.replace( + struct.pack("<q", 99 * 10**8).hex(), + struct.pack("<q", 98 * 10**8).hex(), + ) + tx_replace = self.nodes[0].signrawtransactionwithwallet(tx_replace)['hex'] + # Total balance is given by the sum of outputs of the tx + total_amount = sum([o['value'] for o in self.nodes[0].decoderawtransaction(tx_replace)['vout']]) + self.sync_all() + self.nodes[1].sendrawtransaction(hexstring=tx_replace, maxfeerate=0) + + # Now confirm tx_replace + block_reorg = self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY)[0] + self.sync_all() + assert_equal(self.nodes[0].getbalance(minconf=0), total_amount) + + self.log.info('Put txs back into mempool of node 1 (not node 0)') + self.nodes[0].invalidateblock(block_reorg) + self.nodes[1].invalidateblock(block_reorg) + assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted + self.nodes[0].generatetoaddress(1, ADDRESS_WATCHONLY) + assert_equal(self.nodes[0].getbalance(minconf=0), 0) # wallet txs not in the mempool are untrusted + + # Now confirm tx_orig + self.restart_node(1, ['-persistmempool=0']) + connect_nodes_bi(self.nodes, 0, 1) + sync_blocks(self.nodes) + self.nodes[1].sendrawtransaction(tx_orig) + self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY) + self.sync_all() + assert_equal(self.nodes[0].getbalance(minconf=0), total_amount + 1) # The reorg recovered our fee of 1 coin + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 0c6ccbef0f..568b1f28d8 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -79,6 +79,10 @@ class BumpFeeTest(BitcoinTestFramework): test_unconfirmed_not_spendable(rbf_node, rbf_node_address) test_bumpfee_metadata(rbf_node, dest_address) test_locked_wallet_fails(rbf_node, dest_address) + test_change_script_match(rbf_node, dest_address) + # These tests wipe out a number of utxos that are expected in other tests + test_small_output_with_feerate_succeeds(rbf_node, dest_address) + test_no_more_inputs_fails(rbf_node, dest_address) self.log.info("Success") @@ -179,6 +183,40 @@ def test_small_output_fails(rbf_node, dest_address): rbfid = spend_one_input(rbf_node, dest_address) assert_raises_rpc_error(-4, "Change output is too small", rbf_node.bumpfee, rbfid, {"totalFee": 50001}) +def test_small_output_with_feerate_succeeds(rbf_node, dest_address): + + # Make sure additional inputs exist + rbf_node.generatetoaddress(101, rbf_node.getnewaddress()) + rbfid = spend_one_input(rbf_node, dest_address) + original_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] + assert_equal(len(original_input_list), 1) + original_txin = original_input_list[0] + # Keep bumping until we out-spend change output + tx_fee = 0 + while tx_fee < Decimal("0.0005"): + new_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] + new_item = list(new_input_list)[0] + assert_equal(len(original_input_list), 1) + assert_equal(original_txin["txid"], new_item["txid"]) + assert_equal(original_txin["vout"], new_item["vout"]) + rbfid_new_details = rbf_node.bumpfee(rbfid) + rbfid_new = rbfid_new_details["txid"] + raw_pool = rbf_node.getrawmempool() + assert rbfid not in raw_pool + assert rbfid_new in raw_pool + rbfid = rbfid_new + tx_fee = rbfid_new_details["origfee"] + + # input(s) have been added + final_input_list = rbf_node.getrawtransaction(rbfid, 1)["vin"] + assert_greater_than(len(final_input_list), 1) + # Original input is in final set + assert [txin for txin in final_input_list + if txin["txid"] == original_txin["txid"] + and txin["vout"] == original_txin["vout"]] + + rbf_node.generatetoaddress(1, rbf_node.getnewaddress()) + assert_equal(rbf_node.gettransaction(rbfid)["confirmations"], 1) def test_dust_to_fee(rbf_node, dest_address): # check that if output is reduced to dust, it will be converted to fee @@ -278,19 +316,37 @@ def test_locked_wallet_fails(rbf_node, dest_address): rbf_node.walletlock() assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first.", rbf_node.bumpfee, rbfid) + rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) + +def test_change_script_match(rbf_node, dest_address): + """Test that the same change addresses is used for the replacement transaction when possible.""" + def get_change_address(tx): + tx_details = rbf_node.getrawtransaction(tx, 1) + txout_addresses = [txout['scriptPubKey']['addresses'][0] for txout in tx_details["vout"]] + return [address for address in txout_addresses if rbf_node.getaddressinfo(address)["ischange"]] + # Check that there is only one change output + rbfid = spend_one_input(rbf_node, dest_address) + change_addresses = get_change_address(rbfid) + assert_equal(len(change_addresses), 1) + + # Now find that address in each subsequent tx, and no other change + bumped_total_tx = rbf_node.bumpfee(rbfid, {"totalFee": 2000}) + assert_equal(change_addresses, get_change_address(bumped_total_tx['txid'])) + bumped_rate_tx = rbf_node.bumpfee(bumped_total_tx["txid"]) + assert_equal(change_addresses, get_change_address(bumped_rate_tx['txid'])) -def spend_one_input(node, dest_address): +def spend_one_input(node, dest_address, change_size=Decimal("0.00049000")): tx_input = dict( sequence=BIP125_SEQUENCE_NUMBER, **next(u for u in node.listunspent() if u["amount"] == Decimal("0.00100000"))) - rawtx = node.createrawtransaction( - [tx_input], {dest_address: Decimal("0.00050000"), - node.getrawchangeaddress(): Decimal("0.00049000")}) + destinations = {dest_address: Decimal("0.00050000")} + if change_size > 0: + destinations[node.getrawchangeaddress()] = change_size + rawtx = node.createrawtransaction([tx_input], destinations) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx["hex"]) return txid - def submit_block_with_tx(node, tx): ctx = CTransaction() ctx.deserialize(io.BytesIO(hex_str_to_bytes(tx))) @@ -307,6 +363,12 @@ def submit_block_with_tx(node, tx): node.submitblock(block.serialize(True).hex()) return block +def test_no_more_inputs_fails(rbf_node, dest_address): + # feerate rbf requires confirmed outputs when change output doesn't exist or is insufficient + rbf_node.generatetoaddress(1, dest_address) + # spend all funds, no change output + rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True) + assert_raises_rpc_error(-4, "Unable to create transaction: Insufficient funds", rbf_node.bumpfee, rbfid) if __name__ == "__main__": BumpFeeTest().main() diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index 72df714d80..9de30d0374 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -166,11 +166,12 @@ class ImportRescanTest(BitcoinTestFramework): timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1) self.nodes[0].generate(1) - self.sync_blocks() + self.sync_all() # For each variation of wallet key import, invoke the import RPC and # check the results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: + self.log.info('Run import for variant {}'.format(variant)) variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))] @@ -192,10 +193,11 @@ class ImportRescanTest(BitcoinTestFramework): # Generate a block containing the new transactions. self.nodes[0].generate(1) assert_equal(self.nodes[0].getrawmempool(), []) - self.sync_blocks() + self.sync_all() # Check the latest results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: + self.log.info('Run check for variant {}'.format(variant)) if not variant.expect_disabled: variant.expected_balance += variant.sent_amount variant.expected_txs += 1 |