diff --git a/tests/test_pay.py b/tests/test_pay.py index d07ab9175ba9..8146b58f47a6 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -2436,6 +2436,110 @@ def test_lockup_drain(node_factory, bitcoind): l2.pay(l1, total // 2) +@pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") +def test_htlc_too_dusty_outgoing(node_factory, bitcoind): + """ Try to hit the 'too much dust' limit, should fail the HTLC """ + feerate = 10000 + max_dust_limit_sat = 50000 + + l1, l2 = node_factory.line_graph(2, opts={'may_reconnect': True, + 'feerates': (feerate, feerate, feerate, feerate), + 'max-dust-htlc-exposure-msat': '{}sat'.format(max_dust_limit_sat), + 'allow_warning': True}) + + # l2 holds all of l1's htlcs hostage + l2.rpc.dev_ignore_htlcs(id=l1.info['id'], ignore=True) + + # l2's max dust limit is set to 100k + htlc_val_sat = 5000 + htlc_val_msat = htlc_val_sat * 1000 + num_dusty_htlcs = max_dust_limit_sat // htlc_val_sat + + # add a some non-dusty htlcs, these will fail when we raise the dust limit + route = l1.rpc.getroute(l2.info['id'], 10000 * 1000, 1)['route'] + for i in range(0, 3): + inv = l2.rpc.invoice((10000 * 1000), str(i + 100), str(i + 100)) + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') + res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) + assert res['status'] == 'pending' + + # add some dusty-htlcs + route = l1.rpc.getroute(l2.info['id'], htlc_val_msat, 1)['route'] + for i in range(0, num_dusty_htlcs): + inv = l2.rpc.invoice(htlc_val_msat, str(i), str(i)) + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') + res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) + assert res['status'] == 'pending' + + # one more should tip it over, and return a payment failure + inv = l2.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs), str(num_dusty_htlcs)) + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + l1.daemon.wait_for_log('CHANNEL_ERR_DUST_FAILURE') + wait_for(lambda: only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])['status'] == 'failed') + + # but we can still add a non dust htlc + route = l1.rpc.getroute(l2.info['id'], 10000 * 1000, 1)['route'] + inv = l2.rpc.invoice((10000 * 1000), str(120), str(120)) + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + l2.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') + res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) + assert res['status'] == 'pending' + + # Ok, adjust our feerate upward, so the non-dust htlcs are now dust + # note that this is above the buffer we've been keeping, so the channel + # should automatically fail + l1.set_feerates([feerate * 2] * 4, False) + l1.restart() + + # the channel should fail -- too much dust + inv = l2.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs + 1), str(num_dusty_htlcs + 1)) + with pytest.raises(RpcError, match=r'WIRE_UNKNOWN_NEXT_PEER'): + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + + +@pytest.mark.developer("needs DEVELOPER=1 for dev_ignore_htlcs") +def test_htlc_too_dusty_incoming(node_factory, bitcoind): + """ Try to hit the 'too much dust' limit, should fail the HTLC """ + feerate = 30000 + l1, l2, l3 = node_factory.line_graph(3, opts=[{'may_reconnect': True, + 'feerates': (feerate, feerate, feerate, feerate), + 'max-dust-htlc-exposure-msat': '200000sat'}, + {'may_reconnect': True, + 'feerates': (feerate, feerate, feerate, feerate), + 'max-dust-htlc-exposure-msat': '100000sat', + 'fee-base': 0, + 'fee-per-satoshi': 0}, + {'max-dust-htlc-exposure-msat': '500000sat'}], + wait_for_announce=True) + + # on the l2->l3, and l3 holds all the htlcs hostage + # have l3 hold onto all the htlcs and not fulfill them + l3.rpc.dev_ignore_htlcs(id=l2.info['id'], ignore=True) + + # l2's max dust limit is set to 100k + max_dust_limit_sat = 100000 + htlc_val_sat = 10000 + htlc_val_msat = htlc_val_sat * 1000 + num_dusty_htlcs = max_dust_limit_sat // htlc_val_sat + route = l1.rpc.getroute(l3.info['id'], htlc_val_msat, 1)['route'] + + # l1 sends as much money as it can + for i in range(0, num_dusty_htlcs): + inv = l3.rpc.invoice(htlc_val_msat, str(i), str(i)) + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + l3.daemon.wait_for_log(r'their htlc .* dev_ignore_htlcs') + res = only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments']) + assert res['status'] == 'pending' + + # one more should tip it over, and return a payment failure + inv = l3.rpc.invoice(htlc_val_msat, str(num_dusty_htlcs), str(num_dusty_htlcs)) + l1.rpc.sendpay(route, inv['payment_hash'], payment_secret=inv['payment_secret']) + l2.daemon.wait_for_log('failing immediately, as requested') + wait_for(lambda: only_one(l1.rpc.listsendpays(payment_hash=inv['payment_hash'])['payments'])['status'] == 'failed') + + def test_error_returns_blockheight(node_factory, bitcoind): """Test that incorrect_or_unknown_payment_details returns block height""" l1, l2 = node_factory.line_graph(2)