From f0fa5d14019e2af8070d0bc5bb1a88c0f0d2dad8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 18 Feb 2021 15:37:43 +1030 Subject: [PATCH] offers: handle re-fetching the same invoice twice. We get a label clash: easy, just re-serve: ``` 2021-02-18T04:29:37.474Z **BROKEN** plugin-offers: Failed invoice_request lnr1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcyyqwtp0rmsgquvuacqcl5cdfzwzmu3v8tqgvpqs8e80dlmxm7ey4xwrqdsqqqqqqqqqqqqqqqq2pqqfqpqynzqzx9rylzy40ernj4jzc3p2dwy3n8x6lqeaywwk725ghx4kx63pcfxgg2z3nsn80jzge06nt3ks8pr6rvnujq48376lpmrr3cq04nurpy783eyr0awh5773lrlmjek07rjf0nx4g9235ulkcs7jp2h5gumjyquhadh846da3jptxm9g0qz5lne4hjhag for offer 1cb0bc7b8201c673b8063f4c352270b7c8b0eb02181040f93bdbfd9b7ec92a67: Got JSON error: {\"code\":900,\"message\":\"Duplicate label\",\"data\":{\"label\":\"1cb0bc7b8201c673b8063f4c352270b7c8b0eb02181040f93bdbfd9b7ec92a67-08c5193e2255f91ce5590b110a9ae2466736be0cf48e75bcaa22e6ad8da88709-1\",\"bolt12\":\"lni1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcyyqwtp0rmsgquvuacqcl5cdfzwzmu3v8tqgvpqs8e80dlmxm7ey4xwzqrw4lauzsc2ajk26mv0ysxxmmxvejk2grxdaezqun4wd68jggvpkqqqqqqqqqqqqqqqqpgyqq7ypymf9efe2jj5r2mzunlqz67d75ht3ukxk0x9ftkcuknrgepsgupwfqpqynzqzx9rylzy40ernj4jzc3p2dwy3n8x6lqeaywwk725ghx4kx63pcf9qzxqt0dxq4zqwtz2qu44gzx7nzczc494cce2tgph5xgu5sn7vh8frky9z5n08xj9sp3yaxe9cqs5vss59r8pxwlyy3jl4xhrdqwz85xe9qqgcpda590qs9khxdx5qpetlx0j6ap0wsxagssmy2qjvhjp2kc3na54pht3pp76c405upne360lh8rzye32xxq6l0phpkk9pu9lwxnqkxuwt2nqqr9u\",\"payment_hash\":\"396250395aa046f4c58162a5ae31952d01bd0c8e5213f32e748ec428a9379cd2\",\"msatoshi\":7700446,\"amount_msat\":\"7700446msat\",\"status\":\"unpaid\",\"description\":\"Weekly coffee for rusty!\",\"expires_at\":1614832137,\"local_offer_id\":\"1cb0bc7b8201c673b8063f4c352270b7c8b0eb02181040f93bdbfd9b7ec92a67\"}} ``` Signed-off-by: Rusty Russell --- plugins/offers_invreq_hook.c | 21 ++++++++++++++++++++- tests/test_pay.py | 12 ++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 852549113..996c1a318 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -160,6 +161,7 @@ static struct command_result *error(struct command *cmd, json_tok_full(buf, err)); } +/* We can fail to create the invoice if we've already done so. */ static struct command_result *createinvoice_done(struct command *cmd, const char *buf, const jsmntok_t *result, @@ -182,6 +184,23 @@ static struct command_result *createinvoice_done(struct command *cmd, return send_onion_reply(cmd, ir->buf, ir->replytok, "invoice", rawinv); } +static struct command_result *createinvoice_error(struct command *cmd, + const char *buf, + const jsmntok_t *err, + struct invreq *ir) +{ + u32 code; + + /* If it already exists, we can reuse its bolt12 directly. */ + if (json_scan(tmpctx, buf, err, + "{code:%}", JSON_SCAN(json_to_u32, &code)) == NULL + && code == INVOICE_LABEL_ALREADY_EXISTS) { + return createinvoice_done(cmd, buf, + json_get_member(buf, err, "data"), ir); + } + return error(cmd, buf, err, ir); +} + static struct command_result *create_invoicereq(struct command *cmd, struct invreq *ir) { @@ -189,7 +208,7 @@ static struct command_result *create_invoicereq(struct command *cmd, /* Now, write invoice to db (returns the signed version) */ req = jsonrpc_request_start(cmd->plugin, cmd, "createinvoice", - createinvoice_done, error, ir); + createinvoice_done, createinvoice_error, ir); json_add_string(req->js, "invstring", invoice_encode(tmpctx, ir->inv)); json_add_preimage(req->js, "preimage", &ir->preimage); diff --git a/tests/test_pay.py b/tests/test_pay.py index 27ddc1301..3eb7bca31 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4061,6 +4061,18 @@ def test_fetchinvoice(node_factory, bitcoind): assert period3['paywindow_end'] == period3['starttime'] l1.rpc.pay(ret['invoice'], label='test paywindow') + # We can get another invoice, as many times as we want. + # (It may return the same one!). + while int(time.time()) <= period3['paywindow_start']: + time.sleep(1) + + l1.rpc.call('fetchinvoice', {'offer': offer, + 'recurrence_counter': 1, + 'recurrence_label': 'test paywindow'}) + l1.rpc.call('fetchinvoice', {'offer': offer, + 'recurrence_counter': 1, + 'recurrence_label': 'test paywindow'}) + # Wait until too late! while int(time.time()) <= period3['paywindow_end']: time.sleep(1)