diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4dda50516041..2fc9fbb92405 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,6 +8,7 @@ wallet/ @cdecker *.py @cdecker wallet/invoices.* @ZmnSCPxj +plugins/multifundchannel.c @ZmnSCPxj common/param.* @wythe common/json.* @wythe diff --git a/bitcoin/psbt.h b/bitcoin/psbt.h index 70551afdff51..2047ee8ee97f 100644 --- a/bitcoin/psbt.h +++ b/bitcoin/psbt.h @@ -17,6 +17,16 @@ struct bitcoin_signature; struct bitcoin_txid; struct pubkey; +/** psbt_destroy - Destroy a PSBT that is not tal-allocated + * + * @psbt - the PSBT to destroy + * + * WARNING Do NOT call this function directly if you got the + * PSBT from create_psbt, new_psbt, psbt_from_bytes, + * psbt_from_b64, or fromwire_wally_psbt. + * Those functions register this function as a `tal_destructor` + * automatically. + */ void psbt_destroy(struct wally_psbt *psbt); /** diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index a88d866acfad..2cf9df54575e 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -846,6 +846,28 @@ def listsendpays(self, bolt11=None, payment_hash=None): } return self.call("listsendpays", payload) + def multifundchannel(self, destinations, feerate=None, minconf=None, utxos=None, minchannels=None, **kwargs): + """ + Fund channels to an array of {destinations}, + each entry of which is a dict of node {id} + and {amount} to fund, and optionally whether + to {announce} and how much {push_msat} to + give outright to the node. + You may optionally specify {feerate}, + {minconf} depth, and the {utxos} set to use + for the single transaction that funds all + the channels. + """ + payload = { + "destinations": destinations, + "feerate": feerate, + "minconf": minconf, + "utxos": utxos, + "minchannels": minchannels, + } + payload.update({k: v for k, v in kwargs.items()}) + return self.call("multifundchannel", payload) + def newaddr(self, addresstype=None): """Get a new address of type {addresstype} of the internal wallet. """ diff --git a/doc/Makefile b/doc/Makefile index e9f343d4d61a..e5c9b8d45882 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -36,6 +36,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-listpays.7 \ doc/lightning-listpeers.7 \ doc/lightning-listsendpays.7 \ + doc/lightning-multifundchannel.7 \ doc/lightning-newaddr.7 \ doc/lightning-pay.7 \ doc/lightning-plugin.7 \ diff --git a/doc/index.rst b/doc/index.rst index 7721c1221664..03c9433f98d7 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -64,6 +64,7 @@ c-lightning Documentation lightning-listpeers lightning-listsendpays lightning-listtransactions + lightning-multifundchannel lightning-newaddr lightning-pay lightning-ping diff --git a/doc/lightning-fundchannel.7 b/doc/lightning-fundchannel.7 index 0700ecb5cc35..830a331b3b88 100644 --- a/doc/lightning-fundchannel.7 +++ b/doc/lightning-fundchannel.7 @@ -18,7 +18,7 @@ This auto-connection can fail if C-lightning does not know how to contact the target node; see \fBlightning-connect\fR(7)\. Once the transaction is confirmed, normal channel operations may begin\. Readiness -is indicated by \fBlistpeers\fR reporting a \fIstate\fR of CHANNELD_NORMAL +is indicated by \fBlistpeers\fR reporting a \fIstate\fR of \fBCHANNELD_NORMAL\fR for the channel\. @@ -96,10 +96,10 @@ channel parameters (funding limits, channel reserves, fees, etc\.)\. .SH SEE ALSO \fBlightning-connect\fR(7), lightning-listfunds(), \fBlightning-listpeers\fR(7), -\fBlightning-feerates\fR(7) +\fBlightning-feerates\fR(7), \fBlightning-multifundchannel\fR(7) .SH RESOURCES Main web site: \fIhttps://github.com/ElementsProject/lightning\fR -\" SHA256STAMP:7d1358db44e2b81554fe1b105865e93d489d470bdbc43b7e09c7bddf7f10f396 +\" SHA256STAMP:f885008c6e60738b1ec9c5def19cc0877b0f9a72498ffa60e9fb3277ed9d9919 diff --git a/doc/lightning-fundchannel.7.md b/doc/lightning-fundchannel.7.md index c7955d3419f4..f314dd811c44 100644 --- a/doc/lightning-fundchannel.7.md +++ b/doc/lightning-fundchannel.7.md @@ -20,7 +20,7 @@ This auto-connection can fail if C-lightning does not know how to contact the target node; see lightning-connect(7). Once the transaction is confirmed, normal channel operations may begin. Readiness -is indicated by **listpeers** reporting a *state* of CHANNELD\_NORMAL +is indicated by **listpeers** reporting a *state* of `CHANNELD_NORMAL` for the channel. *id* is the peer id obtained from **connect**. @@ -82,7 +82,7 @@ SEE ALSO -------- lightning-connect(7), lightning-listfunds(), lightning-listpeers(7), -lightning-feerates(7) +lightning-feerates(7), lightning-multifundchannel(7) RESOURCES --------- diff --git a/doc/lightning-multifundchannel.7 b/doc/lightning-multifundchannel.7 new file mode 100644 index 000000000000..79e703698d4d --- /dev/null +++ b/doc/lightning-multifundchannel.7 @@ -0,0 +1,170 @@ +.TH "LIGHTNING-MULTIFUNDCHANNEL" "7" "" "" "lightning-multifundchannel" +.SH NAME +lightning-multifundchannel - Command for establishing many lightning channels +.SH SYNOPSIS + +\fBmultifundchannel\fR \fIdestinations\fR [\fIfeerate\fR] [\fIminconf\fR] [\fIutxos\fR] [\fIminchannels\fR] + +.SH DESCRIPTION + +The \fBmultifundchannel\fR RPC command opens multiple payment channels +with nodes by committing a single funding transaction to the blockchain +that is shared by all channels\. + + +If not already connected, \fBmultifundchannel\fR will automatically attempt +to connect; you may provide a \fI@host:port\fR hint appended to the node ID +so that c-lightning can learn how to connect to the node; +see \fBlightning-connect\fR(7)\. + + +Once the transaction is confirmed, normal channel operations may begin\. +Readiness is indicated by \fBlistpeers\fR reporting a \fIstate\fR of +\fBCHANNELD_NORMAL\fR for the channel\. + + +\fIdestinations\fR is an array of objects, with the fields: + +.RS +.IP \[bu] +\fIid\fR is the node ID, with an optional \fI@host:port\fR appended to it +in a manner understood by \fBconnect\fR; see \fBlightning-connect\fR(7)\. +Each entry in the \fIdestinations\fR array must have a unique node \fIid\fR\. +.IP \[bu] +\fIamount\fR is the amount in satoshis taken from the internal wallet +to fund the channel\. +The string \fIall\fR can be used to specify all available funds +(or 16,777,215 satoshi if more is available and large channels were +not negotiated with the peer)\. +Otherwise it is in satoshi precision; it can be + a whole number, + a whole number ending in \fIsat\fR, + a whole number ending in \fI000msat\fR, or + a number with 1 to 8 decimal places ending in \fIbtc\fR\. +The value cannot be less than the dust limit, currently 546 satoshi +as of this writing, nor more than 16,777,215 satoshi +(unless large channels were negotiated with the peer)\. +.IP \[bu] +\fIannounce\fR is an optional flag that indicates whether to announce +the channel with this, default \fBtrue\fR\. +If set to \fBfalse\fR, the channel is unpublished\. +.IP \[bu] +\fIpush_msat\fR is the amount of millisatoshis to outright give to the +node\. +This is a gift to the peer, and you do not get a proof-of-payment +out of this\. + +.RE + +There must be at least one entry in \fIdestinations\fR; +it cannot be an empty array\. + + +\fIfeerate\fR is an optional feerate used for the opening transaction and as +initial feerate for commitment and HTLC transactions\. It can be one of +the strings \fIurgent\fR (aim for next block), \fInormal\fR (next 4 blocks or +so) or \fIslow\fR (next 100 blocks or so) to use lightningd’s internal +estimates: \fInormal\fR is the default\. + + +Otherwise, \fIfeerate\fR is a number, with an optional suffix: \fIperkw\fR means +the number is interpreted as satoshi-per-kilosipa (weight), and \fIperkb\fR +means it is interpreted bitcoind-style as satoshi-per-kilobyte\. Omitting +the suffix is equivalent to \fIperkb\fR\. + + +\fIminconf\fR specifies the minimum number of confirmations that used +outputs should have\. Default is 1\. + + +\fIutxos\fR specifies the utxos to be used to fund the channel, as an array +of "txid:vout"\. + + +\fIminchannels\fR, if specified, will re-attempt funding as long as at least +this many peers remain (must not be zero)\. +The \fBmultifundchannel\fR command will only fail if too many peers fail +the funding process\. + +.SH RETURN VALUE + +On success, the \fItx\fR and \fItxid\fR of the signed and broadcsted funding +transaction is returned\. +This command opens multiple channels with a single large transaction, +thus only one transaction is returned\. + + +If \fIminchannels\fR was specified and is less than the number of destinations, +then it is possible that one or more of the destinations +do not have a channel even if \fBmultifundchannel\fR succeeded\. + + +An array of \fIchannel_ids\fR is returned; +each entry of the array is an object, + with an \fIid\fR field being the node ID of the peer, + an \fIoutnum\fR field being the output number of the transaction + that anchors this channel, + and \fIchannel_id\fR field being the channel ID with that peer\. + + +An array of \fIfailed\fR is returned, +which contains the destinations that were removed +due to failures (this can only happen on success if \fIminchannels\fR was specified)\. +Each entry of the array is an object, + with an \fIid\fR field being the node ID of the removed peer, + \fImethod\fR field describing what phase of funding the peer failed, + and \fIerror\fR field of the exact error returned by the method\. + + +On failure, none of the channels are created\. + + +The following error codes may occur: + +.RS +.IP \[bu] +-1: Catchall nonspecific error\. +.IP \[bu] +300: The maximum allowed funding amount is exceeded\. +.IP \[bu] +301: There are not enough funds in the internal wallet (including fees) to create the transaction\. +.IP \[bu] +302: The output amount is too small, and would be considered dust\. +.IP \[bu] +303: Broadcasting of the funding transaction failed, the internal call to bitcoin-cli returned with an error\. + +.RE + +Failure may also occur if \fBlightningd\fR and the peer cannot agree on +channel parameters (funding limits, channel reserves, fees, etc\.)\. +See lightning-fundchannel_\fBstart\fR(7) and lightning-fundchannel_\fBcomplete\fR(7)\. + + +There may be rare edge cases where a communications failure later in +the channel funding process will cancel the funding locally, but +the peer thinks the channel is already waiting for funding lockin\. +In that case, the next time we connect to the peer, our node will +tell the peer to forget the channel, but some nodes (in particular, +c-lightning nodes) will disconnect when our node tells them to +forget the channel\. +If you immediately \fBmultifundchannel\fR with that peer, it could +trigger this connect-forget-disconnect behavior, causing the +second \fBmultifundchannel\fR to fail as well due to disconnection\. +Doing a \fBconnect\fR with the peers separately, and waiting for a +few seconds, should help clear this hurdle; +running \fBmultifundchannel\fR a third time would also clear this\. + +.SH AUTHOR + +ZmnSCPxj \fI is mainly responsible\. + +.SH SEE ALSO + +\fBlightning-connect\fR(7), lightning-listfunds(), \fBlightning-listpeers\fR(7), +\fBlightning-fundchannel\fR(7) + +.SH RESOURCES + +Main web site: \fIhttps://github.com/ElementsProject/lightning\fR + +\" SHA256STAMP:c4bf503a2f8d981131960bedc07e9f08a0b65246964852a34df4292a9a36b663 diff --git a/doc/lightning-multifundchannel.7.md b/doc/lightning-multifundchannel.7.md new file mode 100644 index 000000000000..93928f449725 --- /dev/null +++ b/doc/lightning-multifundchannel.7.md @@ -0,0 +1,144 @@ +lightning-multifundchannel -- Command for establishing many lightning channels +============================================================================== + +SYNOPSIS +-------- + +**multifundchannel** *destinations* \[*feerate*\] \[*minconf*\] \[*utxos*\] \[*minchannels*\] + +DESCRIPTION +----------- + +The **multifundchannel** RPC command opens multiple payment channels +with nodes by committing a single funding transaction to the blockchain +that is shared by all channels. + +If not already connected, **multifundchannel** will automatically attempt +to connect; you may provide a *@host:port* hint appended to the node ID +so that c-lightning can learn how to connect to the node; +see lightning-connect(7). + +Once the transaction is confirmed, normal channel operations may begin. +Readiness is indicated by **listpeers** reporting a *state* of +`CHANNELD_NORMAL` for the channel. + +*destinations* is an array of objects, with the fields: + +* *id* is the node ID, with an optional *@host:port* appended to it + in a manner understood by **connect**; see lightning-connect(7). + Each entry in the *destinations* array must have a unique node *id*. +* *amount* is the amount in satoshis taken from the internal wallet + to fund the channel. + The string *all* can be used to specify all available funds + (or 16,777,215 satoshi if more is available and large channels were + not negotiated with the peer). + Otherwise it is in satoshi precision; it can be + a whole number, + a whole number ending in *sat*, + a whole number ending in *000msat*, or + a number with 1 to 8 decimal places ending in *btc*. + The value cannot be less than the dust limit, currently 546 satoshi + as of this writing, nor more than 16,777,215 satoshi + (unless large channels were negotiated with the peer). +* *announce* is an optional flag that indicates whether to announce + the channel with this, default `true`. + If set to `false`, the channel is unpublished. +* *push\_msat* is the amount of millisatoshis to outright give to the + node. + This is a gift to the peer, and you do not get a proof-of-payment + out of this. + +There must be at least one entry in *destinations*; +it cannot be an empty array. + +*feerate* is an optional feerate used for the opening transaction and as +initial feerate for commitment and HTLC transactions. It can be one of +the strings *urgent* (aim for next block), *normal* (next 4 blocks or +so) or *slow* (next 100 blocks or so) to use lightningd’s internal +estimates: *normal* is the default. + +Otherwise, *feerate* is a number, with an optional suffix: *perkw* means +the number is interpreted as satoshi-per-kilosipa (weight), and *perkb* +means it is interpreted bitcoind-style as satoshi-per-kilobyte. Omitting +the suffix is equivalent to *perkb*. + +*minconf* specifies the minimum number of confirmations that used +outputs should have. Default is 1. + +*utxos* specifies the utxos to be used to fund the channel, as an array +of "txid:vout". + +*minchannels*, if specified, will re-attempt funding as long as at least +this many peers remain (must not be zero). +The **multifundchannel** command will only fail if too many peers fail +the funding process. + +RETURN VALUE +------------ + +On success, the *tx* and *txid* of the signed and broadcsted funding +transaction is returned. +This command opens multiple channels with a single large transaction, +thus only one transaction is returned. + +If *minchannels* was specified and is less than the number of destinations, +then it is possible that one or more of the destinations +do not have a channel even if **multifundchannel** succeeded. + +An array of *channel\_ids* is returned; +each entry of the array is an object, + with an *id* field being the node ID of the peer, + an *outnum* field being the output number of the transaction + that anchors this channel, + and *channel_id* field being the channel ID with that peer. + +An array of *failed* is returned, +which contains the destinations that were removed +due to failures (this can only happen on success if *minchannels* was specified). +Each entry of the array is an object, + with an *id* field being the node ID of the removed peer, + *method* field describing what phase of funding the peer failed, + and *error* field of the exact error returned by the method. + +On failure, none of the channels are created. + +The following error codes may occur: +* -1: Catchall nonspecific error. +- 300: The maximum allowed funding amount is exceeded. +- 301: There are not enough funds in the internal wallet (including fees) to create the transaction. +- 302: The output amount is too small, and would be considered dust. +- 303: Broadcasting of the funding transaction failed, the internal call to bitcoin-cli returned with an error. + +Failure may also occur if **lightningd** and the peer cannot agree on +channel parameters (funding limits, channel reserves, fees, etc.). +See lightning-fundchannel\_start(7) and lightning-fundchannel\_complete(7). + +There may be rare edge cases where a communications failure later in +the channel funding process will cancel the funding locally, but +the peer thinks the channel is already waiting for funding lockin. +In that case, the next time we connect to the peer, our node will +tell the peer to forget the channel, but some nodes (in particular, +c-lightning nodes) will disconnect when our node tells them to +forget the channel. +If you immediately **multifundchannel** with that peer, it could +trigger this connect-forget-disconnect behavior, causing the +second **multifundchannel** to fail as well due to disconnection. +Doing a **connect** with the peers separately, and waiting for a +few seconds, should help clear this hurdle; +running **multifundchannel** a third time would also clear this. + +AUTHOR +------ + +ZmnSCPxj <> is mainly responsible. + +SEE ALSO +-------- + +lightning-connect(7), lightning-listfunds(), lightning-listpeers(7), +lightning-fundchannel(7) + +RESOURCES +--------- + +Main web site: diff --git a/plugins/.gitignore b/plugins/.gitignore index b1f5cab7168a..8f5d88a6006c 100644 --- a/plugins/.gitignore +++ b/plugins/.gitignore @@ -2,3 +2,4 @@ autoclean bcli fundchannel pay +multifundchannel diff --git a/plugins/Makefile b/plugins/Makefile index b59e6119a517..28e6e7b53f5e 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -24,6 +24,9 @@ PLUGIN_PAY_LIB_SRC := plugins/libplugin-pay.c PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o) +PLUGIN_MULTIFUNDCHANNEL_SRC := plugins/multifundchannel.c +PLUGIN_MULTIFUNDCHANNEL_OBJS := $(PLUGIN_MULTIFUNDCHANNEL_SRC:.c=.o) + PLUGIN_ALL_SRC := \ $(PLUGIN_AUTOCLEAN_SRC) \ $(PLUGIN_BCLI_SRC) \ @@ -31,6 +34,7 @@ PLUGIN_ALL_SRC := \ $(PLUGIN_KEYSEND_SRC) \ $(PLUGIN_TXPREPARE_SRC) \ $(PLUGIN_LIB_SRC) \ + $(PLUGIN_MULTIFUNDCHANNEL_SRC) \ $(PLUGIN_PAY_LIB_SRC) \ $(PLUGIN_PAY_SRC) PLUGIN_ALL_HEADER := \ @@ -44,6 +48,7 @@ PLUGINS := \ plugins/fundchannel \ plugins/keysend \ plugins/pay \ + plugins/multifundchannel \ plugins/txprepare # Make sure these depend on everything. @@ -105,4 +110,6 @@ plugins/bcli: bitcoin/chainparams.o $(PLUGIN_BCLI_OBJS) $(PLUGIN_LIB_OBJS) $(PLU plugins/keysend: bitcoin/chainparams.o wire/tlvstream.o wire/onion$(EXP)_wiregen.o $(PLUGIN_KEYSEND_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_PAY_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(PLUGIN_KEYSEND_OBJS): $(PLUGIN_PAY_LIB_HEADER) +plugins/multifundchannel: bitcoin/chainparams.o common/addr.o $(PLUGIN_MULTIFUNDCHANNEL_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) + $(PLUGIN_ALL_OBJS): $(PLUGIN_LIB_HEADER) diff --git a/plugins/multifundchannel.c b/plugins/multifundchannel.c new file mode 100644 index 000000000000..16e9fd83854f --- /dev/null +++ b/plugins/multifundchannel.c @@ -0,0 +1,2074 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Current state of the funding process. */ +enum multifundchannel_state { + /* We have not yet performed `fundchannel_start`. */ + MULTIFUNDCHANNEL_START_NOT_YET = 0, + /* The `connect` command failed. `*/ + MULTIFUNDCHANNEL_CONNECT_FAILED, + /* The `fundchannel_start` command succeeded. */ + MULTIFUNDCHANNEL_STARTED, + /* The `fundchannel_start` command failed. */ + MULTIFUNDCHANNEL_START_FAILED, + /* The `fundchannel_complete` command failed. */ + MULTIFUNDCHANNEL_COMPLETE_FAILED, + /* The transaction might now be broadcasted. */ + MULTIFUNDCHANNEL_DONE +}; + +/* The object for a single destination. */ +struct multifundchannel_destination { + /* The overall multifundchannel command object. */ + struct multifundchannel_command *mfc; + + /* The overall multifundchannel_command contains an + array of multifundchannel_destinations. + This provides the index within the array. + + This is used in debug printing. + */ + unsigned int index; + + /* ID for this destination. */ + struct node_id id; + /* Address hint for this destination, NULL if not + specified. + */ + const char *addrhint; + /* The features this destination has. */ + const u8 *their_features; + + /* Whether we have `fundchannel_start`, failed `connect` or + `fundchannel_complete`, etc. + */ + enum multifundchannel_state state; + + /* The actual target script and address. */ + const u8 *funding_script; + const char *funding_addr; + + /* The amount to be funded for this destination. + If the specified amount is "all" then the `all` + flag is set, and the amount is initially 0 until + we have figured out how much exactly "all" is, + after the dryrun stage. + */ + bool all; + struct amount_sat amount; + + /* The output index for this destination. */ + unsigned int outnum; + + /* Whether the channel to this destination will + be announced. + */ + bool announce; + /* How much of the initial funding to push to + the destination. + */ + struct amount_msat push_msat; + + /* The actual channel_id. */ + const char *channel_id; + + /* Any error messages. */ + const char *error; + errcode_t code; +}; + +/* Stores a destination that was removed due to some failure. */ +struct multifundchannel_removed { + /* The destination we removed. */ + struct node_id id; + /* The method that failed: + connect, fundchannel_start, fundchannel_complete. + */ + const char *method; + /* The error that caused this destination to be removed, in JSON. */ + const char *error; + errcode_t code; +}; + +/* The object for a single multifundchannel command. */ +struct multifundchannel_command { + /* A unique numeric identifier for this particular + multifundchannel execution. + + This is used for debug logs; we want to be able to + identify *which* multifundchannel is being described + in the debug logs, especially if the user runs + multiple `multifundchannel` commands in parallel, or + in very close sequence, which might confuse us with + *which* debug message belongs with *which* command. + + We actually just reuse the id from the cmd. + Store it here for easier access. + */ + u64 id; + + /* The plugin-level command. */ + struct command *cmd; + /* An array of destinations. */ + struct multifundchannel_destination *destinations; + /* Number of pending parallel fundchannel_start or + fundchannel_complete. + */ + size_t pending; + + /* The feerate desired by the user. */ + const char *feerate_str; + /* The minimum number of confirmations for owned + UTXOs to be selected. + */ + u32 minconf; + /* The set of utxos to be used. */ + const char *utxos_str; + /* How long should we keep going if things fail. */ + size_t minchannels; + /* Array of destinations that were removed in a best-effort + attempt to fund as many channels as possible. + */ + struct multifundchannel_removed *removeds; + + /* The PSBT of the funding transaction we are building. + Prior to `fundchannel_start` completing for all destinations, + this contains an unsigned incomplete transaction that is just a + reservation of the inputs. + After `fundchannel_start`, this contains an unsigned transaction + with complete outputs. + After `fundchannel_complete`, this contains a signed, finalized + transaction. + */ + struct wally_psbt *psbt; + /* The actual feerate of the PSBT. */ + u32 feerate_per_kw; + /* The expected weight of the PSBT after adding in all the outputs. + * In weight units (sipa). */ + u32 estimated_final_weight; + /* Excess satoshi from the PSBT. + * If "all" this is the entire amount; if not "all" this is the + * proposed change amount, which if dusty should be donated to + * the miners. + */ + struct amount_sat excess_sat; + + /* A convenient change address. NULL at the start, filled in + * if we detect we need it. */ + const u8 *change_scriptpubkey; + /* Whether we need a change output. */ + bool change_needed; + /* The change amount. */ + struct amount_sat change_amount; + + /* The txid of the final funding transaction. */ + struct bitcoin_txid *txid; + + /* The actual tx of the actual final funding transaction + that was broadcast. + */ + const char *final_tx; + const char *final_txid; +}; + +extern const struct chainparams *chainparams; + +/* Flag set when any of the destinations has a value of "all". */ +static bool has_all(const struct multifundchannel_command *mfc) +{ + for (size_t i = 0; i < tal_count(mfc->destinations); i++) { + if (mfc->destinations[i].all) + return true; + } + return false; +} + +/*----------------------------------------------------------------------------- +Command Cleanup +-----------------------------------------------------------------------------*/ + +/*~ +We disallow the use of command_fail and forward_error directly +in the rest of the code. + +This ensures that if we ever fail a multifundchannel, we do cleanup +by doing fundchannel_cancel and unreserveinputs. +*/ + +/* TODO: This is lengthy enough to deserve its own source file, +clocking in at 240 loc. +*/ + +/* Object for performing cleanup. */ +struct multifundchannel_cleanup { + size_t pending; + struct command_result *(*cb)(void *arg); + void *arg; +}; + +/* Cleans up a PSBT. */ +static void +mfc_cleanup_psbt(struct command *cmd, + struct multifundchannel_cleanup *cleanup, + struct wally_psbt *psbt); +/* Cleans up a `fundchannel_start`ed node id. */ +static void +mfc_cleanup_fc(struct command *cmd, + struct multifundchannel_cleanup *cleanup, + struct multifundchannel_destination *dest); +/* Run at completion of all cleanup tasks. */ +static struct command_result * +mfc_cleanup_complete(struct multifundchannel_cleanup *cleanup); + +/* Core cleanup function. */ +static struct command_result * +mfc_cleanup_(struct multifundchannel_command *mfc, + struct command_result *(*cb)(void *arg), + void *arg) +{ + struct multifundchannel_cleanup *cleanup; + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": cleanup!", mfc->id); + + cleanup = tal(mfc, struct multifundchannel_cleanup); + cleanup->pending = 0; + cleanup->cb = cb; + cleanup->arg = arg; + + if (mfc->psbt) { + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": unreserveinputs task.", mfc->id); + + ++cleanup->pending; + mfc_cleanup_psbt(mfc->cmd, cleanup, mfc->psbt); + } + for (i = 0; i < tal_count(mfc->destinations); ++i) { + struct multifundchannel_destination *dest; + dest = &mfc->destinations[i]; + + /* If not started, nothing to clean up. */ + if (dest->state != MULTIFUNDCHANNEL_STARTED) + continue; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: " + "fundchannel_cancel task.", + mfc->id, dest->index); + + ++cleanup->pending; + mfc_cleanup_fc(mfc->cmd, cleanup, dest); + } + + if (cleanup->pending == 0) + return mfc_cleanup_complete(cleanup); + else + return command_still_pending(mfc->cmd); +} +static struct command_result * +mfc_cleanup_done(struct command *cmd, + const char *buf UNUSED, + const jsmntok_t *res UNUSED, + struct multifundchannel_cleanup *cleanup) +{ + --cleanup->pending; + if (cleanup->pending == 0) + return mfc_cleanup_complete(cleanup); + else + return command_still_pending(cmd); +} +/* Cleans up a txid by doing `txdiscard` on it. */ +static void +mfc_cleanup_psbt(struct command *cmd, + struct multifundchannel_cleanup *cleanup, + struct wally_psbt *psbt) +{ + struct out_req *req = jsonrpc_request_start(cmd->plugin, + cmd, + "unreserveinputs", + &mfc_cleanup_done, + &mfc_cleanup_done, + cleanup); + json_add_psbt(req->js, "psbt", psbt); + send_outreq(cmd->plugin, req); +} +/* Cleans up a `fundchannel_start` by doing `fundchannel_cancel` on +the node. +*/ +static void +mfc_cleanup_fc(struct command *cmd, + struct multifundchannel_cleanup *cleanup, + struct multifundchannel_destination *dest) +{ + struct out_req *req = jsonrpc_request_start(cmd->plugin, + cmd, + "fundchannel_cancel", + &mfc_cleanup_done, + &mfc_cleanup_done, + cleanup); + json_add_node_id(req->js, "id", &dest->id); + + send_outreq(cmd->plugin, req); +} +/* Done when all cleanup operations have completed. */ +static struct command_result * +mfc_cleanup_complete(struct multifundchannel_cleanup *cleanup) +{ + tal_steal(tmpctx, cleanup); + return cleanup->cb(cleanup->arg); +} +#define mfc_cleanup(mfc, cb, arg) \ + mfc_cleanup_(mfc, typesafe_cb(struct command_result *, void *, \ + (cb), (arg)), \ + (arg)) + +/* Use this instead of command_fail. */ +static struct command_result * +mfc_fail(struct multifundchannel_command *, errcode_t code, + const char *fmt, ...); +/* Use this instead of forward_error. */ +static struct command_result * +mfc_forward_error(struct command *cmd, + const char *buf, const jsmntok_t *error, + struct multifundchannel_command *); +/* Use this instead of command_finished. */ +static struct command_result * +mfc_finished(struct multifundchannel_command *, struct json_stream *response); +/* Use this instead of command_err_raw. */ +static struct command_result * +mfc_err_raw(struct multifundchannel_command *, const char *json_string); + +/*---------------------------------------------------------------------------*/ + +/* These are the actual implementations of the cleanup entry functions. */ + +struct mfc_fail_object { + struct multifundchannel_command *mfc; + struct command *cmd; + errcode_t code; + const char *msg; +}; +static struct command_result * +mfc_fail_complete(struct mfc_fail_object *obj); +static struct command_result * +mfc_fail(struct multifundchannel_command *mfc, errcode_t code, + const char *fmt, ...) +{ + struct mfc_fail_object *obj; + const char *msg; + va_list ap; + + va_start(ap, fmt); + msg = tal_vfmt(mfc, fmt, ap); + va_end(ap); + + obj = tal(mfc, struct mfc_fail_object); + obj->mfc = mfc; + obj->cmd = mfc->cmd; + obj->code = code; + obj->msg = msg; + + return mfc_cleanup(mfc, &mfc_fail_complete, obj); +} +static struct command_result * +mfc_fail_complete(struct mfc_fail_object *obj) +{ + plugin_log(obj->mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": cleanup done, failing.", obj->mfc->id); + return command_fail(obj->cmd, obj->code, "%s", obj->msg); +} + +struct mfc_err_raw_object { + struct multifundchannel_command *mfc; + const char *error; +}; +static struct command_result * +mfc_err_raw_complete(struct mfc_err_raw_object *obj); +static struct command_result * +mfc_err_raw(struct multifundchannel_command *mfc, const char *json_string) +{ + struct mfc_err_raw_object *obj; + + obj = tal(mfc, struct mfc_err_raw_object); + obj->mfc = mfc; + obj->error = tal_strdup(obj, json_string); + + return mfc_cleanup(mfc, &mfc_err_raw_complete, obj); +} +static struct command_result * +mfc_err_raw_complete(struct mfc_err_raw_object *obj) +{ + plugin_log(obj->mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": cleanup done, failing raw.", obj->mfc->id); + return command_err_raw(obj->mfc->cmd, obj->error); +} +static struct command_result * +mfc_forward_error(struct command *cmd, + const char *buf, const jsmntok_t *error, + struct multifundchannel_command *mfc) +{ + plugin_log(cmd->plugin, LOG_DBG, + "mfc %"PRIu64": forwarding error, about to cleanup.", + mfc->id); + return mfc_err_raw(mfc, json_strdup(tmpctx, buf, error)); +} + +struct mfc_finished_object { + struct multifundchannel_command *mfc; + struct command *cmd; + struct json_stream *response; +}; +static struct command_result * +mfc_finished_complete(struct mfc_finished_object *obj); +static struct command_result * +mfc_finished(struct multifundchannel_command *mfc, + struct json_stream *response) +{ + struct mfc_finished_object *obj; + + /* The response will be constructed by jsonrpc_stream_success, + which allocates off the command, so it should be safe to + just store it here. + */ + obj = tal(mfc, struct mfc_finished_object); + obj->mfc = mfc; + obj->cmd = mfc->cmd; + obj->response = response; + + return mfc_cleanup(mfc, &mfc_finished_complete, obj); +} +static struct command_result * +mfc_finished_complete(struct mfc_finished_object *obj) +{ + plugin_log(obj->mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": cleanup done, finishing command.", + obj->mfc->id); + return command_finished(obj->cmd, obj->response); +} + +/*----------------------------------------------------------------------------- +Input Validation +-----------------------------------------------------------------------------*/ + +/* Validates the destinations input argument. + +Returns NULL if checking of destinations array worked, +or non-NULL if it failed (and this function has already +executed mfc_fail). +*/ +static struct command_result * +param_destinations_array(struct command *cmd, const char *name, + const char *buffer, const jsmntok_t *tok, + struct multifundchannel_destination **dests) +{ + size_t i; + const jsmntok_t *json_dest; + bool has_all = false; + + if (tok->type != JSMN_ARRAY) + return command_fail_badparam(cmd, name, buffer, tok, + "must be an array"); + if (tok->size < 1) + return command_fail_badparam(cmd, name, buffer, tok, + "must have at least one entry"); + + *dests = tal_arr(cmd, struct multifundchannel_destination, tok->size); + for (i = 0; i < tal_count(*dests); ++i) + (*dests)[i].state = MULTIFUNDCHANNEL_START_NOT_YET; + + json_for_each_arr(i, json_dest, tok) { + struct multifundchannel_destination *dest; + const char *id; + char *addrhint; + struct amount_sat *amount; + bool *announce; + struct amount_msat *push_msat; + + dest = &(*dests)[i]; + + if (!param(cmd, buffer, json_dest, + p_req("id", param_string, &id), + p_req("amount", param_sat_or_all, &amount), + p_opt_def("announce", param_bool, &announce, true), + p_opt_def("push_msat", param_msat, &push_msat, + AMOUNT_MSAT(0)), + NULL)) + return command_param_failed(); + + addrhint = strchr(id, '@'); + if (addrhint) { + /* Split at @. */ + size_t idlen = (size_t) (addrhint - id); + addrhint = tal_strdup(*dests, addrhint + 1); + id = tal_strndup(*dests, take(id), idlen); + } + + if (!node_id_from_hexstr(id, strlen(id), &dest->id)) + return command_fail_badparam(cmd, name, buffer, + json_dest, + "invalid node id"); + + if (!amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)) && + amount_sat_less(*amount, chainparams->dust_limit)) + return command_fail_badparam(cmd, name, buffer, + json_dest, + "output would be dust"); + + dest->index = i; + dest->addrhint = addrhint; + dest->their_features = NULL; + dest->funding_script = NULL; + dest->funding_addr = NULL; + dest->all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)); + dest->amount = dest->all ? AMOUNT_SAT(0) : *amount; + dest->announce = *announce; + dest->push_msat = *push_msat; + dest->channel_id = NULL; + dest->error = NULL; + + /* Only one destination can have "all" indicator. */ + if (dest->all) { + if (has_all) + return command_fail_badparam(cmd, name, buffer, + json_dest, + "only one destination " + "can indicate \"all\" " + "for 'amount'."); + else + has_all = true; + } + + /* Make sure every id is unique. */ + for (size_t j = 0; j < i; j++) { + if (node_id_eq(&dest->id, &(*dests)[j].id)) + return command_fail_badparam(cmd, name, buffer, + json_dest, + "Duplicate destination"); + } + } + + return NULL; +} + +/*----------------------------------------------------------------------------- +Command Processing +-----------------------------------------------------------------------------*/ + +/* Function to redo multifundchannel after a failure. +*/ +static struct command_result * +redo_multifundchannel(struct multifundchannel_command *mfc, + const char *failing_method); + +static struct command_result * +perform_multiconnect(struct multifundchannel_command *mfc); + +/* Initiate the multifundchannel execution. */ +static struct command_result * +perform_multifundchannel(struct multifundchannel_command *mfc) +{ + return perform_multiconnect(mfc); +} + +/*---------------------------------------------------------------------------*/ +/*~ +First, connect to all the peers. + +This is a convenience both to us and to the user. + +We delegate parsing for valid node IDs to the +`multiconnect`. +In addition, this means the user does not have to +connect to the specified nodes. + +In particular, some implementations (including some +versions of C-Lightning) will disconnect in case +of funding channel failure. +And with a *multi* funding, it is more likely to +fail due to having to coordinate many more nodes. +*/ + +static void +connect_dest(struct multifundchannel_destination *dest); + +/* Initiate the multiconnect. */ +static struct command_result * +perform_multiconnect(struct multifundchannel_command *mfc) +{ + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": multiconnect.", mfc->id); + + mfc->pending = tal_count(mfc->destinations); + + for (i = 0; i < tal_count(mfc->destinations); ++i) + connect_dest(&mfc->destinations[i]); + + assert(mfc->pending != 0); + return command_still_pending(mfc->cmd); +} + +static struct command_result * +connect_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_destination *dest); +static struct command_result * +connect_err(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct multifundchannel_destination *dest); + +static void +connect_dest(struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + struct command *cmd = mfc->cmd; + const char *id; + struct out_req *req; + + id = node_id_to_hexstr(tmpctx, &dest->id); + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: connect %s.", + mfc->id, dest->index, id); + + req = jsonrpc_request_start(cmd->plugin, cmd, + "connect", + &connect_ok, + &connect_err, + dest); + if (dest->addrhint) + json_add_string(req->js, "id", + tal_fmt(tmpctx, "%s@%s", + id, + dest->addrhint)); + else + json_add_node_id(req->js, "id", &dest->id); + send_outreq(cmd->plugin, req); +} + +static struct command_result * +connect_done(struct multifundchannel_destination *dest); + +static struct command_result * +connect_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + const jsmntok_t *features_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: connect done.", + mfc->id, dest->index); + + features_tok = json_get_member(buf, result, "features"); + if (!features_tok) + plugin_err(cmd->plugin, + "'connect' did not return 'features'? %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + dest->their_features = json_tok_bin_from_hex(mfc, buf, features_tok); + if (!dest->their_features) + plugin_err(cmd->plugin, + "'connect' has unparesable 'features'? %.*s", + json_tok_full_len(features_tok), + json_tok_full(buf, features_tok)); + + return connect_done(dest); +} +static struct command_result * +connect_err(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + const jsmntok_t *code_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: failed! connect %s: %.*s.", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id), + json_tok_full_len(error), + json_tok_full(buf, error)); + + code_tok = json_get_member(buf, error, "code"); + if (!code_tok) + plugin_err(cmd->plugin, + "`connect` failure did not have `code`? " + "%.*s", + json_tok_full_len(error), + json_tok_full(buf, error)); + if (!json_to_errcode(buf, code_tok, &dest->code)) + plugin_err(cmd->plugin, + "`connect` has unparseable `code`? " + "%.*s", + json_tok_full_len(code_tok), + json_tok_full(buf, code_tok)); + + dest->state = MULTIFUNDCHANNEL_CONNECT_FAILED; + dest->error = json_strdup(mfc, buf, error); + + return connect_done(dest); +} + +static struct command_result * +after_multiconnect(struct multifundchannel_command *mfc); + +static struct command_result * +connect_done(struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + + --mfc->pending; + if (mfc->pending == 0) + return after_multiconnect(mfc); + else + return command_still_pending(mfc->cmd); +} + +static struct command_result * +perform_fundpsbt(struct multifundchannel_command *mfc); + +/* Check results of connect. */ +static struct command_result * +after_multiconnect(struct multifundchannel_command *mfc) +{ + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": multiconnect done.", mfc->id); + + /* Check if anyone failed. */ + for (i = 0; i < tal_count(mfc->destinations); ++i) { + struct multifundchannel_destination *dest; + + dest = &mfc->destinations[i]; + + assert(dest->state == MULTIFUNDCHANNEL_START_NOT_YET + || dest->state == MULTIFUNDCHANNEL_CONNECT_FAILED); + + if (dest->state != MULTIFUNDCHANNEL_CONNECT_FAILED) + continue; + + /* One of them failed, oh no. */ + return redo_multifundchannel(mfc, "connect"); + } + + return perform_fundpsbt(mfc); +} + +/*---------------------------------------------------------------------------*/ + +/*~ Create an initial funding PSBT. + +This creation of the initial funding PSBT is solely to reserve inputs for +our use. +This lets us initiate later with fundchannel_start with confidence that we +can actually afford the channels we will create. +*/ + +static struct command_result * +after_fundpsbt(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc); + +static struct command_result * +perform_fundpsbt(struct multifundchannel_command *mfc) +{ + struct out_req *req; + + /* If the user specified utxos we should use utxopsbt instead + * of fundpsbt. */ + if (mfc->utxos_str) { + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": utxopsbt.", + mfc->id); + + req = jsonrpc_request_start(mfc->cmd->plugin, + mfc->cmd, + "utxopsbt", + &after_fundpsbt, + &mfc_forward_error, + mfc); + json_add_jsonstr(req->js, "utxos", mfc->utxos_str); + json_add_bool(req->js, "reservedok", false); + } else { + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": fundpsbt.", + mfc->id); + + req = jsonrpc_request_start(mfc->cmd->plugin, + mfc->cmd, + "fundpsbt", + &after_fundpsbt, + &mfc_forward_error, + mfc); + json_add_u32(req->js, "minconf", mfc->minconf); + } + + /* The entire point is to reserve the inputs. */ + json_add_bool(req->js, "reserve", true); + /* How much do we need to reserve? */ + if (has_all(mfc)) + json_add_string(req->js, "satoshi", "all"); + else { + struct amount_sat sum = AMOUNT_SAT(0); + for (size_t i = 0; i < tal_count(mfc->destinations); ++i) { + if (!amount_sat_add(&sum, + sum, mfc->destinations[i].amount)) + return mfc_fail(mfc, JSONRPC2_INVALID_PARAMS, + "Overflow while summing " + "destination values."); + } + json_add_string(req->js, "satoshi", + type_to_string(tmpctx, struct amount_sat, + &sum)); + } + json_add_string(req->js, "feerate", + mfc->feerate_str ? mfc->feerate_str : "normal"); + + { + size_t startweight; + size_t num_outs = tal_count(mfc->destinations); + /* Assume 1 input. + * As long as lightningd does not select more than 252 + * inputs, that estimation should be correct. + */ + startweight = bitcoin_tx_core_weight(1, num_outs) + + ( bitcoin_tx_output_weight( 1 /* OP_0 */ + + 1 /* OP_PUSHDATA */ + + 32 /* P2WSH */ + ) + * num_outs + ) + ; + json_add_string(req->js, "startweight", + tal_fmt(tmpctx, "%zu", startweight)); + } + + return send_outreq(mfc->cmd->plugin, req); +} + +static struct command_result * +compute_mfc_all(struct multifundchannel_command *mfc); +static struct command_result * +handle_mfc_change(struct multifundchannel_command *mfc); + +static struct command_result * +after_fundpsbt(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc) +{ + const jsmntok_t *field; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": %s done.", + mfc->id, mfc->utxos_str ? "utxopsbt" : "fundpsbt"); + + field = json_get_member(buf, result, "psbt"); + if (!field) + goto fail; + + mfc->psbt = psbt_from_b64(mfc, + buf + field->start, + field->end - field->start); + if (!mfc->psbt) + goto fail; + + field = json_get_member(buf, result, "feerate_per_kw"); + if (!field || !json_to_u32(buf, field, &mfc->feerate_per_kw)) + goto fail; + + field = json_get_member(buf, result, "estimated_final_weight"); + if (!field || !json_to_u32(buf, field, &mfc->estimated_final_weight)) + goto fail; + + /* msat LOL. */ + field = json_get_member(buf, result, "excess_msat"); + if (!field || !parse_amount_sat(&mfc->excess_sat, + buf + field->start, + field->end - field->start)) + goto fail; + + if (has_all(mfc)) + return compute_mfc_all(mfc); + return handle_mfc_change(mfc); + +fail: + plugin_err(mfc->cmd->plugin, + "Unexpected result from fundpsbt/utxopsbt: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + +} + +/* If one of the destinations specified "all", figure out how much that is. */ +static struct command_result * +compute_mfc_all(struct multifundchannel_command *mfc) +{ + size_t all_index = SIZE_MAX; + struct multifundchannel_destination *all_dest; + + assert(has_all(mfc)); + + for (size_t i = 0; i < tal_count(mfc->destinations); ++i) { + struct multifundchannel_destination *dest; + dest = &mfc->destinations[i]; + if (dest->all) { + assert(all_index == SIZE_MAX); + all_index = i; + continue; + } + /* Subtract the amount from the excess. */ + if (!amount_sat_sub(&mfc->excess_sat, + mfc->excess_sat, dest->amount)) + /* Not enough funds! */ + return mfc_fail(mfc, FUND_CANNOT_AFFORD, + "Insufficient funds."); + } + assert(all_index != SIZE_MAX); + all_dest = &mfc->destinations[all_index]; + + /* Is the excess above the dust amount? */ + if (amount_sat_less(mfc->excess_sat, chainparams->dust_limit)) + return mfc_fail(mfc, FUND_OUTPUT_IS_DUST, + "Output 'all' %s would be dust", + type_to_string(tmpctx, struct amount_sat, + &mfc->excess_sat)); + + /* Assign the remainder to the 'all' output. */ + all_dest->amount = mfc->excess_sat; + if (!feature_negotiated(plugin_feature_set(mfc->cmd->plugin), + all_dest->their_features, + OPT_LARGE_CHANNELS) + && amount_sat_greater(all_dest->amount, + chainparams->max_funding)) + all_dest->amount = chainparams->max_funding; + /* Remove it from the excess. */ + bool ok = amount_sat_sub(&mfc->excess_sat, + mfc->excess_sat, all_dest->amount); + assert(ok); + /* Remove the 'all' flag. */ + all_dest->all = false; + + /* Continue. */ + return handle_mfc_change(mfc); +} + +static struct command_result * +acquire_change_address(struct multifundchannel_command *mfc); +static struct command_result * +mfc_psbt_acquired(struct multifundchannel_command *mfc); + +static struct command_result * +handle_mfc_change(struct multifundchannel_command *mfc) +{ + size_t change_weight; + struct amount_sat change_fee; + struct amount_sat change_min_limit; + + /* Determine if adding a change output is worth it. + * Get the weight of a change output and how much it + * costs. + */ + change_weight = bitcoin_tx_output_weight( 1 /* OP_0 */ + + 1 /* OP_PUSHDATA */ + + 20 /* P2WPKH */ + ); + change_fee = amount_tx_fee(mfc->feerate_per_kw, change_weight); + /* The limit is equal to the change_fee plus the dust limit. */ + if (!amount_sat_add(&change_min_limit, + change_fee, chainparams->dust_limit)) + plugin_err(mfc->cmd->plugin, + "Overflow dust limit and change fee."); + + /* Is the excess over the limit? */ + if (amount_sat_greater(mfc->excess_sat, change_min_limit)) { + bool ok = amount_sat_sub(&mfc->change_amount, + mfc->excess_sat, change_fee); + assert(ok); + mfc->change_needed = true; + if (!mfc->change_scriptpubkey) + return acquire_change_address(mfc); + } else + mfc->change_needed = false; + + return mfc_psbt_acquired(mfc); +} + +static struct command_result * +after_newaddr(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc); + +static struct command_result * +acquire_change_address(struct multifundchannel_command *mfc) +{ + struct out_req *req; + req = jsonrpc_request_start(mfc->cmd->plugin, mfc->cmd, + "newaddr", + &after_newaddr, &mfc_forward_error, + mfc); + json_add_string(req->js, "addresstype", "bech32"); + return send_outreq(mfc->cmd->plugin, req); +} +static struct command_result * +after_newaddr(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc) +{ + const jsmntok_t *field; + + field = json_get_member(buf, result, "bech32"); + if (!field) + plugin_err(cmd->plugin, + "No bech32 field in newaddr result: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + if (json_to_address_scriptpubkey(mfc, chainparams, buf, field, + &mfc->change_scriptpubkey) + != ADDRESS_PARSE_SUCCESS) + plugin_err(cmd->plugin, + "Unparseable bech32 field in newaddr result: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + + return mfc_psbt_acquired(mfc); +} + +static struct command_result * +perform_fundchannel_start(struct multifundchannel_command *mfc); +static struct command_result * +mfc_psbt_acquired(struct multifundchannel_command *mfc) +{ + return perform_fundchannel_start(mfc); +} + +/*---------------------------------------------------------------------------*/ + +/*~ +We perform all the `fundchannel_start` in parallel. + +We need to parallelize `fundchannel_start` execution +since the command has to wait for a response from +the remote peer. +The remote peer is not under our control and might +respond after a long time. + +By doing them in parallel, the time it takes to +perform all the `fundchannel_start` is only the +slowest time among all peers. +This is important since faster peers might impose a +timeout on channel opening and fail subsequent +steps if we take too long before running +`fundchannel_complete`. +*/ + +static void +fundchannel_start_dest(struct multifundchannel_destination *dest); +static struct command_result * +perform_fundchannel_start(struct multifundchannel_command *mfc) +{ + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": fundchannel_start parallel.", mfc->id); + + mfc->pending = tal_count(mfc->destinations); + + for (i = 0; i < tal_count(mfc->destinations); ++i) + fundchannel_start_dest(&mfc->destinations[i]); + + assert(mfc->pending != 0); + return command_still_pending(mfc->cmd); +} + +/* Handles fundchannel_start success and failure. */ +static struct command_result * +fundchannel_start_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_destination *dest); +static struct command_result * +fundchannel_start_err(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct multifundchannel_destination *dest); + +static void +fundchannel_start_dest(struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + struct command *cmd = mfc->cmd; + struct out_req *req; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: fundchannel_start %s.", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id)); + + req = jsonrpc_request_start(cmd->plugin, + cmd, + "fundchannel_start", + &fundchannel_start_ok, + &fundchannel_start_err, + dest); + + json_add_node_id(req->js, "id", &dest->id); + assert(!dest->all); + json_add_string(req->js, "amount", + fmt_amount_sat(tmpctx, &dest->amount)); + + if (mfc->feerate_str) + json_add_string(req->js, "feerate", mfc->feerate_str); + json_add_bool(req->js, "announce", dest->announce); + json_add_string(req->js, "push_msat", + fmt_amount_msat(tmpctx, &dest->push_msat)); + + send_outreq(cmd->plugin, req); +} + +static struct command_result * +fundchannel_start_done(struct multifundchannel_destination *dest); + +static struct command_result * +fundchannel_start_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + const jsmntok_t *address_tok; + const jsmntok_t *script_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: fundchannel_start %s done.", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id)); + + /* Extract funding_address. */ + address_tok = json_get_member(buf, result, "funding_address"); + if (!address_tok) + plugin_err(cmd->plugin, + "fundchannel_start did not " + "return 'funding_address': %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + dest->funding_addr = json_strdup(dest->mfc, buf, address_tok); + /* Extract scriptpubkey. */ + script_tok = json_get_member(buf, result, "scriptpubkey"); + if (!script_tok) + plugin_err(cmd->plugin, + "fundchannel_start did not " + "return 'scriptpubkey': %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + dest->funding_script = json_tok_bin_from_hex(dest->mfc, + buf, script_tok); + if (!dest->funding_script) + plugin_err(cmd->plugin, + "fundchannel_start did not " + "return parseable 'scriptpubkey': %.*s", + json_tok_full_len(script_tok), + json_tok_full(buf, script_tok)); + + dest->state = MULTIFUNDCHANNEL_STARTED; + + return fundchannel_start_done(dest); +} +static struct command_result * +fundchannel_start_err(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + const jsmntok_t *code_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: " + "failed! fundchannel_start %s: %.*s.", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id), + json_tok_full_len(error), + json_tok_full(buf, error)); + + code_tok = json_get_member(buf, error, "code"); + if (!code_tok) + plugin_err(cmd->plugin, + "`fundchannel_start` failure did not have `code`? " + "%.*s", + json_tok_full_len(error), + json_tok_full(buf, error)); + if (!json_to_errcode(buf, code_tok, &dest->code)) + plugin_err(cmd->plugin, + "`fundchannel_start` has unparseable `code`? " + "%.*s", + json_tok_full_len(code_tok), + json_tok_full(buf, code_tok)); + + /* + You might be wondering why we do not just use + mfc_forward_error here. + The reason is that other `fundchannel_start` + commands are running in the meantime, + and it is still ambiguous whether the opening + of other destinations was started or not. + + After all parallel `fundchannel_start`s have + completed, we can then fail. + */ + + dest->state = MULTIFUNDCHANNEL_START_FAILED; + dest->error = json_strdup(dest->mfc, buf, error); + + return fundchannel_start_done(dest); +} + +static struct command_result * +after_fundchannel_start(struct multifundchannel_command *mfc); + +static struct command_result * +fundchannel_start_done(struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + + --mfc->pending; + if (mfc->pending == 0) + return after_fundchannel_start(mfc); + else + return command_still_pending(mfc->cmd); +} + +static struct command_result * +perform_funding_tx_finalize(struct multifundchannel_command *mfc); + +/* All fundchannel_start commands have returned with either +success or failure. +*/ +static struct command_result * +after_fundchannel_start(struct multifundchannel_command *mfc) +{ + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": parallel fundchannel_start done.", + mfc->id); + + /* Check if any fundchannel_start failed. */ + for (i = 0; i < tal_count(mfc->destinations); ++i) { + struct multifundchannel_destination *dest; + + dest = &mfc->destinations[i]; + + assert(dest->state == MULTIFUNDCHANNEL_STARTED + || dest->state == MULTIFUNDCHANNEL_START_FAILED); + + if (dest->state != MULTIFUNDCHANNEL_START_FAILED) + continue; + + /* One of them failed, oh no. */ + return redo_multifundchannel(mfc, "fundchannel_start"); + } + + /* Next step. */ + return perform_funding_tx_finalize(mfc); +} + +/*---------------------------------------------------------------------------*/ + +/*~ The PSBT we are holding currently has no outputs. +We now proceed to filling in those outputs now that +we know what the funding scriptpubkeys are. + +First thing we do is to shuffle the outputs. +This is needed in order to decorrelate the transaction +outputs from the parameters passed into the command. +We should assume that the caller of the command might +inadvertently leak important privacy data in the order +of its arguments, so we shuffle the outputs. +*/ + +static struct command_result * +perform_fundchannel_complete(struct multifundchannel_command *mfc); + +static struct command_result * +perform_funding_tx_finalize(struct multifundchannel_command *mfc) +{ + struct multifundchannel_destination **deck; + char *content = tal_strdup(tmpctx, ""); + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": Creating funding tx.", + mfc->id); + + /* Construct a deck of destinations. */ + deck = tal_arr(tmpctx, struct multifundchannel_destination *, + tal_count(mfc->destinations) + mfc->change_needed); + for (size_t i = 0; i < tal_count(mfc->destinations); ++i) + deck[i] = &mfc->destinations[i]; + /* Add a NULL into the deck as a proxy for change output, if + * needed. */ + if (mfc->change_needed) + deck[tal_count(mfc->destinations)] = NULL; + /* Fisher-Yates shuffle. */ + for (size_t i = tal_count(deck); i > 1; --i) { + size_t j = pseudorand(i); + if (j == i - 1) + continue; + struct multifundchannel_destination *tmp; + tmp = deck[j]; + deck[j] = deck[i - 1]; + deck[i - 1] = tmp; + } + + /* Now that we have the outputs shuffled, add outputs to the PSBT. */ + for (unsigned int outnum = 0; outnum < tal_count(deck); ++outnum) { + if (outnum != 0) + tal_append_fmt(&content, ", "); + if (deck[outnum]) { + /* Funding outpoint. */ + struct multifundchannel_destination *dest; + dest = deck[outnum]; + (void) psbt_append_output(mfc->psbt, + dest->funding_script, + dest->amount); + dest->outnum = outnum; + tal_append_fmt(&content, "%s: %s", + type_to_string(tmpctx, struct node_id, + &dest->id), + type_to_string(tmpctx, + struct amount_sat, + &dest->amount)); + } else { + /* Change output. */ + assert(mfc->change_needed); + (void) psbt_append_output(mfc->psbt, + mfc->change_scriptpubkey, + mfc->change_amount); + tal_append_fmt(&content, "change: %s", + type_to_string(tmpctx, + struct amount_sat, + &mfc->change_amount)); + } + } + + /* Elements requires a fee output. */ + if (chainparams->is_elements) { + struct amount_sat inputs = AMOUNT_SAT(0); + struct amount_sat outputs = AMOUNT_SAT(0); + struct amount_sat fees; + for (size_t i = 0; i < mfc->psbt->num_inputs; ++i) + if (!amount_sat_add(&inputs, + inputs, + psbt_input_get_amount(mfc->psbt, + i))) + plugin_err(mfc->cmd->plugin, + "Overflow while adding inputs"); + for (size_t i = 0; i < mfc->psbt->num_outputs; ++i) + if (!amount_sat_add(&outputs, + outputs, + psbt_output_get_amount(mfc->psbt, + i))) + plugin_err(mfc->cmd->plugin, + "Overflow while adding outputs"); + if (!amount_sat_sub(&fees, inputs, outputs)) + fees = AMOUNT_SAT(0); + /* If there is any fee at all, add the fee output. */ + if (!amount_sat_eq(fees, AMOUNT_SAT(0))) + (void) psbt_append_output(mfc->psbt, NULL, fees); + } + + /* Generate the TXID. */ + mfc->txid = tal(mfc, struct bitcoin_txid); + psbt_txid(mfc->psbt, mfc->txid, NULL); + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": funding tx %s: %s", + mfc->id, + type_to_string(tmpctx, struct bitcoin_txid, + mfc->txid), + content); + + /* Now we can feed the TXID and outnums to the peer. */ + return perform_fundchannel_complete(mfc); +} + +/*---------------------------------------------------------------------------*/ +/*~ +We now have an unsigned funding transaction in +mfc->psbt and mfc->txid that puts the money into +2-of-2 channel outpoints. + +However, we cannot sign and broadcast it yet! +We need to get backout transactions --- the initial +commitment transactions --- in case any of the +peers disappear later. +Those initial commitment transactions are the +unilateral close (force-close) transactions +for each channel. +With unilateral opportunity to close, we can then +safely broadcast the tx, so that in case the +peer disappears, we can recover our funds. + +The `fundchannel_complete` command performs the +negotiation with the peer to sign the initial +commiteent transactions. +Only once the `lightningd` has the transactions +signed does the `fundchannel_complete` command +return with a success. +After that point we can `signpsbt`+`sendpsbt` +the transaction. +*/ + +static void +fundchannel_complete_dest(struct multifundchannel_destination *dest); + +static struct command_result * +perform_fundchannel_complete(struct multifundchannel_command *mfc) +{ + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": parallel fundchannel_complete.", + mfc->id); + + mfc->pending = tal_count(mfc->destinations); + + for (i = 0; i < tal_count(mfc->destinations); ++i) + fundchannel_complete_dest(&mfc->destinations[i]); + + assert(mfc->pending != 0); + return command_still_pending(mfc->cmd); +} + +static struct command_result * +fundchannel_complete_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_destination *dest); +static struct command_result * +fundchannel_complete_err(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct multifundchannel_destination *dest); + +static void +fundchannel_complete_dest(struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + struct command *cmd = mfc->cmd; + struct out_req *req; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: fundchannel_complete %s.", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id)); + + req = jsonrpc_request_start(cmd->plugin, + cmd, + "fundchannel_complete", + &fundchannel_complete_ok, + &fundchannel_complete_err, + dest); + json_add_node_id(req->js, "id", &dest->id); + json_add_string(req->js, "txid", + type_to_string(tmpctx, struct bitcoin_txid, + mfc->txid)); + json_add_num(req->js, "txout", dest->outnum); + + send_outreq(cmd->plugin, req); +} + +static struct command_result * +fundchannel_complete_done(struct multifundchannel_destination *dest); + +static struct command_result * +fundchannel_complete_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + const jsmntok_t *channel_id_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: fundchannel_complete %s done.", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id)); + + channel_id_tok = json_get_member(buf, result, "channel_id"); + if (!channel_id_tok) + plugin_err(cmd->plugin, + "fundchannel_complete no channel_id: %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + dest->channel_id = json_strdup(mfc, buf, channel_id_tok); + + return fundchannel_complete_done(dest); +} +static struct command_result * +fundchannel_complete_err(struct command *cmd, + const char *buf, + const jsmntok_t *error, + struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + const jsmntok_t *code_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: " + "failed! fundchannel_complete %s: %.*s", + mfc->id, dest->index, + node_id_to_hexstr(tmpctx, &dest->id), + json_tok_full_len(error), json_tok_full(buf, error)); + + code_tok = json_get_member(buf, error, "code"); + if (!code_tok) + plugin_err(cmd->plugin, + "`fundchannel_complete` failure " + "did not have `code`? " + "%.*s", + json_tok_full_len(error), + json_tok_full(buf, error)); + if (!json_to_errcode(buf, code_tok, &dest->code)) + plugin_err(cmd->plugin, + "`fundchannel_complete` has unparseable `code`? " + "%.*s", + json_tok_full_len(error), + json_tok_full(buf, error)); + + dest->state = MULTIFUNDCHANNEL_COMPLETE_FAILED; + dest->error = json_strdup(mfc, buf, error); + + return fundchannel_complete_done(dest); +} + +static struct command_result * +after_fundchannel_complete(struct multifundchannel_command *mfc); + +static struct command_result * +fundchannel_complete_done(struct multifundchannel_destination *dest) +{ + struct multifundchannel_command *mfc = dest->mfc; + + --mfc->pending; + if (mfc->pending == 0) + return after_fundchannel_complete(mfc); + else + return command_still_pending(mfc->cmd); +} + +static struct command_result * +perform_sendpsbt(struct multifundchannel_command *mfc); + +static struct command_result * +after_fundchannel_complete(struct multifundchannel_command *mfc) +{ + unsigned int i; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": parallel fundchannel_complete done.", + mfc->id); + + /* Check if any fundchannel_complete failed. */ + for (i = 0; i < tal_count(mfc->destinations); ++i) { + struct multifundchannel_destination *dest; + + dest = &mfc->destinations[i]; + + assert(dest->state == MULTIFUNDCHANNEL_STARTED + || dest->state == MULTIFUNDCHANNEL_COMPLETE_FAILED); + + if (dest->state != MULTIFUNDCHANNEL_COMPLETE_FAILED) + continue; + + /* One of them failed, oh no. */ + return redo_multifundchannel(mfc, "fundchannel_complete"); + } + + return perform_sendpsbt(mfc); +} + +/*---------------------------------------------------------------------------*/ +/*~ +Finally with everything set up correctly we `signpsbt`+`sendpsbt` the +funding transaction. +*/ + +static struct command_result * +after_signpsbt(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc); + +static struct command_result * +perform_sendpsbt(struct multifundchannel_command *mfc) +{ + struct out_req *req; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": signpsbt.", mfc->id); + + req = jsonrpc_request_start(mfc->cmd->plugin, mfc->cmd, + "signpsbt", + &after_signpsbt, + &mfc_forward_error, + mfc); + json_add_psbt(req->js, "psbt", mfc->psbt); + return send_outreq(mfc->cmd->plugin, req); +} + +static struct command_result * +after_sendpsbt(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc); + +static struct command_result * +after_signpsbt(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc) +{ + const jsmntok_t *field; + struct wally_psbt *psbt; + struct out_req *req; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": signpsbt done.", mfc->id); + + field = json_get_member(buf, result, "signed_psbt"); + if (!field) + plugin_err(mfc->cmd->plugin, + "signpsbt did not return 'signed_psbt'? %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + psbt = psbt_from_b64(mfc, + buf + field->start, + field->end - field->start); + if (!psbt) + plugin_err(mfc->cmd->plugin, + "signpsbt gave unparseable 'signed_psbt'? %.*s", + json_tok_full_len(field), + json_tok_full(buf, field)); + + /* Replace the PSBT. */ + tal_free(mfc->psbt); + mfc->psbt = tal_steal(mfc, psbt); + + /*~ Now mark all destinations as being done. + Why mark it now *before* doing `sendpsbt` rather than after? + Because `sendpsbt` will do approximately this: + + 1. `sendpsbt` launches `bitcoin-cli`. + 2. `bitcoin-cli` connects to a `bitcoind` over JSON-RPC + over HTTP. + 3. `bitcoind` validates the transactions and puts it int + its local mempool. + 4. `bitcoind` tells `bitcoin-cli` it all went well. + 5. `bitcoin-cli` tells `sendpsbt` it all went well. + + If some interruption or problem occurs between steps 3 + and 4, then the transaction is already in some node + mempool and will likely be broadcast, but `sendpsbt` has + failed. + + And so we have to mark the channels as being "done" + *before* we do `sendpsbt`. + If not, if we error on `sendpsbt`, that would cause us to + `fundchannel_cancel` all the peers, but that is risky, + as, the funding transaction could still have been + broadcast and the channels funded. + + That is, we treat `sendpsbt` failure as a possible + false negative. + */ + for (size_t i = 0; i < tal_count(mfc->destinations); ++i) { + struct multifundchannel_destination *dest; + dest = &mfc->destinations[i]; + dest->state = MULTIFUNDCHANNEL_DONE; + } + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": sendpsbt.", mfc->id); + + req = jsonrpc_request_start(mfc->cmd->plugin, mfc->cmd, + "sendpsbt", + &after_sendpsbt, + &mfc_forward_error, + mfc); + json_add_psbt(req->js, "psbt", mfc->psbt); + return send_outreq(mfc->cmd->plugin, req); +} + +static struct command_result * +multifundchannel_finished(struct multifundchannel_command *mfc); + +static struct command_result * +after_sendpsbt(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct multifundchannel_command *mfc) +{ + const jsmntok_t *tx_tok; + const jsmntok_t *txid_tok; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": sendpsbt done.", mfc->id); + + tx_tok = json_get_member(buf, result, "tx"); + if (!tx_tok) + plugin_err(cmd->plugin, + "sendpsbt response has no 'tx': %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + mfc->final_tx = json_strdup(mfc, buf, tx_tok); + + txid_tok = json_get_member(buf, result, "txid"); + if (!txid_tok) + plugin_err(cmd->plugin, + "sendpsbt response has no 'txid': %.*s", + json_tok_full_len(result), + json_tok_full(buf, result)); + mfc->final_txid = json_strdup(mfc, buf, txid_tok); + + /* PSBT is no longer something we need to clean up. */ + mfc->psbt = tal_free(mfc->psbt); + + return multifundchannel_finished(mfc); +} + +/*---------------------------------------------------------------------------*/ +/*~ +And finally we are done. +*/ + +static struct command_result * +multifundchannel_finished(struct multifundchannel_command *mfc) +{ + unsigned int i; + struct json_stream *out; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": done.", mfc->id); + + out = jsonrpc_stream_success(mfc->cmd); + json_add_string(out, "tx", mfc->final_tx); + json_add_string(out, "txid", mfc->final_txid); + json_array_start(out, "channel_ids"); + for (i = 0; i < tal_count(mfc->destinations); ++i) { + json_object_start(out, NULL); + json_add_node_id(out, "id", &mfc->destinations[i].id); + json_add_string(out, "channel_id", mfc->destinations[i].channel_id); + json_add_num(out, "outnum", mfc->destinations[i].outnum); + json_object_end(out); + } + json_array_end(out); + + json_array_start(out, "failed"); + for (i = 0; i < tal_count(mfc->removeds); ++i) { + json_object_start(out, NULL); + json_add_node_id(out, "id", &mfc->removeds[i].id); + json_add_string(out, "method", mfc->removeds[i].method); + json_add_jsonstr(out, "error", mfc->removeds[i].error); + json_object_end(out); + } + json_array_end(out); + + return mfc_finished(mfc, out); +} + +/*~ We do cleanup, then we remove failed destinations and if we still have + * the minimum number, re-run. +*/ +struct multifundchannel_redo { + struct multifundchannel_command *mfc; + const char *failing_method; +}; + +static struct command_result * +post_cleanup_redo_multifundchannel(struct multifundchannel_redo *redo); + +static struct command_result * +redo_multifundchannel(struct multifundchannel_command *mfc, + const char *failing_method) +{ + struct multifundchannel_redo *redo; + + assert(mfc->pending == 0); + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": trying redo despite '%s' failure; " + "will cleanup for now.", + mfc->id, failing_method); + + redo = tal(mfc, struct multifundchannel_redo); + redo->mfc = mfc; + redo->failing_method = failing_method; + + return mfc_cleanup(mfc, &post_cleanup_redo_multifundchannel, redo); +} + +/* Return true if this destination failed, false otherwise. */ +static bool dest_failed(struct multifundchannel_destination *dest) +{ + switch (dest->state) { + case MULTIFUNDCHANNEL_START_NOT_YET: + case MULTIFUNDCHANNEL_STARTED: + case MULTIFUNDCHANNEL_DONE: + return false; + + case MULTIFUNDCHANNEL_CONNECT_FAILED: + case MULTIFUNDCHANNEL_START_FAILED: + case MULTIFUNDCHANNEL_COMPLETE_FAILED: + return true; + } + abort(); +} + +/* Filter the failing destinations. */ +static struct command_result * +post_cleanup_redo_multifundchannel(struct multifundchannel_redo *redo) +{ + struct multifundchannel_command *mfc = redo->mfc; + const char *failing_method = redo->failing_method; + size_t i; + struct multifundchannel_destination *old_destinations; + struct multifundchannel_destination *new_destinations; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": Filtering destinations.", + mfc->id); + + /* We got the args now. */ + tal_free(redo); + + /* Clean up previous funding transaction if any. */ + mfc->psbt = tal_free(mfc->psbt); + mfc->txid = tal_free(mfc->txid); + + /* Filter out destinations. */ + old_destinations = tal_steal(tmpctx, mfc->destinations); + mfc->destinations = NULL; + new_destinations = tal_arr(mfc, struct multifundchannel_destination, + 0); + + for (i = 0; i < tal_count(old_destinations); ++i) { + struct multifundchannel_destination *dest; + + dest = &old_destinations[i]; + + if (dest_failed(dest)) { + struct multifundchannel_removed removed; + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: " + "failed.", + mfc->id, dest->index); + + removed.id = dest->id; + removed.method = failing_method; + removed.error = dest->error; + removed.code = dest->code; + /* Add to removeds. */ + tal_arr_expand(&mfc->removeds, removed); + } else { + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64", dest %u: " + "succeeded.", + mfc->id, dest->index); + + /* Reset its state. */ + dest->state = MULTIFUNDCHANNEL_START_NOT_YET; + /* We do not mess with `dest->index` so we have + a stable id between reattempts. + */ + /* Re-add to new destinations. */ + tal_arr_expand(&new_destinations, *dest); + } + } + mfc->destinations = new_destinations; + + if (tal_count(mfc->destinations) < mfc->minchannels) { + /* Too many failed. */ + struct json_stream *out; + + assert(tal_count(mfc->removeds) != 0); + + plugin_log(mfc->cmd->plugin, LOG_DBG, + "mfc %"PRIu64": %zu destinations failed, failing.", + mfc->id, tal_count(mfc->removeds)); + + /* Blame it on the last. */ + i = tal_count(mfc->removeds) - 1; + + out = jsonrpc_stream_fail_data(mfc->cmd, + mfc->removeds[i].code, + tal_fmt(tmpctx, + "'%s' failed", + failing_method)); + json_add_node_id(out, "id", &mfc->removeds[i].id); + json_add_string(out, "method", failing_method); + json_add_jsonstr(out, "error", mfc->removeds[i].error); + + /* Close 'data'. */ + json_object_end(out); + + return mfc_finished(mfc, out); + } + + /* Okay, we still have destinations to try --- reinvoke. */ + return perform_multifundchannel(mfc); +} + +static struct command_result *param_positive_number(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + unsigned int **num) +{ + struct command_result *res = param_number(cmd, name, buffer, tok, num); + if (res) + return res; + if (**num == 0) + return command_fail_badparam(cmd, name, buffer, tok, + "should be a positive integer"); + return NULL; +} + +/*----------------------------------------------------------------------------- +Command Entry Point +-----------------------------------------------------------------------------*/ + +/* Entry function. */ +static struct command_result * +json_multifundchannel(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct multifundchannel_destination *dests; + const char *feerate_str; + u32 *minconf; + const jsmntok_t *utxos_tok; + u32 *minchannels; + + struct multifundchannel_command *mfc; + + if (!param(cmd, buf, params, + p_req("destinations", param_destinations_array, &dests), + p_opt("feerate", param_string, &feerate_str), + p_opt_def("minconf", param_number, &minconf, 1), + p_opt("utxos", param_tok, &utxos_tok), + p_opt("minchannels", param_positive_number, &minchannels), + NULL)) + return command_param_failed(); + + /* Should exist; it would only nonexist if it were a notification. */ + assert(cmd->id); + + mfc = tal(cmd, struct multifundchannel_command); + mfc->id = *cmd->id; + mfc->cmd = cmd; + + /* Steal destinations array, and set up mfc pointers */ + mfc->destinations = tal_steal(mfc, dests); + for (size_t i = 0; i < tal_count(mfc->destinations); i++) + mfc->destinations[i].mfc = mfc; + + mfc->feerate_str = feerate_str; + mfc->minconf = *minconf; + if (utxos_tok) + mfc->utxos_str = tal_strndup(mfc, json_tok_full(buf, utxos_tok), + json_tok_full_len(utxos_tok)); + else + mfc->utxos_str = NULL; + /* Default is that all must succeed. */ + mfc->minchannels = minchannels ? *minchannels : tal_count(mfc->destinations); + mfc->removeds = tal_arr(mfc, struct multifundchannel_removed, 0); + mfc->psbt = NULL; + mfc->change_scriptpubkey = NULL; + mfc->txid = NULL; + mfc->final_tx = NULL; + mfc->final_txid = NULL; + + return perform_multifundchannel(mfc); +} + +static +const struct plugin_command multifundchannel_commands[] = { + { + "multifundchannel", + "channels", + "Fund channels to {destinations}, which is an array of " + "objects containing peer {id}, {amount}, and optional " + "{announce} and {push_msat}. " + "A single transaction will be used to fund all the " + "channels. " + "Use {feerate} for the transaction, select outputs that are " + "buried {minconf} blocks deep, or specify a set of {utxos}.", + "Fund multiple channels at once.", + json_multifundchannel + } +}; +static +const unsigned int num_multifundchannel_commands = + ARRAY_SIZE(multifundchannel_commands); + +static +void multifundchannel_init(struct plugin *plugin, + const char *buf UNUSED, + const jsmntok_t *config UNUSED) +{ + /* Save our chainparams. */ + const char *network_name; + + network_name = rpc_delve(tmpctx, plugin, "listconfigs", + take(json_out_obj(NULL, "config", + "network")), + ".network"); + chainparams = chainparams_for_network(network_name); +} + +int main(int argc, char **argv) +{ + setup_locale(); + plugin_main(argv, + &multifundchannel_init, PLUGIN_RESTARTABLE, + true, + NULL, + multifundchannel_commands, num_multifundchannel_commands, + NULL, 0, NULL, 0, NULL); +} diff --git a/tests/test_connection.py b/tests/test_connection.py index a0551d2c4b73..0589452d6a7d 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1229,6 +1229,257 @@ def test_funding_external_wallet(node_factory, bitcoind): l3.rpc.close(l2.info["id"]) +def test_multifunding_simple(node_factory, bitcoind): + ''' + Simple test for multifundchannel. + ''' + l1, l2, l3, l4 = node_factory.get_nodes(4) + + l1.fundwallet(2000000) + + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l4.info['id'], l4.port), + "amount": 50000}] + + l1.rpc.multifundchannel(destinations) + bitcoind.generate_block(6, wait_for_mempool=1) + + for node in [l1, l2, l3, l4]: + node.daemon.wait_for_log(r'to CHANNELD_NORMAL') + + for ldest in [l2, l3, l4]: + inv = ldest.rpc.invoice(5000, 'inv', 'inv')['bolt11'] + l1.rpc.pay(inv) + + +def test_multifunding_one(node_factory, bitcoind): + ''' + Test that multifunding can still fund to one destination. + ''' + l1, l2, l3 = node_factory.get_nodes(3) + + l1.fundwallet(2000000) + + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}] + + l1.rpc.multifundchannel(destinations) + + # Now check if we connect to the node first before + # multifundchannel. + l1.rpc.connect(l3.info['id'], 'localhost', port=l3.port) + # Omit the connect hint. + destinations = [{"id": '{}'.format(l3.info['id']), + "amount": 50000}] + + l1.rpc.multifundchannel(destinations, minconf=0) + + bitcoind.generate_block(6, wait_for_mempool=1) + for node in [l1, l2, l3]: + node.daemon.wait_for_log(r'to CHANNELD_NORMAL') + + for ldest in [l2, l3]: + inv = ldest.rpc.invoice(5000, 'inv', 'inv')['bolt11'] + l1.rpc.pay(inv) + + +@unittest.skipIf(not DEVELOPER, "disconnect=... needs DEVELOPER=1") +def test_multifunding_disconnect(node_factory): + ''' + Test disconnection during multifundchannel + ''' + # TODO: Note that @WIRE_FUNDING_SIGNED does not + # work. + # See test_disconnect_half_signed. + # If disconnected when the peer believes it sent + # WIRE_FUNDING_SIGNED but before we actually + # receive it, the peer continues to monitor our + # funding tx, but we have forgotten it and will + # never send it. + disconnects = ["-WIRE_INIT", + "-WIRE_ACCEPT_CHANNEL", + "@WIRE_ACCEPT_CHANNEL", + "+WIRE_ACCEPT_CHANNEL", + "-WIRE_FUNDING_SIGNED"] + l1 = node_factory.get_node() + l2 = node_factory.get_node(disconnect=disconnects) + l3 = node_factory.get_node() + + l1.fundwallet(2000000) + + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}] + + # Funding to l2 will fail, and we should properly + # inform l3 to back out as well. + for d in disconnects: + with pytest.raises(RpcError): + l1.rpc.multifundchannel(destinations) + + # TODO: failing at the fundchannel_complete phase + # (@WIRE_FUNDING_SIGNED +@WIRE_FUNDING_SIGNED) + # leaves the peer (l2 in this case) in a state + # where it is waiting for an incoming channel, + # even though we no longer have a channel going to + # that peer. + # Reconnecting with the peer will clear up that + # confusion, but then the peer will disconnect + # after a random amount of time. + + destinations = [{"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}] + + # This should succeed. + l1.rpc.multifundchannel(destinations) + + +def test_multifunding_wumbo(node_factory): + ''' + Test wumbo channel imposition in multifundchannel. + ''' + l1, l2, l3 = node_factory.get_nodes(3, + opts=[{'large-channels': None}, + {'large-channels': None}, + {}]) + + l1.fundwallet(1 << 26) + + # This should fail. + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 1 << 24}] + with pytest.raises(RpcError, match='Amount exceeded'): + l1.rpc.multifundchannel(destinations) + + # This should succeed. + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 1 << 24}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}] + l1.rpc.multifundchannel(destinations) + + +def test_multifunding_param_failures(node_factory): + ''' + Test that multifunding handles errors in parameters. + ''' + l1, l2, l3 = node_factory.get_nodes(3) + + l1.fundwallet(1 << 26) + + # No connection hint to unconnected node. + destinations = [{"id": '{}'.format(l2.info['id']), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}] + with pytest.raises(RpcError): + l1.rpc.multifundchannel(destinations) + + # Duplicated destination. + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}] + with pytest.raises(RpcError): + l1.rpc.multifundchannel(destinations) + + # Empty destinations. + with pytest.raises(RpcError): + l1.rpc.multifundchannel([]) + + # Required destination fields missing. + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port)}] + with pytest.raises(RpcError): + l1.rpc.multifundchannel(destinations) + destinations = [{"amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}] + with pytest.raises(RpcError): + l1.rpc.multifundchannel(destinations) + + +@unittest.skipIf(not DEVELOPER, "disconnect=... needs DEVELOPER=1") +def test_multifunding_best_effort(node_factory, bitcoind): + ''' + Check that best_effort flag works. + ''' + disconnects = ["-WIRE_INIT", + "-WIRE_ACCEPT_CHANNEL", + "-WIRE_FUNDING_SIGNED"] + l1 = node_factory.get_node() + l2 = node_factory.get_node() + l3 = node_factory.get_node(disconnect=disconnects) + l4 = node_factory.get_node() + + l1.fundwallet(2000000) + + destinations = [{"id": '{}@localhost:{}'.format(l2.info['id'], l2.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l3.info['id'], l3.port), + "amount": 50000}, + {"id": '{}@localhost:{}'.format(l4.info['id'], l4.port), + "amount": 50000}] + + for i, d in enumerate(disconnects): + # Should succeed due to best-effort flag. + l1.rpc.multifundchannel(destinations, minchannels=2) + bitcoind.generate_block(6, wait_for_mempool=1) + + # Only l3 should fail to have channels. + for node in [l1, l2, l4]: + node.daemon.wait_for_log(r'to CHANNELD_NORMAL') + + # There should be working channels to l2 and l4. + for ldest in [l2, l4]: + inv = ldest.rpc.invoice(5000, 'i{}'.format(i), 'i{}'.format(i))['bolt11'] + l1.rpc.pay(inv) + + # Function to find the SCID of the channel that is + # currently open. + # Cannot use LightningNode.get_channel_scid since + # it assumes the *first* channel found is the one + # wanted, but in our case we close channels and + # open again, so multiple channels may remain + # listed. + def get_funded_channel_scid(n1, n2): + peers = n1.rpc.listpeers(n2.info['id'])['peers'] + assert len(peers) == 1 + peer = peers[0] + channels = peer['channels'] + assert channels + for c in channels: + state = c['state'] + if state in ('CHANNELD_AWAITING_LOCKIN', 'CHANNELD_NORMAL'): + return c['short_channel_id'] + assert False + + # Now close channels to l2 and l4, for the next run. + l1.rpc.close(get_funded_channel_scid(l1, l2)) + l1.rpc.close(get_funded_channel_scid(l1, l4)) + + for node in [l1, l2, l4]: + node.daemon.wait_for_log(r'to CLOSINGD_COMPLETE') + + # With 2 down, it will fail to fund channel + l2.stop() + l3.stop() + with pytest.raises(RpcError, match=r'Connection refused'): + l1.rpc.multifundchannel(destinations, minchannels=2) + + # This works though. + l1.rpc.multifundchannel(destinations, minchannels=1) + + def test_lockin_between_restart(node_factory, bitcoind): l1 = node_factory.get_node(may_reconnect=True) l2 = node_factory.get_node(options={'funding-confirms': 3},