diff options
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/feature_init.py | 41 | ||||
-rwxr-xr-x | test/functional/interface_rest.py | 53 | ||||
-rwxr-xr-x | test/functional/test_framework/test_node.py | 9 | ||||
-rwxr-xr-x | test/functional/wallet_abandonconflict.py | 101 | ||||
-rw-r--r-- | test/sanitizer_suppressions/ubsan | 2 |
5 files changed, 96 insertions, 110 deletions
diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 4b56b0c26b..dbd71a8b2d 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -3,8 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Stress tests related to node initialization.""" -import random -import time import os from pathlib import Path @@ -26,7 +24,6 @@ class InitStressTest(BitcoinTestFramework): def run_test(self): """ - test terminating initialization after seeing a certain log line. - - test terminating init after seeing a random number of log lines. - test removing certain essential files to test startup error paths. """ # TODO: skip Windows for now since it isn't clear how to SIGTERM. @@ -76,46 +73,14 @@ class InitStressTest(BitcoinTestFramework): for terminate_line in lines_to_terminate_after: self.log.info(f"Starting node and will exit after line '{terminate_line}'") - node.start(extra_args=['-txindex=1']) - - num_total_logs = node.wait_for_debug_log([terminate_line], ignore_case=True) - self.log.debug(f"Terminating node after {num_total_logs} log lines seen") + with node.wait_for_debug_log([terminate_line], ignore_case=True): + node.start(extra_args=['-txindex=1']) + self.log.debug("Terminating node after terminate line was found") sigterm_node() check_clean_start() self.stop_node(0) - self.log.info( - f"Terminate at some random point in the init process (max logs: {num_total_logs})") - - for _ in range(40): - num_logs = len(Path(node.debug_log_path).read_text().splitlines()) - additional_lines = random.randint(1, num_total_logs) - self.log.debug(f"Starting node and will exit after {additional_lines} lines") - node.start(extra_args=['-txindex=1']) - logfile = open(node.debug_log_path, 'rb') - - MAX_SECS_TO_WAIT = 10 - start = time.time() - num_lines = 0 - - while True: - line = logfile.readline() - if line: - num_lines += 1 - - if num_lines >= (num_logs + additional_lines) or \ - (time.time() - start) > MAX_SECS_TO_WAIT: - self.log.debug(f"Terminating node after {num_lines} log lines seen") - sigterm_node() - break - - if node.process.poll() is not None: - raise AssertionError("node failed to start") - - check_clean_start() - self.stop_node(0) - self.log.info("Test startup errors after removing certain essential files") files_to_disturb = { diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index 06aa5608bb..a3d949c6a8 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -6,21 +6,28 @@ from decimal import Decimal from enum import Enum +import http.client from io import BytesIO import json from struct import pack, unpack - -import http.client import urllib.parse + +from test_framework.messages import ( + BLOCK_HEADER_SIZE, + COIN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, ) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) -from test_framework.messages import BLOCK_HEADER_SIZE INVALID_PARAM = "abc" UNKNOWN_PARAM = "0000000000000000000000000000000000000000000000000000000000000000" @@ -43,14 +50,13 @@ def filter_output_indices_by_value(vouts, value): class RESTTest (BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [["-rest", "-blockfilterindex=1"], []] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body='', status=200, ret_type=RetType.JSON): rest_uri = '/rest' + uri if req_type == ReqType.JSON: @@ -79,17 +85,11 @@ class RESTTest (BitcoinTestFramework): def run_test(self): self.url = urllib.parse.urlparse(self.nodes[0].url) - self.log.info("Mine blocks and send Bitcoin to node 1") + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() - # Random address so node1's balance doesn't increase - not_related_address = "2MxqoHEdNQTyYeX1mHcbrrpzgojbosTpCvJ" - - self.generate(self.nodes[0], 1) - self.generatetoaddress(self.nodes[1], 100, not_related_address) - - assert_equal(self.nodes[0].getbalance(), 50) - - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + self.log.info("Broadcast test transaction and sync nodes") + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=int(0.1 * COIN)) self.sync_all() self.log.info("Test the /tx URI") @@ -115,11 +115,9 @@ class RESTTest (BitcoinTestFramework): self.log.info("Query an unspent TXO using the /getutxos URI") - self.generatetoaddress(self.nodes[1], 1, not_related_address) + self.generate(self.wallet, 1) bb_hash = self.nodes[0].getbestblockhash() - assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) - # Check chainTip response json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}") assert_equal(json_obj['chaintipHash'], bb_hash) @@ -161,7 +159,7 @@ class RESTTest (BitcoinTestFramework): response_hash = output.read(32)[::-1].hex() assert_equal(bb_hash, response_hash) # check if getutxo's chaintip during calculation was fine - assert_equal(chain_height, 102) # chain height must be 102 + assert_equal(chain_height, 201) # chain height must be 201 (pre-mined chain [200] + generated block [1]) self.log.info("Test the /getutxos URI with and without /checkmempool") # Create a transaction, check that it's found with /checkmempool, but @@ -169,7 +167,7 @@ class RESTTest (BitcoinTestFramework): # found with or without /checkmempool. # do a tx and don't sync - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=int(0.1 * COIN)) json_obj = self.test_rest_request(f"/tx/{txid}") # get the spent output to later check for utxo (should be spent by then) spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout']) @@ -301,11 +299,13 @@ class RESTTest (BitcoinTestFramework): self.log.info("Test tx inclusion in the /mempool and /block URIs") - # Make 3 tx and mine them on node 1 + # Make 3 chained txs and mine them on node 1 txs = [] - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) + input_txid = txid + for _ in range(3): + utxo_to_spend = self.wallet.get_utxo(txid=input_txid) + txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['txid']) + input_txid = txs[-1] self.sync_all() # Check that there are exactly 3 transactions in the TX memory pool before generating the block @@ -351,5 +351,6 @@ class RESTTest (BitcoinTestFramework): json_obj = self.test_rest_request("/chaininfo") assert_equal(json_obj['bestblockhash'], bb_hash) + if __name__ == '__main__': RESTTest().main() diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 0b9154a030..289e83579b 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -422,7 +422,8 @@ class TestNode(): time.sleep(0.05) self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) - def wait_for_debug_log(self, expected_msgs, timeout=10, ignore_case=False) -> int: + @contextlib.contextmanager + def wait_for_debug_log(self, expected_msgs, timeout=60, ignore_case=False): """ Block until we see a particular debug log message fragment or until we exceed the timeout. Return: @@ -432,6 +433,8 @@ class TestNode(): prev_size = self.debug_log_bytes() re_flags = re.MULTILINE | (re.IGNORECASE if ignore_case else 0) + yield + while True: found = True with open(self.debug_log_path, encoding='utf-8') as dl: @@ -443,8 +446,7 @@ class TestNode(): found = False if found: - num_logs = len(log.splitlines()) - return num_logs + return if time.time() >= time_end: print_log = " - " + "\n - ".join(log.splitlines()) @@ -456,7 +458,6 @@ class TestNode(): self._raise_assertion_error( 'Expected messages "{}" does not partially match log:\n\n{}\n\n'.format( str(expected_msgs), print_log)) - return -1 # useless return to satisfy linter @contextlib.contextmanager def profile_with_perf(self, profile_name: str): diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 27d9d8da88..36fcdb36d6 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -29,20 +29,25 @@ class AbandonConflictTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): + # create two wallets to tests conflicts from both sender's and receiver's sides + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.nodes[0].createwallet(wallet_name="bob") + bob = self.nodes[0].get_wallet_rpc("bob") + self.generate(self.nodes[1], COINBASE_MATURITY) - balance = self.nodes[0].getbalance() - txA = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) - txB = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) - txC = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + balance = alice.getbalance() + txA = alice.sendtoaddress(alice.getnewaddress(), Decimal("10")) + txB = alice.sendtoaddress(alice.getnewaddress(), Decimal("10")) + txC = alice.sendtoaddress(alice.getnewaddress(), Decimal("10")) self.sync_mempools() self.generate(self.nodes[1], 1) # Can not abandon non-wallet transaction - assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: self.nodes[0].abandontransaction(txid='ff' * 32)) + assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: alice.abandontransaction(txid='ff' * 32)) # Can not abandon confirmed transaction - assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: self.nodes[0].abandontransaction(txid=txA)) + assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: alice.abandontransaction(txid=txA)) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert balance - newbalance < Decimal("0.001") #no more than fees lost balance = newbalance @@ -50,9 +55,9 @@ class AbandonConflictTest(BitcoinTestFramework): self.disconnect_nodes(0, 1) # Identify the 10btc outputs - nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10")) - nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10")) - nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10")) + nA = next(tx_out["vout"] for tx_out in alice.gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10")) + nB = next(tx_out["vout"] for tx_out in alice.gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10")) + nC = next(tx_out["vout"] for tx_out in alice.gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10")) inputs = [] # spend 10btc outputs from txA and txB @@ -60,39 +65,40 @@ class AbandonConflictTest(BitcoinTestFramework): inputs.append({"txid": txB, "vout": nB}) outputs = {} - outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998") - outputs[self.nodes[1].getnewaddress()] = Decimal("5") - signed = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + outputs[alice.getnewaddress()] = Decimal("14.99998") + outputs[bob.getnewaddress()] = Decimal("5") + signed = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs)) txAB1 = self.nodes[0].sendrawtransaction(signed["hex"]) # Identify the 14.99998btc output - nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998")) + nAB = next(tx_out["vout"] for tx_out in alice.gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998")) #Create a child tx spending AB1 and C inputs = [] inputs.append({"txid": txAB1, "vout": nAB}) inputs.append({"txid": txC, "vout": nC}) outputs = {} - outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996") - signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + outputs[alice.getnewaddress()] = Decimal("24.9996") + signed2 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs)) txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"]) # Create a child tx spending ABC2 signed3_change = Decimal("24.999") inputs = [{"txid": txABC2, "vout": 0}] - outputs = {self.nodes[0].getnewaddress(): signed3_change} - signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + outputs = {alice.getnewaddress(): signed3_change} + signed3 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs)) # note tx is never directly referenced, only abandoned as a child of the above self.nodes[0].sendrawtransaction(signed3["hex"]) # In mempool txs from self should increase balance from change - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("30") + signed3_change) balance = newbalance # Restart the node with a higher min relay fee so the parent tx is no longer in mempool # TODO: redo with eviction self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) assert self.nodes[0].getmempoolinfo()['loaded'] # Verify txs no longer in either node's mempool @@ -101,25 +107,25 @@ class AbandonConflictTest(BitcoinTestFramework): # Not in mempool txs from self should only reduce balance # inputs are still spent, but change not received - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - signed3_change) # Unconfirmed received funds that are not in mempool, also shouldn't show # up in unconfirmed balance - balances = self.nodes[0].getbalances()['mine'] + balances = alice.getbalances()['mine'] assert_equal(balances['untrusted_pending'] + balances['trusted'], newbalance) # Also shouldn't show up in listunspent - assert not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)] + assert not txABC2 in [utxo["txid"] for utxo in alice.listunspent(0)] balance = newbalance # Abandon original transaction and verify inputs are available again # including that the child tx was also abandoned - self.nodes[0].abandontransaction(txAB1) - newbalance = self.nodes[0].getbalance() + alice.abandontransaction(txAB1) + newbalance = alice.getbalance() assert_equal(newbalance, balance + Decimal("30")) balance = newbalance self.log.info("Check abandoned transactions in listsinceblock") - listsinceblock = self.nodes[0].listsinceblock() + listsinceblock = alice.listsinceblock() txAB1_listsinceblock = [d for d in listsinceblock['transactions'] if d['txid'] == txAB1 and d['category'] == 'send'] for tx in txAB1_listsinceblock: assert_equal(tx['abandoned'], True) @@ -128,49 +134,53 @@ class AbandonConflictTest(BitcoinTestFramework): # Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"]) + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) assert self.nodes[0].getmempoolinfo()['loaded'] assert_equal(len(self.nodes[0].getrawmempool()), 0) - assert_equal(self.nodes[0].getbalance(), balance) + assert_equal(alice.getbalance(), balance) # But if it is received again then it is unabandoned # And since now in mempool, the change is available # But its child tx remains abandoned self.nodes[0].sendrawtransaction(signed["hex"]) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("20") + Decimal("14.99998")) balance = newbalance # Send child tx again so it is unabandoned self.nodes[0].sendrawtransaction(signed2["hex"]) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("10") - Decimal("14.99998") + Decimal("24.9996")) balance = newbalance # Remove using high relay fee again self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) assert self.nodes[0].getmempoolinfo()['loaded'] assert_equal(len(self.nodes[0].getrawmempool()), 0) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("24.9996")) balance = newbalance self.log.info("Test transactions conflicted by a double spend") + self.nodes[0].loadwallet("bob") + bob = self.nodes[0].get_wallet_rpc("bob") + # Create a double spend of AB1 by spending again from only A's 10 output # Mine double spend from node 1 inputs = [] inputs.append({"txid": txA, "vout": nA}) outputs = {} - outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999") - tx = self.nodes[0].createrawtransaction(inputs, outputs) - signed = self.nodes[0].signrawtransactionwithwallet(tx) - self.nodes[1].sendrawtransaction(signed["hex"]) - self.generate(self.nodes[1], 1, sync_fun=self.no_op) - + outputs[self.nodes[1].getnewaddress()] = Decimal("3.9999") + outputs[bob.getnewaddress()] = Decimal("5.9999") + tx = alice.createrawtransaction(inputs, outputs) + signed = alice.signrawtransactionwithwallet(tx) + double_spend_txid = self.nodes[1].sendrawtransaction(signed["hex"]) self.connect_nodes(0, 1) - self.sync_blocks() + self.generate(self.nodes[1], 1) - tx_list = self.nodes[0].listtransactions() + tx_list = alice.listtransactions() conflicted = [tx for tx in tx_list if tx["confirmations"] < 0] assert_equal(4, len(conflicted)) @@ -179,7 +189,7 @@ class AbandonConflictTest(BitcoinTestFramework): assert_equal(2, len(wallet_conflicts)) double_spends = [tx for tx in tx_list if tx["walletconflicts"] and tx["confirmations"] > 0] - assert_equal(1, len(double_spends)) + assert_equal(2, len(double_spends)) # one for each output double_spend = double_spends[0] # Test the properties of the conflicted transactions, i.e. with confirmations < 0. @@ -198,8 +208,19 @@ class AbandonConflictTest(BitcoinTestFramework): assert_equal(double_spend["walletconflicts"], [tx["txid"]]) assert_equal(tx["walletconflicts"], [double_spend["txid"]]) + # Test walletconflicts on the receiver's side + txinfo = bob.gettransaction(txAB1) + assert_equal(txinfo['confirmations'], -1) + assert_equal(txinfo['walletconflicts'], [double_spend['txid']]) + + double_spends = [tx for tx in bob.listtransactions() if tx["walletconflicts"] and tx["confirmations"] > 0] + assert_equal(1, len(double_spends)) + double_spend = double_spends[0] + assert_equal(double_spend_txid, double_spend['txid']) + assert_equal(double_spend["walletconflicts"], [txAB1]) + # Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance + Decimal("20")) balance = newbalance @@ -207,7 +228,7 @@ class AbandonConflictTest(BitcoinTestFramework): # Invalidate the block with the double spend and B's 10 BTC output should no longer be available # Don't think C's should either self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() #assert_equal(newbalance, balance - Decimal("10")) self.log.info("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer") self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315") diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index 4ebcaf1a15..ec13acb689 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -70,9 +70,7 @@ implicit-integer-sign-change:prevector.h implicit-integer-sign-change:script/bitcoinconsensus.cpp implicit-integer-sign-change:script/interpreter.cpp implicit-integer-sign-change:serialize.h -implicit-integer-sign-change:test/streams_tests.cpp implicit-integer-sign-change:txmempool.cpp -implicit-integer-sign-change:zmq/zmqpublishnotifier.cpp implicit-signed-integer-truncation:addrman.cpp implicit-signed-integer-truncation:addrman.h implicit-signed-integer-truncation:crypto/ |