diff --git a/tests/test_pay.py b/tests/test_pay.py index 93d49a4da..62fbef033 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -976,30 +976,73 @@ def test_forward_pad_fees_and_cltv(node_factory, bitcoind): assert only_one(l3.rpc.listinvoices('test_forward_pad_fees_and_cltv')['invoices'])['status'] == 'paid' -def test_forward_stats(node_factory): - l1, l2, l3 = node_factory.line_graph(3, announce=True) +def test_forward_stats(node_factory, bitcoind): + """Check that we track forwarded payments correctly. - inv = l3.rpc.invoice(100000, "first", "desc")['bolt11'] - l1.rpc.pay(inv) + We wire up the network to have l1 as payment initiator, l2 as + forwarded (the one we check) and l3-l5 as payment recipients. l3 + accepts correctly, l4 refects (because it doesn't know the payment + hash) and l5 will keep the HTLC dangling by disconnecting. - inchan = l2.rpc.listpeers(l1.info['id'])['peers'][0]['channels'][0] - outchan = l2.rpc.listpeers(l3.info['id'])['peers'][0]['channels'][0] + """ + amount = 10**4 + l1, l2, l3 = node_factory.line_graph(3, announce=False) + l4 = node_factory.get_node() + l5 = node_factory.get_node(may_fail=True) + l2.openchannel(l4, 10**6, announce=False) + l2.openchannel(l5, 10**6, announce=True) + + bitcoind.generate_block(5) - def extract_stats(c): - return {k: v for k, v in c.items() if 'in_' in k or 'out_' in k} + wait_for(lambda: len(l1.rpc.listchannels()['channels']) == 8) - instats = extract_stats(inchan) - outstats = extract_stats(outchan) + payment_hash = l3.rpc.invoice(amount, "first", "desc")['payment_hash'] + route = l1.rpc.getroute(l3.info['id'], amount, 1)['route'] + + l1.rpc.sendpay(route, payment_hash) + l1.rpc.waitsendpay(payment_hash) + + # l4 rejects since it doesn't know the payment_hash + route = l1.rpc.getroute(l4.info['id'], amount, 1)['route'] + payment_hash = "F" * 64 + with pytest.raises(RpcError): + l1.rpc.sendpay(route, payment_hash) + l1.rpc.waitsendpay(payment_hash) + + # l5 will hold the HTLC hostage and walk away + route = l1.rpc.getroute(l5.info['id'], amount, 1)['route'] + payment_hash = l5.rpc.invoice(amount, "first", "desc")['payment_hash'] + l1.rpc.sendpay(route, payment_hash) + + # Wait for the HTLC to be added, then try to kill it to keep the + # HTLC pending. This is racy but there seems to be no good + # alternative + l5.daemon.wait_for_log(r'RCVD_ADD_HTLC/SENT_ADD_HTLC') + l5.daemon.wait_for_log(r'peer_out WIRE_COMMITMENT_SIGNED') + l5.daemon.kill() + print("Killed!") + + # Select all forwardings, ordered by htlc_id to ensure the order + # matches below + forwardings = l2.db_query("SELECT *, in_msatoshi - out_msatoshi as fee " + "FROM forwarded_payments " + "ORDER BY in_htlc_id;") + assert(len(forwardings) == 3) + states = [f['state'] for f in forwardings] + assert(states == [1, 2, 0]) # settled, failed, offered + + inchan = l2.rpc.listpeers(l1.info['id'])['peers'][0]['channels'][0] + outchan = l2.rpc.listpeers(l3.info['id'])['peers'][0]['channels'][0] # Check that we correctly account channel changes - assert instats['in_payments_offered'] == 1 - assert instats['in_payments_fulfilled'] == 1 - assert instats['in_msatoshi_offered'] >= 100000 - assert instats['in_msatoshi_offered'] == instats['in_msatoshi_fulfilled'] + assert inchan['in_payments_offered'] == 3 + assert inchan['in_payments_fulfilled'] == 1 + assert inchan['in_msatoshi_offered'] >= 3 * amount + assert inchan['in_msatoshi_fulfilled'] >= amount - assert outstats['out_payments_offered'] == 1 - assert outstats['out_payments_fulfilled'] == 1 - assert outstats['out_msatoshi_offered'] >= 100000 - assert outstats['out_msatoshi_offered'] == outstats['out_msatoshi_fulfilled'] + assert outchan['out_payments_offered'] == 1 + assert outchan['out_payments_fulfilled'] == 1 + assert outchan['out_msatoshi_offered'] >= amount + assert outchan['out_msatoshi_offered'] == outchan['out_msatoshi_fulfilled'] - assert outstats['out_msatoshi_fulfilled'] < instats['in_msatoshi_fulfilled'] + assert outchan['out_msatoshi_fulfilled'] < inchan['in_msatoshi_fulfilled'] diff --git a/tests/utils.py b/tests/utils.py index 3fd409a95..e5444fd93 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -380,8 +380,12 @@ class LightningNode(object): self.may_fail = may_fail self.may_reconnect = may_reconnect - def openchannel(self, remote_node, capacity, addrtype="p2sh-segwit", confirm=True, announce=True): + def openchannel(self, remote_node, capacity, addrtype="p2sh-segwit", confirm=True, announce=True, connect=True): addr, wallettxid = self.fundwallet(10 * capacity, addrtype) + + if connect and remote_node.info['id'] not in [p['id'] for p in self.rpc.listpeers()['peers']]: + self.rpc.connect(remote_node.info['id'], '127.0.0.1', remote_node.daemon.port) + fundingtx = self.rpc.fundchannel(remote_node.info['id'], capacity) # Wait for the funding transaction to be in bitcoind's mempool