Skip to content

Commit 77d7fee

Browse files
committed
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 b7491a0 commit 77d7fee

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)) {
@@ -2513,6 +2523,8 @@ static struct payment *new_payment(const tal_t *ctx,
25132523
bool attempt_ongoing(struct plugin *plugin, const struct sha256 *payment_hash)
25142524
{
25152525
struct xpay *xpay = xpay_of(plugin);
2526+
if (!xpay->ready)
2527+
return false;
25162528
const struct payment *payment;
25172529

25182530
list_for_each(&xpay->payments, payment, list) {
@@ -2815,6 +2827,7 @@ static struct command_result *json_sendamount(struct command *cmd,
28152827
const char *buffer,
28162828
const jsmntok_t *params)
28172829
{
2830+
struct xpay *xpay = xpay_of(cmd->plugin);
28182831
struct amount_msat *send_msat, *maxfee;
28192832
struct amount_msat invoice_msat;
28202833
const char *invstring;
@@ -2837,6 +2850,8 @@ static struct command_result *json_sendamount(struct command *cmd,
28372850
p_opt("label", param_label, &label),
28382851
NULL))
28392852
return command_param_failed();
2853+
if (!xpay->ready)
2854+
return command_fail(cmd, PLUGIN_ERROR, "xpay is initializing");
28402855

28412856
// FIXME: why does xpay returns this only after
28422857
// preapproveinvoice_succeed?
@@ -2901,21 +2916,15 @@ static struct command_result *json_sendamount(struct command *cmd,
29012916
label, NULL, false, false, send_msat);
29022917
}
29032918

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

@@ -2927,6 +2936,7 @@ static struct command_result *getinfo_done(struct command *aux_cmd,
29272936
{
29282937
struct xpay *xpay = xpay_of(aux_cmd->plugin);
29292938
const char *err;
2939+
struct out_req *req;
29302940

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

29422980
static struct command_result *populate_private_layer(struct command *cmd,
@@ -2968,15 +3006,6 @@ static struct command_result *age_layer(struct command *cmd, struct payment *pay
29683006
return send_outreq(req);
29693007
}
29703008

2971-
static struct command_result *xpay_layer_created(struct command *aux_cmd,
2972-
const char *method,
2973-
const char *buf,
2974-
const jsmntok_t *result,
2975-
void *unused)
2976-
{
2977-
return aux_command_done(aux_cmd);
2978-
}
2979-
29803009
static struct command_result *json_xkeysend(struct command *cmd,
29813010
const char *buf,
29823011
const jsmntok_t *params)
@@ -3007,6 +3036,8 @@ static struct command_result *json_xkeysend(struct command *cmd,
30073036
p_opt("extratlvs", param_extra_tlvs, &extra_fields),
30083037
NULL))
30093038
return command_param_failed();
3039+
if (!xpay->ready)
3040+
return command_fail(cmd, PLUGIN_ERROR, "xpay is initializing");
30103041

30113042
randbytes(&preimage, sizeof(preimage));
30123043
sha256(&payment_hash, &preimage, sizeof(preimage));
@@ -3100,20 +3131,6 @@ static const char *init(struct command *init_cmd,
31003131
json_add_u32(req->js, "last_height", 0);
31013132
send_outreq(req);
31023133

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

@@ -3408,6 +3425,7 @@ int main(int argc, char *argv[])
34083425
xpay->slow_mode = false;
34093426
xpay->dev_no_age = false;
34103427
xpay->user_layers = tal_arr(xpay, const char *, 0);
3428+
xpay->ready = false;
34113429
list_head_init(&xpay->payments);
34123430
plugin_main(argv, init, take(xpay),
34133431
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)