Skip to content

Commit 4eb8023

Browse files
ksedgwicdaywalker90
authored andcommitted
lightningd: store raw failure message so waitsendpay always has raw_message
If a payment's HTLC failure completes before waitsendpay is called, wait_payment() rebuilds the error from the database, which did not persist the raw BOLT4 failure message: a 2019 FIXME in that path set fail->msg = NULL, silently dropping raw_message from the error data. This is the cause of the test_error_returns_blockheight CI flake - the test loses the race occasionally, calls waitsendpay after the failure has landed, and KeyErrors on the missing raw_message. Add a failmsg column to the payments table (with a downgrade drop), persist fail->msg when recording the failure, and read it back in wait_payment. Local and self-payment failures store NULL as before, since no onion failure message exists for them; failed payments recorded before this migration also return NULL, matching the old behavior. The flaky test now also calls waitsendpay a second time, which deterministically takes the database-replay path, so the regression is covered without any timing dependence. Changelog-Fixed: JSON-RPC: `waitsendpay` error data now includes `raw_message` even when the payment already failed before the command was called. Fixes: #9341
1 parent 372d8f7 commit 4eb8023

5 files changed

Lines changed: 40 additions & 10 deletions

File tree

‎lightningd/pay.c‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,8 @@ void payment_failed(struct lightningd *ld,
652652
fail ? fail->erring_channel : NULL,
653653
NULL,
654654
failstr,
655-
fail ? fail->channel_dir : 0);
655+
fail ? fail->channel_dir : 0,
656+
fail ? fail->msg : NULL);
656657

