diff --git a/tests/test_misc.py b/tests/test_misc.py index b31ae3bf2..fd8b6d053 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,4 +1,3 @@ -from decimal import Decimal from fixtures import * # noqa: F401,F403 from flaky import flaky # noqa: F401 from lightning import RpcError @@ -360,184 +359,6 @@ def test_bech32_funding(node_factory): assert only_one(fundingtx['vin'])['txid'] == res['wallettxid'] -def test_withdraw(node_factory, bitcoind): - amount = 1000000 - # Don't get any funds from previous runs. - l1 = node_factory.get_node(random_hsm=True) - l2 = node_factory.get_node(random_hsm=True) - addr = l1.rpc.newaddr()['bech32'] - - # Add some funds to withdraw later - for i in range(10): - l1.bitcoin.rpc.sendtoaddress(addr, amount / 10**8 + 0.01) - - bitcoind.generate_block(1) - wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) - - # Reach around into the db to check that outputs were added - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 10 - - waddr = l1.bitcoin.rpc.getnewaddress() - # Now attempt to withdraw some (making sure we collect multiple inputs) - with pytest.raises(RpcError): - l1.rpc.withdraw('not an address', amount) - with pytest.raises(RpcError): - l1.rpc.withdraw(waddr, 'not an amount') - with pytest.raises(RpcError): - l1.rpc.withdraw(waddr, -amount) - with pytest.raises(RpcError, match=r'Cannot afford transaction'): - l1.rpc.withdraw(waddr, amount * 100) - - out = l1.rpc.withdraw(waddr, 2 * amount) - - # Make sure bitcoind received the withdrawal - unspent = l1.bitcoin.rpc.listunspent(0) - withdrawal = [u for u in unspent if u['txid'] == out['txid']] - - assert(withdrawal[0]['amount'] == Decimal('0.02')) - - # Now make sure two of them were marked as spent - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 2 - - # Now send some money to l2. - # lightningd uses P2SH-P2WPKH - waddr = l2.rpc.newaddr('bech32')['bech32'] - l1.rpc.withdraw(waddr, 2 * amount) - bitcoind.generate_block(1) - - # Make sure l2 received the withdrawal. - wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == 1) - outputs = l2.db_query('SELECT value FROM outputs WHERE status=0;') - assert only_one(outputs)['value'] == 2 * amount - - # Now make sure an additional two of them were marked as spent - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 4 - - # Simple test for withdrawal to P2WPKH - # Address from: https://bc-2.jp/tools/bech32demo/index.html - waddr = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080' - with pytest.raises(RpcError): - l1.rpc.withdraw('xx1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx', 2 * amount) - with pytest.raises(RpcError): - l1.rpc.withdraw('tb1pw508d6qejxtdg4y5r3zarvary0c5xw7kdl9fad', 2 * amount) - with pytest.raises(RpcError): - l1.rpc.withdraw('tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxxxxxx', 2 * amount) - l1.rpc.withdraw(waddr, 2 * amount) - bitcoind.generate_block(1) - # Now make sure additional two of them were marked as spent - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 6 - - # Simple test for withdrawal to P2WSH - # Address from: https://bc-2.jp/tools/bech32demo/index.html - waddr = 'bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry' - with pytest.raises(RpcError): - l1.rpc.withdraw('xx1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7', 2 * amount) - with pytest.raises(RpcError): - l1.rpc.withdraw('tb1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qsm03tq', 2 * amount) - with pytest.raises(RpcError): - l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qxxxxxx', 2 * amount) - l1.rpc.withdraw(waddr, 2 * amount) - bitcoind.generate_block(1) - # Now make sure additional two of them were marked as spent - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 8 - - # failure testing for invalid SegWit addresses, from BIP173 - # HRP character out of range - with pytest.raises(RpcError): - l1.rpc.withdraw(' 1nwldj5', 2 * amount) - # overall max length exceeded - with pytest.raises(RpcError): - l1.rpc.withdraw('an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx', 2 * amount) - # No separator character - with pytest.raises(RpcError): - l1.rpc.withdraw('pzry9x0s0muk', 2 * amount) - # Empty HRP - with pytest.raises(RpcError): - l1.rpc.withdraw('1pzry9x0s0muk', 2 * amount) - # Invalid witness version - with pytest.raises(RpcError): - l1.rpc.withdraw('BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2', 2 * amount) - # Invalid program length for witness version 0 (per BIP141) - with pytest.raises(RpcError): - l1.rpc.withdraw('BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P', 2 * amount) - # Mixed case - with pytest.raises(RpcError): - l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7', 2 * amount) - # Non-zero padding in 8-to-5 conversion - with pytest.raises(RpcError): - l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv', 2 * amount) - - # Should have 6 outputs available. - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 6 - - # Test withdrawal to self. - l1.rpc.withdraw(l1.rpc.newaddr('bech32')['bech32'], 'all', minconf=0) - bitcoind.generate_block(1) - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 1 - - l1.rpc.withdraw(waddr, 'all', minconf=0) - assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 0 - - # This should fail, can't even afford fee. - with pytest.raises(RpcError, match=r'Cannot afford transaction'): - l1.rpc.withdraw(waddr, 'all') - - -def test_minconf_withdraw(node_factory, bitcoind): - """Issue 2518: ensure that ridiculous confirmation levels don't overflow - - The number of confirmations is used to compute a maximum height that is to - be accepted. If the current height is smaller than the number of - confirmations we wrap around and just select everything. The fix is to - clamp the maxheight parameter to a positive small number. - - """ - amount = 1000000 - # Don't get any funds from previous runs. - l1 = node_factory.get_node(random_hsm=True) - addr = l1.rpc.newaddr()['bech32'] - - # Add some funds to withdraw later - for i in range(10): - l1.bitcoin.rpc.sendtoaddress(addr, amount / 10**8 + 0.01) - - bitcoind.generate_block(1) - - wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) - with pytest.raises(RpcError): - l1.rpc.withdraw(destination=addr, satoshi=10000, feerate='normal', minconf=9999999) - - -def test_addfunds_from_block(node_factory, bitcoind): - """Send funds to the daemon without telling it explicitly - """ - # Previous runs with same bitcoind can leave funds! - l1 = node_factory.get_node(random_hsm=True) - - addr = l1.rpc.newaddr()['bech32'] - bitcoind.rpc.sendtoaddress(addr, 0.1) - bitcoind.generate_block(1) - - wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1) - - outputs = l1.db_query('SELECT value FROM outputs WHERE status=0;') - assert only_one(outputs)['value'] == 10000000 - - # The address we detect must match what was paid to. - output = only_one(l1.rpc.listfunds()['outputs']) - assert output['address'] == addr - - # Send all our money to a P2WPKH address this time. - addr = l1.rpc.newaddr("bech32")['bech32'] - l1.rpc.withdraw(addr, "all") - bitcoind.generate_block(1) - time.sleep(1) - - # The address we detect must match what was paid to. - output = only_one(l1.rpc.listfunds()['outputs']) - assert output['address'] == addr - - def test_io_logging(node_factory, executor): l1 = node_factory.get_node(options={'log-level': 'io'}) l2 = node_factory.get_node() diff --git a/tests/test_wallet.py b/tests/test_wallet.py new file mode 100644 index 000000000..7918f0552 --- /dev/null +++ b/tests/test_wallet.py @@ -0,0 +1,186 @@ +from decimal import Decimal +from fixtures import * # noqa: F401,F403 +from flaky import flaky # noqa: F401 +from lightning import RpcError +from utils import only_one, wait_for + +import pytest +import time + + +def test_withdraw(node_factory, bitcoind): + amount = 1000000 + # Don't get any funds from previous runs. + l1 = node_factory.get_node(random_hsm=True) + l2 = node_factory.get_node(random_hsm=True) + addr = l1.rpc.newaddr()['bech32'] + + # Add some funds to withdraw later + for i in range(10): + l1.bitcoin.rpc.sendtoaddress(addr, amount / 10**8 + 0.01) + + bitcoind.generate_block(1) + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) + + # Reach around into the db to check that outputs were added + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 10 + + waddr = l1.bitcoin.rpc.getnewaddress() + # Now attempt to withdraw some (making sure we collect multiple inputs) + with pytest.raises(RpcError): + l1.rpc.withdraw('not an address', amount) + with pytest.raises(RpcError): + l1.rpc.withdraw(waddr, 'not an amount') + with pytest.raises(RpcError): + l1.rpc.withdraw(waddr, -amount) + with pytest.raises(RpcError, match=r'Cannot afford transaction'): + l1.rpc.withdraw(waddr, amount * 100) + + out = l1.rpc.withdraw(waddr, 2 * amount) + + # Make sure bitcoind received the withdrawal + unspent = l1.bitcoin.rpc.listunspent(0) + withdrawal = [u for u in unspent if u['txid'] == out['txid']] + + assert(withdrawal[0]['amount'] == Decimal('0.02')) + + # Now make sure two of them were marked as spent + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 2 + + # Now send some money to l2. + # lightningd uses P2SH-P2WPKH + waddr = l2.rpc.newaddr('bech32')['bech32'] + l1.rpc.withdraw(waddr, 2 * amount) + bitcoind.generate_block(1) + + # Make sure l2 received the withdrawal. + wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == 1) + outputs = l2.db_query('SELECT value FROM outputs WHERE status=0;') + assert only_one(outputs)['value'] == 2 * amount + + # Now make sure an additional two of them were marked as spent + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 4 + + # Simple test for withdrawal to P2WPKH + # Address from: https://bc-2.jp/tools/bech32demo/index.html + waddr = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080' + with pytest.raises(RpcError): + l1.rpc.withdraw('xx1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx', 2 * amount) + with pytest.raises(RpcError): + l1.rpc.withdraw('tb1pw508d6qejxtdg4y5r3zarvary0c5xw7kdl9fad', 2 * amount) + with pytest.raises(RpcError): + l1.rpc.withdraw('tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxxxxxx', 2 * amount) + l1.rpc.withdraw(waddr, 2 * amount) + bitcoind.generate_block(1) + # Now make sure additional two of them were marked as spent + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 6 + + # Simple test for withdrawal to P2WSH + # Address from: https://bc-2.jp/tools/bech32demo/index.html + waddr = 'bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry' + with pytest.raises(RpcError): + l1.rpc.withdraw('xx1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7', 2 * amount) + with pytest.raises(RpcError): + l1.rpc.withdraw('tb1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qsm03tq', 2 * amount) + with pytest.raises(RpcError): + l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qxxxxxx', 2 * amount) + l1.rpc.withdraw(waddr, 2 * amount) + bitcoind.generate_block(1) + # Now make sure additional two of them were marked as spent + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=2')[0]['c'] == 8 + + # failure testing for invalid SegWit addresses, from BIP173 + # HRP character out of range + with pytest.raises(RpcError): + l1.rpc.withdraw(' 1nwldj5', 2 * amount) + # overall max length exceeded + with pytest.raises(RpcError): + l1.rpc.withdraw('an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx', 2 * amount) + # No separator character + with pytest.raises(RpcError): + l1.rpc.withdraw('pzry9x0s0muk', 2 * amount) + # Empty HRP + with pytest.raises(RpcError): + l1.rpc.withdraw('1pzry9x0s0muk', 2 * amount) + # Invalid witness version + with pytest.raises(RpcError): + l1.rpc.withdraw('BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2', 2 * amount) + # Invalid program length for witness version 0 (per BIP141) + with pytest.raises(RpcError): + l1.rpc.withdraw('BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P', 2 * amount) + # Mixed case + with pytest.raises(RpcError): + l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7', 2 * amount) + # Non-zero padding in 8-to-5 conversion + with pytest.raises(RpcError): + l1.rpc.withdraw('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv', 2 * amount) + + # Should have 6 outputs available. + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 6 + + # Test withdrawal to self. + l1.rpc.withdraw(l1.rpc.newaddr('bech32')['bech32'], 'all', minconf=0) + bitcoind.generate_block(1) + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 1 + + l1.rpc.withdraw(waddr, 'all', minconf=0) + assert l1.db_query('SELECT COUNT(*) as c FROM outputs WHERE status=0')[0]['c'] == 0 + + # This should fail, can't even afford fee. + with pytest.raises(RpcError, match=r'Cannot afford transaction'): + l1.rpc.withdraw(waddr, 'all') + + +def test_minconf_withdraw(node_factory, bitcoind): + """Issue 2518: ensure that ridiculous confirmation levels don't overflow + + The number of confirmations is used to compute a maximum height that is to + be accepted. If the current height is smaller than the number of + confirmations we wrap around and just select everything. The fix is to + clamp the maxheight parameter to a positive small number. + + """ + amount = 1000000 + # Don't get any funds from previous runs. + l1 = node_factory.get_node(random_hsm=True) + addr = l1.rpc.newaddr()['bech32'] + + # Add some funds to withdraw later + for i in range(10): + l1.bitcoin.rpc.sendtoaddress(addr, amount / 10**8 + 0.01) + + bitcoind.generate_block(1) + + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 10) + with pytest.raises(RpcError): + l1.rpc.withdraw(destination=addr, satoshi=10000, feerate='normal', minconf=9999999) + + +def test_addfunds_from_block(node_factory, bitcoind): + """Send funds to the daemon without telling it explicitly + """ + # Previous runs with same bitcoind can leave funds! + l1 = node_factory.get_node(random_hsm=True) + + addr = l1.rpc.newaddr()['bech32'] + bitcoind.rpc.sendtoaddress(addr, 0.1) + bitcoind.generate_block(1) + + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 1) + + outputs = l1.db_query('SELECT value FROM outputs WHERE status=0;') + assert only_one(outputs)['value'] == 10000000 + + # The address we detect must match what was paid to. + output = only_one(l1.rpc.listfunds()['outputs']) + assert output['address'] == addr + + # Send all our money to a P2WPKH address this time. + addr = l1.rpc.newaddr("bech32")['bech32'] + l1.rpc.withdraw(addr, "all") + bitcoind.generate_block(1) + time.sleep(1) + + # The address we detect must match what was paid to. + output = only_one(l1.rpc.listfunds()['outputs']) + assert output['address'] == addr