diff --git a/common/addr.c b/common/addr.c index b1eae073e..e7ff17c3a 100644 --- a/common/addr.c +++ b/common/addr.c @@ -36,3 +36,79 @@ char *encode_scriptpubkey_to_addr(const tal_t *ctx, return out; } + +static const char *segwit_addr_net_decode(int *witness_version, + uint8_t *witness_program, + size_t *witness_program_len, + const char *addrz, + const struct chainparams *chainparams) +{ + if (segwit_addr_decode(witness_version, witness_program, + witness_program_len, chainparams->onchain_hrp, + addrz)) + return chainparams->onchain_hrp; + else + return NULL; +} + +bool decode_scriptpubkey_from_addr(const tal_t *ctx, + const struct chainparams *chainparams, + const char *address, + u8 **scriptpubkey) +{ + struct bitcoin_address destination; + int witness_version; + /* segwit_addr_net_decode requires a buffer of size 40, and will + * not write to the buffer if the address is too long, so a buffer + * of fixed size 40 will not overflow. */ + uint8_t witness_program[40]; + size_t witness_program_len; + const char *bech32; + u8 addr_version; + + if (ripemd160_from_base58(&addr_version, &destination.addr, + address, strlen(address))) { + if (addr_version == chainparams->p2pkh_version) { + *scriptpubkey = scriptpubkey_p2pkh(ctx, &destination); + return true; + } else if (addr_version == chainparams->p2sh_version) { + *scriptpubkey = + scriptpubkey_p2sh_hash(ctx, &destination.addr); + return true; + } else { + return false; + } + /* Insert other parsers that accept pointer+len here. */ + return false; + } + + bech32 = segwit_addr_net_decode(&witness_version, witness_program, + &witness_program_len, address, + chainparams); + if (bech32) { + bool witness_ok; + + if (witness_version == 0) { + witness_ok = (witness_program_len == 20 || + witness_program_len == 32); + } else if (witness_version == 1) { + witness_ok = (witness_program_len == 32); + } else { + witness_ok = true; + } + + if (!witness_ok) + return false; + + if (!streq(bech32, chainparams->onchain_hrp)) + return false; + + *scriptpubkey = scriptpubkey_witness_raw(ctx, witness_version, + witness_program, + witness_program_len); + return true; + } + + /* Insert other parsers that accept null-terminated string here. */ + return false; +} diff --git a/common/addr.h b/common/addr.h index 1f8f0ffba..7ab70f501 100644 --- a/common/addr.h +++ b/common/addr.h @@ -8,4 +8,9 @@ char *encode_scriptpubkey_to_addr(const tal_t *ctx, const struct chainparams *chainparams, const u8 *scriptpubkey); +bool decode_scriptpubkey_from_addr(const tal_t *ctx, + const struct chainparams *chainparams, + const char *address, + u8 **scriptpubkey); + #endif /* LIGHTNING_COMMON_ADDR_H */ diff --git a/common/channel_id.h b/common/channel_id.h index 1e1b3c4d5..8ca48628a 100644 --- a/common/channel_id.h +++ b/common/channel_id.h @@ -43,4 +43,7 @@ char *fmt_channel_id(const tal_t *ctx, const struct channel_id *channel_id); void towire_channel_id(u8 **pptr, const struct channel_id *channel_id); bool fromwire_channel_id(const u8 **cursor, size_t *max, struct channel_id *channel_id); + +char *fmt_channel_id(const tal_t *ctx, const struct channel_id *channel_id); + #endif /* LIGHTNING_COMMON_CHANNEL_ID_H */ diff --git a/common/json_param.c b/common/json_param.c index 216451271..413814423 100644 --- a/common/json_param.c +++ b/common/json_param.c @@ -465,6 +465,22 @@ struct command_result *param_string(struct command *cmd, const char *name, return NULL; } +/* Extract a string or a json array */ +struct command_result *param_string_or_array(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + struct str_or_arr **result) +{ + *result = tal(cmd, struct str_or_arr); + (*result)->arr = NULL; + (*result)->str = NULL; + if (tok->type == JSMN_ARRAY) { + (*result)->arr = tok; + return NULL; + } + + return param_string(cmd, name, buffer, tok, &(*result)->str); +} + struct command_result *param_invstring(struct command *cmd, const char *name, const char * buffer, const jsmntok_t *tok, const char **str) diff --git a/common/json_param.h b/common/json_param.h index 07db22f3d..cb06e080a 100644 --- a/common/json_param.h +++ b/common/json_param.h @@ -198,6 +198,17 @@ struct command_result *param_string(struct command *cmd, const char *name, const char * buffer, const jsmntok_t *tok, const char **str); +struct str_or_arr +{ + const char *str; + const jsmntok_t *arr; +}; + +/* Extract a string or a json array */ +struct command_result *param_string_or_array(struct command *cmd, const char *name, + const char * buffer, const jsmntok_t *tok, + struct str_or_arr **result); + /* Extract an invoice string from a generic string, strip the `lightning:` * prefix from it if needed. */ struct command_result *param_invstring(struct command *cmd, const char *name, diff --git a/plugins/Makefile b/plugins/Makefile index 1e2ff2168..efe64d379 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -50,13 +50,15 @@ PLUGIN_SPENDER_SRC := \ plugins/spender/main.c \ plugins/spender/multifundchannel.c \ plugins/spender/multiwithdraw.c \ - plugins/spender/openchannel.c + plugins/spender/openchannel.c \ + plugins/spender/splice.c PLUGIN_SPENDER_HEADER := \ plugins/spender/multifundchannel.h \ plugins/spender/multiwithdraw.h \ plugins/spender/fundchannel.h \ plugins/spender/multifundchannel.h \ - plugins/spender/openchannel.h + plugins/spender/openchannel.h \ + plugins/spender/splice.h PLUGIN_SPENDER_OBJS := $(PLUGIN_SPENDER_SRC:.c=.o) PLUGIN_RECOVER_SRC := plugins/recover.c @@ -153,6 +155,7 @@ PLUGIN_COMMON_OBJS := \ bitcoin/signature.o \ bitcoin/tx.o \ bitcoin/varint.o \ + common/addr.o \ common/amount.o \ common/autodata.o \ common/coin_mvt.o \ @@ -179,6 +182,7 @@ PLUGIN_COMMON_OBJS := \ common/psbt_open.o \ common/pseudorand.o \ common/random_select.o \ + common/splice_script.o \ common/setup.o \ common/status_levels.o \ common/utils.o \ diff --git a/plugins/spender/main.c b/plugins/spender/main.c index 5a94785da..0f4151876 100644 --- a/plugins/spender/main.c +++ b/plugins/spender/main.c @@ -3,6 +3,7 @@ #include #include #include +#include /*~ The spender plugin contains various commands that handle * spending from the onchain wallet. */ @@ -27,6 +28,7 @@ int main(int argc, char **argv) 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, splice_commands, num_splice_commands); /* tal_expand(&commands, whatever_commands, num_whatever_commands); */ notifs = tal_arr(NULL, struct plugin_notification, 0); diff --git a/plugins/spender/splice.c b/plugins/spender/splice.c new file mode 100644 index 000000000..f6963e5e6 --- /dev/null +++ b/plugins/spender/splice.c @@ -0,0 +1,1449 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); diff --git a/plugins/spender/splice.h b/plugins/spender/splice.h new file mode 100644 index 000000000..4f4e1ad2c --- /dev/null +++ b/plugins/spender/splice.h @@ -0,0 +1,59 @@ +#ifndef LIGHTNING_PLUGINS_SPENDER_SPLICE_H +#define LIGHTNING_PLUGINS_SPENDER_SPLICE_H +#include "config.h" + +#include + +extern const struct plugin_command splice_commands[]; +extern const size_t num_splice_commands; + +enum splice_cmd_state { + SPLICE_CMD_NONE = 0, + SPLICE_CMD_INIT, + SPLICE_CMD_UPDATE, + SPLICE_CMD_UPDATE_NEEDS_CHANGES, + SPLICE_CMD_UPDATE_DONE, + SPLICE_CMD_RECVED_SIGS, + SPLICE_CMD_DONE, +}; + +struct splice_cmd_action_state { + enum splice_cmd_state state; +}; + +struct splice_cmd { + /* The plugin-level command. */ + struct command *cmd; + /* Script input by user */ + const char *script; + /* The result of parsing the script or json */ + struct splice_script_result **actions; + /* The states of actions at the same index */ + struct splice_cmd_action_state **states; + /* The active psbt */ + struct wally_psbt *psbt; + /* Output result but don't do any action */ + bool dryrun; + /* Execute the splice and abort at the last moment */ + bool wetrun; + /* Feerate queried from lightningd */ + u32 feerate_per_kw; + /* Override max feerate */ + bool force_feerate; + /* How many wallet inputs have we added to the psbt */ + int wallet_inputs_to_signed; + /* Final result */ + struct bitcoin_txid final_txid; + /* Has the fee been calculated yet */ + bool fee_calculated; + /* The amount of sats provided by the user in the inital psbt */ + struct amount_sat initial_funds; + /* The minimum sats that must go back into the wallet */ + struct amount_sat emergency_sat; + /* A verbose debug log of all the splice states */ + char *debug_log; + /* Counter used for more readable debug logs */ + int debug_counter; +}; + +#endif /* LIGHTNING_PLUGINS_SPENDER_SPLICE_H */