657658
tell_waiters_failed(ld, payment_hash, payment, pay_errcode,
658659
failonion, fail, failstr);
@@ -675,6 +676,7 @@ static struct command_result *wait_payment(struct lightningd *ld,
675676
struct short_channel_id *failchannel;
676677
u8 *failupdate;
677678
char *faildetail;
679+
u8 *failmsg;
678680
struct routing_failure *fail;
679681
int faildirection;
680682
enum jsonrpc_errcode rpcerrorcode;
@@ -715,7 +717,8 @@ static struct command_result *wait_payment(struct lightningd *ld,
715717
&failchannel,
716718
&failupdate,
717719
&faildetail,
718-
&faildirection);
720+
&faildirection,
721+
&failmsg);
719722
/* Old DB might not save failure information */
720723
if (!failonionreply && !failnode) {
721724
return command_fail(cmd, PAY_UNSPECIFIED_ERROR,
@@ -745,8 +748,7 @@ static struct command_result *wait_payment(struct lightningd *ld,
745748
fail->erring_channel = NULL;
746749
}
747750

748-
/* FIXME: We don't store this! */
749-
fail->msg = NULL;
751+
fail->msg = tal_dup_talarr(fail, u8, failmsg);
750752

751753
/* Peers which fail directly can hit this! */
752754
if (failcode & BADONION)
@@ -1533,7 +1535,7 @@ static struct command_result *self_payment(struct lightningd *ld,
15331535
fail->failcode, fail->erring_node,
15341536
NULL, NULL,
15351537
err,
1536-
0);
1538+
0, NULL);
15371539
/* We do this even though there really can't be any waiters,
15381540
* since we didn't block. */
15391541
tell_waiters_failed(ld, rhash, payment, PAY_DESTINATION_PERM_FAIL,

‎tests/test_pay.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2888,6 +2888,15 @@ def test_error_returns_blockheight(node_factory, bitcoind):
28882888
assert (err.value.error['data']['raw_message']
28892889
== '400f{:016x}{:08x}'.format(100, bitcoind.rpc.getblockcount()))
28902890

2891+
# A second waitsendpay replays the failure from the database (the
2892+
# path a waitsendpay racing the failure takes): raw_message must
2893+
# survive that round trip too.
2894+
with pytest.raises(RpcError, match=r"INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS.*'erring_index': 1") as err:
2895+
l1.rpc.waitsendpay('00' * 32, TIMEOUT)
2896+
2897+
assert (err.value.error['data']['raw_message']
2898+
== '400f{:016x}{:08x}'.format(100, bitcoind.rpc.getblockcount()))
2899+
28912900

28922901
@unittest.skipIf(TEST_NETWORK != 'regtest', "Invoice is network specific")
28932902
def test_pay_no_secret(node_factory, bitcoind):

‎wallet/migrations.c‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,10 @@ static const struct db_migration dbmigrations[] = {
11821182
* writes stop in the release that removes chaintopology, freezing all
11831183
* the legacy tables at the same height. */
11841184
{NULL, migrate_backfill_bwatch_tables, NULL, NULL},
1185+
/* Raw BOLT4 failure message, so waitsendpay can report it even
1186+
* after the failure was recorded (issue #9341). */
1187+
{SQL("ALTER TABLE payments ADD failmsg BLOB;"), NULL,
1188+
SQL("ALTER TABLE payments DROP COLUMN failmsg"), NULL},
11851189
/* ^v26.09 */
11861190
};
11871191

‎wallet/wallet.c‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4322,7 +4322,8 @@ void wallet_payment_get_failinfo(const tal_t *ctx,
43224322
struct short_channel_id **failchannel,
43234323
u8 **failupdate,
43244324
char **faildetail,
4325-
int *faildirection)
4325+
int *faildirection,
4326+
u8 **failmsg)
43264327
{
43274328
struct db_stmt *stmt;
43284329
bool resb;
@@ -4332,6 +4333,7 @@ void wallet_payment_get_failinfo(const tal_t *ctx,
43324333
", failindex, failcode"
43334334
", failnode, failscid"
43344335
", failupdate, faildetail, faildirection"
4336+
", failmsg"
43354337
" FROM payments"
43364338
" WHERE payment_hash=? AND partid=? AND groupid=?;"));
43374339
db_bind_sha256(stmt, payment_hash);
@@ -4366,6 +4368,10 @@ void wallet_payment_get_failinfo(const tal_t *ctx,
43664368
*faildetail = db_col_strdup(ctx, stmt, "faildetail");
43674369
else
43684370
*faildetail = NULL;
4371+
if (db_col_is_null(stmt, "failmsg"))
4372+
*failmsg = NULL;
4373+
else
4374+
*failmsg = db_col_arr(ctx, stmt, "failmsg", u8);
43694375

43704376
tal_free(stmt);
43714377
}
@@ -4381,7 +4387,8 @@ void wallet_payment_set_failinfo(struct wallet *wallet,
43814387
const struct short_channel_id *failchannel,
43824388
const u8 *failupdate /*tal_arr*/,
43834389
const char *faildetail,
4384-
int faildirection)
4390+
int faildirection,
4391+
const u8 *failmsg /*tal_arr*/)
43854392
{
43864393
struct db_stmt *stmt;
43874394

@@ -4395,6 +4402,7 @@ void wallet_payment_set_failinfo(struct wallet *wallet,
43954402
" , faildirection=?"
43964403
" , failupdate=?"
43974404
" , faildetail=?"
4405+
" , failmsg=?"
43984406
" WHERE payment_hash=?"
43994407
" AND partid=?;"));
44004408
if (failonionreply)
@@ -4425,6 +4433,8 @@ void wallet_payment_set_failinfo(struct wallet *wallet,
44254433
else
44264434
db_bind_null(stmt);
44274435

4436+
db_bind_talarr(stmt, failmsg);
4437+
44284438
db_bind_sha256(stmt, payment_hash);
44294439
db_bind_u64(stmt, partid);
44304440

‎wallet/wallet.h‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,10 @@ void wallet_payment_set_status(struct wallet *wallet,
10491049
* `payment_hash`.
10501050
*
10511051
* Data is allocated as children of the given context. *faildirection
1052-
* is only set if *failchannel is set non-NULL.
1052+
* is only set if *failchannel is set non-NULL. *failmsg is NULL when
1053+
* no raw onion failure message was recorded (local and self-payment
1054+
* failures, or payments that failed before the failmsg column
1055+
* existed).
10531056
*/
10541057
void wallet_payment_get_failinfo(const tal_t *ctx,
10551058
struct wallet *wallet,
@@ -1065,7 +1068,8 @@ void wallet_payment_get_failinfo(const tal_t *ctx,
10651068
struct short_channel_id **failchannel,
10661069
u8 **failupdate,
10671070
char **faildetail,
1068-
int *faildirection);
1071+
int *faildirection,
1072+
u8 **failmsg);
10691073
/**
10701074
* wallet_payment_set_failinfo - Set failure information for a given
10711075
* `payment_hash`.
@@ -1081,7 +1085,8 @@ void wallet_payment_set_failinfo(struct wallet *wallet,
10811085
const struct short_channel_id *failchannel,
10821086
const u8 *failupdate,
10831087
const char *faildetail,
1084-
int faildirection);
1088+
int faildirection,
1089+
const u8 *failmsg);
10851090

10861091
/**
10871092
* payments_first: get first payment, optionally filtering by status

0 commit comments

Comments
 (0)