Skip to content

Commit 473b9de

Browse files
Lagrang3ddustin
authored andcommitted
xpay: add a flag to indicate completion of initialization
Add a "ready" flag that becomes true after the plugin has finished initializing. Usually RPC calls during initialization are done using synchronous communication but xpay uses hooks to bind itself to "pay". For some reason registering to hooks and using rcp_scan at init cannot be done. Therefore xpay's initialization is asynchronous which has the downside of race conditions like: trying to make a payment while xpay does not know yet the current node id. This is unlikely to happen in real life but it breaks our tests randomly. Fixes flake `tests/test_xpay.py:test_xpay_fake_channeld` ``` Valgrind error file: valgrind-errors.356233 ==356233== Conditional jump or move depends on uninitialised value(s) ==356233== at 0x1116ED: handle_block_added (xpay.c:3159) ==356233== by 0x11D7A8: ld_command_handle (libplugin.c:2144) ==356233== by 0x11DB8A: ld_read_json (libplugin.c:2282) ==356233== by 0x15DAC1: next_plan (io.c:60) ==356233== by 0x15DF4C: do_plan (io.c:422) ==356233== by 0x15E005: io_ready (io.c:439) ==356233== by 0x15F99B: io_loop (poll.c:470) ==356233== by 0x11DFD6: plugin_main (libplugin.c:2481) ==356233== by 0x11876E: main (xpay.c:3412) ==356233== { <insert_a_suppression_name_here> Memcheck:Cond fun:handle_block_added fun:ld_command_handle fun:ld_read_json fun:next_plan fun:do_plan fun:io_ready fun:io_loop fun:plugin_main fun:main } ``` Changelog-None Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
1 parent aaefc60 commit 473b9de

2 files changed

Lines changed: 57 additions & 37 deletions

File tree

plugins/xpay/xpay.c

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ struct xpay {
5252
/* Suppress calls to askrene-age */
5353
bool dev_no_age;
5454
const char **user_layers;
55+
/* We cannot initialize xpay with rpc_scan, we use instead asynchronous
56+
* requests to fetch the node_id, the blockheight and create the xpay
57+
* layer in askrene. Hence we need a flag to signal when we are ready
58+
* for processing payments otherwise we get a race condition: a payment
59+
* arrives and we don't know our own node_id yet, for example. In
60+
* practice this will be more useful for tests. */
61+
bool ready;
5562
};
5663

5764
static struct xpay *xpay_of(struct plugin *plugin)
@@ -2303,6 +2310,7 @@ static struct command_result *json_xpay_params(struct command *cmd,
23032310
const jsmntok_t *params,
23042311
bool as_pay)
23052312
{
2313+
struct xpay *xpay = xpay_of(cmd->plugin);
23062314
struct amount_msat *msat, *maxfee, *partial;
23072315
const char *invstring;
23082316
const char **layers;
@@ -2329,6 +2337,8 @@ static struct command_result *json_xpay_params(struct command *cmd,
23292337
p_opt_dev("dev_use_shadow", param_bool, &dev_use_shadow, true),
23302338
NULL))
23312339
return command_param_failed();
2340+
if (!xpay->ready)
2341+
return command_fail(cmd, PLUGIN_ERROR, "xpay is initializing");
23322342

