diff --git a/test/functional/feature_notifications.py b/test/functional/feature_notifications.py --- a/test/functional/feature_notifications.py +++ b/test/functional/feature_notifications.py @@ -6,12 +6,14 @@ """Test the -alertnotify, -blocknotify and -walletnotify options.""" import os -from test_framework.address import ADDRESS_ECREG_UNSPENDABLE +from test_framework.address import ADDRESS_ECREG_UNSPENDABLE, keyhash_to_p2pkh from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, - wait_until + disconnect_nodes, + wait_until, + hex_str_to_bytes, ) FORK_WARNING_MESSAGE = "Warning: Large-work fork detected, forking after block {}" @@ -114,6 +116,63 @@ sorted(txids_rpc), sorted( os.listdir( self.walletnotify_dir))) + for tx_file in os.listdir(self.walletnotify_dir): + os.remove(os.path.join(self.walletnotify_dir, tx_file)) + + # Conflicting transactions tests. Give node 0 same wallet seed as + # node 1, generate spends from node 0, and check notifications + # triggered by node 1 + self.log.info("test -walletnotify with conflicting transactions") + self.nodes[0].sethdseed( + seed=self.nodes[1].dumpprivkey( + keyhash_to_p2pkh( + hex_str_to_bytes( + self.nodes[1].getwalletinfo()['hdseedid'])[::-1]))) + self.nodes[0].rescanblockchain() + self.nodes[0].generatetoaddress(100, ADDRESS_ECREG_UNSPENDABLE) + + # Generate transaction on node 0, sync mempools, and check for + # notification on node 1. + tx1 = self.nodes[0].sendtoaddress( + address=ADDRESS_ECREG_UNSPENDABLE, amount=100) + assert_equal(tx1 in self.nodes[0].getrawmempool(), True) + self.sync_mempools() + self.expect_wallet_notify([tx1]) + + # Add tx1 transaction to new block, checking for a notification + # and the correct number of confirmations. + self.nodes[0].generatetoaddress(1, ADDRESS_ECREG_UNSPENDABLE) + self.sync_blocks() + self.expect_wallet_notify([tx1]) + assert_equal(self.nodes[1].gettransaction(tx1)["confirmations"], 1) + + # Generate conflicting transactions with the nodes disconnected. + # Sending almost the entire available balance on each node, but + # with a slightly different amount, ensures that there will be + # a conflict. + balance = self.nodes[0].getbalance() + disconnect_nodes(self.nodes[0], self.nodes[1]) + tx2_node0 = self.nodes[0].sendtoaddress( + address=ADDRESS_ECREG_UNSPENDABLE, amount=balance - 20) + tx2_node1 = self.nodes[1].sendtoaddress( + address=ADDRESS_ECREG_UNSPENDABLE, amount=balance - 21) + assert tx2_node0 != tx2_node1 + self.expect_wallet_notify([tx2_node1]) + # So far tx2_node1 has no conflicting tx + assert not self.nodes[1].gettransaction( + tx2_node1)['walletconflicts'] + + # Mine a block on node0, reconnect the nodes, check that tx2_node1 + # has a conflicting tx after syncing with node0. + self.nodes[0].generatetoaddress(1, ADDRESS_ECREG_UNSPENDABLE) + connect_nodes(self.nodes[0], self.nodes[1]) + self.sync_blocks() + assert tx2_node0 in self.nodes[1].gettransaction(tx2_node1)[ + 'walletconflicts'] + + # node1's wallet will notify of the new confirmed transaction tx2_0 + # and about the conflicted transaction tx2_1. + self.expect_wallet_notify([tx2_node0, tx2_node1]) # Create an invalid chain and ensure the node warns. self.log.info("test -alertnotify for forked chain") @@ -137,6 +196,16 @@ for notify_file in os.listdir(self.alertnotify_dir): os.remove(os.path.join(self.alertnotify_dir, notify_file)) + def expect_wallet_notify(self, tx_ids): + wait_until( + lambda: len(os.listdir(self.walletnotify_dir)) >= len(tx_ids), + timeout=10) + assert_equal( + sorted(notify_outputname(self.wallet, tx_id) for tx_id in tx_ids), + sorted(os.listdir(self.walletnotify_dir))) + for tx_file in os.listdir(self.walletnotify_dir): + os.remove(os.path.join(self.walletnotify_dir, tx_file)) + if __name__ == '__main__': NotificationsTest().main()