lisa neigut <nifty...@gmail.com> writes: > Hello fellow Lightning devs! > > What follows is a draft for the new dual funding flow. Note that the > `option_will_fund_for_food` specification has been omitted for this draft.
Hi! Wow, my mailer really mangled this! I've liberally demangled below as I quote. The proposal is great, but intense, so I've bikeshedded the language. My only objection is that I'd love to simplify RBF. > ===== Proposal > > Create a new channel open protocol set (v2), with three new message types: > `funding_puts2`, `commitment_signed2`, and `funding_signed2`, plus two for > negotiating RBF, `init_rbf` and `accept_rbf`. > > > Quick overview of the message exchange for v2: > > +----+ +----+ > | |--(1)--- open_channel2 ---->| | > | |<-(2)-- accept_channel2 ----| | > | | | | > | |--(3)-- funding_puts2 ----->| | > | |<-(4)-- funding_puts2 ----- | | > | | | | > | |--(5)-- commitment_signed2 -->| | > | |<-(6)-- commitment_signed2 ---| | > | A | | B | > | |--(7)--- funding_signed2 ---->| | > | |<-(8)--- funding_signed2 -----| | > | | | | > | |--(a)--- init_rbf ----------->| | > | |<-(b)--- accept_rbf ----------| | > | | | | > | |--(9)--- funding_locked2 ---->| | > | |<-(10)---funding_locked2 -----| | > +----+ +----+ > where node A is the ‘initiator’ and node B is the ‘dual-funder’ We currently use the terms funder and fundee, which are now inaccurate ofc. Perhaps 'opener' and 'accepter' are not great english, but they map to the messages well? > Willingness to use v2 is flagged in init via `option_dual_fund`. > `init` > > local channel feature flag, `option_dual_fund` > > == Channel establishment with dual_funding > > ____`open_channel2`: > [32:chain_hash] > … // unchanged > [1:channel_flags] > [?: options_tlv] Always prefix variable fields by length, even this one. Otherwise we can never extend, and you never know... [2:tlv_len] [tlv_len:opening_tlv] I think we can remove `funding_satoshis` here; we'll know when they add their inputs, so it's redundant. Another subtle point is the feerate_per_kw field; in the old scheme it applied to the first commitment tx, but here it applies to both the first commitment tx and the funding tx itself (unless option_simplified_commitment, though roasbeef has suggested further splitting that option, in which case we'll want another fee field here). > options_tlv: Let's call this `opening_tlv` since there are other TLVs coming? > 1. > Type: 1 `option_upfront_shutdown_script` > 1. > > [2:len] > 2. > > Value: `shutdown_scriptpubkey` > If nodes have negotiated `option_dual_fund` > The sending node: > - > MAY begin channel establishment using `open_channel2` - MUST NOT send `open_channel`. > Otherwise the receiving node: > - > MUST return an error. This is a requirement for receiving `open_channel` IIUC? ie. The receiving node MUST fail the channel if: ... - `option_dual_fund` has been negotiated. > ____ `accept_channel2`: > > [32:temporary_channel_id] > … // unchanged > [33:first_per_commitment_point] > [?: options_tlv] > If we call this `opening_tlv` we can just reuse the definition from before. > ____`funding_puts2` We can probably drop the 2 here and call it, um.. `funding_compose`? (Thanks thesaurus.com). I get where you're going with 'puts, but it took me a while :) > This message exchanges the input and output information necessary to > compose the funding transaction. > > [32:temporary_channel_id] > [`2`:`num_inputs`] > [`num_inputs*input_info`] > [`2`:`num_outputs`] > [`num_outputs`*ouput_info`] typo: output_info > 1. subtype: `input_info` > 2. data: > * [`8`:`satoshis`] > * [`32`:`prevtxid`] > * [`4`:`prevtxoutnum`] > * [`2`:`scriptlen`] > * [`scriptlen`:`script`] > * [`2`:`max_extra_witness_len`] > * [`2`:`wscriptlen`] > * [`wscriptlen`:`wscript`] > > 1. subtype: `output_info` > 2. data: > * [`8`:`satoshis`] > * [`2`:`scriptlen`] > * [`scriptlen`:`script`] > > Requirements: > > The sending node: > > - MUST ensure each `input_info` refers to an existing UTXO > - MUST ensure the `output_info`.`script` is a standard script > - MUST NOT spend any UTXOs specified in funding_puts2 until/unless the > channel establishment has failed > If is the initiator (A): > - MUST NOT send an empty message (`num_inputs` + `num_outputs` = 0) > > If is the dual-funder (B): > - > consider the `put_limit` the total number of `num_inputs` plus > `num_outputs` from `funding_puts2`, with minimum 2. > - > MUST NOT send a number of `input_data` and/or `output_data` which > exceeds the `put_limit` Side note: I wonder if we should relax this limit when we talk about `option_will_fund_for_food`? > - > MAY send an empty message Be explicit? MAY offer zero `num_inputs` and `num_outputs`. That's not quite an empty message... > The receiving node: > > If is the initiator (A): > > - > MUST fail the channel if the `num_inputs` plus `num_outputs` is greater > than the `put_limit` How about MAY? It's a protection thing, but less to change when we option_will_fund_for_food. Unless we set the `put_limit` to min (4) or something in that case? Oh, it needs to check max_extra_witness_len is reasonable too, since that will affect the fees. Each signature adds 74, and pubkey adds 34, so I think MUST BE less than 500 is perfectly reasonable (for both reader and writer). > If has not yet sent a `fund_puts2` for this channel establishment > - SHOULD send their `fund_puts2` message > > Otherwise > - SHOULD send `commitment_signed2` Perhaps add '- MUST use the sent and received `input_info` and `output_info` to create the funding transaction, using `max_extra_witness_len` for each `input_info` and `feerate_per_kw` from `open_channel2`.' Side note: we need to define max_extra_witness_len as the total marshalled length of the extra witness data which will be supplied (ie. sizeof(varint) + sizeof(data) for each one). > Rationale: > > Each node must have a complete set of the transaction inputs/outputs, to > derive the funding transaction hash. > > There is a dual_funding protocol that only requires one side to send their > witness data and inputs. This is more efficient, however it results in > asymmetry between the nodes, where one is revealing their UTXOs before the > funding transaction is committed.. We mitigate this asymmetry by asking the > initiator (A) to send their set of inputs before the dual-funder (B) does. > > NB: This is reusing the input/output structures from the Splicing proposal, > but with a more generalized name. > > > ____`commitment_signed2` I just realized that `commitment_signed` is the name of the message we use during HTLC / fee updates. But since the message is identical to this one in both form and purpose, I think we can reuse it here. > This message exchanges the counterparty’s signature for the first > commitment transaction, so it can broadcast the funding transaction knowing > that funds can be redeemed. > > [32: `channel_id`] > [`64`: commitment_signature`] > > Requirements: > > The sending node: > > - MUST derive the `channel_id` from the funding transaction’s id > - MUST set signature to the valid signature, using its funding_pubkey for > the initial commitment transaction, as defined in BOLT #3 This is good. > > If it has not received a `funding_puts2` > > - > > MUST NOT send their `commitment_signature` This is implied by the requirement that they generate the funding transaction. For better or worse, we don't usually spell out requirements not to send things out of order. > The receiving node: > > - MUST verify the commitment signature is valid for the funding > transaction -> commitment transaction that it has derived independently > > If this signature is invalid it > > - MUST fail the channel > > > If it has not sent a `commitment_signed2` message > > - MUST send their `commitment_signed2` message > > If this is in a flow initiated from `init_rbf`: > - Perhaps be explicit here: 'If this commitment_signed2 was in response to an `init_rbf` message:'? > MUST save the temporary_channel_id until the channel funding transaction > has been locked (this is the channel id of the currently broadcast > transaction) Confused by this, see commentry down later. > Rationale: > > In the previous channel establishment protocol, we were able to compress > the commitment signature exchange into `funding_created`/`funding_signed`. > With dual funding, we need interaction to build the funding transaction -- > commitment sig exchange is now a separate step. > ___`funding_signed2` > > This message exchanges the witness data for the inputs that were originally > sent in the `funding_puts2` message. > > [`32`:`channel_id`] > [`2`:`num_witnesses`] > [`num_witnesses*witness_stack`] > > Requirements: > > The sending node: > - MUST remember the details of this funding transaction - MUST send one `witness_stack` for each `output_info` it sent in `funding_puts2`. > - MUST NOT send a `witness_stack` whose length exceeds the corresponding > `max_extra_witness_len` > > If they have NOT received a valid `commitment_signed2` message > - MUST not send their `funding_signed2` message Redundant, but so vital I agree it needs to be stated. > The receiving node: > > - SHOULD check that the number of witnesses sent matches the number of > inputs "SHOULD check" is a spec anti-pattern :) if `num_witnesses` does not equal `num_inputs` received in `funding_puts2`: - MUST fail the channel. > If a `witness_stack` length exceeds the corresponding > `max_extra_witness_len`: > > - > > MAY error. MUST? > If is the `initiator` (A): > > - > > SHOULD apply `witness` to the funding transaction and broadcast the > result. Since this is symmetrical, you can drop "if it is the `initiator`"? > Rationale: > > Exchanging witness data allows either side to broadcast the funding > transaction. It also maintains the information symmetry between the nodes. > > ___`funding_locked2` > > // same as v1 > > Requirements: > > A dual-funding node (B): > > - > > SHOULD broadcast their funding transaction if it does not see the > transaction broadcast after a reasonable timeout. Let's just reuse `funding_locked` maybe? Not sure why this should wait for broadcast? > == RBF for Channel Establishment v2 > > _____`init_rbf` > > This message is sent by the initiator, after the funding transaction has > been broadcast but before the `funding_locked2` has been exchanged. > > [32: `channel_id`] > [8: funding_satoshis] > [8:dust_limit_satoshis] > [8:channel_reserve_satoshis] > [4: feerate_per_kw] > [`2`:`num_inputs`] > [`num_inputs*input_info`] > [`2`:`num_outputs`] > [`num_outputs`*ouput_info`] Typo again :) > Requirements > > The sending node: > - MUST be the initiator (A) > - MAY update the amount, fee rate, dust limit, or channel reserve for the > channel - MAY send init_rbf if it considers the most recent funding tx unlikely to be confirmed in reasonable time. - MUST set `feerate_per_kw` larger than the most recent funding tx. Do we really want to negotiate everything again? It seems like the funder should be able to maybe add *new* inputs and outputs (though TBH I think that's going to be unusual enough that we could omit it), but doing a wholesale replacement means we have to be careful that the all RBFs end up having at least one input in common. Yech. > The receiving node: > > - MAY reject (error) the RBF request if: > - > > the fee rate, dust, or channel reserve is unreasonable > - > > MUST reject (error) the RBF request if: > - > > the `fee_rate` is less than the rate that was originally proposed > - > > the `funding_satoshis` amount is less than the previous negotiated > `push_mast` > - > > It considers the `feerate_per_kw` too small for timely processing or > unreasonable > - > > the `dust_limit_satoshis` is greater than the > `channel_reserve_satoshis` > - > > the initiator’s amount for the initial commitment transaction is not > sufficient for full fee payment > - > > the `inputs`.`satoshis` does not sum to the `funding_satoshis` > - > > the `funding_satoshis` is insufficient to create the transaction at > the new `fee_rate` > - > > there is no overlap in the proposed inputs and the original input set > included in the currently published funding transaction More subtly, there must be a common subset of inputs between every two funding txs. > - > > they have already received or sent a `funding_locked2` message > - > > If there are no errors or unreasonable demands: > - > > SHOULD send an `accept_rbf` > > > Rationale: > > Once an `init_rbf` has been accepted by the dual-funding node, the message > flow returns to `commitment_signed2` and proceeds as above, with the > exception that the `temporary_channel_id` remains as the `channel_id` for > the currently published but unmined transaction. By this stage, we are no longer using temporary_channel_id though. But it's an excellent point I had missed. The channel_id changes on each renegotiation. We could either switch channel_id *after* each accept_rbf, or keep the original channel_id until funding_locked2 (in which case it should have a "final_channel_id" field, to make sure we're talking about the same funding tx). Since we have to handle the "oops, old one got in!" it might be weird to see `funding_locked2` with an old txid. Perhaps we stick with the original channel_id until then, and flip *after* funding_locked2 is sent and received. And yeah, no `update_fee`, `announcement_signatures` until that funding_locked2 exchange is complete, so we don't get those with an unsettled channel_id. > The channel id that becomes fixed for this node will be determined by the > `funding_locked2` message. > > ___`accept_rbf` > > This message accepts an RBF request, and renegotiates a dual-funder’s > funds, dust limit, and channel reserve, and sends the dual-funder’s updated > puts. I would make this an empty message, simply an ack. And note that the channel_id after this is that of the RBFed tx. The question then becomes what do we do about reconnection. I suggest: opener: if we haven't sent funding_signed, consider it cancelled. If we've received funding_signed, it's obviously locked in. If we sent and didn't received, re-xmit. accepter: must remember rbf if we sent commitment_signed2. If we received funding_signed it's locked in. If we receive an init_rbf, drop the one we remembered. If we receive funding_signed, continue. We still need to address the funding_tx construction; BIP69-style seems like an unnecessary information leak here. A 128-bit seed in open_channel2 could be added, with sorting by SHA(seed | <marshal of input> | <marshal of witness>) and SHA(seed | <marshal of output>)? Phew! Rusty. _______________________________________________ Lightning-dev mailing list Lightningfirstname.lastname@example.org https://lists.linuxfoundation.org/mailman/listinfo/lightning-dev