23332343
/* Is this a one-shot vibe payment? Kids these days! */
23342344
if (!as_pay && bolt12_has_offer_prefix(invstring)) {
@@ -2514,6 +2524,8 @@ bool attempt_ongoing(struct plugin *plugin, const struct sha256 *payment_hash,
25142524
u64 groupid)
25152525
{
25162526
struct xpay *xpay = xpay_of(plugin);
2527+
if (!xpay->ready)
2528+
return false;
25172529
const struct payment *payment;
25182530

25192531
list_for_each(&xpay->payments, payment, list) {
@@ -2817,6 +2829,7 @@ static struct command_result *json_sendamount(struct command *cmd,
28172829
const char *buffer,
28182830
const jsmntok_t *params)
28192831
{
2832+
struct xpay *xpay = xpay_of(cmd->plugin);
28202833
struct amount_msat *send_msat, *maxfee;
28212834
struct amount_msat invoice_msat;
28222835
const char *invstring;
@@ -2839,6 +2852,8 @@ static struct command_result *json_sendamount(struct command *cmd,
28392852
p_opt("label", param_label, &label),
28402853
NULL))
28412854
return command_param_failed();
2855+
if (!xpay->ready)
2856+
return command_fail(cmd, PLUGIN_ERROR, "xpay is initializing");
28422857

28432858
// FIXME: why does xpay returns this only after
28442859
// preapproveinvoice_succeed?
@@ -2903,21 +2918,15 @@ static struct command_result *json_sendamount(struct command *cmd,
29032918
label, NULL, false, false, send_msat);
29042919
}
29052920

2906-
static struct command_result *getchaininfo_done(struct command *aux_cmd,
2907-
const char *method,
2908-
const char *buf,
2909-
const jsmntok_t *result,
2910-
void *unused)
2921+
static struct command_result *xpay_layer_created(struct command *aux_cmd,
2922+
const char *method,
2923+
const char *buf,
2924+
const jsmntok_t *result,
2925+
void *unused)
29112926
{
29122927
struct xpay *xpay = xpay_of(aux_cmd->plugin);
2913-
2914-
/* We use headercount from the backend, in case we're still syncing */
2915-
if (!json_to_u32(buf, json_get_member(buf, result, "headercount"),
2916-
&xpay->blockheight)) {
2917-
plugin_err(aux_cmd->plugin, "Bad getchaininfo '%.*s'",
2918-
json_tok_full_len(result),
2919-
json_tok_full(buf, result));
2920-
}
2928+
xpay->ready = true;
2929+
plugin_log(aux_cmd->plugin, LOG_INFORM, "xpay is ready");
29212930
return aux_command_done(aux_cmd);
29222931
}
29232932

@@ -2929,6 +2938,7 @@ static struct command_result *getinfo_done(struct command *aux_cmd,
29292938
{
29302939
struct xpay *xpay = xpay_of(aux_cmd->plugin);
29312940
const char *err;
2941+
struct out_req *req;
29322942

29332943
err = json_scan(tmpctx, buf, result,
29342944
"{id:%}", JSON_SCAN(json_to_pubkey, &xpay->local_id));
@@ -2938,7 +2948,35 @@ static struct command_result *getinfo_done(struct command *aux_cmd,
29382948
json_tok_full(buf, result),
29392949
err);
29402950
}
2941-
return aux_command_done(aux_cmd);
2951+
2952+
req = jsonrpc_request_start(aux_cmd, "askrene-create-layer",
2953+
xpay_layer_created, plugin_broken_cb,
2954+
"askrene-create-layer");
2955+
json_add_string(req->js, "layer", "xpay");
2956+
json_add_bool(req->js, "persistent", true);
2957+
return send_outreq(req);
2958+
}
2959+
2960+
static struct command_result *getchaininfo_done(struct command *aux_cmd,
2961+
const char *method,
2962+
const char *buf,
2963+
const jsmntok_t *result,
2964+
void *unused)
2965+
{
2966+
struct xpay *xpay = xpay_of(aux_cmd->plugin);
2967+
struct out_req *req;
2968+
2969+
/* We use headercount from the backend, in case we're still syncing */
2970+
if (!json_to_u32(buf, json_get_member(buf, result, "headercount"),
2971+
&xpay->blockheight)) {
2972+
plugin_err(aux_cmd->plugin, "Bad getchaininfo '%.*s'",
2973+
json_tok_full_len(result),
2974+
json_tok_full(buf, result));
2975+
}
2976+
2977+
req = jsonrpc_request_start(aux_cmd, "getinfo", getinfo_done,
2978+
plugin_broken_cb, "getinfo");
2979+
return send_outreq(req);
29422980
}
29432981

29442982
static struct command_result *populate_private_layer(struct command *cmd,
@@ -2970,15 +3008,6 @@ static struct command_result *age_layer(struct command *cmd, struct payment *pay
29703008
return send_outreq(req);
29713009
}
29723010

2973-
static struct command_result *xpay_layer_created(struct command *aux_cmd,
2974-
const char *method,
2975-
const char *buf,
2976-
const jsmntok_t *result,
2977-
void *unused)
2978-
{
2979-
return aux_command_done(aux_cmd);
2980-
}
2981-
29823011
static struct command_result *json_xkeysend(struct command *cmd,
29833012
const char *buf,
29843013
const jsmntok_t *params)
@@ -3009,6 +3038,8 @@ static struct command_result *json_xkeysend(struct command *cmd,
30093038
p_opt("extratlvs", param_extra_tlvs, &extra_fields),
30103039
NULL))
30113040
return command_param_failed();
3041+
if (!xpay->ready)
3042+
return command_fail(cmd, PLUGIN_ERROR, "xpay is initializing");
30123043

30133044
randbytes(&preimage, sizeof(preimage));
30143045
sha256(&payment_hash, &preimage, sizeof(preimage));
@@ -3102,20 +3133,6 @@ static const char *init(struct command *init_cmd,
31023133
json_add_u32(req->js, "last_height", 0);
31033134
send_outreq(req);
31043135

3105-
req = jsonrpc_request_start(aux_command(init_cmd), "getinfo",
3106-
getinfo_done,
3107-
plugin_broken_cb,
3108-
"getinfo");
3109-
send_outreq(req);
3110-
3111-
req = jsonrpc_request_start(aux_command(init_cmd), "askrene-create-layer",
3112-
xpay_layer_created,
3113-
plugin_broken_cb,
3114-
"askrene-create-layer");
3115-
json_add_string(req->js, "layer", "xpay");
3116-
json_add_bool(req->js, "persistent", true);
3117-
send_outreq(req);
3118-
31193136
return NULL;
31203137
}
31213138

@@ -3410,6 +3427,7 @@ int main(int argc, char *argv[])
34103427
xpay->slow_mode = false;
34113428
xpay->dev_no_age = false;
34123429
xpay->user_layers = tal_arr(xpay, const char *, 0);
3430+
xpay->ready = false;
34133431
list_head_init(&xpay->payments);
34143432
plugin_main(argv, init, take(xpay),
34153433
PLUGIN_RESTARTABLE, true, NULL,

tests/test_xpay.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ def test_xpay_fake_channeld(node_factory, bitcoind, chainparams, slow_mode):
259259
{'allow_bad_gossip': True,
260260
'log-level': 'info',
261261
}])
262+
l1.daemon.logsearch_start = 0
263+
l1.daemon.wait_for_log('xpay is ready')
262264

263265
# l1 needs to know l2's shaseed for the channel so it can make revocations
264266
hsmfile = os.path.join(l2.daemon.lightning_dir, TEST_NETWORK, "hsm_secret")

0 commit comments

Comments
 (0)