The PR branch HEAD was db4efdeb6d at the time of this review club meeting.
Notes
Funding a transaction involves selecting inputs with a total amount that covers both the
payment(s) and the fees for the transaction itself. The target feerate is calculated at the
beginning, and the fees depend on the total size of the transaction.
In some use cases, users may want to fund a transaction with external inputs, i.e., inputs that
are not controlled by the user themselves, but some external wallet such as that of a LN
channel counterparty. The challenge in this scenario is accounting for these inputs when funding
the fees of the transaction - the wallet needs to know their size.
Given solving data such as public keys, scripts, or descriptors, the wallet can approximate
the size of the input and corresponding witness data. For example,
this stackexchange post
breaks down the possible sizes of a P2PKH input.
PR #17211 added support for allowing
users to provide solving data to help the wallet determine the size of external input(s).
Some sizes might differ depending on how the external wallet generates the signature. In these
cases, the wallet uses the maximum possible size to avoid underestimating the fees needed to
reach the target feerate.
Still, solving data might not be available and external inputs may be nonstandard.
PR #23201 adds support for specifying the weights
of external inputs in the wallet RPCs send, walletcreatefundedpsbt, and fundrawtransaction.
This allows the wallet to take these inputs into account when funding the transaction at a target
feerate, even if it doesn’t have the solving data for calculating the size itself.
This is achieved by adding a map from the input’s outpoint to weight in CCoinControl.
If a user provides both solving data and input weights, the provided weight overrides the size
calculated using solving data.
Raw transactions are serialized in a specific
format which
includes lengths and sizes (e.g. number of inputs in vin and number of bytes in the signature
script) represented as Compact Size Unsigned
Integers,
which vary in length depending on the value.
The helper function, FillInputToWeight(), fills a CTxIn by adding to the witness stack
until it reaches a target weight. It takes into account situations in which increasing the
size of the witness stack also increases the size of the Compact Size Uint used to encode its length.
What are some use cases of allowing external inputs to be used in send,
walletcreatefundedpsbt, fundrawtransaction, and bumpfee?
Why might a user want to specify input weights instead of using the existing solving_data option?
Why does FundTransaction() need to know the external inputs ahead of time at all?
In the interface modified by this PR, how would a user call {send, walletcreatefundedpsbt,
fundrawtransaction} to specify a maximum input weight?
The RPCs will throw a JSONRPCError: RPC_INVALID_PARAMETER if the specified weight is below 165.
Why can’t the weight of an input be less than 165? (Hint: see the definition of
GetTransactionInputWeight()
and witness serialization specified in
BIP144).
Quiz: Given that an external input is a {P2PKH, P2WPKH, P2WSH, P2TR}, can you calculate the
maximum weight you need to add to the transaction you’re funding?
What is the purpose of CCoinControl? What are the different purposes of CCoinControl versus
CoinSelectionParams?
What is a Compact Size Unsigned Integer? Where is it used in the Bitcoin protocol?
How does the FillInputToWeight() function fill an input to the target weight? Why does it care
when the weight to add is between 253 and 263?
<glozow> Today we're looking at #23201, "Allow users to specify input weights when funding a transaction", which seems to be a favorite among lightning devs
<bitcoin_pleb_pau> in my understanding, this PR creates a new way to find the input weight of an external input. prior, we used solving data, now we use wallet RPC's to get more accurate data.
<svav> It allows for accounting for any external inputs of a transaction in the fee estimation of the transaction, so that any transactions involving external inputs can be processed successfully
<OliverOffing> So we're talking about a multi-sig tx, yes? Since it's an external input, I'm guessing it'd need at least 2 signatures… (sanity check)
<bitcoin_pleb_pau> michaelfolkson The notes say it is useful for lightning channels, where the other party in the channel has a set of keys that we don't own
<achow101> an external input is an input that the wallet is unable to solve for, where solving means that if it had private keys available, a spending transaction could be created
<achow101> this usually means that the wallet is unaware of those inputs as it is not watching for them, but there are some cases where the wallet may be watching for those inputs but lack solving data
<svav> I have a general question about GitHub. Where is there a description of the functionality of this (or any) PR, because when I look I just find the comments. Do you only get the code to look at, or does the PR submitter give a summary anywhere?
<stickies-v> this technically doesn't have to be multisig though, right? Could just be a single input transaction where the keys of that single input are stored somewhere outside of core?
<bitcoin_pleb_pau> ok glozow thanks. in my understanding the argument is passed 'manually' but that doesn't mean it requires user handling, it's still done 'automatically' but just on a case-by-case basis, and this case-by-case is what your aiming at when you say manually?
<tarun> I too don't want to derail the convo, but doesn't the external input need to be known (and hence its weight) immediately prior to publishing and so why would we need to estimate?
<michaelfolkson> It is more the Core wallet doesn't understand how to spend the output than we are unable to spend the output using external wallet (e.g. Lightning wallet)
<larryruane> svav: some (but not most) PRs reference a GitHub "issue" (aka a ticket) that usually explains more background; in this case there is a ticket: https://github.com/bitcoin/bitcoin/issues/23187
<glozow> stickies-v: yes. OliverOffing: i usually think of multisig as a single input. perhaps a more clear example here is a transaction with multiple inputs, controlled by us and other parties
<glozow> tarun: when we're funding the transaction, we're trying to put fees on it to reach a target feerate. if we don't know what the weight will be, we won't know how much in fees to put on it
<stickies-v> tarun: I think what you might be missing is that a known scriptpubkey can have an unknown scriptsig length. E.g. if you know a P2SH scriptpubkey, you don't know how long the scriptsig is going to be (script is unknown until input is spent)
<glozow> great. let's also answer the first question which is about motivation - What are some use cases of allowing external inputs to be used in send, walletcreatefundedpsbt, fundrawtransaction, and bumpfee?
<larryruane> I was surprised `fundrawtransaction` is on this list, because doesn't that fund (only) from sources (UTXOs) that this wallet has the keys for? So wouldn't it know the size?
<glozow> tarun: no worries! it's a fundamental part of understanding the PR so thanks for bringing it up. the ordering of questions isn't that important
<michaelfolkson> A collaborative Lightning channel close is just a spend from a 2-of-2. If you want to construct a transaction from that 2-of-2 to a single key address in your Core wallet?
<ziggie> larryruane>Note that all existing inputs must have their previous output transaction be in the wallet> so I guess this PR introduces this possiblity to include inputs thats are external ?
<michaelfolkson> It gets a bit complicated because with Lightning you are dealing with two wallets, a Lightning wallet and the Core wallet. And the Core wallet doesn't understand what the Lightning wallet is doing
<achow101> michaelfolkson: no. the lightning use case here is to be able to spend HTLCs because the wallet does not recognize HTLCs, even if tracking those outputs
<glozow> The RPCs will throw a `JSONRPCError: RPC_INVALID_PARAMETER` if the specified weight is below 165. Why can’t the weight of an input be less than 165?
<bitcoin_pleb_pau> Question: Does using an RPC call to request the weight of an external input sacrifice some privacy because it conveys what kind of script-spending-conditions are associated with a public key?
<bitcoin_pleb_pau> I need to brush up on my understanding of RPCs...I always assumed it was only between nodes, but glozow points out, if i understand, that when you are manipulating your wallet, your wallet is communicating with Your OWN node via RPC?
<glozow> if you see the link to GetTransactionInputWeight, you'll see that the weight = 4 * serialized size of nonwitness data + serialized size of witness data
<achow101> bitcoin_pleb_pau: RPC is not used to communicate between nodes nor between any components of core. it is an external interface for users to interact with Core in a programmatic way
<OliverOffing> With a new argument called `input_weights` which is expected to contain an array of dicts, which should include the fields `txid`, `vout`, and the explicitly defined `weight` of that tx
<OliverOffing> i guess the code base usually calls it just `weight` because it usually knows the exact number -- external inputs is kind of an edge case
<OliverOffing> "If you're reading the Satoshi client code (BitcoinQT) it refers to this encoding as a "CompactSize". Modern Bitcoin Core also has the VARINT macro which implements an even more compact integer for the purpose of local storage (which is incompatible with "CompactSize" described here). VARINT is not a part of the protocol."
<glozow> let's try to squeeze in one more question: How does the FillInputToWeight() function fill an input to the target weight? Why does it care when the weight to add is between 253 and 263?
<sipa> Also, originally CompactSize was only used for the length of vectors or other things, not as a generic number encoding. The BIP152 compact block encoding however started using the compactsize encode for non-length things.