diff options
Diffstat (limited to 'tests/functional_tests/transfer.py')
-rwxr-xr-x | tests/functional_tests/transfer.py | 400 |
1 files changed, 394 insertions, 6 deletions
diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index 56a2514d9..1da075318 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -30,6 +30,7 @@ from __future__ import print_function import json +import util_resources import pprint from deepdiff import DeepDiff pp = pprint.PrettyPrinter(indent=2) @@ -46,6 +47,17 @@ seeds = [ 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', ] +def diff_transfers(actual_transfers, expected_transfers, ignore_order = True): + # The payments containers aren't ordered; re-scanning can lead to diff orders + diff = DeepDiff(actual_transfers, expected_transfers, ignore_order = ignore_order) + if diff != {}: + pp.pprint(diff) + assert diff == {} + +def diff_incoming_transfers(actual_transfers, expected_transfers): + # wallet2 m_transfers container is ordered and order should be the same across rescans + diff_transfers(actual_transfers, expected_transfers, ignore_order = False) + class TransferTest(): def run_test(self): self.reset() @@ -64,6 +76,8 @@ class TransferTest(): self.check_multiple_submissions() self.check_scan_tx() self.check_subtract_fee_from_outputs() + self.check_background_sync() + self.check_background_sync_reorg_recovery() def reset(self): print('Resetting blockchain') @@ -875,12 +889,6 @@ class TransferTest(): print('Testing scan_tx') - def diff_transfers(actual_transfers, expected_transfers): - diff = DeepDiff(actual_transfers, expected_transfers) - if diff != {}: - pp.pprint(diff) - assert diff == {} - # set up sender_wallet sender_wallet = self.wallet[0] try: sender_wallet.close_wallet() @@ -1162,5 +1170,385 @@ class TransferTest(): except AssertionError: pass + def check_background_sync(self): + daemon = Daemon() + + print('Testing background sync') + + # Some helper functions + def stop_with_wrong_inputs(wallet, wallet_password, seed = ''): + invalid = False + try: wallet.stop_background_sync(wallet_password = wallet_password, seed = seed) + except: invalid = True + assert invalid + + def open_with_wrong_password(wallet, filename, password): + invalid_password = False + try: wallet.open_wallet(filename, password = password) + except: invalid_password = True + assert invalid_password + + def restore_wallet(wallet, seed, filename = '', password = ''): + wallet.close_wallet() + if filename != '': + util_resources.remove_wallet_files(filename) + wallet.restore_deterministic_wallet(seed = seed, filename = filename, password = password) + wallet.auto_refresh(enable = False) + assert wallet.get_transfers() == {} + + def assert_correct_transfers(wallet, expected_transfers, expected_inc_transfers, expected_balance): + diff_transfers(wallet.get_transfers(), expected_transfers) + diff_incoming_transfers(wallet.incoming_transfers(transfer_type = 'all'), expected_inc_transfers) + assert wallet.get_balance().balance == expected_balance + + # Set up sender_wallet. Prepare to sweep single output to receiver. + # We're testing a sweep because it makes sure background sync can + # properly pick up txs which do not have a change output back to sender. + sender_wallet = self.wallet[0] + try: sender_wallet.close_wallet() + except: pass + sender_wallet.restore_deterministic_wallet(seed = seeds[0]) + sender_wallet.auto_refresh(enable = False) + sender_wallet.refresh() + res = sender_wallet.incoming_transfers(transfer_type = 'available') + unlocked = [x for x in res.transfers if x.unlocked and x.amount > 0] + assert len(unlocked) > 0 + ki = unlocked[0].key_image + amount = unlocked[0].amount + spent_txid = unlocked[0].tx_hash + sender_wallet.refresh() + res = sender_wallet.get_transfers() + out_len = 0 if 'out' not in res else len(res.out) + sender_starting_balance = sender_wallet.get_balance().balance + + # Background sync type options + reuse_password = sender_wallet.background_sync_options.reuse_password + custom_password = sender_wallet.background_sync_options.custom_password + + # set up receiver_wallet + receiver_wallet = self.wallet[1] + try: receiver_wallet.close_wallet() + except: pass + receiver_wallet.restore_deterministic_wallet(seed = seeds[1]) + receiver_wallet.auto_refresh(enable = False) + receiver_wallet.refresh() + res = receiver_wallet.get_transfers() + in_len = 0 if 'in' not in res else len(res['in']) + receiver_starting_balance = receiver_wallet.get_balance().balance + + # transfer from sender_wallet to receiver_wallet + dst = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + res = sender_wallet.sweep_single(dst, key_image = ki) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + assert res.fee > 0 + fee = res.fee + assert res.amount == amount - fee + + expected_sender_balance = sender_starting_balance - amount + expected_receiver_balance = receiver_starting_balance + (amount - fee) + + print('Checking background sync on outgoing wallet') + sender_wallet.setup_background_sync(background_sync_type = reuse_password) + sender_wallet.start_background_sync() + # Mine block to an uninvolved wallet + daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) + # sender should still be able to scan the transfer normally because we + # spent an output that had a known key image + sender_wallet.refresh() + transfers = sender_wallet.get_transfers() + assert 'pending' not in transfers or len(transfers.pending) == 0 + assert 'pool' not in transfers or len (transfers.pool) == 0 + assert len(transfers.out) == out_len + 1 + tx = [x for x in transfers.out if x.txid == txid] + assert len(tx) == 1 + tx = tx[0] + assert tx.amount == amount - fee + assert tx.fee == fee + assert len(tx.destinations) == 1 + assert tx.destinations[0].amount == amount - fee + assert tx.destinations[0].address == dst + incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') + assert len([x for x in incoming_transfers.transfers if x.tx_hash == spent_txid and x.key_image == ki and x.spent]) == 1 + assert sender_wallet.get_balance().balance == expected_sender_balance + + # Restore and check background syncing outgoing wallet + restore_wallet(sender_wallet, seeds[0]) + sender_wallet.setup_background_sync(background_sync_type = reuse_password) + sender_wallet.start_background_sync() + sender_wallet.refresh() + for i, out_tx in enumerate(transfers.out): + if 'destinations' in out_tx: + del transfers.out[i]['destinations'] # destinations are not expected after wallet restore + # sender's balance should be higher because can't detect spends while + # background sync enabled, only receives + background_bal = sender_wallet.get_balance().balance + assert background_bal > expected_sender_balance + background_transfers = sender_wallet.get_transfers() + assert 'out' not in background_transfers or len(background_transfers.out) == 0 + assert 'in' in background_transfers and len(background_transfers['in']) > 0 + background_incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') + assert len(background_incoming_transfers) == len(incoming_transfers) + assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 + assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == spent_txid]) == 1 + + # Try to stop background sync with the wrong seed + stop_with_wrong_inputs(sender_wallet, wallet_password = '', seed = seeds[1]) + + # Stop background sync and check transfers update correctly + sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Check stopping a wallet with wallet files saved to disk + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + sender_wallet.start_background_sync() + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) + stop_with_wrong_inputs(sender_wallet, 'wrong_password') + sender_wallet.stop_background_sync(wallet_password = 'test_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Close wallet while background syncing, then reopen + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + sender_wallet.start_background_sync() + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) + sender_wallet.close_wallet() + open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') + sender_wallet.open_wallet('test1', password = 'test_password') + # It should reopen with spend key loaded and correctly scan all transfers + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Close wallet while syncing normally, then reopen + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + sender_wallet.close_wallet() + open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') + sender_wallet.open_wallet('test1', password = 'test_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Create background cache using custom password, then use it to sync, then reopen main wallet + for background_cache_password in ['background_password', '']: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.setup_background_sync(background_sync_type = custom_password, wallet_password = 'test_password', background_cache_password = background_cache_password) + assert util_resources.file_exists('test1.background') + assert util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + open_with_wrong_password(sender_wallet, 'test1.background', 'test_password') + sender_wallet.open_wallet('test1.background', password = background_cache_password) + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) + sender_wallet.close_wallet() + sender_wallet.open_wallet('test1', password = 'test_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Check that main wallet keeps background cache encrypted with custom password in sync + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = 'background_password') + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + sender_wallet.close_wallet() + sender_wallet.open_wallet('test1.background', password = 'background_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Try using wallet password as custom background password + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + same_password = False + try: sender_wallet.setup_background_sync(background_sync_type = custom_password, wallet_password = 'test_password', background_cache_password = 'test_password') + except: same_password = True + assert same_password + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + + # Turn off background sync + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + if background_sync_type == custom_password: + assert util_resources.file_exists('test1.background') + assert util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + assert util_resources.file_exists('test1.background') + assert util_resources.file_exists('test1.background.keys') + else: + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.open_wallet('test1', password = 'test_password') + sender_wallet.setup_background_sync(background_sync_type = sender_wallet.background_sync_options.off, wallet_password = 'test_password') + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.open_wallet('test1', password = 'test_password') + + # Sanity check against outgoing wallet restored at height 0 + sender_wallet.close_wallet() + sender_wallet.restore_deterministic_wallet(seed = seeds[0], restore_height = 0) + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + print('Checking background sync on incoming wallet') + receiver_wallet.setup_background_sync(background_sync_type = reuse_password) + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + transfers = receiver_wallet.get_transfers() + assert 'pending' not in transfers or len(transfers.pending) == 0 + assert 'pool' not in transfers or len (transfers.pool) == 0 + assert len(transfers['in']) == in_len + 1 + tx = [x for x in transfers['in'] if x.txid == txid] + assert len(tx) == 1 + tx = tx[0] + assert tx.amount == amount - fee + assert tx.fee == fee + incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') + assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image == '' and not x.spent]) == 1 + assert receiver_wallet.get_balance().balance == expected_receiver_balance + + # Restore and check background syncing incoming wallet + restore_wallet(receiver_wallet, seeds[1]) + receiver_wallet.setup_background_sync(background_sync_type = reuse_password) + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + if 'out' in transfers: + for i, out_tx in enumerate(transfers.out): + if 'destinations' in out_tx: + del transfers.out[i]['destinations'] # destinations are not expected after wallet restore + background_bal = receiver_wallet.get_balance().balance + assert background_bal >= expected_receiver_balance + background_transfers = receiver_wallet.get_transfers() + assert 'out' not in background_transfers or len(background_transfers.out) == 0 + assert 'in' in background_transfers and len(background_transfers['in']) > 0 + background_incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') + assert len(background_incoming_transfers) == len(incoming_transfers) + assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 + assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == txid]) == 1 + + # Stop background sync and check transfers update correctly + receiver_wallet.stop_background_sync(wallet_password = '', seed = seeds[1]) + diff_transfers(receiver_wallet.get_transfers(), transfers) + incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') + assert len(background_incoming_transfers) == len(incoming_transfers) + assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image != '' and not x.spent]) == 1 + assert receiver_wallet.get_balance().balance == expected_receiver_balance + + # Check a fresh incoming wallet with wallet files saved to disk and encrypted with password + restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') + receiver_wallet.setup_background_sync(background_sync_type = reuse_password, wallet_password = 'test_password') + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + assert_correct_transfers(receiver_wallet, background_transfers, background_incoming_transfers, background_bal) + stop_with_wrong_inputs(receiver_wallet, 'wrong_password') + receiver_wallet.stop_background_sync(wallet_password = 'test_password') + assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) + + # Close receiver's wallet while background sync is enabled then reopen + restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') + receiver_wallet.setup_background_sync(background_sync_type = reuse_password, wallet_password = 'test_password') + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + diff_transfers(receiver_wallet.get_transfers(), background_transfers) + diff_incoming_transfers(receiver_wallet.incoming_transfers(transfer_type = 'all'), background_incoming_transfers) + assert receiver_wallet.get_balance().balance == background_bal + receiver_wallet.close_wallet() + receiver_wallet.open_wallet('test2', password = 'test_password') + # It should reopen with spend key loaded and correctly scan all transfers + assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) + + # Sanity check against incoming wallet restored at height 0 + receiver_wallet.close_wallet() + receiver_wallet.restore_deterministic_wallet(seed = seeds[1], restore_height = 0) + receiver_wallet.refresh() + assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) + + # Clean up + util_resources.remove_wallet_files('test1') + util_resources.remove_wallet_files('test2') + for i in range(2): + self.wallet[i].close_wallet() + self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) + + def check_background_sync_reorg_recovery(self): + daemon = Daemon() + + print('Testing background sync reorg recovery') + + # Disconnect daemon from peers + daemon.out_peers(0) + + # Background sync type options + sender_wallet = self.wallet[0] + reuse_password = sender_wallet.background_sync_options.reuse_password + custom_password = sender_wallet.background_sync_options.custom_password + + for background_sync_type in [reuse_password, custom_password]: + # Set up wallet saved to disk + sender_wallet.close_wallet() + util_resources.remove_wallet_files('test1') + sender_wallet.restore_deterministic_wallet(seed = seeds[0], filename = 'test1', password = '') + sender_wallet.auto_refresh(enable = False) + sender_wallet.refresh() + sender_starting_balance = sender_wallet.get_balance().balance + + # Send tx and mine a block + amount = 1000000000000 + assert sender_starting_balance > amount + dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': amount} + res = sender_wallet.transfer([dst]) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + + daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) + + # Make sure the wallet can see the tx + sender_wallet.refresh() + transfers = sender_wallet.get_transfers() + assert 'pool' not in transfers or len (transfers.pool) == 0 + tx = [x for x in transfers.out if x.txid == txid] + assert len(tx) == 1 + tx = tx[0] + assert sender_wallet.get_balance().balance < (sender_starting_balance - amount) + + # Pop the block while background syncing + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = '', background_cache_password = background_cache_password) + sender_wallet.start_background_sync() + daemon.pop_blocks(1) + daemon.flush_txpool() + + daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) + + # Make sure the wallet can no longer see the tx + sender_wallet.refresh() + sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) + transfers = sender_wallet.get_transfers() + no_tx = [x for x in transfers.out if x.txid == txid] + assert len(no_tx) == 0 + assert sender_wallet.get_balance().balance == sender_starting_balance + + # Clean up + daemon.out_peers(12) + util_resources.remove_wallet_files('test1') + self.wallet[0].close_wallet() + self.wallet[0].restore_deterministic_wallet(seed = seeds[0]) + if __name__ == '__main__': TransferTest().run_test() |