When paying with injectpaymentonion we need to manually decode the error from the onion. Changelog-None. Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
569 lines
19 KiB
C
569 lines
19 KiB
C
#include "config.h"
|
|
#include <common/json_param.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/onion_encode.h>
|
|
#include <plugins/renepay/json.h>
|
|
#include <plugins/renepay/payplugin.h>
|
|
#include <plugins/renepay/sendpay.h>
|
|
|
|
static struct command_result *param_route_hops(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
struct route_hop **hops)
|
|
{
|
|
size_t i;
|
|
const jsmntok_t *t;
|
|
const char *err;
|
|
|
|
if (tok->type != JSMN_ARRAY)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s must be an array", name);
|
|
|
|
*hops = tal_arr(cmd, struct route_hop, tok->size);
|
|
json_for_each_arr(i, t, tok)
|
|
{
|
|
struct amount_msat amount_msat;
|
|
struct node_id id;
|
|
struct short_channel_id channel;
|
|
unsigned delay, direction;
|
|
|
|
err = json_scan(tmpctx, buffer, t,
|
|
"{amount_msat:%,id:%,channel:%,direction:%,delay:%}",
|
|
JSON_SCAN(json_to_msat, &amount_msat),
|
|
JSON_SCAN(json_to_node_id, &id),
|
|
JSON_SCAN(json_to_short_channel_id, &channel),
|
|
JSON_SCAN(json_to_number, &direction),
|
|
JSON_SCAN(json_to_number, &delay)
|
|
);
|
|
if (err != NULL) {
|
|
return command_fail(
|
|
cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Error parsing route_hop %s[%zu]: %s", name, i,
|
|
err);
|
|
}
|
|
|
|
if (direction != 0 && direction != 1)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"direction must be either 0 or 1");
|
|
|
|
(*hops)[i].amount = amount_msat;
|
|
(*hops)[i].node_id = id;
|
|
(*hops)[i].delay = delay;
|
|
(*hops)[i].scid = channel;
|
|
(*hops)[i].direction = direction;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *param_blinded_path(struct command *cmd,
|
|
const char *name,
|
|
const char *buffer,
|
|
const jsmntok_t *tok,
|
|
struct blinded_path **blinded_path)
|
|
{
|
|
size_t i;
|
|
const jsmntok_t *t, *pathtok, *datatok;
|
|
const char *err;
|
|
|
|
*blinded_path = tal(cmd, struct blinded_path);
|
|
err = json_scan(
|
|
tmpctx, buffer, tok, "{first_node_id:%,first_path_key:%}",
|
|
JSON_SCAN(json_to_pubkey, &(*blinded_path)->first_node_id.pubkey),
|
|
JSON_SCAN(json_to_pubkey, &(*blinded_path)->first_path_key));
|
|
if (err != NULL) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Error parsing blinded_path %s: %s", name,
|
|
err);
|
|
}
|
|
pathtok = json_get_member(buffer, tok, "path");
|
|
if (!pathtok)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s does not have a path", name);
|
|
if (pathtok->type != JSMN_ARRAY)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"path in %s must be an array", name);
|
|
|
|
(*blinded_path)->path =
|
|
tal_arr(*blinded_path, struct blinded_path_hop *, pathtok->size);
|
|
json_for_each_arr(i, t, pathtok)
|
|
{
|
|
(*blinded_path)->path[i] =
|
|
tal((*blinded_path)->path, struct blinded_path_hop);
|
|
struct blinded_path_hop *hop = (*blinded_path)->path[i];
|
|
|
|
err =
|
|
json_scan(tmpctx, buffer, t, "{blinded_node_id:%}",
|
|
JSON_SCAN(json_to_pubkey, &hop->blinded_node_id));
|
|
if (err != NULL)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Error parsing path[%zu]: %s", i,
|
|
err);
|
|
|
|
datatok =
|
|
json_get_member(buffer, t, "encrypted_recipient_data");
|
|
if (!datatok)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Error parsing path[%zu]: unable "
|
|
"to get encrypted_recipient_data",
|
|
i);
|
|
hop->encrypted_recipient_data =
|
|
json_tok_bin_from_hex(hop, buffer, datatok);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct renesendpay {
|
|
struct route_hop *route;
|
|
struct sha256 payment_hash;
|
|
u64 groupid, partid;
|
|
|
|
u32 final_cltv;
|
|
struct amount_msat total_amount;
|
|
struct amount_msat deliver_amount;
|
|
struct amount_msat sent_amount;
|
|
struct node_id destination;
|
|
|
|
struct secret *payment_secret;
|
|
struct blinded_path *blinded_path;
|
|
|
|
const char *invoice, *label, *description;
|
|
const u8 *metadata;
|
|
|
|
struct secret *shared_secrets;
|
|
unsigned int blockheight;
|
|
};
|
|
|
|
static struct command_result *rpc_fail(struct command *cmd,
|
|
const char *method,
|
|
const char *buffer,
|
|
const jsmntok_t *toks,
|
|
struct renesendpay *renesendpay)
|
|
{
|
|
plugin_log(cmd->plugin, LOG_UNUSUAL,
|
|
"renesendpay failed calling %s: %.*s", method,
|
|
json_tok_full_len(toks), json_tok_full(buffer, toks));
|
|
const jsmntok_t *codetok = json_get_member(buffer, toks, "code");
|
|
const jsmntok_t *msgtok = json_get_member(buffer, toks, "message");
|
|
const char *msg;
|
|
if (msgtok)
|
|
msg = json_strdup(tmpctx, buffer, msgtok);
|
|
else
|
|
msg = "";
|
|
u32 errcode;
|
|
if (codetok != NULL)
|
|
json_to_u32(buffer, codetok, &errcode);
|
|
else
|
|
errcode = PLUGIN_ERROR;
|
|
struct json_stream *response = jsonrpc_stream_fail(
|
|
cmd, errcode,
|
|
tal_fmt(tmpctx, "%s failed: %s", method, msg ? msg : "\"\""));
|
|
json_object_start(response, "data");
|
|
json_add_sha256(response, "payment_hash", &renesendpay->payment_hash);
|
|
json_add_string(response, "status", "failed");
|
|
json_add_amount_msat(response, "amount_sent_msat",
|
|
renesendpay->sent_amount);
|
|
json_object_end(response);
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static void sphinx_append_blinded_path(const tal_t *ctx,
|
|
struct sphinx_path *sp,
|
|
const struct blinded_path *blinded_path,
|
|
const struct amount_msat deliver,
|
|
const struct amount_msat total,
|
|
const u32 final_cltv)
|
|
{
|
|
const size_t pathlen = tal_count(blinded_path->path);
|
|
bool ret;
|
|
|
|
for (size_t i = 0; i < pathlen; i++) {
|
|
bool first = (i == 0);
|
|
bool final = (i == pathlen - 1);
|
|
|
|
const struct blinded_path_hop *bhop = blinded_path->path[i];
|
|
const u8 *payload = onion_blinded_hop(
|
|
ctx, final ? &deliver : NULL, final ? &total : NULL,
|
|
final ? &final_cltv : NULL, bhop->encrypted_recipient_data,
|
|
first ? &blinded_path->first_path_key : NULL);
|
|
// FIXME: better handle error here
|
|
ret = sphinx_add_hop_has_length(
|
|
sp,
|
|
first ? &blinded_path->first_node_id.pubkey
|
|
: &bhop->blinded_node_id,
|
|
take(payload));
|
|
assert(ret);
|
|
}
|
|
}
|
|
|
|
static void sphinx_append_final_hop(const tal_t *ctx,
|
|
struct sphinx_path *sp,
|
|
const struct secret *payment_secret,
|
|
const struct node_id *node,
|
|
const struct amount_msat deliver,
|
|
const struct amount_msat total,
|
|
const u32 final_cltv,
|
|
const u8 *payment_metadata)
|
|
{
|
|
struct pubkey destination;
|
|
bool ret = pubkey_from_node_id(&destination, node);
|
|
assert(ret);
|
|
|
|
const u8 *payload = onion_final_hop(ctx, deliver, final_cltv, total,
|
|
payment_secret, payment_metadata);
|
|
// FIXME: better handle error here
|
|
ret = sphinx_add_hop_has_length(sp, &destination, take(payload));
|
|
assert(ret);
|
|
}
|
|
|
|
static const u8 *create_onion(const tal_t *ctx,
|
|
struct renesendpay *renesendpay,
|
|
const struct node_id first_node,
|
|
const size_t first_index)
|
|
{
|
|
bool ret;
|
|
const tal_t *this_ctx = tal(ctx, tal_t);
|
|
struct node_id current_node = first_node;
|
|
struct pubkey node;
|
|
const u8 *payload;
|
|
const size_t pathlen = tal_count(renesendpay->route);
|
|
|
|
struct sphinx_path *sp =
|
|
sphinx_path_new(this_ctx, renesendpay->payment_hash.u.u8,
|
|
sizeof(renesendpay->payment_hash.u.u8));
|
|
|
|
for (size_t i = first_index; i < pathlen; i++) {
|
|
/* Encrypted message is for node[i] but the data is hop[i+1],
|
|
* therein lays the problem with sendpay's API. */
|
|
ret = pubkey_from_node_id(&node, ¤t_node);
|
|
assert(ret);
|
|
|
|
struct route_hop *hop = &renesendpay->route[i];
|
|
payload =
|
|
onion_nonfinal_hop(this_ctx, &hop->scid, hop->amount,
|
|
hop->delay + renesendpay->blockheight);
|
|
// FIXME: better handle error here
|
|
ret = sphinx_add_hop_has_length(sp, &node, take(payload));
|
|
assert(ret);
|
|
current_node = renesendpay->route[i].node_id;
|
|
}
|
|
|
|
const u32 final_cltv = renesendpay->final_cltv + renesendpay->blockheight;
|
|
if(renesendpay->blinded_path){
|
|
sphinx_append_blinded_path(this_ctx,
|
|
sp,
|
|
renesendpay->blinded_path,
|
|
renesendpay->deliver_amount,
|
|
renesendpay->total_amount,
|
|
final_cltv);
|
|
}else{
|
|
sphinx_append_final_hop(this_ctx,
|
|
sp,
|
|
renesendpay->payment_secret,
|
|
¤t_node,
|
|
renesendpay->deliver_amount,
|
|
renesendpay->total_amount,
|
|
final_cltv,
|
|
renesendpay->metadata);
|
|
}
|
|
|
|
struct secret *shared_secrets;
|
|
struct onionpacket *packet = create_onionpacket(
|
|
this_ctx, sp, ROUTING_INFO_SIZE, &shared_secrets);
|
|
renesendpay->shared_secrets = tal_steal(renesendpay, shared_secrets);
|
|
|
|
const u8 *onion = serialize_onionpacket(ctx, packet);
|
|
tal_free(this_ctx);
|
|
return onion;
|
|
}
|
|
|
|
static struct command_result *renesendpay_done(struct command *cmd,
|
|
const char *method UNUSED,
|
|
const char *buffer,
|
|
const jsmntok_t *toks,
|
|
struct renesendpay *renesendpay)
|
|
{
|
|
const char *err;
|
|
u64 created_index;
|
|
u32 timestamp;
|
|
err = json_scan(tmpctx, buffer, toks, "{created_index:%,created_at:%}",
|
|
JSON_SCAN(json_to_u64, &created_index),
|
|
JSON_SCAN(json_to_u32, ×tamp));
|
|
if (err)
|
|
return command_fail(
|
|
cmd, JSONRPC2_INVALID_PARAMS,
|
|
"renesendpay failed to read response from sendonion: %s",
|
|
err);
|
|
|
|
struct json_stream *response = jsonrpc_stream_success(cmd);
|
|
json_add_string(response, "message",
|
|
"Monitor status with listpays or waitsendpay");
|
|
|
|
json_add_u64(response, "created_index", created_index);
|
|
json_add_u32(response, "created_at", timestamp);
|
|
json_add_sha256(response, "payment_hash", &renesendpay->payment_hash);
|
|
json_add_u64(response, "groupid", renesendpay->groupid);
|
|
json_add_u64(response, "partid", renesendpay->partid);
|
|
json_add_node_id(response, "destination", &renesendpay->destination);
|
|
json_add_amount_msat(response, "amount_sent_msat",
|
|
renesendpay->sent_amount);
|
|
json_add_amount_msat(response, "amount_delivered_msat",
|
|
renesendpay->deliver_amount);
|
|
json_add_amount_msat(response, "amount_total_msat",
|
|
renesendpay->total_amount);
|
|
json_add_string(response, "invoice", renesendpay->invoice);
|
|
json_add_string(response, "status", "pending");
|
|
|
|
const jsmntok_t *preimagetok =
|
|
json_get_member(buffer, toks, "payment_preimage");
|
|
if (preimagetok)
|
|
json_add_tok(response, "payment_preimage", preimagetok, buffer);
|
|
|
|
if (renesendpay->label)
|
|
json_add_string(response, "label", renesendpay->label);
|
|
if (renesendpay->description)
|
|
json_add_string(response, "description",
|
|
renesendpay->description);
|
|
if (renesendpay->metadata)
|
|
json_add_hex_talarr(response, "payment_metadata",
|
|
renesendpay->metadata);
|
|
|
|
if (renesendpay->shared_secrets) {
|
|
json_array_start(response, "shared_secrets");
|
|
for (size_t i = 0; i < tal_count(renesendpay->shared_secrets);
|
|
i++) {
|
|
json_add_secret(response, NULL,
|
|
&renesendpay->shared_secrets[i]);
|
|
}
|
|
json_array_end(response);
|
|
}
|
|
|
|
/* FIXME: shall we report the blinded path, secret and route used? */
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static struct command_result *renesendpay_finished(struct command *cmd,
|
|
struct renesendpay *renesendpay)
|
|
{
|
|
struct json_stream *response = jsonrpc_stream_success(cmd);
|
|
json_add_string(response, "message",
|
|
"Monitor status with listpays or waitsendpay");
|
|
json_add_sha256(response, "payment_hash", &renesendpay->payment_hash);
|
|
json_add_u64(response, "groupid", renesendpay->groupid);
|
|
json_add_u64(response, "partid", renesendpay->partid);
|
|
json_add_node_id(response, "destination", &renesendpay->destination);
|
|
json_add_amount_msat(response, "amount_sent_msat",
|
|
renesendpay->sent_amount);
|
|
json_add_amount_msat(response, "amount_delivered_msat",
|
|
renesendpay->deliver_amount);
|
|
json_add_amount_msat(response, "amount_total_msat",
|
|
renesendpay->total_amount);
|
|
json_add_string(response, "invoice", renesendpay->invoice);
|
|
json_add_string(response, "status", "pending");
|
|
|
|
if (renesendpay->label)
|
|
json_add_string(response, "label", renesendpay->label);
|
|
if (renesendpay->description)
|
|
json_add_string(response, "description",
|
|
renesendpay->description);
|
|
if (renesendpay->metadata)
|
|
json_add_hex_talarr(response, "payment_metadata",
|
|
renesendpay->metadata);
|
|
|
|
if (renesendpay->shared_secrets) {
|
|
json_array_start(response, "shared_secrets");
|
|
for (size_t i = 0; i < tal_count(renesendpay->shared_secrets);
|
|
i++) {
|
|
json_add_secret(response, NULL,
|
|
&renesendpay->shared_secrets[i]);
|
|
}
|
|
json_array_end(response);
|
|
}
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static u32 initial_cltv_delta(const struct renesendpay *renesendpay)
|
|
{
|
|
if (tal_count(renesendpay->route) == 0)
|
|
return renesendpay->final_cltv;
|
|
return renesendpay->route[0].delay;
|
|
}
|
|
|
|
static struct command_result *waitblockheight_done(struct command *cmd,
|
|
const char *method UNUSED,
|
|
const char *buffer,
|
|
const jsmntok_t *toks,
|
|
struct renesendpay *renesendpay)
|
|
{
|
|
const char *err;
|
|
err = json_scan(tmpctx, buffer, toks, "{blockheight:%}",
|
|
JSON_SCAN(json_to_u32, &renesendpay->blockheight));
|
|
renesendpay->blockheight += 1;
|
|
if (err)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"renesendpay failed to read blockheight "
|
|
"from waitblockheight response.");
|
|
|
|
const u8 *onion;
|
|
struct out_req *req;
|
|
|
|
if (tal_count(renesendpay->route) > 0) {
|
|
onion = create_onion(tmpctx, renesendpay,
|
|
renesendpay->route[0].node_id, 1);
|
|
req = jsonrpc_request_start(cmd, "sendonion", renesendpay_done,
|
|
rpc_fail, renesendpay);
|
|
json_add_hex_talarr(req->js, "onion", onion);
|
|
|
|
json_add_amount_msat(req->js, "amount_msat",
|
|
renesendpay->deliver_amount);
|
|
|
|
const struct route_hop *hop = &renesendpay->route[0];
|
|
json_object_start(req->js, "first_hop");
|
|
json_add_amount_msat(req->js, "amount_msat", hop->amount);
|
|
json_add_num(req->js, "delay",
|
|
hop->delay + renesendpay->blockheight);
|
|
json_add_node_id(req->js, "id", &hop->node_id);
|
|
json_add_short_channel_id(req->js, "channel", hop->scid);
|
|
json_object_end(req->js);
|
|
|
|
json_array_start(req->js, "shared_secrets");
|
|
for (size_t i = 0; i < tal_count(renesendpay->shared_secrets);
|
|
i++) {
|
|
json_add_secret(req->js, NULL,
|
|
&renesendpay->shared_secrets[i]);
|
|
}
|
|
json_array_end(req->js);
|
|
|
|
json_add_node_id(req->js, "destination",
|
|
&renesendpay->destination);
|
|
json_add_sha256(req->js, "payment_hash",
|
|
&renesendpay->payment_hash);
|
|
json_add_u64(req->js, "partid", renesendpay->partid);
|
|
json_add_u64(req->js, "groupid", renesendpay->groupid);
|
|
if (renesendpay->label)
|
|
json_add_string(req->js, "label", renesendpay->label);
|
|
if (renesendpay->description)
|
|
json_add_string(req->js, "description",
|
|
renesendpay->description);
|
|
if (renesendpay->invoice)
|
|
json_add_string(req->js, "bolt11",
|
|
renesendpay->invoice);
|
|
} else {
|
|
/* This is either a self-payment or a payment through a blinded
|
|
* path that starts at our node. */
|
|
// FIXME: do this with injectpaymentonion.
|
|
// FIXME: we could make all payments with injectpaymentonion but
|
|
// we need to make sure first that we don't lose older features,
|
|
// like for example to be able to show in listsendpays the
|
|
// recepient of the payment.
|
|
onion = create_onion(tmpctx, renesendpay, pay_plugin->my_id, 0);
|
|
req = jsonrpc_request_start(cmd, "injectpaymentonion",
|
|
renesendpay_done, rpc_fail,
|
|
renesendpay);
|
|
|
|
json_add_hex_talarr(req->js, "onion", onion);
|
|
json_add_sha256(req->js, "payment_hash",
|
|
&renesendpay->payment_hash);
|
|
json_add_u64(req->js, "partid", renesendpay->partid);
|
|
json_add_u64(req->js, "groupid", renesendpay->groupid);
|
|
if (renesendpay->label)
|
|
json_add_string(req->js, "label", renesendpay->label);
|
|
if (renesendpay->invoice)
|
|
json_add_string(req->js, "invstring",
|
|
renesendpay->invoice);
|
|
json_add_amount_msat(req->js, "amount_msat",
|
|
renesendpay->sent_amount);
|
|
json_add_amount_msat(req->js, "destination_msat",
|
|
renesendpay->deliver_amount);
|
|
json_add_u32(req->js, "cltv_expiry",
|
|
initial_cltv_delta(renesendpay) +
|
|
renesendpay->blockheight);
|
|
}
|
|
send_outreq(req);
|
|
return renesendpay_finished(cmd, renesendpay);
|
|
}
|
|
|
|
struct command_result *json_renesendpay(struct command *cmd,
|
|
const char *buf,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct route_hop *route;
|
|
struct sha256 *payment_hash;
|
|
const char *invoice, *label, *description;
|
|
struct amount_msat *amount, *total_amount;
|
|
u64 *groupid, *partid;
|
|
u32 *final_cltv;
|
|
struct node_id *destination;
|
|
u8 *metadata;
|
|
|
|
/* only used in the case of BOLT11 */
|
|
struct secret *payment_secret;
|
|
|
|
/* only used in the case of BOLT12 */
|
|
struct blinded_path *blinded_path;
|
|
|
|
if (!param(cmd, buf, params,
|
|
p_req("route", param_route_hops, &route),
|
|
p_req("payment_hash", param_sha256, &payment_hash),
|
|
p_req("groupid", param_u64, &groupid),
|
|
p_req("partid", param_u64, &partid),
|
|
p_req("amount_msat", param_msat, &amount),
|
|
p_req("total_amount_msat", param_msat, &total_amount),
|
|
p_req("destination", param_node_id, &destination),
|
|
p_req("final_cltv", param_u32, &final_cltv),
|
|
p_opt("payment_secret", param_secret, &payment_secret),
|
|
p_opt("blinded_path", param_blinded_path, &blinded_path),
|
|
p_opt("invoice", param_invstring, &invoice),
|
|
p_opt("label", param_string, &label),
|
|
p_opt("description", param_string, &description),
|
|
p_opt("metadata", param_bin_from_hex, &metadata),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if (payment_secret && blinded_path)
|
|
return command_fail(
|
|
cmd, JSONRPC2_INVALID_PARAMS,
|
|
"A payment cannot have both a secret and a blinded path.");
|
|
if (!payment_secret && !blinded_path)
|
|
return command_fail(
|
|
cmd, JSONRPC2_INVALID_PARAMS,
|
|
"For a BOLT11 payment a payment_secret "
|
|
"must be specified and for a BOLT12 "
|
|
"payment a blinded_path must be specified.");
|
|
|
|
plugin_log(cmd->plugin, LOG_DBG, "renesendpay called: %.*s",
|
|
json_tok_full_len(params), json_tok_full(buf, params));
|
|
|
|
struct renesendpay *renesendpay = tal(cmd, struct renesendpay);
|
|
renesendpay->route = tal_steal(renesendpay, route);
|
|
renesendpay->payment_hash = *payment_hash;
|
|
renesendpay->partid = *partid;
|
|
renesendpay->groupid = *groupid;
|
|
|
|
if (tal_count(renesendpay->route) > 0)
|
|
renesendpay->sent_amount = renesendpay->route[0].amount;
|
|
else /* this might be a self pay */
|
|
renesendpay->sent_amount = *amount;
|
|
|
|
renesendpay->total_amount = *total_amount;
|
|
renesendpay->deliver_amount = *amount;
|
|
renesendpay->final_cltv = *final_cltv;
|
|
|
|
renesendpay->destination = *destination;
|
|
|
|
renesendpay->payment_secret = tal_steal(renesendpay, payment_secret);
|
|
renesendpay->blinded_path = tal_steal(renesendpay, blinded_path);
|
|
|
|
renesendpay->invoice = tal_steal(renesendpay, invoice);
|
|
renesendpay->label = tal_steal(renesendpay, label);
|
|
renesendpay->description = tal_steal(renesendpay, description);
|
|
renesendpay->metadata = tal_steal(renesendpay, metadata);
|
|
renesendpay->shared_secrets = NULL;
|
|
|
|
struct out_req *req =
|
|
jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done,
|
|
rpc_fail, renesendpay);
|
|
json_add_num(req->js, "blockheight", 0);
|
|
return send_outreq(req);
|
|
}
|