Changeset View
Changeset View
Standalone View
Standalone View
test/functional/wallet_avoidreuse.py
Show All 10 Lines | from test_framework.util import ( | ||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
connect_nodes, | connect_nodes, | ||||
) | ) | ||||
def reset_balance(node, discardaddr): | def reset_balance(node, discardaddr): | ||||
'''Throw away all owned coins by the node so it gets a balance of 0.''' | '''Throw away all owned coins by the node so it gets a balance of 0.''' | ||||
balance = node.getbalance(avoid_reuse=False) | balance = node.getbalance(avoid_reuse=False) | ||||
if balance > 0.5: | if balance > 500000: | ||||
node.sendtoaddress( | node.sendtoaddress( | ||||
address=discardaddr, | address=discardaddr, | ||||
amount=balance, | amount=balance, | ||||
subtractfeefromamount=True, | subtractfeefromamount=True, | ||||
avoid_reuse=False) | avoid_reuse=False) | ||||
def count_unspent(node): | def count_unspent(node): | ||||
Show All 24 Lines | |||||
def assert_unspent(node, total_count=None, total_sum=None, | def assert_unspent(node, total_count=None, total_sum=None, | ||||
reused_supported=None, reused_count=None, reused_sum=None): | reused_supported=None, reused_count=None, reused_sum=None): | ||||
'''Make assertions about a node's unspent output statistics''' | '''Make assertions about a node's unspent output statistics''' | ||||
stats = count_unspent(node) | stats = count_unspent(node) | ||||
if total_count is not None: | if total_count is not None: | ||||
assert_equal(stats["total"]["count"], total_count) | assert_equal(stats["total"]["count"], total_count) | ||||
if total_sum is not None: | if total_sum is not None: | ||||
assert_approx(stats["total"]["sum"], total_sum, 0.001) | assert_approx(stats["total"]["sum"], total_sum, 1000) | ||||
if reused_supported is not None: | if reused_supported is not None: | ||||
assert_equal(stats["reused"]["supported"], reused_supported) | assert_equal(stats["reused"]["supported"], reused_supported) | ||||
if reused_count is not None: | if reused_count is not None: | ||||
assert_equal(stats["reused"]["count"], reused_count) | assert_equal(stats["reused"]["count"], reused_count) | ||||
if reused_sum is not None: | if reused_sum is not None: | ||||
assert_approx(stats["reused"]["sum"], reused_sum, 0.001) | assert_approx(stats["reused"]["sum"], reused_sum, 0.001) | ||||
def assert_balances(node, mine): | def assert_balances(node, mine): | ||||
'''Make assertions about a node's getbalances output''' | '''Make assertions about a node's getbalances output''' | ||||
got = node.getbalances()["mine"] | got = node.getbalances()["mine"] | ||||
for k, v in mine.items(): | for k, v in mine.items(): | ||||
assert_approx(got[k], v, 0.001) | assert_approx(got[k], v, 1000) | ||||
class AvoidReuseTest(BitcoinTestFramework): | class AvoidReuseTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = False | self.setup_clean_chain = False | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
# This test isn't testing txn relay/timing, so set whitelist on the | # This test isn't testing txn relay/timing, so set whitelist on the | ||||
▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | def test_immutable(self): | ||||
self.nodes[1].unloadwallet(tempwallet) | self.nodes[1].unloadwallet(tempwallet) | ||||
def test_change_remains_change(self, node): | def test_change_remains_change(self, node): | ||||
self.log.info( | self.log.info( | ||||
"Test that change doesn't turn into non-change when spent") | "Test that change doesn't turn into non-change when spent") | ||||
reset_balance(node, node.getnewaddress()) | reset_balance(node, node.getnewaddress()) | ||||
addr = node.getnewaddress() | addr = node.getnewaddress() | ||||
txid = node.sendtoaddress(addr, 1) | txid = node.sendtoaddress(addr, 1000000) | ||||
out = node.listunspent(minconf=0, query_options={'minimumAmount': 2}) | out = node.listunspent( | ||||
minconf=0, query_options={ | |||||
'minimumAmount': 2000000}) | |||||
assert_equal(len(out), 1) | assert_equal(len(out), 1) | ||||
assert_equal(out[0]['txid'], txid) | assert_equal(out[0]['txid'], txid) | ||||
changeaddr = out[0]['address'] | changeaddr = out[0]['address'] | ||||
# Make sure it's starting out as change as expected | # Make sure it's starting out as change as expected | ||||
assert node.getaddressinfo(changeaddr)['ischange'] | assert node.getaddressinfo(changeaddr)['ischange'] | ||||
for logical_tx in node.listtransactions(): | for logical_tx in node.listtransactions(): | ||||
assert logical_tx.get('address') != changeaddr | assert logical_tx.get('address') != changeaddr | ||||
Show All 13 Lines | def test_sending_from_reused_address_without_avoid_reuse(self): | ||||
where it fails in test_sending_from_reused_address_fails. | where it fails in test_sending_from_reused_address_fails. | ||||
''' | ''' | ||||
self.log.info( | self.log.info( | ||||
"Test sending from reused address with avoid_reuse=false") | "Test sending from reused address with avoid_reuse=false") | ||||
fundaddr = self.nodes[1].getnewaddress() | fundaddr = self.nodes[1].getnewaddress() | ||||
retaddr = self.nodes[0].getnewaddress() | retaddr = self.nodes[0].getnewaddress() | ||||
self.nodes[0].sendtoaddress(fundaddr, 10) | self.nodes[0].sendtoaddress(fundaddr, 10000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# listunspent should show 1 single, unused 10 BCH output | # listunspent should show 1 single, unused 10 BCH output | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=1, | total_count=1, | ||||
total_sum=10, | total_sum=10000000, | ||||
reused_supported=True, | reused_supported=True, | ||||
reused_count=0) | reused_count=0) | ||||
# getbalances should show no used, 10 BCH trusted | # getbalances should show no used, 10 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10}) | assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10000000}) | ||||
# node 0 should not show a used entry, as it does not enable | # node 0 should not show a used entry, as it does not enable | ||||
# avoid_reuse | # avoid_reuse | ||||
assert("used" not in self.nodes[0].getbalances()["mine"]) | assert("used" not in self.nodes[0].getbalances()["mine"]) | ||||
self.nodes[1].sendtoaddress(retaddr, 5) | self.nodes[1].sendtoaddress(retaddr, 5000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# listunspent should show 1 single, unused 5 BCH output | # listunspent should show 1 single, unused 5 BCH output | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=1, | total_count=1, | ||||
total_sum=5, | total_sum=5000000, | ||||
reused_supported=True, | reused_supported=True, | ||||
reused_count=0) | reused_count=0) | ||||
# getbalances should show no used, 5 BCH trusted | # getbalances should show no used, 5 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) | assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) | ||||
self.nodes[0].sendtoaddress(fundaddr, 10) | self.nodes[0].sendtoaddress(fundaddr, 10000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# listunspent should show 2 total outputs (5, 10 BCH), one unused (5), | # listunspent should show 2 total outputs (5, 10 BCH), one unused (5), | ||||
# one reused (10) | # one reused (10) | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=2, | total_count=2, | ||||
total_sum=15, | total_sum=15000000, | ||||
reused_count=1, | reused_count=1, | ||||
reused_sum=10) | reused_sum=10000000) | ||||
# getbalances should show 10 used, 5 BCH trusted | # getbalances should show 10 used, 5 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5}) | assert_balances( | ||||
self.nodes[1], | |||||
mine={ | |||||
"used": 10000000, | |||||
"trusted": 5000000}) | |||||
self.nodes[1].sendtoaddress( | self.nodes[1].sendtoaddress( | ||||
address=retaddr, amount=10, avoid_reuse=False) | address=retaddr, amount=10000000, avoid_reuse=False) | ||||
# listunspent should show 1 total outputs (5 BCH), unused | # listunspent should show 1 total outputs (5 BCH), unused | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=1, | total_count=1, | ||||
total_sum=5, | total_sum=5000000, | ||||
reused_count=0) | reused_count=0) | ||||
# getbalances should show no used, 5 BCH trusted | # getbalances should show no used, 5 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) | assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) | ||||
# node 1 should now have about 5 BCH left (for both cases) | # node 1 should now have about 5 BCH left (for both cases) | ||||
assert_approx(self.nodes[1].getbalance(), 5, 0.001) | assert_approx(self.nodes[1].getbalance(), 5000000, 1000) | ||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001) | assert_approx( | ||||
self.nodes[1].getbalance( | |||||
avoid_reuse=False), | |||||
5000000, | |||||
1000) | |||||
def test_sending_from_reused_address_fails(self): | def test_sending_from_reused_address_fails(self): | ||||
''' | ''' | ||||
Test the simple case where [1] generates a new address A, then | Test the simple case where [1] generates a new address A, then | ||||
[0] sends 10 BCH to A. | [0] sends 10 BCH to A. | ||||
[1] spends 5 BCH from A. (leaving roughly 5 BCH useable) | [1] spends 5 BCH from A. (leaving roughly 5 BCH useable) | ||||
[0] sends 10 BCH to A again. | [0] sends 10 BCH to A again. | ||||
[1] tries to spend 10 BCH (fails; dirty). | [1] tries to spend 10 BCH (fails; dirty). | ||||
[1] tries to spend 4 BCH (succeeds; change address sufficient) | [1] tries to spend 4 BCH (succeeds; change address sufficient) | ||||
''' | ''' | ||||
self.log.info("Test sending from reused address fails") | self.log.info("Test sending from reused address fails") | ||||
fundaddr = self.nodes[1].getnewaddress(label="", address_type="legacy") | fundaddr = self.nodes[1].getnewaddress(label="", address_type="legacy") | ||||
retaddr = self.nodes[0].getnewaddress() | retaddr = self.nodes[0].getnewaddress() | ||||
self.nodes[0].sendtoaddress(fundaddr, 10) | self.nodes[0].sendtoaddress(fundaddr, 10000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# listunspent should show 1 single, unused 10 BCH output | # listunspent should show 1 single, unused 10 BCH output | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=1, | total_count=1, | ||||
total_sum=10, | total_sum=10000000, | ||||
reused_supported=True, | reused_supported=True, | ||||
reused_count=0) | reused_count=0) | ||||
# getbalances should show no used, 10 BCH trusted | # getbalances should show no used, 10 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10}) | assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10000000}) | ||||
self.nodes[1].sendtoaddress(retaddr, 5) | self.nodes[1].sendtoaddress(retaddr, 5000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# listunspent should show 1 single, unused 5 BCH output | # listunspent should show 1 single, unused 5 BCH output | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=1, | total_count=1, | ||||
total_sum=5, | total_sum=5000000, | ||||
reused_supported=True, | reused_supported=True, | ||||
reused_count=0) | reused_count=0) | ||||
# getbalances should show no used, 5 BCH trusted | # getbalances should show no used, 5 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) | assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) | ||||
# For the second send, we transmute it to a related single-key address | # For the second send, we transmute it to a related single-key address | ||||
# to make sure it's also detected as re-use | # to make sure it's also detected as re-use | ||||
# NB: this is not very useful for ABC, but we keep the new variable | # NB: this is not very useful for ABC, but we keep the new variable | ||||
# name for consistency. | # name for consistency. | ||||
new_fundaddr = fundaddr | new_fundaddr = fundaddr | ||||
self.nodes[0].sendtoaddress(new_fundaddr, 10) | self.nodes[0].sendtoaddress(new_fundaddr, 10000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# listunspent should show 2 total outputs (5, 10 BCH), one unused (5), | # listunspent should show 2 total outputs (5, 10 BCH), one unused (5), | ||||
# one reused (10) | # one reused (10) | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=2, | total_count=2, | ||||
total_sum=15, | total_sum=15000000, | ||||
reused_count=1, | reused_count=1, | ||||
reused_sum=10) | reused_sum=10000000) | ||||
# getbalances should show 10 used, 5 BCH trusted | # getbalances should show 10 used, 5 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5}) | assert_balances( | ||||
self.nodes[1], | |||||
mine={ | |||||
"used": 10000000, | |||||
"trusted": 5000000}) | |||||
# node 1 should now have a balance of 5 (no dirty) or 15 (including | # node 1 should now have a balance of 5 (no dirty) or 15 (including | ||||
# dirty) | # dirty) | ||||
assert_approx(self.nodes[1].getbalance(), 5, 0.001) | assert_approx(self.nodes[1].getbalance(), 5000000, 1000) | ||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001) | assert_approx( | ||||
self.nodes[1].getbalance( | |||||
avoid_reuse=False), | |||||
15000000, | |||||
1000) | |||||
assert_raises_rpc_error(-6, "Insufficient funds", | assert_raises_rpc_error(-6, "Insufficient funds", | ||||
self.nodes[1].sendtoaddress, retaddr, 10) | self.nodes[1].sendtoaddress, retaddr, 10000000) | ||||
self.nodes[1].sendtoaddress(retaddr, 4) | self.nodes[1].sendtoaddress(retaddr, 4000000) | ||||
# listunspent should show 2 total outputs (1, 10 BCH), one unused (1), | # listunspent should show 2 total outputs (1, 10 BCH), one unused (1), | ||||
# one reused (10) | # one reused (10) | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=2, | total_count=2, | ||||
total_sum=11, | total_sum=11000000, | ||||
reused_count=1, | reused_count=1, | ||||
reused_sum=10) | reused_sum=10000000) | ||||
# getbalances should show 10 used, 1 BCH trusted | # getbalances should show 10 used, 1 BCH trusted | ||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1}) | assert_balances( | ||||
self.nodes[1], | |||||
mine={ | |||||
"used": 10000000, | |||||
"trusted": 1000000}) | |||||
# node 1 should now have about 1 BCH left (no dirty) and 11 (including | # node 1 should now have about 1 BCH left (no dirty) and 11 (including | ||||
# dirty) | # dirty) | ||||
assert_approx(self.nodes[1].getbalance(), 1, 0.001) | assert_approx(self.nodes[1].getbalance(), 1000000, 1000) | ||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001) | assert_approx( | ||||
self.nodes[1].getbalance( | |||||
avoid_reuse=False), | |||||
11000000, | |||||
1000) | |||||
def test_getbalances_used(self): | def test_getbalances_used(self): | ||||
''' | ''' | ||||
getbalances and listunspent should pick up on reused addresses | getbalances and listunspent should pick up on reused addresses | ||||
immediately, even for address reusing outputs created before the first | immediately, even for address reusing outputs created before the first | ||||
transaction was spending from that address | transaction was spending from that address | ||||
''' | ''' | ||||
self.log.info("Test getbalances used category") | self.log.info("Test getbalances used category") | ||||
# node under test should be completely empty | # node under test should be completely empty | ||||
assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) | assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) | ||||
new_addr = self.nodes[1].getnewaddress() | new_addr = self.nodes[1].getnewaddress() | ||||
ret_addr = self.nodes[0].getnewaddress() | ret_addr = self.nodes[0].getnewaddress() | ||||
# send multiple transactions, reusing one address | # send multiple transactions, reusing one address | ||||
for _ in range(11): | for _ in range(11): | ||||
self.nodes[0].sendtoaddress(new_addr, 1) | self.nodes[0].sendtoaddress(new_addr, 1000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# send transaction that should not use all the available outputs | # send transaction that should not use all the available outputs | ||||
# per the current coin selection algorithm | # per the current coin selection algorithm | ||||
self.nodes[1].sendtoaddress(ret_addr, 5) | self.nodes[1].sendtoaddress(ret_addr, 5000000) | ||||
# getbalances and listunspent should show the remaining outputs | # getbalances and listunspent should show the remaining outputs | ||||
# in the reused address as used/reused | # in the reused address as used/reused | ||||
assert_unspent( | assert_unspent( | ||||
self.nodes[1], | self.nodes[1], | ||||
total_count=2, | total_count=2, | ||||
total_sum=6, | total_sum=6000000, | ||||
reused_count=1, | reused_count=1, | ||||
reused_sum=1) | reused_sum=1000000) | ||||
assert_balances(self.nodes[1], mine={"used": 1, "trusted": 5}) | assert_balances( | ||||
self.nodes[1], | |||||
mine={ | |||||
"used": 1000000, | |||||
"trusted": 5000000}) | |||||
def test_full_destination_group_is_preferred(self): | def test_full_destination_group_is_preferred(self): | ||||
''' | ''' | ||||
Test the case where [1] only has 11 outputs of 1 BCH in the same reused | Test the case where [1] only has 11 outputs of 1 BCH in the same reused | ||||
address and tries to send a small payment of 0.5 BCH. The wallet | address and tries to send a small payment of 0.5 BCH. The wallet | ||||
should use 10 outputs from the reused address as inputs and not a | should use 10 outputs from the reused address as inputs and not a | ||||
single 1 BCH input, in order to join several outputs from the reused | single 1 BCH input, in order to join several outputs from the reused | ||||
address. | address. | ||||
''' | ''' | ||||
self.log.info( | self.log.info( | ||||
"Test that full destination groups are preferred in coin selection") | "Test that full destination groups are preferred in coin selection") | ||||
# Node under test should be empty | # Node under test should be empty | ||||
assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) | assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) | ||||
new_addr = self.nodes[1].getnewaddress() | new_addr = self.nodes[1].getnewaddress() | ||||
ret_addr = self.nodes[0].getnewaddress() | ret_addr = self.nodes[0].getnewaddress() | ||||
# Send 11 outputs of 1 BCH to the same, reused address in the wallet | # Send 11 outputs of 1 BCH to the same, reused address in the wallet | ||||
for _ in range(11): | for _ in range(11): | ||||
self.nodes[0].sendtoaddress(new_addr, 1) | self.nodes[0].sendtoaddress(new_addr, 1000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# Sending a transaction that is smaller than each one of the | # Sending a transaction that is smaller than each one of the | ||||
# available outputs | # available outputs | ||||
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=0.5) | txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=500000) | ||||
inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] | inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] | ||||
# The transaction should use 10 inputs exactly | # The transaction should use 10 inputs exactly | ||||
assert_equal(len(inputs), 10) | assert_equal(len(inputs), 10) | ||||
def test_all_destination_groups_are_used(self): | def test_all_destination_groups_are_used(self): | ||||
''' | ''' | ||||
Test the case where [1] only has 22 outputs of 1 BCH in the same reused | Test the case where [1] only has 22 outputs of 1 BCH in the same reused | ||||
address and tries to send a payment of 20.5 BCH. The wallet | address and tries to send a payment of 20.5 BCH. The wallet | ||||
should use all 22 outputs from the reused address as inputs. | should use all 22 outputs from the reused address as inputs. | ||||
''' | ''' | ||||
self.log.info("Test that all destination groups are used") | self.log.info("Test that all destination groups are used") | ||||
# Node under test should be empty | # Node under test should be empty | ||||
assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) | assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) | ||||
new_addr = self.nodes[1].getnewaddress() | new_addr = self.nodes[1].getnewaddress() | ||||
ret_addr = self.nodes[0].getnewaddress() | ret_addr = self.nodes[0].getnewaddress() | ||||
# Send 22 outputs of 1 BCH to the same, reused address in the wallet | # Send 22 outputs of 1 BCH to the same, reused address in the wallet | ||||
for _ in range(22): | for _ in range(22): | ||||
self.nodes[0].sendtoaddress(new_addr, 1) | self.nodes[0].sendtoaddress(new_addr, 1000000) | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
# Sending a transaction that needs to use the full groups | # Sending a transaction that needs to use the full groups | ||||
# of 10 inputs but also the incomplete group of 2 inputs. | # of 10 inputs but also the incomplete group of 2 inputs. | ||||
txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20.5) | txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20500000) | ||||
inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] | inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] | ||||
# The transaction should use 22 inputs exactly | # The transaction should use 22 inputs exactly | ||||
assert_equal(len(inputs), 22) | assert_equal(len(inputs), 22) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
AvoidReuseTest().main() | AvoidReuseTest().main() |