mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-22 14:42:40 +01:00
The command called “splice” can take a json payload or a ‘splice script’, process it into a list of ‘actions’ and then execute those actions. These actions include or will include everything you would want to do with a splice: * Splice into a channel * Splice out of a channel * Fund from wallet * Deposit to wallet * Send funds to bitcoin address Changelog-Added: A new magic “splice” command is added that can take a ‘splice script’ or json payload and perform any complex splice across multiple channels merging the result into a single transaction. Some features are disabled and will be added in time.
1449 lines
43 KiB
C
1449 lines
43 KiB
C
#include "config.h"
|
|
#include <bitcoin/psbt.h>
|
|
#include <bitcoin/script.h>
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/err/err.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <ccan/tal/tal.h>
|
|
#include <common/addr.h>
|
|
#include <common/json_param.h>
|
|
#include <common/json_parse.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/psbt_open.h>
|
|
#include <common/splice_script.h>
|
|
#include <plugins/spender/splice.h>
|
|
|
|
struct abort_pkg {
|
|
struct splice_cmd *splice_cmd;
|
|
enum jsonrpc_errcode code;
|
|
char *str;
|
|
};
|
|
|
|
static void debug_log_to_json(struct json_stream *response,
|
|
const char *debug_log)
|
|
{
|
|
char **lines = tal_strsplit(tmpctx, debug_log, "\n", STR_NO_EMPTY);
|
|
|
|
for (size_t i = 0; lines[i]; i++)
|
|
json_add_string(response, NULL, lines[i]);
|
|
}
|
|
|
|
static struct command_result *make_error(struct command *cmd,
|
|
struct abort_pkg *abort_pkg,
|
|
const char *phase)
|
|
{
|
|
struct splice_cmd *splice_cmd = abort_pkg->splice_cmd;
|
|
char *str = abort_pkg->str;
|
|
struct json_stream *response = jsonrpc_stream_fail(cmd,
|
|
abort_pkg->code,
|
|
str ?: phase);
|
|
|
|
if (splice_cmd->debug_log) {
|
|
json_array_start(response, "log");
|
|
debug_log_to_json(response, splice_cmd->debug_log);
|
|
json_array_end(response);
|
|
}
|
|
|
|
tal_free(abort_pkg);
|
|
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static struct command_result *unreserve_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct abort_pkg *abort_pkg)
|
|
{
|
|
struct splice_cmd *splice_cmd = abort_pkg->splice_cmd;
|
|
struct json_stream *response;
|
|
struct bitcoin_tx *tx;
|
|
u8 *tx_bytes;
|
|
|
|
if (splice_cmd->wetrun) {
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
if (splice_cmd->psbt) {
|
|
json_add_psbt(response, "psbt", splice_cmd->psbt);
|
|
|
|
tx = bitcoin_tx_with_psbt(tmpctx, splice_cmd->psbt);
|
|
tx_bytes = linearize_tx(tmpctx, tx);
|
|
json_add_hex(response, "tx", tx_bytes,
|
|
tal_bytelen(tx_bytes));
|
|
json_add_txid(response, "txid",
|
|
&splice_cmd->final_txid);
|
|
}
|
|
|
|
json_array_start(response, "log");
|
|
debug_log_to_json(response, splice_cmd->debug_log);
|
|
json_array_end(response);
|
|
|
|
tal_free(abort_pkg);
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
return make_error(cmd, abort_pkg, "unreserve_get_result");
|
|
}
|
|
|
|
static struct command_result *abort_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct abort_pkg *abort_pkg)
|
|
{
|
|
struct out_req *req;
|
|
struct splice_cmd *splice_cmd = abort_pkg->splice_cmd;
|
|
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"unreserveinputs(psbt:%p)", splice_cmd->psbt);
|
|
|
|
if (!splice_cmd->psbt)
|
|
return make_error(cmd, abort_pkg, "abort_get_result");
|
|
|
|
req = jsonrpc_request_start(cmd, "unreserveinputs",
|
|
unreserve_get_result, forward_error,
|
|
abort_pkg);
|
|
|
|
json_add_psbt(req->js, "psbt", splice_cmd->psbt);
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *do_fail(struct command *cmd,
|
|
struct splice_cmd *splice_cmd,
|
|
enum jsonrpc_errcode code,
|
|
const char *str TAKES)
|
|
{
|
|
struct out_req *req;
|
|
struct abort_pkg *abort_pkg;
|
|
size_t added;
|
|
|
|
/* If we encounter an error, wetrun is canceled */
|
|
splice_cmd->wetrun = false;
|
|
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"splice_error(psbt:%p, splice_cmd_stat:%p)",
|
|
splice_cmd->psbt, splice_cmd);
|
|
|
|
abort_pkg = tal(cmd->plugin, struct abort_pkg);
|
|
abort_pkg->splice_cmd = tal_steal(abort_pkg, splice_cmd);
|
|
abort_pkg->str = tal_strdup(abort_pkg, str);
|
|
abort_pkg->code = code;
|
|
|
|
req = jsonrpc_request_start(cmd, "abort_channels",
|
|
abort_get_result, forward_error, abort_pkg);
|
|
|
|
added = 0;
|
|
json_array_start(req->js, "channel_ids");
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
if (splice_cmd->actions[i]->channel_id) {
|
|
added++;
|
|
json_add_channel_id(req->js, NULL,
|
|
splice_cmd->actions[i]->channel_id);
|
|
}
|
|
}
|
|
json_array_end(req->js);
|
|
|
|
if (!added) {
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"No channels were stfu'ed, skipping to unreserve"
|
|
" (psbt:%p)", splice_cmd->psbt);
|
|
return abort_get_result(cmd, NULL, NULL, NULL, abort_pkg);
|
|
}
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *splice_error(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *error,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
char *str = tal_fmt(NULL, "%s: %.*s",
|
|
methodname,
|
|
error->end - error->start,
|
|
buf + error->start);
|
|
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS, take(str));
|
|
}
|
|
|
|
struct splice_index_pkg {
|
|
struct splice_cmd *splice_cmd;
|
|
size_t index;
|
|
};
|
|
|
|
static struct command_result *splice_error_pkg(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *error,
|
|
struct splice_index_pkg *pkg)
|
|
{
|
|
return splice_error(cmd, methodname, buf, error, pkg->splice_cmd);
|
|
}
|
|
|
|
static struct command_result *calc_in_ppm_and_fee(struct command *cmd,
|
|
struct splice_cmd *splice_cmd,
|
|
struct amount_sat onchain_fee)
|
|
{
|
|
struct splice_script_result *action;
|
|
struct amount_sat out_sats = splice_cmd->initial_funds;
|
|
bool is_any_paying_fee = false;
|
|
|
|
/* First add all sats going into general fund */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
if (action->pays_fee)
|
|
is_any_paying_fee = true;
|
|
if (!amount_sat_add(&out_sats, out_sats, action->out_sat))
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Unable to add out_sats");
|
|
if (action->out_ppm)
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Unable to resolve out_ppm");
|
|
}
|
|
|
|
/* Now take away all sats being spent by general fund */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
if (!amount_sat_sub(&out_sats, out_sats, action->in_sat))
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Unable to sub out_sats");
|
|
}
|
|
|
|
/* If no one voulenteers to pay the fee, we take it out of the general
|
|
* fund. */
|
|
if (!is_any_paying_fee) {
|
|
if (!amount_sat_sub(&out_sats, out_sats, onchain_fee))
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
tal_fmt(tmpctx,
|
|
"Unable to take onchain fee %s"
|
|
" fromm general funds of %s",
|
|
fmt_amount_sat(tmpctx, onchain_fee),
|
|
fmt_amount_sat(tmpctx, out_sats)));
|
|
}
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
if (action->in_ppm) {
|
|
/* ppm percentage calculation:
|
|
* action->in_sat = out_sats * in_ppm / 1000000 */
|
|
assert(amount_sat_is_zero(action->in_sat));
|
|
if (!amount_sat_mul(&action->in_sat, out_sats, action->in_ppm))
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Unable to mul sats & in_ppm");
|
|
action->in_sat = amount_sat_div(action->in_sat, 1000000);
|
|
action->in_ppm = 0;
|
|
}
|
|
|
|
/* If this item pays the fee, subtract it from either their
|
|
* in_sats or add it to out_sats. */
|
|
if (action->pays_fee && !amount_sat_is_zero(action->in_sat)) {
|
|
if (!amount_sat_sub(&action->in_sat, action->in_sat,
|
|
onchain_fee))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to sub fee from"
|
|
" item in_sat");
|
|
}
|
|
if (action->pays_fee && !amount_sat_is_zero(action->out_sat)) {
|
|
if (!amount_sat_add(&action->out_sat, action->out_sat,
|
|
onchain_fee))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to add fee to"
|
|
" item out_sat");
|
|
}
|
|
}
|
|
|
|
/* validate result */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
if (!action->channel_id)
|
|
continue;
|
|
if (!amount_sat_is_zero(action->in_sat))
|
|
continue;
|
|
if (!amount_sat_is_zero(action->out_sat))
|
|
continue;
|
|
if (!amount_sat_is_zero(action->lease_sat))
|
|
continue;
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Each channel action must include non-zero"
|
|
" in sats, out sats, or lease sats.");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *continue_splice(struct command *cmd,
|
|
struct splice_cmd *splice_cmd);
|
|
|
|
static bool json_to_msat_to_sat(const char *buffer, const jsmntok_t *tok,
|
|
struct amount_sat *sat)
|
|
{
|
|
struct amount_msat msat;
|
|
|
|
if (!json_to_msat(buffer, tok, &msat))
|
|
return false;
|
|
return amount_msat_to_sat(sat, msat);
|
|
}
|
|
|
|
static struct splice_script_result *output_wallet(struct splice_cmd *splice_cmd)
|
|
{
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
struct splice_script_result *action = splice_cmd->actions[i];
|
|
if (!action->onchain_wallet)
|
|
continue;
|
|
if (action->in_ppm || !amount_sat_is_zero(action->in_sat))
|
|
return action;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *addpsbt_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct splice_index_pkg *pkg)
|
|
{
|
|
struct splice_cmd *splice_cmd = pkg->splice_cmd;
|
|
size_t index = pkg->index;
|
|
struct splice_script_result *action = splice_cmd->actions[index];
|
|
const jsmntok_t *tok;
|
|
struct amount_sat excess_sat;
|
|
struct splice_script_result *out_wallet;
|
|
|
|
tal_free(pkg);
|
|
tok = json_get_member(buf, result, "psbt");
|
|
|
|
tal_free(splice_cmd->psbt);
|
|
splice_cmd->psbt = json_to_psbt(splice_cmd, buf, tok);
|
|
assert(splice_cmd->psbt);
|
|
|
|
tok = json_get_member(buf, result, "excess_msat");
|
|
if (tok) {
|
|
if (!json_to_msat_to_sat(buf, tok, &excess_sat))
|
|
return command_fail_badparam(cmd, "addpsbt", buf, tok,
|
|
"invalid excess_msat");
|
|
|
|
if (!amount_sat_is_zero(excess_sat)) {
|
|
if (!amount_sat_add(&action->out_sat, action->out_sat,
|
|
excess_sat))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to add excess sats");
|
|
|
|
out_wallet = output_wallet(splice_cmd);
|
|
if (out_wallet) {
|
|
if (!out_wallet->in_ppm
|
|
&& !amount_sat_add(&out_wallet->in_sat,
|
|
out_wallet->in_sat,
|
|
excess_sat))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to add excess"
|
|
" sats to existing"
|
|
" wallet output");
|
|
}
|
|
else {
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Putting change back into same"
|
|
" wallet outpoint not yet"
|
|
" supported");
|
|
}
|
|
}
|
|
}
|
|
|
|
tok = json_get_member(buf, result, "emergency_sat");
|
|
if (tok) {
|
|
if (!amount_sat_is_zero(splice_cmd->emergency_sat))
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Internal error: two"
|
|
" emergency_sat");
|
|
if (!json_to_msat_to_sat(buf, tok, &splice_cmd->emergency_sat))
|
|
return command_fail_badparam(cmd, "addpsbt", buf, tok,
|
|
"invalid emergency_sat");
|
|
}
|
|
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *onchain_wallet_fund(struct command *cmd,
|
|
struct splice_cmd *splice_cmd,
|
|
size_t index)
|
|
{
|
|
struct splice_script_result *action = splice_cmd->actions[index];
|
|
struct splice_cmd_action_state *state = splice_cmd->states[index];
|
|
struct out_req *req;
|
|
struct splice_index_pkg *pkg;
|
|
const char *command;
|
|
bool addinginputs = !amount_sat_is_zero(action->out_sat);
|
|
|
|
pkg = tal(cmd->plugin, struct splice_index_pkg);
|
|
pkg->splice_cmd = splice_cmd;
|
|
pkg->index = index;
|
|
|
|
command = "addpsbtoutput";
|
|
if (addinginputs) {
|
|
command = "addpsbtinput";
|
|
splice_cmd->wallet_inputs_to_signed++;
|
|
/* DTODO track which specific inputs are added and only sign
|
|
* those */
|
|
}
|
|
|
|
req = jsonrpc_request_start(cmd, command,
|
|
addpsbt_get_result,
|
|
splice_error_pkg, pkg);
|
|
|
|
if (!amount_sat_is_zero(action->out_sat)) {
|
|
json_add_sats(req->js, "satoshi", action->out_sat);
|
|
assert(splice_cmd->feerate_per_kw);
|
|
json_add_u32(req->js, "min_feerate", splice_cmd->feerate_per_kw);
|
|
}
|
|
else {
|
|
json_add_sats(req->js, "satoshi", action->in_sat);
|
|
}
|
|
|
|
json_add_psbt(req->js, "initialpsbt", splice_cmd->psbt);
|
|
json_add_bool(req->js, "add_initiator_serial_ids", true);
|
|
if (addinginputs)
|
|
json_add_bool(req->js, "mark_our_inputs", true);
|
|
|
|
state->state = SPLICE_CMD_DONE;
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *feerate_get_result(struct command *cmd,
|
|
const char *method,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
const jsmntok_t *tok = json_get_member(buf, result, "perkw");
|
|
tok = json_get_member(buf, tok, "opening");
|
|
|
|
if (!json_to_u32(buf, tok, &splice_cmd->feerate_per_kw))
|
|
return command_fail_badparam(cmd, "opening", buf,
|
|
tok, "invalid u32");
|
|
|
|
if (!splice_cmd->feerate_per_kw)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Failed to load a default feerate");
|
|
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"got feerate %"PRIu32" perkw", splice_cmd->feerate_per_kw);
|
|
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *load_feerate(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct out_req *req;
|
|
|
|
req = jsonrpc_request_start(cmd, "feerates",
|
|
feerate_get_result, splice_error,
|
|
splice_cmd);
|
|
|
|
json_add_string(req->js, "style", "perkw");
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static size_t calc_weight(struct splice_cmd *splice_cmd,
|
|
bool simulate_wallet_outputs)
|
|
{
|
|
struct splice_script_result *action;
|
|
struct wally_psbt *psbt = splice_cmd->psbt;
|
|
size_t weight = 0;
|
|
size_t extra_inputs = 0;
|
|
size_t extra_outputs = 0;
|
|
|
|
/* BOLT #2:
|
|
* The rest of the transaction bytes' fees are the responsibility of
|
|
* the peer who contributed that input or output via `tx_add_input` or
|
|
* `tx_add_output`, at the agreed upon `feerate`.
|
|
*/
|
|
for (size_t i = 0; i < psbt->num_inputs; i++)
|
|
weight += psbt_input_get_weight(psbt, i);
|
|
|
|
for (size_t i = 0; i < psbt->num_outputs; i++)
|
|
weight += psbt_output_get_weight(psbt, i);
|
|
|
|
/* Count the splice input & outputs manually */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
if (simulate_wallet_outputs && action->onchain_wallet) {
|
|
if (!amount_sat_is_zero(action->in_sat) || action->in_ppm) {
|
|
weight += bitcoin_tx_output_weight(BITCOIN_SCRIPTPUBKEY_P2TR_LEN);
|
|
extra_outputs++;
|
|
}
|
|
|
|
} else if (splice_cmd->actions[i]->channel_id) {
|
|
weight += bitcoin_tx_output_weight(BITCOIN_SCRIPTPUBKEY_P2WSH_LEN);
|
|
weight += bitcoin_tx_input_weight(true,
|
|
bitcoin_tx_2of2_input_witness_weight());
|
|
extra_inputs++;
|
|
extra_outputs++;
|
|
}
|
|
}
|
|
|
|
/* DTODO make a test to confirm weight calculation is correct */
|
|
|
|
/* BOLT #2:
|
|
* The *initiator* is responsible for paying the fees for the following fields,
|
|
* to be referred to as the `common fields`.
|
|
*
|
|
* - version
|
|
* - segwit marker + flag
|
|
* - input count
|
|
* - output count
|
|
* - locktime
|
|
*/
|
|
weight += bitcoin_tx_core_weight(psbt->num_inputs + extra_inputs,
|
|
psbt->num_outputs + extra_outputs);
|
|
|
|
return weight;
|
|
}
|
|
|
|
static struct command_result *splice_init_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
const jsmntok_t *tok = json_get_member(buf, result, "psbt");
|
|
|
|
tal_free(splice_cmd->psbt);
|
|
splice_cmd->psbt = json_to_psbt(splice_cmd, buf, tok);
|
|
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *splice_init(struct command *cmd,
|
|
struct splice_cmd *splice_cmd,
|
|
size_t index)
|
|
{
|
|
struct splice_script_result *action = splice_cmd->actions[index];
|
|
struct splice_cmd_action_state *state = splice_cmd->states[index];
|
|
struct out_req *req;
|
|
|
|
req = jsonrpc_request_start(cmd, "splice_init",
|
|
splice_init_get_result, splice_error,
|
|
splice_cmd);
|
|
|
|
json_add_channel_id(req->js, "channel_id", action->channel_id);
|
|
if (!amount_sat_is_zero(action->in_sat)) {
|
|
json_add_u64(req->js, "relative_amount",
|
|
action->in_sat.satoshis); /* Raw: signed RPC */
|
|
} else if (!amount_sat_is_zero(action->out_sat)) {
|
|
json_add_string(req->js, "relative_amount",
|
|
tal_fmt(req->js, "-%"PRIu64,
|
|
action->out_sat.satoshis)); /* Raw: signed RPC */
|
|
} else {
|
|
json_add_sats(req->js, "relative_amount", amount_sat(0));
|
|
}
|
|
json_add_psbt(req->js, "initialpsbt", splice_cmd->psbt);
|
|
json_add_u32(req->js, "feerate_per_kw", splice_cmd->feerate_per_kw);
|
|
json_add_bool(req->js, "skip_stfu", true);
|
|
json_add_bool(req->js, "force_feerate", splice_cmd->force_feerate);
|
|
|
|
state->state = SPLICE_CMD_INIT;
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *splice_update_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct splice_index_pkg *pkg)
|
|
{
|
|
size_t index = pkg->index;
|
|
struct splice_cmd *splice_cmd = pkg->splice_cmd;
|
|
struct splice_cmd_action_state *state = splice_cmd->states[index];
|
|
const jsmntok_t *tok;
|
|
struct wally_psbt *psbt;
|
|
enum splice_cmd_state old_state = state->state;
|
|
bool got_sigs;
|
|
|
|
tal_free(pkg);
|
|
|
|
/* DTODO: juggle serial ids correctly for cross-channel splice */
|
|
tok = json_get_member(buf, result, "psbt");
|
|
psbt = json_to_psbt(splice_cmd, buf, tok);
|
|
|
|
if (psbt_contribs_changed(splice_cmd->psbt, psbt))
|
|
for (size_t i = 0; i < tal_count(splice_cmd->states); i++)
|
|
if (splice_cmd->actions[i]->channel_id)
|
|
splice_cmd->states[i]->state = SPLICE_CMD_UPDATE_NEEDS_CHANGES;
|
|
|
|
assert(psbt);
|
|
tal_free(splice_cmd->psbt);
|
|
splice_cmd->psbt = tal_steal(splice_cmd, psbt);
|
|
|
|
tok = json_get_member(buf, result, "signatures_secured");
|
|
if (!json_to_bool(buf, tok, &got_sigs))
|
|
return command_fail_badparam(cmd, "signatures_secured", buf,
|
|
tok, "invalid bool");
|
|
|
|
if (old_state != SPLICE_CMD_UPDATE)
|
|
state->state = SPLICE_CMD_UPDATE;
|
|
else
|
|
state->state = got_sigs ? SPLICE_CMD_RECVED_SIGS : SPLICE_CMD_UPDATE_DONE;
|
|
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *splice_update(struct command *cmd,
|
|
struct splice_cmd *splice_cmd,
|
|
size_t index)
|
|
{
|
|
struct splice_script_result *action = splice_cmd->actions[index];
|
|
struct out_req *req;
|
|
struct splice_index_pkg *pkg = tal(cmd->plugin, struct splice_index_pkg);
|
|
|
|
pkg->splice_cmd = splice_cmd;
|
|
pkg->index = index;
|
|
|
|
plugin_log(cmd->plugin, LOG_DBG,
|
|
"splice_update(channel_id:%s)",
|
|
fmt_channel_id(tmpctx, action->channel_id));
|
|
|
|
req = jsonrpc_request_start(cmd, "splice_update",
|
|
splice_update_get_result, splice_error_pkg,
|
|
pkg);
|
|
|
|
json_add_channel_id(req->js, "channel_id", action->channel_id);
|
|
json_add_psbt(req->js, "psbt", splice_cmd->psbt);
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *signpsbt_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
const jsmntok_t *tok = json_get_member(buf, result, "signed_psbt");
|
|
struct channel_id *channel_ids;
|
|
|
|
tal_free(splice_cmd->psbt);
|
|
|
|
splice_cmd->psbt = json_to_psbt(splice_cmd, buf, tok);
|
|
splice_cmd->wallet_inputs_to_signed = 0;
|
|
|
|
/* After signing we add channel_ids to the PSBT for splice_signed */
|
|
channel_ids = tal_arr(NULL, struct channel_id, 0);
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++)
|
|
if (splice_cmd->actions[i]->channel_id)
|
|
tal_arr_expand(&channel_ids,
|
|
*splice_cmd->actions[i]->channel_id);
|
|
|
|
psbt_set_channel_ids(splice_cmd->psbt, channel_ids);
|
|
tal_free(channel_ids);
|
|
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *signpsbt(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct out_req *req;
|
|
size_t num_to_be_signed;
|
|
|
|
req = jsonrpc_request_start(cmd, "signpsbt",
|
|
signpsbt_get_result, splice_error,
|
|
splice_cmd);
|
|
|
|
/* Use input markers to identify which inputs
|
|
* are ours, only sign those */
|
|
json_array_start(req->js, "signonly");
|
|
num_to_be_signed = 0;
|
|
for (size_t i = 0; i < splice_cmd->psbt->num_inputs; i++) {
|
|
if (psbt_input_is_ours(&splice_cmd->psbt->inputs[i])) {
|
|
json_add_num(req->js, NULL, i);
|
|
num_to_be_signed++;
|
|
}
|
|
}
|
|
json_array_end(req->js);
|
|
|
|
json_add_psbt(req->js, "psbt", splice_cmd->psbt);
|
|
|
|
/* If we have no inputs to be signed, skip ahead */
|
|
if (!num_to_be_signed) {
|
|
splice_cmd->wallet_inputs_to_signed = 0;
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct splice_script_result *requires_our_sigs(struct splice_cmd *splice_cmd,
|
|
size_t *index,
|
|
bool *multiple_require_sigs)
|
|
{
|
|
struct splice_script_result *action = NULL;
|
|
*index = UINT32_MAX;
|
|
*multiple_require_sigs = false;
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
if (splice_cmd->states[i]->state == SPLICE_CMD_UPDATE_DONE) {
|
|
/* There can only be one node that requires our sigs */
|
|
if (action) {
|
|
*multiple_require_sigs = true;
|
|
return NULL;
|
|
}
|
|
action = splice_cmd->actions[i];
|
|
*index = i;
|
|
}
|
|
}
|
|
return action;
|
|
}
|
|
|
|
static struct command_result *splice_signed_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *result,
|
|
struct splice_index_pkg *pkg)
|
|
{
|
|
size_t index = pkg->index;
|
|
struct splice_cmd *splice_cmd = pkg->splice_cmd;
|
|
const jsmntok_t *tok;
|
|
|
|
tal_free(pkg);
|
|
|
|
tok = json_get_member(buf, result, "psbt");
|
|
tal_free(splice_cmd->psbt);
|
|
splice_cmd->psbt = json_to_psbt(splice_cmd, buf, tok);
|
|
|
|
tok = json_get_member(buf, result, "txid");
|
|
if (!json_to_txid(buf, tok, &splice_cmd->final_txid))
|
|
return command_fail_badparam(cmd, "txid", buf,
|
|
tok, "invalid txid");
|
|
|
|
splice_cmd->states[index]->state = SPLICE_CMD_DONE;
|
|
|
|
return continue_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *splice_signed(struct command *cmd,
|
|
struct splice_cmd *splice_cmd,
|
|
size_t index)
|
|
{
|
|
struct splice_script_result *action = splice_cmd->actions[index];
|
|
struct out_req *req;
|
|
struct splice_index_pkg *pkg;
|
|
|
|
pkg = tal(cmd->plugin, struct splice_index_pkg);
|
|
pkg->splice_cmd = splice_cmd;
|
|
pkg->index = index;
|
|
|
|
req = jsonrpc_request_start(cmd, "splice_signed",
|
|
splice_signed_get_result, splice_error_pkg,
|
|
pkg);
|
|
|
|
json_add_channel_id(req->js, "channel_id", action->channel_id);
|
|
json_add_psbt(req->js, "psbt", splice_cmd->psbt);
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *check_emergency_sat(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct amount_sat to_wallet = AMOUNT_SAT(0);
|
|
if (amount_sat_is_zero(splice_cmd->emergency_sat))
|
|
return NULL;
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
struct splice_script_result *action = splice_cmd->actions[i];
|
|
if (action->onchain_wallet)
|
|
if (!amount_sat_add(&to_wallet, to_wallet,
|
|
action->in_sat))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to amount_sat_add"
|
|
" wallet amounts for"
|
|
" emergency_sat calc");
|
|
}
|
|
|
|
if (!amount_sat_greater_eq(to_wallet, splice_cmd->emergency_sat))
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
tal_fmt(tmpctx,
|
|
"Amount going to onchain wallet %s is"
|
|
" not enough to meet the emergency"
|
|
" minimum of %s",
|
|
fmt_amount_sat(tmpctx, to_wallet),
|
|
fmt_amount_sat(tmpctx, splice_cmd->emergency_sat)));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *cmd_state_string(enum splice_cmd_state state)
|
|
{
|
|
switch (state) {
|
|
case SPLICE_CMD_NONE:
|
|
return " ";
|
|
case SPLICE_CMD_INIT:
|
|
return " INIT ";
|
|
case SPLICE_CMD_UPDATE:
|
|
return " UPDATE ";
|
|
case SPLICE_CMD_UPDATE_NEEDS_CHANGES:
|
|
return "UPDATE_NEEDS_CHANGES";
|
|
case SPLICE_CMD_UPDATE_DONE:
|
|
return " UPDATE_DONE ";
|
|
case SPLICE_CMD_RECVED_SIGS:
|
|
return " RECVED_SIGS ";
|
|
case SPLICE_CMD_DONE:
|
|
return " DONE ";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void add_to_debug_log(struct splice_cmd *scmd, const char *phase)
|
|
{
|
|
char **log = &scmd->debug_log;
|
|
if (!*log)
|
|
return;
|
|
|
|
tal_append_fmt(log, "#%d: (%s)\n", ++scmd->debug_counter, phase);
|
|
|
|
for (size_t i = 0; i < tal_count(scmd->actions); i++) {
|
|
struct splice_script_result *action = scmd->actions[i];
|
|
struct splice_cmd_action_state *state = scmd->states[i];
|
|
|
|
tal_append_fmt(log, "[%s] %s\n",
|
|
cmd_state_string(state->state),
|
|
splice_to_string(tmpctx, action));
|
|
}
|
|
}
|
|
|
|
static struct command_result *handle_wetrun(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct out_req *req;
|
|
struct abort_pkg *abort_pkg;
|
|
size_t added;
|
|
|
|
abort_pkg = tal(cmd->plugin, struct abort_pkg);
|
|
abort_pkg->splice_cmd = tal_steal(abort_pkg, splice_cmd);
|
|
abort_pkg->str = NULL;
|
|
|
|
req = jsonrpc_request_start(cmd, "abort_channels",
|
|
abort_get_result, forward_error, abort_pkg);
|
|
|
|
added = 0;
|
|
json_array_start(req->js, "channel_ids");
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
if (splice_cmd->actions[i]->channel_id) {
|
|
added++;
|
|
json_add_channel_id(req->js, NULL,
|
|
splice_cmd->actions[i]->channel_id);
|
|
}
|
|
}
|
|
json_array_end(req->js);
|
|
|
|
if (!added)
|
|
return unreserve_get_result(cmd, NULL, NULL, NULL, abort_pkg);
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *continue_splice(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct splice_script_result *action;
|
|
struct splice_cmd_action_state *state;
|
|
struct command_result *result;
|
|
size_t index;
|
|
size_t weight;
|
|
struct amount_sat onchain_fee;
|
|
bool multiple_require_sigs;
|
|
|
|
add_to_debug_log(splice_cmd, "continue_splice");
|
|
|
|
if (!splice_cmd->feerate_per_kw)
|
|
return load_feerate(cmd, splice_cmd);
|
|
|
|
/* On first pass we add wallet actions that contribute funds */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
if (state->state != SPLICE_CMD_NONE)
|
|
continue;
|
|
if (splice_cmd->actions[i]->onchain_wallet
|
|
&& !amount_sat_is_zero(splice_cmd->actions[i]->out_sat)) {
|
|
state->state = SPLICE_CMD_DONE;
|
|
return onchain_wallet_fund(cmd, splice_cmd, i);
|
|
}
|
|
}
|
|
|
|
if (!splice_cmd->fee_calculated) {
|
|
splice_cmd->fee_calculated = true;
|
|
|
|
/* We calculate the weight simulator wallet outputs */
|
|
weight = calc_weight(splice_cmd, true);
|
|
onchain_fee = amount_tx_fee(splice_cmd->feerate_per_kw, weight);
|
|
|
|
plugin_log(cmd->plugin, LOG_INFORM,
|
|
"Splice fee is %s at %"PRIu32" perkw (%.02f sat/vB) "
|
|
"on tx where our personal vbytes are %.02f",
|
|
fmt_amount_sat(tmpctx, onchain_fee),
|
|
splice_cmd->feerate_per_kw,
|
|
4 * splice_cmd->feerate_per_kw / 1000.0f,
|
|
weight / 4.0f);
|
|
|
|
result = calc_in_ppm_and_fee(cmd, splice_cmd, onchain_fee);
|
|
if (result)
|
|
return result;
|
|
}
|
|
|
|
/* Only after fee calcualtion can we add wallet actions taking funds */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
if (state->state != SPLICE_CMD_NONE)
|
|
continue;
|
|
if (splice_cmd->actions[i]->onchain_wallet
|
|
&& !amount_sat_is_zero(splice_cmd->actions[i]->in_sat)) {
|
|
state->state = SPLICE_CMD_DONE;
|
|
return onchain_wallet_fund(cmd, splice_cmd, i);
|
|
}
|
|
}
|
|
|
|
result = check_emergency_sat(cmd, splice_cmd);
|
|
if (result)
|
|
return result;
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
if (state->state != SPLICE_CMD_NONE)
|
|
continue;
|
|
if (!action->channel_id)
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Internal error; should not get"
|
|
" here with non-channels with state"
|
|
" NONE");
|
|
return splice_init(cmd, splice_cmd, i);
|
|
}
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
if (state->state == SPLICE_CMD_INIT
|
|
|| state->state == SPLICE_CMD_UPDATE_NEEDS_CHANGES)
|
|
return splice_update(cmd, splice_cmd, i);
|
|
}
|
|
|
|
/* It is possible to receive a signature when we do splice_update with
|
|
* no changes. Therefore wetrun must abort here to prevent any of our
|
|
* peers locking up funds */
|
|
if (splice_cmd->wetrun)
|
|
return handle_wetrun(cmd, splice_cmd);
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
if (state->state == SPLICE_CMD_UPDATE)
|
|
return splice_update(cmd, splice_cmd, i);
|
|
}
|
|
|
|
/* The signpsbt operation also adds channel_ids to psbt */
|
|
if (splice_cmd->wallet_inputs_to_signed)
|
|
return signpsbt(cmd, splice_cmd);
|
|
|
|
if (requires_our_sigs(splice_cmd, &index, &multiple_require_sigs))
|
|
return splice_signed(cmd, splice_cmd, index);
|
|
|
|
if (multiple_require_sigs)
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Requested splice is impossible because multiple"
|
|
" peers demand they do not sign first. Someone"
|
|
" must sign first.");
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
if (i != index && state->state == SPLICE_CMD_RECVED_SIGS)
|
|
return splice_signed(cmd, splice_cmd, i);
|
|
}
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++)
|
|
assert(splice_cmd->states[i]->state == SPLICE_CMD_DONE);
|
|
|
|
add_to_debug_log(splice_cmd, "continue_splice-finished");
|
|
|
|
struct json_stream *response = jsonrpc_stream_success(cmd);
|
|
json_add_psbt(response, "psbt", splice_cmd->psbt);
|
|
json_add_txid(response, "txid", &splice_cmd->final_txid);
|
|
if (splice_cmd->debug_log) {
|
|
json_array_start(response, "log");
|
|
debug_log_to_json(response, splice_cmd->debug_log);
|
|
json_array_end(response);
|
|
}
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static struct command_result *execute_splice(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct splice_script_result *action;
|
|
struct splice_cmd_action_state *state;
|
|
struct wally_psbt_output *output;
|
|
u64 serial_id;
|
|
int pays_fee;
|
|
u8 *scriptpubkey;
|
|
|
|
/* Basic validation */
|
|
pays_fee = 0;
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
int dest_count = 0;
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
|
|
if (splice_cmd->actions[i]->out_ppm)
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Should be no out_ppm on final");
|
|
if (splice_cmd->actions[i]->pays_fee) {
|
|
if (pays_fee)
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Only one item may pay fee");
|
|
pays_fee++;
|
|
}
|
|
if (splice_cmd->actions[i]->channel_id)
|
|
dest_count++;
|
|
if (splice_cmd->actions[i]->bitcoin_address)
|
|
dest_count++;
|
|
if (splice_cmd->actions[i]->onchain_wallet)
|
|
dest_count++;
|
|
if (dest_count < 1)
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Must specify 1 destination per");
|
|
if (dest_count > 1)
|
|
return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Too many destinations per");
|
|
|
|
/* If user specifies both sats in and out, we just use the
|
|
* larger of the two and subtract the smaller. */
|
|
if (amount_sat_greater(action->in_sat, action->out_sat)) {
|
|
if (!amount_sat_sub(&action->in_sat, action->in_sat,
|
|
action->out_sat))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to sub out_sat from"
|
|
" in_sat");
|
|
action->out_sat = amount_sat(0);
|
|
} else {
|
|
if (!amount_sat_sub(&action->out_sat, action->out_sat,
|
|
action->in_sat))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Unable to sub in_sat from"
|
|
" out_sat");
|
|
action->in_sat = amount_sat(0);
|
|
}
|
|
}
|
|
|
|
add_to_debug_log(splice_cmd, "execute_splice");
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
state = splice_cmd->states[i];
|
|
char *bitcoin_address;
|
|
|
|
/* Load (only one) feerate if user provided one */
|
|
if (action->feerate_per_kw) {
|
|
if (splice_cmd->feerate_per_kw)
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Only one item may set"
|
|
" feerate");
|
|
splice_cmd->feerate_per_kw = action->feerate_per_kw;
|
|
}
|
|
|
|
/* Fund out to bitcoin address */
|
|
if (action->bitcoin_address) {
|
|
if (!amount_sat_is_zero(action->in_sat))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Cannot fund from bitcoin"
|
|
" address");
|
|
if (!decode_scriptpubkey_from_addr(cmd, chainparams,
|
|
action->bitcoin_address,
|
|
&scriptpubkey))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Bitcoin address"
|
|
" unrecognized");
|
|
|
|
/* Reencode scriptpubkey to addr for verification */
|
|
bitcoin_address = encode_scriptpubkey_to_addr(tmpctx,
|
|
chainparams,
|
|
scriptpubkey);
|
|
if (!bitcoin_address)
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Bitcoin scriptpubkey failed"
|
|
" reencoding for address");
|
|
|
|
if (!strcmp(bitcoin_address, action->bitcoin_address))
|
|
return do_fail(cmd, splice_cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Bitcoin scriptpubkey failed"
|
|
" validation for address");
|
|
|
|
output = psbt_append_output(splice_cmd->psbt,
|
|
scriptpubkey,
|
|
action->in_sat);
|
|
|
|
/* DTODO: support dynamic address payouts (percent) */
|
|
|
|
serial_id = psbt_new_output_serial(splice_cmd->psbt,
|
|
TX_INITIATOR);
|
|
psbt_output_set_serial_id(splice_cmd->psbt, output,
|
|
serial_id);
|
|
|
|
state->state = SPLICE_CMD_DONE;
|
|
|
|
add_to_debug_log(splice_cmd,
|
|
"execute_splice-load_btcaddress");
|
|
}
|
|
}
|
|
|
|
return continue_splice(cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *adjust_pending_out_ppm(struct splice_script_result **actions,
|
|
struct channel_id channel_id,
|
|
struct amount_sat available_funds,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
for (size_t i = 0; i < tal_count(actions); i++) {
|
|
if (!actions[i]->channel_id)
|
|
continue;
|
|
if (!channel_id_eq(actions[i]->channel_id, &channel_id))
|
|
continue;
|
|
/* Skip channels not using out_ppm */
|
|
if (!actions[i]->out_ppm)
|
|
continue;
|
|
|
|
/* For now max (asterisks) means 100% but that may change in the
|
|
* future */
|
|
if (actions[i]->out_ppm == UINT32_MAX)
|
|
actions[i]->out_ppm = 1000000;
|
|
|
|
/* ppm percentage calculation:
|
|
* action->out_sat = available_funds * out_ppm / 1000000 */
|
|
if (!amount_sat_mul(&actions[i]->out_sat, available_funds,
|
|
actions[i]->out_ppm))
|
|
return command_fail(splice_cmd->cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Unable to mul sats(%s) &"
|
|
" out_ppm(%"PRIu32") for channel id"
|
|
" %s",
|
|
fmt_amount_sat(tmpctx, available_funds),
|
|
actions[i]->out_ppm,
|
|
fmt_channel_id(tmpctx, &channel_id));
|
|
actions[i]->out_sat = amount_sat_div(actions[i]->out_sat,
|
|
1000000);
|
|
actions[i]->out_ppm = 0;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *stfu_channels_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *toks,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
const jsmntok_t *jchannels, *jchannel;
|
|
size_t i;
|
|
const char *err;
|
|
struct command_result *result;
|
|
|
|
jchannels = json_get_member(buf, toks, "channels");
|
|
json_for_each_arr(i, jchannel, jchannels) {
|
|
struct channel_id channel_id;
|
|
struct amount_sat sat;
|
|
|
|
memset(&channel_id, 0, sizeof(channel_id));
|
|
memset(&sat, 0, sizeof(sat));
|
|
|
|
err = json_scan(tmpctx, buf, jchannel,
|
|
"{channel_id?:%,available_msat?:%}",
|
|
JSON_SCAN(json_to_channel_id, &channel_id),
|
|
JSON_SCAN(json_to_msat_to_sat, &sat));
|
|
if (err)
|
|
errx(1, "Bad stfu_channels.channels %zu: %s",
|
|
i, err);
|
|
|
|
result = adjust_pending_out_ppm(splice_cmd->actions,
|
|
channel_id, sat, splice_cmd);
|
|
if (result)
|
|
return result;
|
|
}
|
|
|
|
return execute_splice(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *splice_dryrun(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
char **lines;
|
|
unsigned int i;
|
|
struct json_stream *response;
|
|
const char *str;
|
|
|
|
response = jsonrpc_stream_success(cmd);
|
|
json_array_start(response, "dryrun");
|
|
|
|
str = splicearr_to_string(response, splice_cmd->actions);
|
|
lines = tal_strsplit(response, take(str), "\n", STR_NO_EMPTY);
|
|
for (i = 0; lines[i] != NULL; i++)
|
|
json_add_string(response, NULL, lines[i]);
|
|
json_array_end(response);
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
static struct command_result *handle_splice_cmd(struct command *cmd,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct out_req *req;
|
|
|
|
if (splice_cmd->dryrun)
|
|
return splice_dryrun(cmd, splice_cmd);
|
|
|
|
req = jsonrpc_request_start(cmd, "stfu_channels",
|
|
stfu_channels_get_result,
|
|
splice_error, splice_cmd);
|
|
|
|
json_array_start(req->js, "channel_ids");
|
|
/* We begin by stfu'ing and getting available balance on all MAX reqs */
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++)
|
|
if (splice_cmd->actions[i]->channel_id)
|
|
json_add_channel_id(req->js, NULL,
|
|
splice_cmd->actions[i]->channel_id);
|
|
json_array_end(req->js);
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
static struct command_result *
|
|
validate_splice_cmd(struct splice_cmd *splice_cmd)
|
|
{
|
|
struct splice_script_result *action;
|
|
int paying_fee_count = 0;
|
|
int channels = 0;
|
|
for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) {
|
|
action = splice_cmd->actions[i];
|
|
/* Taking fee from onchain wallet requires recursive looping
|
|
* since adding more funds adds more input bytes. We don't
|
|
* support it for now. */
|
|
if (action->pays_fee && action->onchain_wallet
|
|
&& action->out_ppm)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Don't support dynamic fee being"
|
|
" added to onchain wallet");
|
|
if (action->onchain_wallet && action->out_ppm)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Don't support dynamic wallet"
|
|
" funding amounts for now");
|
|
if (action->pays_fee && action->onchain_wallet
|
|
&& !amount_sat_is_zero(action->out_sat))
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Don't support wallet funding"
|
|
" being used for fee");
|
|
if (action->pays_fee) {
|
|
if (paying_fee_count)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Only one item may pay the"
|
|
" fee");
|
|
paying_fee_count++;
|
|
}
|
|
if (action->bitcoin_address && action->in_ppm)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Dynamic bitcoin address amounts"
|
|
" not supported for now");
|
|
if (action->channel_id) {
|
|
if (channels)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Multi-channel splice not"
|
|
"supported for now");
|
|
channels++;
|
|
}
|
|
if (action->bitcoin_address)
|
|
return command_fail(splice_cmd->cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Paying out to bitcoin addresses"
|
|
" not supported for now.");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct command_result *listpeerchannels_get_result(struct command *cmd,
|
|
const char *methodname,
|
|
const char *buf,
|
|
const jsmntok_t *toks,
|
|
struct splice_cmd *splice_cmd)
|
|
{
|
|
struct splice_script_error *error;
|
|
struct splice_script_chan **channels;
|
|
struct command_result *result;
|
|
const jsmntok_t *jchannels, *jchannel;
|
|
char **lines;
|
|
struct json_stream *response;
|
|
const char *str;
|
|
size_t i;
|
|
const char *err;
|
|
|
|
channels = tal_arr(tmpctx, struct splice_script_chan*, 0);
|
|
jchannels = json_get_member(buf, toks, "channels");
|
|
json_for_each_arr(i, jchannel, jchannels) {
|
|
tal_arr_expand(&channels, tal(channels,
|
|
struct splice_script_chan));
|
|
|
|
err = json_scan(tmpctx, buf, jchannel,
|
|
"{peer_id?:%,channel_id?:%}",
|
|
JSON_SCAN(json_to_node_id,
|
|
&channels[i]->node_id),
|
|
JSON_SCAN(json_to_channel_id,
|
|
&channels[i]->chan_id));
|
|
if (err)
|
|
errx(1, "Bad listpeerchannels.channels %zu: %s",
|
|
i, err);
|
|
}
|
|
|
|
if (splice_cmd->script) {
|
|
error = parse_splice_script(splice_cmd, splice_cmd->script,
|
|
channels, &splice_cmd->actions);
|
|
if (error) {
|
|
response = jsonrpc_stream_fail(cmd,
|
|
JSONRPC2_INVALID_PARAMS,
|
|
"Splice script compile"
|
|
" failed");
|
|
|
|
json_array_start(response, "compiler_error");
|
|
|
|
str = fmt_splice_script_compiler_error(response,
|
|
splice_cmd->script,
|
|
error);
|
|
lines = tal_strsplit(response, take(str), "\n",
|
|
STR_NO_EMPTY);
|
|
for (i = 0; lines[i] != NULL; i++)
|
|
json_add_string(response, NULL, lines[i]);
|
|
json_array_end(response);
|
|
return command_finished(cmd, response);
|
|
}
|
|
|
|
splice_cmd->states = tal_arr(splice_cmd,
|
|
struct splice_cmd_action_state*,
|
|
tal_count(splice_cmd->actions));
|
|
|
|
for (i = 0; i < tal_count(splice_cmd->states); i++) {
|
|
splice_cmd->states[i] = tal(splice_cmd->states,
|
|
struct splice_cmd_action_state);
|
|
splice_cmd->states[i]->state = SPLICE_CMD_NONE;
|
|
}
|
|
}
|
|
|
|
assert(splice_cmd->actions);
|
|
|
|
result = validate_splice_cmd(splice_cmd);
|
|
if (result)
|
|
return result;
|
|
|
|
return handle_splice_cmd(splice_cmd->cmd, splice_cmd);
|
|
}
|
|
|
|
static struct command_result *
|
|
json_splice(struct command *cmd, const char *buf, const jsmntok_t *params)
|
|
{
|
|
struct out_req *req;
|
|
const char *script;
|
|
const jsmntok_t *json;
|
|
struct wally_psbt *psbt;
|
|
bool *dryrun, *force_feerate, *debug_log, *wetrun;
|
|
struct str_or_arr *str_or_arr;
|
|
|
|
if (!param(cmd, buf, params,
|
|
p_opt("script_or_json", param_string_or_array, &str_or_arr),
|
|
p_opt_def("dryrun", param_bool, &dryrun, false),
|
|
p_opt_def("force_feerate", param_bool, &force_feerate,
|
|
false),
|
|
p_opt_def("debug_log", param_bool, &debug_log, false),
|
|
p_opt_dev("dev-wetrun", param_bool, &wetrun, false),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
if (!str_or_arr)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"Must pass 'script_or_json'");
|
|
|
|
script = str_or_arr->str;
|
|
json = str_or_arr->arr;
|
|
|
|
psbt = create_psbt(cmd, 0, 0, 0);
|
|
|
|
struct splice_cmd *splice_cmd = tal(cmd, struct splice_cmd);
|
|
|
|
splice_cmd->cmd = cmd;
|
|
splice_cmd->script = tal_steal(splice_cmd, script);
|
|
splice_cmd->psbt = tal_steal(splice_cmd, psbt);
|
|
splice_cmd->dryrun = *dryrun;
|
|
splice_cmd->wetrun = *wetrun;
|
|
splice_cmd->feerate_per_kw = 0;
|
|
splice_cmd->force_feerate = *force_feerate;
|
|
splice_cmd->wallet_inputs_to_signed = 0;
|
|
splice_cmd->fee_calculated = false;
|
|
splice_cmd->initial_funds = AMOUNT_SAT(0);
|
|
splice_cmd->emergency_sat = AMOUNT_SAT(0);
|
|
splice_cmd->debug_log = *debug_log ? tal_strdup(splice_cmd, "") : NULL;
|
|
splice_cmd->debug_counter = 0;
|
|
memset(&splice_cmd->final_txid, 0, sizeof(splice_cmd->final_txid));
|
|
|
|
/* If script validates as json, parse it as json instead */
|
|
if (json) {
|
|
if (!json_to_splice(splice_cmd, buf, json,
|
|
&splice_cmd->actions))
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"splice json failed validation");
|
|
|
|
splice_cmd->states = tal_arr(splice_cmd,
|
|
struct splice_cmd_action_state*,
|
|
tal_count(splice_cmd->actions));
|
|
|
|
for (size_t i = 0; i < tal_count(splice_cmd->states); i++) {
|
|
splice_cmd->states[i] = tal(splice_cmd->states,
|
|
struct splice_cmd_action_state);
|
|
splice_cmd->states[i]->state = SPLICE_CMD_NONE;
|
|
}
|
|
}
|
|
|
|
req = jsonrpc_request_start(cmd, "listpeerchannels",
|
|
listpeerchannels_get_result,
|
|
splice_error, splice_cmd);
|
|
|
|
return send_outreq(req);
|
|
}
|
|
|
|
const struct plugin_command splice_commands[] = {
|
|
{
|
|
"dev-splice",
|
|
json_splice
|
|
},
|
|
};
|
|
const size_t num_splice_commands = ARRAY_SIZE(splice_commands);
|