diff --git a/tests/test_closing.py b/tests/test_closing.py index 13dbbbd19..8965ca8d1 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -1495,7 +1495,7 @@ def test_permfail(node_factory, bitcoind): addr = txout['scriptPubKey']['addresses'][0] assert(addr == o['address']) - addr = l1.bitcoin.rpc.getnewaddress() + addr = l1.bitcoin.getnewaddress() l1.rpc.withdraw(addr, "all") diff --git a/tests/test_invoices.py b/tests/test_invoices.py index 258fa6491..d1b47226f 100644 --- a/tests/test_invoices.py +++ b/tests/test_invoices.py @@ -8,7 +8,7 @@ import time import unittest -def test_invoice(node_factory): +def test_invoice(node_factory, chainparams): l1, l2 = node_factory.line_graph(2, fundchannel=False) addr1 = l2.rpc.newaddr('bech32')['bech32'] @@ -17,7 +17,7 @@ def test_invoice(node_factory): inv = l1.rpc.invoice(123000, 'label', 'description', '3700', [addr1, addr2]) after = int(time.time()) b11 = l1.rpc.decodepay(inv['bolt11']) - assert b11['currency'] == 'bcrt' + assert b11['currency'] == chainparams['bip173_prefix'] assert b11['created_at'] >= before assert b11['created_at'] <= after assert b11['payment_hash'] == inv['payment_hash'] diff --git a/tests/test_misc.py b/tests/test_misc.py index 3ef1f43d7..0142bf35c 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,4 +1,5 @@ from bitcoin.rpc import RawProxy +from decimal import Decimal from fixtures import * # noqa: F401,F403 from flaky import flaky # noqa: F401 from lightning import RpcError @@ -453,6 +454,187 @@ def test_bech32_funding(node_factory, chainparams): assert only_one(fundingtx['vin'])['txid'] == res['wallettxid'] +def test_withdraw(node_factory, bitcoind, chainparams): + 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.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 + + if chainparams['name'] != 'regtest': + return + + # 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/utils.py b/tests/utils.py index 7a96d9e5c..7973e20c5 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -387,6 +387,9 @@ class BitcoinD(TailableProc): self.wait_for_log(r'UpdateTip: new best=.* height={}'.format(final_len)) return hashes + def getnewaddress(self): + return self.rpc.getnewaddress() + class ElementsD(BitcoinD): def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None): @@ -426,6 +429,13 @@ class ElementsD(BitcoinD): # As of 0.16, generate() is removed; use generatetoaddress. return self.rpc.generate(numblocks) + def getnewaddress(self): + """Need to get an address and then make it unconfidential + """ + addr = self.rpc.getnewaddress() + info = self.rpc.getaddressinfo(addr) + return info['unconfidential'] + class LightningD(TailableProc): def __init__(self, lightning_dir, bitcoindproxy, port=9735, random_hsm=False, node_id=0):