mirror of
https://github.com/monero-project/monero.git
synced 2024-12-27 01:59:22 -05:00
e71c8bf190
- When background syncing, the wallet wipes the spend key from memory and processes all new transactions. The wallet saves all receives, spends, and "plausible" spends of receives the wallet does not know key images for. - When background sync disabled, the wallet processes all background synced txs and then clears the background sync cache. - Adding "plausible" spends to the background sync cache ensures that the wallet does not need to query the daemon to see if any received outputs were spent while background sync was enabled. This would harm privacy especially for users of 3rd party daemons. - To enable the feature in the CLI wallet, the user can set background-sync to reuse-wallet-password or custom-background-password and the wallet automatically syncs in the background when the wallet locks, then processes all background synced txs when the wallet is unlocked. - The custom-background-password option enables the user to open a distinct background wallet that only has a view key saved and can be opened/closed/synced separately from the main wallet. When the main wallet opens, it processes the background wallet's cache. - To enable the feature in the RPC wallet, there is a new `/setup_background_sync` endpoint. - HW, multsig and view-only wallets cannot background sync.
1555 lines
74 KiB
Python
Executable File
1555 lines
74 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2019-2023, The Monero Project
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without modification, are
|
|
# permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
# conditions and the following disclaimer.
|
|
#
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
# of conditions and the following disclaimer in the documentation and/or other
|
|
# materials provided with the distribution.
|
|
#
|
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
# used to endorse or promote products derived from this software without specific
|
|
# prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
from __future__ import print_function
|
|
import json
|
|
import util_resources
|
|
import pprint
|
|
from deepdiff import DeepDiff
|
|
pp = pprint.PrettyPrinter(indent=2)
|
|
|
|
"""Test simple transfers
|
|
"""
|
|
|
|
from framework.daemon import Daemon
|
|
from framework.wallet import Wallet
|
|
|
|
seeds = [
|
|
'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
|
|
'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
|
|
'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()
|
|
self.create()
|
|
self.mine()
|
|
self.transfer()
|
|
self.check_get_bulk_payments()
|
|
self.check_get_payments()
|
|
self.check_double_spend_detection()
|
|
self.sweep_dust()
|
|
self.sweep_single()
|
|
self.check_destinations()
|
|
self.check_tx_notes()
|
|
self.check_rescan()
|
|
self.check_is_key_image_spent()
|
|
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')
|
|
daemon = Daemon()
|
|
res = daemon.get_height()
|
|
daemon.pop_blocks(res.height - 1)
|
|
daemon.flush_txpool()
|
|
|
|
def create(self):
|
|
print('Creating wallets')
|
|
self.wallet = [None] * len(seeds)
|
|
for i in range(len(seeds)):
|
|
self.wallet[i] = Wallet(idx = i)
|
|
# close the wallet if any, will throw if none is loaded
|
|
try: self.wallet[i].close_wallet()
|
|
except: pass
|
|
res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
|
|
|
|
def mine(self):
|
|
print("Mining some blocks")
|
|
daemon = Daemon()
|
|
|
|
res = daemon.get_info()
|
|
height = res.height
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 100)
|
|
for i in range(len(self.wallet)):
|
|
self.wallet[i].refresh()
|
|
res = self.wallet[i].get_height()
|
|
assert res.height == height + 100
|
|
|
|
def transfer(self):
|
|
daemon = Daemon()
|
|
|
|
print("Creating transfer to self")
|
|
|
|
dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
|
payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'
|
|
|
|
start_balances = [0] * len(self.wallet)
|
|
running_balances = [0] * len(self.wallet)
|
|
for i in range(len(self.wallet)):
|
|
res = self.wallet[i].get_balance()
|
|
start_balances[i] = res.balance
|
|
running_balances[i] = res.balance
|
|
assert res.unlocked_balance <= res.balance
|
|
if i == 0:
|
|
assert res.blocks_to_unlock == 59 # we've been mining to it
|
|
else:
|
|
assert res.blocks_to_unlock == 0
|
|
|
|
print ('Checking short payment IDs cannot be used when not in an integrated address')
|
|
ok = False
|
|
try: self.wallet[0].transfer([dst], ring_size = 16, payment_id = '1234567812345678', get_tx_key = False)
|
|
except: ok = True
|
|
assert ok
|
|
|
|
print ('Checking long payment IDs are rejected')
|
|
ok = False
|
|
try: self.wallet[0].transfer([dst], ring_size = 16, payment_id = payment_id, get_tx_key = False, get_tx_hex = True)
|
|
except: ok = True
|
|
assert ok
|
|
|
|
print ('Checking empty destination is rejected')
|
|
ok = False
|
|
try: self.wallet[0].transfer([], ring_size = 16, get_tx_key = False)
|
|
except: ok = True
|
|
assert ok
|
|
|
|
res = self.wallet[0].transfer([dst], ring_size = 16, get_tx_key = False, get_tx_hex = True)
|
|
assert len(res.tx_hash) == 32*2
|
|
txid = res.tx_hash
|
|
assert len(res.tx_key) == 0
|
|
assert res.amount > 0
|
|
amount = res.amount
|
|
assert res.fee > 0
|
|
fee = res.fee
|
|
assert len(res.tx_blob) > 0
|
|
tx_weight = res.weight
|
|
assert len(res.tx_metadata) == 0
|
|
assert len(res.multisig_txset) == 0
|
|
assert len(res.unsigned_txset) == 0
|
|
unsigned_txset = res.unsigned_txset
|
|
|
|
res = daemon.get_fee_estimate(10)
|
|
assert res.fee > 0
|
|
assert res.quantization_mask > 0
|
|
expected_fee = (res.fee * 1 * tx_weight + res.quantization_mask - 1) // res.quantization_mask * res.quantization_mask
|
|
assert abs(1 - fee / expected_fee) < 0.01
|
|
|
|
self.wallet[0].refresh()
|
|
|
|
res = daemon.get_info()
|
|
height = res.height
|
|
|
|
res = self.wallet[0].get_transfers()
|
|
assert len(res['in']) == height - 1 # coinbases
|
|
assert not 'out' in res or len(res.out) == 0 # not mined yet
|
|
assert len(res.pending) == 1
|
|
assert not 'pool' in res or len(res.pool) == 0
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
for e in res['in']:
|
|
assert e.type == 'block'
|
|
e = res.pending[0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ['', '0000000000000000']
|
|
assert e.type == 'pending'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
|
assert e.double_spend_seen == False
|
|
assert not 'confirmations' in e or e.confirmations == 0
|
|
|
|
running_balances[0] -= fee
|
|
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
res = daemon.getlastblockheader()
|
|
running_balances[0] += res.block_header.reward
|
|
self.wallet[0].refresh()
|
|
|
|
res = self.wallet[0].get_transfers()
|
|
assert len(res['in']) == height # coinbases
|
|
assert len(res.out) == 1 # not mined yet
|
|
assert not 'pending' in res or len(res.pending) == 0
|
|
assert not 'pool' in res or len(res.pool) == 0
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
for e in res['in']:
|
|
assert e.type == 'block'
|
|
e = res.out[0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ['', '0000000000000000']
|
|
assert e.type == 'out'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
|
assert e.double_spend_seen == False
|
|
assert e.confirmations == 1
|
|
|
|
res = self.wallet[0].get_height()
|
|
wallet_height = res.height
|
|
res = self.wallet[0].get_transfer_by_txid(txid)
|
|
assert len(res.transfers) == 1
|
|
assert res.transfers[0] == res.transfer
|
|
t = res.transfer
|
|
assert t.txid == txid
|
|
assert t.payment_id in ['', '0000000000000000']
|
|
assert t.height == wallet_height - 1
|
|
assert t.timestamp > 0
|
|
assert t.amount == 0 # to self, so it's just "pay a fee" really
|
|
assert t.fee == fee
|
|
assert t.note == ''
|
|
assert len(t.destinations) == 1
|
|
assert t.destinations[0] == {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
|
assert t.type == 'out'
|
|
assert t.unlock_time == 0
|
|
assert t.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
|
assert t.double_spend_seen == False
|
|
assert t.confirmations == 1
|
|
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
print("Creating transfer to another, manual relay")
|
|
|
|
dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1000000000000}
|
|
res = self.wallet[0].transfer([dst], ring_size = 16, get_tx_key = True, do_not_relay = True, get_tx_hex = True)
|
|
assert len(res.tx_hash) == 32*2
|
|
txid = res.tx_hash
|
|
assert len(res.tx_key) == 32*2
|
|
assert res.amount == 1000000000000
|
|
amount = res.amount
|
|
assert res.fee > 0
|
|
fee = res.fee
|
|
assert len(res.tx_blob) > 0
|
|
assert len(res.tx_metadata) == 0
|
|
assert len(res.multisig_txset) == 0
|
|
assert len(res.unsigned_txset) == 0
|
|
tx_blob = res.tx_blob
|
|
|
|
res = daemon.send_raw_transaction(tx_blob)
|
|
assert res.not_relayed == False
|
|
assert res.low_mixin == False
|
|
assert res.double_spend == False
|
|
assert res.invalid_input == False
|
|
assert res.invalid_output == False
|
|
assert res.too_big == False
|
|
assert res.overspend == False
|
|
assert res.fee_too_low == False
|
|
|
|
self.wallet[0].refresh()
|
|
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
self.wallet[1].refresh()
|
|
|
|
res = self.wallet[1].get_transfers()
|
|
assert not 'in' in res or len(res['in']) == 0
|
|
assert not 'out' in res or len(res.out) == 0
|
|
assert not 'pending' in res or len(res.pending) == 0
|
|
assert len(res.pool) == 1
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
e = res.pool[0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ["", "0000000000000000"] # long payment IDs are now ignored
|
|
assert e.type == 'pool'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
|
assert e.double_spend_seen == False
|
|
assert not 'confirmations' in e or e.confirmations == 0
|
|
assert e.amount == amount
|
|
assert e.fee == fee
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
res = daemon.getlastblockheader()
|
|
running_balances[0] -= 1000000000000 + fee
|
|
running_balances[0] += res.block_header.reward
|
|
self.wallet[1].refresh()
|
|
running_balances[1] += 1000000000000
|
|
|
|
res = self.wallet[1].get_transfers()
|
|
assert len(res['in']) == 1
|
|
assert not 'out' in res or len(res.out) == 0
|
|
assert not 'pending' in res or len(res.pending) == 0
|
|
assert not 'pool' in res or len(res.pool) == 0
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
e = res['in'][0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ["", "0000000000000000"] # long payment IDs are now ignored
|
|
assert e.type == 'in'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
|
assert e.double_spend_seen == False
|
|
assert e.confirmations == 1
|
|
assert e.amount == amount
|
|
assert e.fee == fee
|
|
|
|
res = self.wallet[1].get_balance()
|
|
assert res.balance == running_balances[1]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 9
|
|
|
|
print('Creating multi out transfer')
|
|
|
|
self.wallet[0].refresh()
|
|
|
|
dst0 = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
|
dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000000}
|
|
dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000}
|
|
res = self.wallet[0].transfer([dst0, dst1, dst2], ring_size = 16, get_tx_key = True)
|
|
assert len(res.tx_hash) == 32*2
|
|
txid = res.tx_hash
|
|
assert len(res.tx_key) == 32*2
|
|
assert res.amount == 1000000000000 + 1100000000000 + 1200000000000
|
|
amount = res.amount
|
|
assert res.fee > 0
|
|
fee = res.fee
|
|
assert len(res.tx_blob) == 0
|
|
assert len(res.tx_metadata) == 0
|
|
assert len(res.multisig_txset) == 0
|
|
assert len(res.unsigned_txset) == 0
|
|
unsigned_txset = res.unsigned_txset
|
|
|
|
running_balances[0] -= 1100000000000 + 1200000000000 + fee
|
|
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
res = daemon.getlastblockheader()
|
|
running_balances[0] += res.block_header.reward
|
|
running_balances[1] += 1100000000000
|
|
running_balances[2] += 1200000000000
|
|
self.wallet[0].refresh()
|
|
|
|
res = self.wallet[0].get_transfers()
|
|
assert len(res['in']) == height + 2
|
|
assert len(res.out) == 3
|
|
assert not 'pending' in res or len(res.pending) == 0
|
|
assert not 'pool' in res or len(res.pool) == 1
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
e = [o for o in res.out if o.txid == txid]
|
|
assert len(e) == 1
|
|
e = e[0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ["", "0000000000000000"] # long payment IDs are now ignored
|
|
assert e.type == 'out'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
|
assert e.double_spend_seen == False
|
|
assert e.confirmations == 1
|
|
|
|
assert e.amount == amount
|
|
assert e.fee == fee
|
|
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
self.wallet[1].refresh()
|
|
res = self.wallet[1].get_transfers()
|
|
assert len(res['in']) == 2
|
|
assert not 'out' in res or len(res.out) == 0
|
|
assert not 'pending' in res or len(res.pending) == 0
|
|
assert not 'pool' in res or len(res.pool) == 0
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
e = [o for o in res['in'] if o.txid == txid]
|
|
assert len(e) == 1
|
|
e = e[0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ["", "0000000000000000"] # long payment IDs are now ignored
|
|
assert e.type == 'in'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
|
|
assert e.double_spend_seen == False
|
|
assert e.confirmations == 1
|
|
assert e.amount == 1100000000000
|
|
assert e.fee == fee
|
|
|
|
res = self.wallet[1].get_balance()
|
|
assert res.balance == running_balances[1]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 9
|
|
|
|
self.wallet[2].refresh()
|
|
res = self.wallet[2].get_transfers()
|
|
assert len(res['in']) == 1
|
|
assert not 'out' in res or len(res.out) == 0
|
|
assert not 'pending' in res or len(res.pending) == 0
|
|
assert not 'pool' in res or len(res.pool) == 0
|
|
assert not 'failed' in res or len(res.failed) == 0
|
|
e = [o for o in res['in'] if o.txid == txid]
|
|
assert len(e) == 1
|
|
e = e[0]
|
|
assert e.txid == txid
|
|
assert e.payment_id in ["", "0000000000000000"] # long payment IDs are now ignored
|
|
assert e.type == 'in'
|
|
assert e.unlock_time == 0
|
|
assert e.subaddr_index.major == 0
|
|
assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
|
|
assert e.address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK'
|
|
assert e.double_spend_seen == False
|
|
assert e.confirmations == 1
|
|
assert e.amount == 1200000000000
|
|
assert e.fee == fee
|
|
|
|
res = self.wallet[2].get_balance()
|
|
assert res.balance == running_balances[2]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 9
|
|
|
|
print('Sending to integrated address')
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
i_pid = '1111111122222222'
|
|
res = self.wallet[0].make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid)
|
|
i_address = res.integrated_address
|
|
res = self.wallet[0].transfer([{'address': i_address, 'amount': 200000000}])
|
|
assert len(res.tx_hash) == 32*2
|
|
i_txid = res.tx_hash
|
|
assert len(res.tx_key) == 32*2
|
|
assert res.amount == 200000000
|
|
i_amount = res.amount
|
|
assert res.fee > 0
|
|
fee = res.fee
|
|
assert len(res.tx_blob) == 0
|
|
assert len(res.tx_metadata) == 0
|
|
assert len(res.multisig_txset) == 0
|
|
assert len(res.unsigned_txset) == 0
|
|
|
|
running_balances[0] -= 200000000 + fee
|
|
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
res = daemon.getlastblockheader()
|
|
running_balances[0] += res.block_header.reward
|
|
running_balances[1] += 200000000
|
|
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
self.wallet[1].refresh()
|
|
res = self.wallet[1].get_balance()
|
|
assert res.balance == running_balances[1]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 9
|
|
|
|
self.wallet[2].refresh()
|
|
res = self.wallet[2].get_balance()
|
|
assert res.balance == running_balances[2]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 8
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
res = daemon.getlastblockheader()
|
|
running_balances[0] += res.block_header.reward
|
|
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == running_balances[0]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 59
|
|
|
|
self.wallet[1].refresh()
|
|
res = self.wallet[1].get_balance()
|
|
assert res.balance == running_balances[1]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 8
|
|
|
|
self.wallet[2].refresh()
|
|
res = self.wallet[2].get_balance()
|
|
assert res.balance == running_balances[2]
|
|
assert res.unlocked_balance <= res.balance
|
|
assert res.blocks_to_unlock == 7
|
|
|
|
|
|
def check_get_bulk_payments(self):
|
|
print('Checking get_bulk_payments')
|
|
|
|
daemon = Daemon()
|
|
res = daemon.get_info()
|
|
height = res.height
|
|
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_bulk_payments()
|
|
assert len(res.payments) >= 83 # at least 83 coinbases
|
|
res = self.wallet[0].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'])
|
|
assert 'payments' not in res or len(res.payments) == 0
|
|
res = self.wallet[0].get_bulk_payments(min_block_height = height)
|
|
assert 'payments' not in res or len(res.payments) == 0
|
|
res = self.wallet[0].get_bulk_payments(min_block_height = height - 40)
|
|
assert len(res.payments) >= 39 # coinbases
|
|
|
|
self.wallet[1].refresh()
|
|
res = self.wallet[1].get_bulk_payments()
|
|
assert len(res.payments) >= 3 # two txes to standard address were sent, plus one to integrated address
|
|
res = self.wallet[1].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'])
|
|
assert not 'payments' in res or len(res.payments) == 0 # long payment IDs are now ignored on receipt
|
|
res = self.wallet[1].get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'])
|
|
assert 'payments' not in res or len(res.payments) == 0 # none with that payment id
|
|
res = self.wallet[1].get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48])
|
|
assert len(res.payments) >= 1 # one tx to integrated address
|
|
|
|
self.wallet[2].refresh()
|
|
res = self.wallet[2].get_bulk_payments()
|
|
assert len(res.payments) >= 1 # one tx was sent
|
|
res = self.wallet[2].get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64])
|
|
assert not 'payments' in res or len(res.payments) == 0 # long payment IDs are now ignored
|
|
|
|
res = self.wallet[1].get_bulk_payments(["1111111122222222"])
|
|
assert len(res.payments) >= 1 # we have one of these
|
|
|
|
def check_get_payments(self):
|
|
print('Checking get_payments')
|
|
|
|
daemon = Daemon()
|
|
res = daemon.get_info()
|
|
height = res.height
|
|
|
|
self.wallet[0].refresh()
|
|
self.wallet[1].refresh()
|
|
|
|
res = self.wallet[0].get_payments('1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde')
|
|
assert 'payments' not in res or len(res.payments) == 0
|
|
|
|
res = self.wallet[1].get_payments('1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde')
|
|
assert 'payments' not in res or len(res.payments) == 0
|
|
|
|
res = self.wallet[1].get_payments('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
|
|
assert 'payments' not in res or len(res.payments) == 0
|
|
|
|
res = self.wallet[1].get_payments(payment_id = '1111111122222222' + '0'*48)
|
|
assert len(res.payments) >= 1 # one tx to integrated address
|
|
|
|
def check_double_spend_detection(self):
|
|
print('Checking double spend detection')
|
|
txes = [[None, None], [None, None]]
|
|
for i in range(2):
|
|
self.wallet[0].restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted')
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
unlocked_balance = res.unlocked_balance
|
|
res = self.wallet[0].sweep_all(address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', do_not_relay = True, get_tx_hex = True)
|
|
assert len(res.tx_hash_list) == 1
|
|
assert len(res.tx_hash_list[0]) == 32*2
|
|
txes[i][0] = res.tx_hash_list[0]
|
|
assert len(res.fee_list) == 1
|
|
assert res.fee_list[0] > 0
|
|
assert len(res.amount_list) == 1
|
|
assert res.amount_list[0] == unlocked_balance - res.fee_list[0]
|
|
assert len(res.tx_blob_list) > 0
|
|
assert len(res.tx_blob_list[0]) > 0
|
|
assert not 'tx_metadata_list' in res or len(res.tx_metadata_list) == 0
|
|
assert not 'multisig_txset' in res or len(res.multisig_txset) == 0
|
|
assert not 'unsigned_txset' in res or len(res.unsigned_txset) == 0
|
|
assert len(res.tx_blob_list) == 1
|
|
txes[i][1] = res.tx_blob_list[0]
|
|
|
|
daemon = Daemon()
|
|
res = daemon.send_raw_transaction(txes[0][1])
|
|
assert res.not_relayed == False
|
|
assert res.low_mixin == False
|
|
assert res.double_spend == False
|
|
assert res.invalid_input == False
|
|
assert res.invalid_output == False
|
|
assert res.too_big == False
|
|
assert res.overspend == False
|
|
assert res.fee_too_low == False
|
|
|
|
res = daemon.get_transactions([txes[0][0]])
|
|
assert len(res.txs) >= 1
|
|
tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0]
|
|
assert tx.in_pool
|
|
assert not tx.double_spend_seen
|
|
|
|
res = daemon.send_raw_transaction(txes[1][1])
|
|
assert res.not_relayed == False
|
|
assert res.low_mixin == False
|
|
assert res.double_spend == True
|
|
assert res.invalid_input == False
|
|
assert res.invalid_output == False
|
|
assert res.too_big == False
|
|
assert res.overspend == False
|
|
assert res.fee_too_low == False
|
|
assert res.too_few_outputs == False
|
|
|
|
res = daemon.get_transactions([txes[0][0]])
|
|
assert len(res.txs) >= 1
|
|
tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0]
|
|
assert tx.in_pool
|
|
assert tx.double_spend_seen
|
|
|
|
def sweep_dust(self):
|
|
print("Sweeping dust")
|
|
daemon = Daemon()
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].sweep_dust()
|
|
assert not 'tx_hash_list' in res or len(res.tx_hash_list) == 0 # there's just one, but it cannot meet the fee
|
|
|
|
def sweep_single(self):
|
|
daemon = Daemon()
|
|
|
|
print("Sending single output")
|
|
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'available')
|
|
for t in res.transfers:
|
|
assert not t.spent
|
|
assert len(res.transfers) > 8 # we mined a lot
|
|
index = 8
|
|
assert not res.transfers[index].spent
|
|
assert res.transfers[index].amount > 0
|
|
ki = res.transfers[index].key_image
|
|
amount = res.transfers[index].amount
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 10) # ensure unlocked
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
balance = res.balance
|
|
res = daemon.is_key_image_spent([ki])
|
|
assert len(res.spent_status) == 1
|
|
assert res.spent_status[0] == 0
|
|
res = self.wallet[0].sweep_single('44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', key_image = ki)
|
|
assert len(res.tx_hash) == 64
|
|
tx_hash = res.tx_hash
|
|
res = daemon.is_key_image_spent([ki])
|
|
assert len(res.spent_status) == 1
|
|
assert res.spent_status[0] == 2
|
|
daemon.generateblocks('44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 1)
|
|
res = daemon.is_key_image_spent([ki])
|
|
assert len(res.spent_status) == 1
|
|
assert res.spent_status[0] == 1
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
new_balance = res.balance
|
|
res = daemon.get_transactions([tx_hash], decode_as_json = True)
|
|
assert len(res.txs) == 1
|
|
tx = res.txs[0]
|
|
assert tx.tx_hash == tx_hash
|
|
assert not tx.in_pool
|
|
assert len(tx.as_json) > 0
|
|
try:
|
|
j = json.loads(tx.as_json)
|
|
except:
|
|
j = None
|
|
assert j
|
|
assert new_balance == balance - amount
|
|
assert len(j['vin']) == 1
|
|
assert j['vin'][0]['key']['k_image'] == ki
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'available')
|
|
assert len([t for t in res.transfers if t.key_image == ki]) == 0
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'unavailable')
|
|
assert len([t for t in res.transfers if t.key_image == ki]) == 1
|
|
|
|
def check_destinations(self):
|
|
daemon = Daemon()
|
|
|
|
print("Checking transaction destinations")
|
|
|
|
dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
|
res = self.wallet[0].transfer([dst])
|
|
assert len(res.tx_hash) == 64
|
|
tx_hash = res.tx_hash
|
|
for i in range(2):
|
|
res = self.wallet[0].get_transfers(pending = True, out = True)
|
|
l = [x for x in (res.pending if i == 0 else res.out) if x.txid == tx_hash]
|
|
assert len(l) == 1
|
|
e = l[0]
|
|
assert len(e.destinations) == 1
|
|
assert e.destinations[0].amount == 1000000000000
|
|
assert e.destinations[0].address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
|
|
|
|
if i == 0:
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
self.wallet[0].refresh()
|
|
|
|
dst = {'address': '8AsN91rznfkBGTY8psSNkJBg9SZgxxGGRUhGwRptBhgr5XSQ1XzmA9m8QAnoxydecSh5aLJXdrgXwTDMMZ1AuXsN1EX5Mtm', 'amount': 1000000000000}
|
|
res = self.wallet[0].transfer([dst])
|
|
assert len(res.tx_hash) == 64
|
|
tx_hash = res.tx_hash
|
|
for i in range(2):
|
|
res = self.wallet[0].get_transfers(pending = True, out = True)
|
|
l = [x for x in (res.pending if i == 0 else res.out) if x.txid == tx_hash]
|
|
assert len(l) == 1
|
|
e = l[0]
|
|
assert len(e.destinations) == 1
|
|
assert e.destinations[0].amount == 1000000000000
|
|
assert e.destinations[0].address == '8AsN91rznfkBGTY8psSNkJBg9SZgxxGGRUhGwRptBhgr5XSQ1XzmA9m8QAnoxydecSh5aLJXdrgXwTDMMZ1AuXsN1EX5Mtm'
|
|
|
|
if i == 0:
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
self.wallet[0].refresh()
|
|
|
|
dst = {'address': '4BxSHvcgTwu25WooY4BVmgdcKwZu5EksVZSZkDd6ooxSVVqQ4ubxXkhLF6hEqtw96i9cf3cVfLw8UWe95bdDKfRQeYtPwLm1Jiw7AKt2LY', 'amount': 1000000000000}
|
|
res = self.wallet[0].transfer([dst])
|
|
assert len(res.tx_hash) == 64
|
|
tx_hash = res.tx_hash
|
|
for i in range(2):
|
|
res = self.wallet[0].get_transfers(pending = True, out = True)
|
|
l = [x for x in (res.pending if i == 0 else res.out) if x.txid == tx_hash]
|
|
assert len(l) == 1
|
|
e = l[0]
|
|
assert len(e.destinations) == 1
|
|
assert e.destinations[0].amount == 1000000000000
|
|
assert e.destinations[0].address == '4BxSHvcgTwu25WooY4BVmgdcKwZu5EksVZSZkDd6ooxSVVqQ4ubxXkhLF6hEqtw96i9cf3cVfLw8UWe95bdDKfRQeYtPwLm1Jiw7AKt2LY'
|
|
|
|
if i == 0:
|
|
daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
|
|
self.wallet[0].refresh()
|
|
|
|
def check_tx_notes(self):
|
|
daemon = Daemon()
|
|
|
|
print('Testing tx notes')
|
|
res = self.wallet[0].get_transfers()
|
|
assert len(res['in']) > 0
|
|
in_txid = res['in'][0].txid
|
|
assert len(res['out']) > 0
|
|
out_txid = res['out'][0].txid
|
|
res = self.wallet[0].get_tx_notes([in_txid, out_txid])
|
|
assert res.notes == ['', '']
|
|
res = self.wallet[0].set_tx_notes([in_txid, out_txid], ['in txid', 'out txid'])
|
|
res = self.wallet[0].get_tx_notes([in_txid, out_txid])
|
|
assert res.notes == ['in txid', 'out txid']
|
|
res = self.wallet[0].get_tx_notes([out_txid, in_txid])
|
|
assert res.notes == ['out txid', 'in txid']
|
|
|
|
def check_rescan(self):
|
|
daemon = Daemon()
|
|
|
|
print('Testing rescan_spent')
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
|
transfers = res.transfers
|
|
res = self.wallet[0].rescan_spent()
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
|
assert transfers == res.transfers
|
|
|
|
for hard in [False, True]:
|
|
print('Testing %s rescan_blockchain' % ('hard' if hard else 'soft'))
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
|
transfers = res.transfers
|
|
res = self.wallet[0].get_transfers()
|
|
t_in = res['in']
|
|
t_out = res.out
|
|
res = self.wallet[0].rescan_blockchain(hard = hard)
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
|
assert transfers == res.transfers
|
|
res = self.wallet[0].get_transfers()
|
|
assert t_in == res['in']
|
|
# some information can not be recovered for out txes
|
|
unrecoverable_fields = ['payment_id', 'destinations', 'note']
|
|
old_t_out = []
|
|
for x in t_out:
|
|
e = {}
|
|
for k in x.keys():
|
|
if not k in unrecoverable_fields:
|
|
e[k] = x[k]
|
|
old_t_out.append(e)
|
|
new_t_out = []
|
|
for x in res.out:
|
|
e = {}
|
|
for k in x.keys():
|
|
if not k in unrecoverable_fields:
|
|
e[k] = x[k]
|
|
new_t_out.append(e)
|
|
assert sorted(old_t_out, key = lambda k: k['txid']) == sorted(new_t_out, key = lambda k: k['txid'])
|
|
|
|
def check_is_key_image_spent(self):
|
|
daemon = Daemon()
|
|
|
|
print('Testing is_key_image_spent')
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
|
transfers = res.transfers
|
|
ki = [x.key_image for x in transfers]
|
|
expected = [1 if x.spent else 0 for x in transfers]
|
|
res = daemon.is_key_image_spent(ki)
|
|
assert res.spent_status == expected
|
|
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'available')
|
|
transfers = res.transfers
|
|
ki = [x.key_image for x in transfers]
|
|
expected = [0 for x in transfers]
|
|
res = daemon.is_key_image_spent(ki)
|
|
assert res.spent_status == expected
|
|
|
|
res = self.wallet[0].incoming_transfers(transfer_type = 'unavailable')
|
|
transfers = res.transfers
|
|
ki = [x.key_image for x in transfers]
|
|
expected = [1 for x in transfers]
|
|
res = daemon.is_key_image_spent(ki)
|
|
assert res.spent_status == expected
|
|
|
|
ki = [ki[-1]] * 5
|
|
expected = [1] * len(ki)
|
|
res = daemon.is_key_image_spent(ki)
|
|
assert res.spent_status == expected
|
|
|
|
ki = ['2'*64, '1'*64]
|
|
expected = [0, 0]
|
|
res = daemon.is_key_image_spent(ki)
|
|
assert res.spent_status == expected
|
|
|
|
def check_multiple_submissions(self):
|
|
daemon = Daemon()
|
|
|
|
print('Testing multiple submissions')
|
|
|
|
dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
|
|
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
balance = res.balance
|
|
|
|
res = self.wallet[0].transfer([dst], ring_size = 16, get_tx_key = False, get_tx_hex = False, get_tx_metadata = True)
|
|
tx_hex = res.tx_metadata
|
|
tx_fee = res.fee
|
|
res = self.wallet[0].relay_tx(tx_hex)
|
|
|
|
# submit again before mined
|
|
res = self.wallet[0].relay_tx(tx_hex)
|
|
daemon.generateblocks('44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 1)
|
|
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == balance - tx_fee
|
|
|
|
balance = res.balance
|
|
|
|
# submit again after mined
|
|
res = self.wallet[0].relay_tx(tx_hex)
|
|
daemon.generateblocks('44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 1)
|
|
|
|
self.wallet[0].refresh()
|
|
res = self.wallet[0].get_balance()
|
|
assert res.balance == balance
|
|
|
|
def check_scan_tx(self):
|
|
daemon = Daemon()
|
|
|
|
print('Testing scan_tx')
|
|
|
|
# set up sender_wallet
|
|
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.get_transfers()
|
|
out_len = 0 if 'out' not in res else len(res.out)
|
|
sender_starting_balance = sender_wallet.get_balance().balance
|
|
amount = 1000000000000
|
|
assert sender_starting_balance > amount
|
|
|
|
# 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 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': amount}
|
|
res = sender_wallet.transfer([dst])
|
|
assert len(res.tx_hash) == 32*2
|
|
txid = res.tx_hash
|
|
assert res.amount == amount
|
|
assert res.fee > 0
|
|
fee = res.fee
|
|
|
|
expected_sender_balance = sender_starting_balance - (amount + fee)
|
|
expected_receiver_balance = receiver_starting_balance + amount
|
|
|
|
test = 'Checking scan_tx on outgoing pool tx'
|
|
for attempt in range(2): # test re-scanning
|
|
print(test + ' (' + ('first attempt' if attempt == 0 else 're-scanning tx') + ')')
|
|
sender_wallet.scan_tx([txid])
|
|
res = sender_wallet.get_transfers()
|
|
assert 'pool' not in res or len(res.pool) == 0
|
|
if out_len == 0:
|
|
assert 'out' not in res
|
|
else:
|
|
assert len(res.out) == out_len
|
|
assert len(res.pending) == 1
|
|
tx = [x for x in res.pending if x.txid == txid]
|
|
assert len(tx) == 1
|
|
tx = tx[0]
|
|
assert tx.amount == amount
|
|
assert tx.fee == fee
|
|
assert len(tx.destinations) == 1
|
|
assert tx.destinations[0].amount == amount
|
|
assert tx.destinations[0].address == dst['address']
|
|
assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
|
|
test = 'Checking scan_tx on incoming pool tx'
|
|
for attempt in range(2): # test re-scanning
|
|
print(test + ' (' + ('first attempt' if attempt == 0 else 're-scanning tx') + ')')
|
|
receiver_wallet.scan_tx([txid])
|
|
res = receiver_wallet.get_transfers()
|
|
assert 'pending' not in res or len(res.pending) == 0
|
|
if in_len == 0:
|
|
assert 'in' not in res
|
|
else:
|
|
assert len(res['in']) == in_len
|
|
assert 'pool' in res and len(res.pool) == 1
|
|
tx = [x for x in res.pool if x.txid == txid]
|
|
assert len(tx) == 1
|
|
tx = tx[0]
|
|
assert tx.amount == amount
|
|
assert tx.fee == fee
|
|
assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
|
|
# mine the tx
|
|
height = daemon.generateblocks(dst['address'], 1).height
|
|
block_header = daemon.getblockheaderbyheight(height = height).block_header
|
|
miner_txid = block_header.miner_tx_hash
|
|
expected_receiver_balance += block_header.reward
|
|
|
|
print('Checking scan_tx on outgoing tx before refresh')
|
|
sender_wallet.scan_tx([txid])
|
|
res = sender_wallet.get_transfers()
|
|
assert 'pending' not in res or len(res.pending) == 0
|
|
assert 'pool' not in res or len (res.pool) == 0
|
|
assert len(res.out) == out_len + 1
|
|
tx = [x for x in res.out if x.txid == txid]
|
|
assert len(tx) == 1
|
|
tx = tx[0]
|
|
assert tx.amount == amount
|
|
assert tx.fee == fee
|
|
assert len(tx.destinations) == 1
|
|
assert tx.destinations[0].amount == amount
|
|
assert tx.destinations[0].address == dst['address']
|
|
assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
|
|
print('Checking scan_tx on outgoing tx after refresh')
|
|
sender_wallet.refresh()
|
|
sender_wallet.scan_tx([txid])
|
|
diff_transfers(sender_wallet.get_transfers(), res)
|
|
assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
|
|
print("Checking scan_tx on outgoing wallet's earliest tx")
|
|
earliest_height = height
|
|
earliest_txid = txid
|
|
for x in res['in']:
|
|
if x.height < earliest_height:
|
|
earliest_height = x.height
|
|
earliest_txid = x.txid
|
|
sender_wallet.scan_tx([earliest_txid])
|
|
diff_transfers(sender_wallet.get_transfers(), res)
|
|
assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
|
|
test = 'Checking scan_tx on outgoing wallet restored at current height'
|
|
for i, out_tx in enumerate(res.out):
|
|
if 'destinations' in out_tx:
|
|
del res.out[i]['destinations'] # destinations are not expected after wallet restore
|
|
out_txids = [x.txid for x in res.out]
|
|
in_txids = [x.txid for x in res['in']]
|
|
all_txs = out_txids + in_txids
|
|
for test_type in ["all txs", "incoming first", "duplicates within", "duplicates across"]:
|
|
print(test + ' (' + test_type + ')')
|
|
sender_wallet.close_wallet()
|
|
sender_wallet.restore_deterministic_wallet(seed = seeds[0], restore_height = height)
|
|
assert sender_wallet.get_transfers() == {}
|
|
if test_type == "all txs":
|
|
sender_wallet.scan_tx(all_txs)
|
|
elif test_type == "incoming first":
|
|
sender_wallet.scan_tx(in_txids)
|
|
sender_wallet.scan_tx(out_txids)
|
|
# TODO: test_type == "outgoing first"
|
|
elif test_type == "duplicates within":
|
|
sender_wallet.scan_tx(all_txs + all_txs)
|
|
elif test_type == "duplicates across":
|
|
sender_wallet.scan_tx(all_txs)
|
|
sender_wallet.scan_tx(all_txs)
|
|
else:
|
|
assert True == False
|
|
diff_transfers(sender_wallet.get_transfers(), res)
|
|
assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
|
|
print('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()
|
|
diff_transfers(sender_wallet.get_transfers(), res)
|
|
assert sender_wallet.get_balance().balance == expected_sender_balance
|
|
|
|
print('Checking scan_tx on incoming txs before refresh')
|
|
receiver_wallet.scan_tx([txid, miner_txid])
|
|
res = receiver_wallet.get_transfers()
|
|
assert 'pending' not in res or len(res.pending) == 0
|
|
assert 'pool' not in res or len (res.pool) == 0
|
|
assert len(res['in']) == in_len + 2
|
|
tx = [x for x in res['in'] if x.txid == txid]
|
|
assert len(tx) == 1
|
|
tx = tx[0]
|
|
assert tx.amount == amount
|
|
assert tx.fee == fee
|
|
assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
|
|
print('Checking scan_tx on incoming txs after refresh')
|
|
receiver_wallet.refresh()
|
|
receiver_wallet.scan_tx([txid, miner_txid])
|
|
diff_transfers(receiver_wallet.get_transfers(), res)
|
|
assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
|
|
print("Checking scan_tx on incoming wallet's earliest tx")
|
|
earliest_height = height
|
|
earliest_txid = txid
|
|
for x in res['in']:
|
|
if x.height < earliest_height:
|
|
earliest_height = x.height
|
|
earliest_txid = x.txid
|
|
receiver_wallet.scan_tx([earliest_txid])
|
|
diff_transfers(receiver_wallet.get_transfers(), res)
|
|
assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
|
|
print('Checking scan_tx on incoming wallet restored at current height')
|
|
txids = [x.txid for x in res['in']]
|
|
if 'out' in res:
|
|
txids = txids + [x.txid for x in res.out]
|
|
receiver_wallet.close_wallet()
|
|
receiver_wallet.restore_deterministic_wallet(seed = seeds[1], restore_height = height)
|
|
assert receiver_wallet.get_transfers() == {}
|
|
receiver_wallet.scan_tx(txids)
|
|
if 'out' in res:
|
|
for i, out_tx in enumerate(res.out):
|
|
if 'destinations' in out_tx:
|
|
del res.out[i]['destinations'] # destinations are not expected after wallet restore
|
|
diff_transfers(receiver_wallet.get_transfers(), res)
|
|
assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
|
|
print('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()
|
|
diff_transfers(receiver_wallet.get_transfers(), res)
|
|
assert receiver_wallet.get_balance().balance == expected_receiver_balance
|
|
|
|
def check_subtract_fee_from_outputs(self):
|
|
daemon = Daemon()
|
|
|
|
print('Testing fee-included transfers')
|
|
|
|
def inner_test_external_transfer(dsts, subtract_fee_from_outputs):
|
|
# refresh wallet and get balance
|
|
self.wallet[0].refresh()
|
|
balance1 = self.wallet[0].get_balance().balance
|
|
|
|
# Check that this transaction is possible with our current balance + other preconditions
|
|
dst_sum = sum(map(lambda x: x['amount'], dsts))
|
|
assert balance1 >= dst_sum
|
|
if subtract_fee_from_outputs:
|
|
assert max(subtract_fee_from_outputs) < len(dsts)
|
|
|
|
# transfer with subtractfeefrom=all
|
|
transfer_res = self.wallet[0].transfer(dsts, subtract_fee_from_outputs = subtract_fee_from_outputs, get_tx_metadata = True)
|
|
tx_hex = transfer_res.tx_metadata
|
|
tx_fee = transfer_res.fee
|
|
amount_spent = transfer_res.amount
|
|
amounts_by_dest = transfer_res.amounts_by_dest.amounts
|
|
|
|
# Assert that fee and amount spent to outputs adds up
|
|
assert tx_fee != 0
|
|
if subtract_fee_from_outputs:
|
|
assert tx_fee + amount_spent == dst_sum
|
|
else:
|
|
assert amount_spent == dst_sum
|
|
|
|
# Check the amounts by each destination that only the destinations set as subtractable
|
|
# got subtracted and that the subtracted dests are approximately correct
|
|
assert len(amounts_by_dest) == len(dsts) # if this fails... idk
|
|
for i in range(len(dsts)):
|
|
if i in subtract_fee_from_outputs: # dest is subtractable
|
|
approx_subtraction = tx_fee // len(subtract_fee_from_outputs)
|
|
assert amounts_by_dest[i] < dsts[i]['amount']
|
|
assert dsts[i]['amount'] - amounts_by_dest[i] - approx_subtraction <= 1
|
|
else:
|
|
assert amounts_by_dest[i] == dsts[i]['amount']
|
|
|
|
# relay tx and generate block (not to us, to simplify balance change calculations)
|
|
relay_res = self.wallet[0].relay_tx(tx_hex)
|
|
daemon.generateblocks('44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 1)
|
|
|
|
# refresh and get balance again
|
|
self.wallet[0].refresh()
|
|
balance2 = self.wallet[0].get_balance().balance
|
|
|
|
# Check that the wallet balance dropped by the correct amount
|
|
balance_drop = balance1 - balance2
|
|
if subtract_fee_from_outputs:
|
|
assert balance_drop == dst_sum
|
|
else:
|
|
assert balance_drop == dst_sum + tx_fee
|
|
|
|
dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000001}
|
|
dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000}
|
|
dst3 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1}
|
|
|
|
inner_test_external_transfer([dst1, dst2], [0, 1])
|
|
inner_test_external_transfer([dst1, dst2], [0])
|
|
inner_test_external_transfer([dst1, dst2], [1])
|
|
inner_test_external_transfer([dst1, dst2], [])
|
|
inner_test_external_transfer([dst1], [0])
|
|
inner_test_external_transfer([dst1], [])
|
|
inner_test_external_transfer([dst3], [])
|
|
try:
|
|
inner_test_external_transfer([dst1, dst3], [0, 1]) # Test subtractfeefrom if one of the outputs would underflow w/o good checks
|
|
raise ValueError('transfer request with tiny subtractable destination should have thrown')
|
|
except:
|
|
pass
|
|
|
|
# Check for JSONRPC error on bad index
|
|
try:
|
|
transfer_res = self.wallet[0].transfer([dst1], subtract_fee_from_outputs = [1])
|
|
raise ValueError('transfer request with index should have thrown')
|
|
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()
|