mirror of
https://github.com/aljazceru/lightning.git
synced 2026-01-06 07:34:21 +01:00
plugins/spender/fundchannel.c: Make fundchannel a thin layer around multifundchannel.
Changelog-Changed: protocol: `fundchannel` now shuffles inputs and outputs, and no longer follows BIP69.
This commit is contained in:
committed by
Rusty Russell
parent
f6c145d2df
commit
9587425f2d
1
plugins/.gitignore
vendored
1
plugins/.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
autoclean
|
||||
bcli
|
||||
fundchannel
|
||||
pay
|
||||
spenderp
|
||||
multifundchannel
|
||||
|
||||
@@ -4,9 +4,6 @@ PLUGIN_PAY_OBJS := $(PLUGIN_PAY_SRC:.c=.o)
|
||||
PLUGIN_AUTOCLEAN_SRC := plugins/autoclean.c
|
||||
PLUGIN_AUTOCLEAN_OBJS := $(PLUGIN_AUTOCLEAN_SRC:.c=.o)
|
||||
|
||||
PLUGIN_FUNDCHANNEL_SRC := plugins/fundchannel.c
|
||||
PLUGIN_FUNDCHANNEL_OBJS := $(PLUGIN_FUNDCHANNEL_SRC:.c=.o)
|
||||
|
||||
PLUGIN_TXPREPARE_SRC := plugins/txprepare.c
|
||||
PLUGIN_TXPREPARE_OBJS := $(PLUGIN_TXPREPARE_SRC:.c=.o)
|
||||
|
||||
@@ -25,18 +22,20 @@ PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h
|
||||
PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o)
|
||||
|
||||
PLUGIN_SPENDER_SRC := \
|
||||
plugins/spender/fundchannel.c \
|
||||
plugins/spender/main.c \
|
||||
plugins/spender/multifundchannel.c \
|
||||
plugins/spender/multiwithdraw.c
|
||||
PLUGIN_SPENDER_HEADER := \
|
||||
plugins/spender/multifundchannel.h \
|
||||
plugins/spender/multiwithdraw.h
|
||||
plugins/spender/multiwithdraw.h \
|
||||
plugins/spender/fundchannel.h \
|
||||
plugins/spender/multifundchannel.h
|
||||
PLUGIN_SPENDER_OBJS := $(PLUGIN_SPENDER_SRC:.c=.o)
|
||||
|
||||
PLUGIN_ALL_SRC := \
|
||||
$(PLUGIN_AUTOCLEAN_SRC) \
|
||||
$(PLUGIN_BCLI_SRC) \
|
||||
$(PLUGIN_FUNDCHANNEL_SRC) \
|
||||
$(PLUGIN_KEYSEND_SRC) \
|
||||
$(PLUGIN_TXPREPARE_SRC) \
|
||||
$(PLUGIN_LIB_SRC) \
|
||||
@@ -52,7 +51,6 @@ PLUGIN_ALL_OBJS := $(PLUGIN_ALL_SRC:.c=.o)
|
||||
PLUGINS := \
|
||||
plugins/autoclean \
|
||||
plugins/bcli \
|
||||
plugins/fundchannel \
|
||||
plugins/keysend \
|
||||
plugins/pay \
|
||||
plugins/txprepare \
|
||||
@@ -108,8 +106,6 @@ $(PLUGIN_PAY_OBJS): $(PLUGIN_PAY_LIB_HEADER)
|
||||
|
||||
plugins/autoclean: bitcoin/chainparams.o $(PLUGIN_AUTOCLEAN_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
|
||||
|
||||
plugins/fundchannel: common/addr.o $(PLUGIN_FUNDCHANNEL_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
|
||||
|
||||
plugins/txprepare: bitcoin/chainparams.o $(PLUGIN_TXPREPARE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
|
||||
|
||||
plugins/bcli: bitcoin/chainparams.o $(PLUGIN_BCLI_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS)
|
||||
|
||||
@@ -1,452 +0,0 @@
|
||||
#include <bitcoin/chainparams.c>
|
||||
#include <bitcoin/script.h>
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <ccan/json_out/json_out.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <common/addr.h>
|
||||
#include <common/amount.h>
|
||||
#include <common/features.h>
|
||||
#include <common/json_stream.h>
|
||||
#include <common/json_tok.h>
|
||||
#include <common/type_to_string.h>
|
||||
#include <common/utils.h>
|
||||
#include <plugins/libplugin.h>
|
||||
|
||||
const char *placeholder_script = "0020b95810f824f843934fa042acd0becba52087813e260edaeebc42b5cb9abe1464";
|
||||
const char *placeholder_funding_addr;
|
||||
|
||||
/* Populated by libplugin */
|
||||
extern const struct chainparams *chainparams;
|
||||
|
||||
struct funding_req {
|
||||
struct node_id *id;
|
||||
const char *feerate_str;
|
||||
const char *funding_str;
|
||||
const char *utxo_str;
|
||||
bool funding_all;
|
||||
struct amount_msat *push_msat;
|
||||
|
||||
/* Features offered by this peer. */
|
||||
const u8 *their_features;
|
||||
|
||||
bool *announce_channel;
|
||||
u32 *minconf;
|
||||
|
||||
/* The prepared tx id */
|
||||
struct bitcoin_txid tx_id;
|
||||
u32 outnum;
|
||||
|
||||
const char *chanstr;
|
||||
const u8 *out_script;
|
||||
const char *funding_addr;
|
||||
|
||||
/* Failing result (NULL on success) */
|
||||
/* Raw JSON from RPC output */
|
||||
const char *error;
|
||||
};
|
||||
|
||||
static struct command_result *send_prior(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *error,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
return command_err_raw(cmd, fr->error);
|
||||
}
|
||||
|
||||
static struct command_result *tx_abort(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *error,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req;
|
||||
|
||||
/* We stash the error so we can return it after we've cleaned up */
|
||||
fr->error = json_strdup(fr, buf, error);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "txdiscard",
|
||||
send_prior, send_prior, fr);
|
||||
json_add_string(req->js, "txid",
|
||||
type_to_string(tmpctx, struct bitcoin_txid, &fr->tx_id));
|
||||
|
||||
/* We need to call txdiscard, and forward the actual cause for the
|
||||
* error after we've cleaned up. We swallow any errors returned by
|
||||
* this call, as we don't really care if it succeeds or not */
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
/* We're basically done, we just need to format the output to match
|
||||
* what the original `fundchannel` returned */
|
||||
static struct command_result *finish(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct json_stream *out;
|
||||
|
||||
out = jsonrpc_stream_success(cmd);
|
||||
json_add_tok(out, "tx", json_get_member(buf, result, "tx"), buf);
|
||||
json_add_string(out, "txid",
|
||||
type_to_string(tmpctx, struct bitcoin_txid, &fr->tx_id));
|
||||
json_add_u32(out, "outnum", fr->outnum);
|
||||
json_add_string(out, "channel_id", fr->chanstr);
|
||||
|
||||
return command_finished(cmd, out);
|
||||
}
|
||||
|
||||
/* We're ready to broadcast the transaction */
|
||||
static struct command_result *send_tx(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
|
||||
struct out_req *req;
|
||||
const jsmntok_t *tok;
|
||||
bool commitments_secured;
|
||||
|
||||
/* For sanity's sake, let's check that it's secured */
|
||||
tok = json_get_member(buf, result, "commitments_secured");
|
||||
if (!json_to_bool(buf, tok, &commitments_secured) || !commitments_secured)
|
||||
/* TODO: better failure path? this should never fail though. */
|
||||
plugin_err(cmd->plugin, "Commitment not secured.");
|
||||
|
||||
/* Stash the channel_id so we can return it when finalized */
|
||||
tok = json_get_member(buf, result, "channel_id");
|
||||
fr->chanstr = json_strdup(fr, buf, tok);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "txsend",
|
||||
finish, tx_abort, fr);
|
||||
json_add_string(req->js, "txid",
|
||||
type_to_string(tmpctx, struct bitcoin_txid, &fr->tx_id));
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *tx_prepare_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
const jsmntok_t *txid_tok;
|
||||
const jsmntok_t *tx_tok;
|
||||
struct out_req *req;
|
||||
const struct bitcoin_tx *tx;
|
||||
const char *hex;
|
||||
bool outnum_found;
|
||||
|
||||
txid_tok = json_get_member(buf, result, "txid");
|
||||
if (!txid_tok)
|
||||
plugin_err(cmd->plugin, "txprepare missing 'txid' field");
|
||||
|
||||
tx_tok = json_get_member(buf, result, "unsigned_tx");
|
||||
if (!tx_tok)
|
||||
plugin_err(cmd->plugin, "txprepare missing 'unsigned_tx' field");
|
||||
|
||||
hex = json_strdup(tmpctx, buf, tx_tok);
|
||||
tx = bitcoin_tx_from_hex(fr, hex, strlen(hex));
|
||||
if (!tx)
|
||||
plugin_err(cmd->plugin, "Unable to parse tx %s", hex);
|
||||
|
||||
/* Find the txout */
|
||||
outnum_found = false;
|
||||
for (size_t i = 0; i < tx->wtx->num_outputs; i++) {
|
||||
const u8 *output_script = bitcoin_tx_output_get_script(fr, tx, i);
|
||||
if (scripteq(output_script, fr->out_script)) {
|
||||
fr->outnum = i;
|
||||
outnum_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!outnum_found)
|
||||
plugin_err(cmd->plugin, "txprepare doesn't include our funding output. "
|
||||
"tx: %s, output: %s",
|
||||
type_to_string(tmpctx, struct bitcoin_tx, tx),
|
||||
tal_hex(tmpctx, fr->out_script));
|
||||
|
||||
hex = json_strdup(tmpctx, buf, txid_tok);
|
||||
if (!bitcoin_txid_from_hex(hex, strlen(hex), &fr->tx_id))
|
||||
plugin_err(cmd->plugin, "Unable to parse txid %s", hex);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "fundchannel_complete",
|
||||
send_tx, tx_abort, fr);
|
||||
json_add_string(req->js, "id", node_id_to_hexstr(tmpctx, fr->id));
|
||||
/* Note that hex is reused from above */
|
||||
json_add_string(req->js, "txid", hex);
|
||||
json_add_u32(req->js, "txout", fr->outnum);
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *cancel_start(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *error,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req;
|
||||
|
||||
/* We stash the error so we can return it after we've cleaned up */
|
||||
fr->error = json_strdup(fr, buf, error);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "fundchannel_cancel",
|
||||
send_prior, send_prior, fr);
|
||||
json_add_string(req->js, "id", node_id_to_hexstr(tmpctx, fr->id));
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static void txprepare(struct json_stream *js,
|
||||
struct funding_req *fr,
|
||||
const char *destination)
|
||||
{
|
||||
/* Add the 'outputs' */
|
||||
json_array_start(js, "outputs");
|
||||
json_object_start(js, NULL);
|
||||
json_add_string(js, destination, fr->funding_str);
|
||||
json_object_end(js);
|
||||
json_array_end(js);
|
||||
|
||||
if (fr->feerate_str)
|
||||
json_add_string(js, "feerate", fr->feerate_str);
|
||||
if (fr->minconf)
|
||||
json_add_u32(js, "minconf", *fr->minconf);
|
||||
if (fr->utxo_str)
|
||||
json_add_jsonstr(js, "utxos", fr->utxo_str);
|
||||
}
|
||||
|
||||
static struct command_result *prepare_actual(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req;
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "txprepare",
|
||||
tx_prepare_done, cancel_start,
|
||||
fr);
|
||||
txprepare(req->js, fr, fr->funding_addr);
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *fundchannel_start_done(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req;
|
||||
|
||||
/* Save the outscript so we can fund the outnum later */
|
||||
fr->out_script = json_tok_bin_from_hex(fr, buf,
|
||||
json_get_member(buf, result, "scriptpubkey"));
|
||||
|
||||
/* Save the funding address, we'll need it later */
|
||||
fr->funding_addr = json_strdup(cmd, buf,
|
||||
json_get_member(buf, result, "funding_address"));
|
||||
|
||||
/* Now that we're ready to go, cancel the reserved tx */
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "txdiscard",
|
||||
prepare_actual, cancel_start,
|
||||
fr);
|
||||
json_add_string(req->js, "txid",
|
||||
type_to_string(tmpctx, struct bitcoin_txid, &fr->tx_id));
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *fundchannel_start(struct command *cmd,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd,
|
||||
"fundchannel_start",
|
||||
fundchannel_start_done,
|
||||
tx_abort, fr);
|
||||
|
||||
json_add_string(req->js, "id", node_id_to_hexstr(tmpctx, fr->id));
|
||||
|
||||
json_add_string(req->js, "amount", fr->funding_str);
|
||||
|
||||
if (fr->feerate_str)
|
||||
json_add_string(req->js, "feerate", fr->feerate_str);
|
||||
if (fr->announce_channel)
|
||||
json_add_bool(req->js, "announce", *fr->announce_channel);
|
||||
if (fr->push_msat)
|
||||
json_add_string(req->js, "push_msat",
|
||||
type_to_string(tmpctx, struct amount_msat, fr->push_msat));
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *post_dryrun(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct bitcoin_tx *tx;
|
||||
const char *hex;
|
||||
struct amount_sat funding;
|
||||
bool funding_found;
|
||||
u8 *placeholder = tal_hexdata(tmpctx, placeholder_script, strlen(placeholder_script));
|
||||
struct amount_asset asset;
|
||||
|
||||
/* Stash the 'reserved' txid to unreserve later */
|
||||
hex = json_strdup(tmpctx, buf, json_get_member(buf, result, "txid"));
|
||||
if (!bitcoin_txid_from_hex(hex, strlen(hex), &fr->tx_id))
|
||||
plugin_err(cmd->plugin, "Unable to parse reserved txid %s", hex);
|
||||
|
||||
|
||||
hex = json_strdup(tmpctx, buf, json_get_member(buf, result, "unsigned_tx"));
|
||||
tx = bitcoin_tx_from_hex(fr, hex, strlen(hex));
|
||||
tx->chainparams = chainparams;
|
||||
|
||||
/* Find the funding amount */
|
||||
funding_found = false;
|
||||
for (size_t i = 0; i < tx->wtx->num_outputs; i++) {
|
||||
const u8 *output_script = bitcoin_tx_output_get_script(tmpctx, tx, i);
|
||||
asset = bitcoin_tx_output_get_amount(tx, i);
|
||||
|
||||
/* We do not support funding a channel with anything but the
|
||||
* main asset, for now. */
|
||||
if (!amount_asset_is_main(&asset))
|
||||
continue;
|
||||
|
||||
if (scripteq(output_script, placeholder)) {
|
||||
funding = amount_asset_to_sat(&asset);
|
||||
funding_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!funding_found)
|
||||
plugin_err(cmd->plugin, "Error creating placebo funding tx, funding_out not found. %s", hex);
|
||||
|
||||
/* Update funding to actual amount */
|
||||
if (fr->funding_all
|
||||
&& !feature_negotiated(plugin_feature_set(cmd->plugin),
|
||||
fr->their_features, OPT_LARGE_CHANNELS)
|
||||
&& amount_sat_greater(funding, chainparams->max_funding))
|
||||
funding = chainparams->max_funding;
|
||||
|
||||
fr->funding_str = type_to_string(fr, struct amount_sat, &funding);
|
||||
return fundchannel_start(cmd, fr);
|
||||
}
|
||||
|
||||
static struct command_result *exec_dryrun(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req;
|
||||
const jsmntok_t *t;
|
||||
|
||||
/* Stash features so we can wumbo. */
|
||||
t = json_get_member(buf, result, "features");
|
||||
if (!t)
|
||||
plugin_err(cmd->plugin, "No features found in connect response?");
|
||||
fr->their_features = json_tok_bin_from_hex(fr, buf, t);
|
||||
if (!fr->their_features)
|
||||
plugin_err(cmd->plugin, "Bad features '%.*s' in connect response?",
|
||||
t->end - t->start, buf + t->start);
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "txprepare",
|
||||
post_dryrun, forward_error,
|
||||
fr);
|
||||
|
||||
/* Now that we've tried connecting, we do a 'dry-run' of txprepare,
|
||||
* so we can get an accurate idea of the funding amount */
|
||||
txprepare(req->js, fr, placeholder_funding_addr);
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
|
||||
}
|
||||
|
||||
static struct command_result *connect_to_peer(struct command *cmd,
|
||||
struct funding_req *fr)
|
||||
{
|
||||
struct out_req *req = jsonrpc_request_start(cmd->plugin, cmd, "connect",
|
||||
exec_dryrun, forward_error,
|
||||
fr);
|
||||
|
||||
json_add_string(req->js, "id", node_id_to_hexstr(tmpctx, fr->id));
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
/* We will use 'id' and 'amount' to build a output: {id: amount}.
|
||||
* For array type, if we miss 'amount', next parameter will be
|
||||
* mistaken for 'amount'.
|
||||
* Note the check for 'output' in 'txprepare' is behind of the checks
|
||||
* for other parameter, so doing a simply check for 'amount' here can
|
||||
* help us locate error correctly.
|
||||
*/
|
||||
static struct command_result *param_string_check_sat(struct command *cmd, const char *name,
|
||||
const char * buffer, const jsmntok_t *tok,
|
||||
const char **str)
|
||||
{
|
||||
struct command_result *res;
|
||||
struct amount_sat *amount;
|
||||
|
||||
res = param_sat_or_all(cmd, name, buffer, tok, &amount);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
return param_string(cmd, name, buffer, tok, str);
|
||||
}
|
||||
|
||||
static struct command_result *json_fundchannel(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
struct funding_req *fr = tal(cmd, struct funding_req);
|
||||
|
||||
if (!param(cmd, buf, params,
|
||||
p_req("id", param_node_id, &fr->id),
|
||||
p_req("amount", param_string_check_sat, &fr->funding_str),
|
||||
p_opt("feerate", param_string, &fr->feerate_str),
|
||||
p_opt_def("announce", param_bool, &fr->announce_channel, true),
|
||||
p_opt_def("minconf", param_number, &fr->minconf, 1),
|
||||
p_opt("utxos", param_string, &fr->utxo_str),
|
||||
p_opt("push_msat", param_msat, &fr->push_msat),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
fr->funding_all = streq(fr->funding_str, "all");
|
||||
|
||||
return connect_to_peer(cmd, fr);
|
||||
}
|
||||
|
||||
static void init(struct plugin *p,
|
||||
const char *buf UNUSED, const jsmntok_t *config UNUSED)
|
||||
{
|
||||
/* Figure out what the 'placeholder' addr is */
|
||||
const char *network_name;
|
||||
u8 *placeholder = tal_hexdata(tmpctx, placeholder_script, strlen(placeholder_script));
|
||||
|
||||
network_name = rpc_delve(tmpctx, p, "listconfigs",
|
||||
take(json_out_obj(NULL, "config",
|
||||
"network")),
|
||||
".network");
|
||||
chainparams = chainparams_for_network(network_name);
|
||||
placeholder_funding_addr = encode_scriptpubkey_to_addr(NULL, chainparams,
|
||||
placeholder);
|
||||
}
|
||||
|
||||
|
||||
static const struct plugin_command commands[] = { {
|
||||
"fundchannel",
|
||||
"channels",
|
||||
"Fund channel with {id} using {amount} (or 'all'), at optional {feerate}. "
|
||||
"Only use outputs that have {minconf} confirmations.",
|
||||
"Initiaties a channel open with node 'id'. Must "
|
||||
"be connected to the node and have enough funds available at the requested minimum confirmation "
|
||||
"depth (minconf)",
|
||||
json_fundchannel
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
setup_locale();
|
||||
plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL, commands,
|
||||
ARRAY_SIZE(commands), NULL, 0, NULL, 0, NULL);
|
||||
}
|
||||
124
plugins/spender/fundchannel.c
Normal file
124
plugins/spender/fundchannel.c
Normal file
@@ -0,0 +1,124 @@
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <ccan/compiler/compiler.h>
|
||||
#include <common/json.h>
|
||||
#include <common/json_stream.h>
|
||||
#include <plugins/spender/fundchannel.h>
|
||||
|
||||
static struct command_result *
|
||||
json_fundchannel(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params);
|
||||
|
||||
const struct plugin_command fundchannel_commands[] = { {
|
||||
"fundchannel",
|
||||
"channels",
|
||||
"Fund channel with {id} using {amount} (or 'all'), at optional {feerate}. "
|
||||
"Only use outputs that have {minconf} confirmations.",
|
||||
"Initiaties a channel open with node 'id'. Must "
|
||||
"be connected to the node and have enough funds available at the requested minimum confirmation "
|
||||
"depth (minconf)",
|
||||
json_fundchannel
|
||||
}
|
||||
};
|
||||
const size_t num_fundchannel_commands = ARRAY_SIZE(fundchannel_commands);
|
||||
|
||||
static struct command_result *
|
||||
fundchannel_get_result(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
void *nothing UNUSED);
|
||||
|
||||
/* Thin wrapper aroud multifundchannel. */
|
||||
static struct command_result *
|
||||
json_fundchannel(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
const char *id;
|
||||
const jsmntok_t *amount;
|
||||
const jsmntok_t *feerate;
|
||||
const jsmntok_t *announce;
|
||||
const jsmntok_t *minconf;
|
||||
const jsmntok_t *utxos;
|
||||
const jsmntok_t *push_msat;
|
||||
|
||||
struct out_req *req;
|
||||
|
||||
if (!param(cmd, buf, params,
|
||||
p_req("id", param_string, &id),
|
||||
p_req("amount", param_tok, &amount),
|
||||
p_opt("feerate", param_tok, &feerate),
|
||||
p_opt("announce", param_tok, &announce),
|
||||
p_opt("minconf", param_tok, &minconf),
|
||||
p_opt("utxos", param_tok, &utxos),
|
||||
p_opt("push_msat", param_tok, &push_msat),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
req = jsonrpc_request_start(cmd->plugin, cmd, "multifundchannel",
|
||||
&fundchannel_get_result, &forward_error,
|
||||
NULL);
|
||||
|
||||
json_array_start(req->js, "destinations");
|
||||
json_object_start(req->js, NULL);
|
||||
json_add_string(req->js, "id", id);
|
||||
json_add_tok(req->js, "amount", amount, buf);
|
||||
if (announce)
|
||||
json_add_tok(req->js, "announce", announce, buf);
|
||||
if (push_msat)
|
||||
json_add_tok(req->js, "push_msat", push_msat, buf);
|
||||
json_object_end(req->js);
|
||||
json_array_end(req->js);
|
||||
if (feerate)
|
||||
json_add_tok(req->js, "feerate", feerate, buf);
|
||||
if (minconf)
|
||||
json_add_tok(req->js, "minconf", minconf, buf);
|
||||
if (utxos)
|
||||
json_add_tok(req->js, "utxos", utxos, buf);
|
||||
|
||||
return send_outreq(cmd->plugin, req);
|
||||
}
|
||||
|
||||
static struct command_result *
|
||||
fundchannel_get_result(struct command *cmd,
|
||||
const char *buf,
|
||||
const jsmntok_t *result,
|
||||
void *nothing UNUSED)
|
||||
{
|
||||
bool ok;
|
||||
const jsmntok_t *tx;
|
||||
const jsmntok_t *txid;
|
||||
const jsmntok_t *channel_ids_array;
|
||||
const jsmntok_t *channel_ids_obj;
|
||||
const jsmntok_t *channel_id;
|
||||
const jsmntok_t *outnum;
|
||||
|
||||
struct json_stream *out;
|
||||
|
||||
ok = true;
|
||||
tx = ok ? json_get_member(buf, result, "tx") : NULL;
|
||||
ok = ok && tx;
|
||||
txid = ok ? json_get_member(buf, result, "txid") : NULL;
|
||||
ok = ok && txid;
|
||||
channel_ids_array = ok ? json_get_member(buf, result, "channel_ids") : NULL;
|
||||
ok = ok && channel_ids_array;
|
||||
channel_ids_obj = ok ? json_get_arr(channel_ids_array, 0) : NULL;
|
||||
ok = ok && channel_ids_obj;
|
||||
channel_id = ok ? json_get_member(buf, channel_ids_obj, "channel_id") : NULL;
|
||||
ok = ok && channel_id;
|
||||
outnum = ok ? json_get_member(buf, channel_ids_obj, "outnum") : NULL;
|
||||
ok = ok && outnum;
|
||||
|
||||
if (!ok)
|
||||
plugin_err(cmd->plugin,
|
||||
"Unexpected result from nultifundchannel: %.*s",
|
||||
json_tok_full_len(result),
|
||||
json_tok_full(buf, result));
|
||||
|
||||
out = jsonrpc_stream_success(cmd);
|
||||
json_add_tok(out, "tx", tx, buf);
|
||||
json_add_tok(out, "txid", txid, buf);
|
||||
json_add_tok(out, "channel_id", channel_id, buf);
|
||||
json_add_tok(out, "outnum", outnum, buf);
|
||||
return command_finished(cmd, out);
|
||||
}
|
||||
10
plugins/spender/fundchannel.h
Normal file
10
plugins/spender/fundchannel.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef LIGHTNING_PLUGINS_SPENDER_FUNDCHANNEL_H
|
||||
#define LIGHTNING_PLUGINS_SPENDER_FUNDCHANNEL_H
|
||||
#include "config.h"
|
||||
|
||||
#include <plugins/libplugin.h>
|
||||
|
||||
extern const struct plugin_command fundchannel_commands[];
|
||||
extern const size_t num_fundchannel_commands;
|
||||
|
||||
#endif /* LIGHTNING_PLUGINS_SPENDER_FUNDCHANNEL_H */
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <common/utils.h>
|
||||
#include <plugins/libplugin.h>
|
||||
#include <plugins/spender/multiwithdraw.h>
|
||||
#include <plugins/spender/fundchannel.h>
|
||||
#include <plugins/spender/multifundchannel.h>
|
||||
|
||||
/*~ The spender plugin contains various commands that handle
|
||||
@@ -22,6 +23,7 @@ int main(int argc, char **argv)
|
||||
commands = tal_arr(owner, struct plugin_command, 0);
|
||||
|
||||
tal_expand(&commands, multiwithdraw_commands, num_multiwithdraw_commands);
|
||||
tal_expand(&commands, fundchannel_commands, num_fundchannel_commands);
|
||||
tal_expand(&commands, multifundchannel_commands, num_multifundchannel_commands);
|
||||
/* tal_expand(&commands, whatever_commands, num_whatever_commands); */
|
||||
|
||||
|
||||
@@ -2528,11 +2528,13 @@ def test_option_upfront_shutdown_script(node_factory, bitcoind, executor):
|
||||
# Figure out what address it will try to use.
|
||||
keyidx = int(l1.db_query("SELECT intval FROM vars WHERE name='bip32_max_index';")[0]['intval'])
|
||||
|
||||
# Expect 1 for change address, 1 for the channel final address,
|
||||
# which are discarded as the 'scratch' tx that the fundchannel
|
||||
# plugin makes, plus 1 for the funding address of the actual
|
||||
# Expect 1 for change address, plus 1 for the funding address of the actual
|
||||
# funding tx.
|
||||
addr = l1.rpc.call('dev-listaddrs', [keyidx + 3])['addresses'][-1]
|
||||
addr = l1.rpc.call('dev-listaddrs', [keyidx + 2])['addresses'][-1]
|
||||
# the above used to be keyidx + 3, but that was when `fundchannel`
|
||||
# used the `txprepare`-`txdiscard`-`txprepare` trick, which skipped
|
||||
# one address in the discarded tx.
|
||||
# Now we use PSBTs, which means we never discard and skip an address.
|
||||
|
||||
# Now, if we specify upfront and it's OK, all good.
|
||||
l1.stop()
|
||||
|
||||
@@ -784,7 +784,7 @@ def test_funding_fail(node_factory, bitcoind):
|
||||
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
||||
|
||||
# We don't have enough left to cover fees if we try to spend it all.
|
||||
with pytest.raises(RpcError, match=r'Could not afford'):
|
||||
with pytest.raises(RpcError, match=r'not afford'):
|
||||
l1.rpc.fundchannel(l2.info['id'], funds)
|
||||
|
||||
# Should still be connected.
|
||||
|
||||
Reference in New Issue
Block a user