Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

option_will_fund: liquidity ads #878

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

niftynei
Copy link
Collaborator

@niftynei niftynei commented Jun 7, 2021

Draft spec for adding 'liquidity ads' to the dual-funding flow. Allows peers to signal fees that they'll charge for committing liquidity into a dual-funded open, which are then locked until the lease expires.

Currently a work in progress.

Builds on #851.

- MUST set `funding_fee_base_sat` to the base fee (in satoshi) it will charge
for any reciprocated funding.
- MUST set `funding_fee_proportional_basis` to the amount (in
thousandths of a satoshi) it will charge per contributed satoshi.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not millionths?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this fits better in u16? As these msgs are gossiped all over the network, bytes matter. Though basis points would be 10,000th, so perhaps rename to "funding_fee_proportional_thousanths" explicitly? (Allowing up to 65% seems nice; 6.5% is probably a bit low).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... names don't match fields, particularly missing _max?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, my thinking was to be consistent and sufficiently granular. I would like 10000ths more than 1000ths.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to say 'thousandths' -- generally it's advisable to expose these to end users as ppm though -- using parts-per-thousandths is a shortcut to reduce the size of the node_announcement TLV

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@niftynei do you mean that when exposed to user as ppm some rounding will be implemented or a more precise value will be negotiated when actually opening the channel?

Copy link
Collaborator Author

@niftynei niftynei Jul 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The channel fee is expressed as "channel_fee_proportional_millionths" and is broadcast via the "channel_update" gossip message. The channel lease commits to a "channel_fee_max_proportional_thousandths". Let's call these "ppm" and "ppt" for short. 1 ppt is equal to 1,000ppm. Practically, setting a 'channel_fee_max_proportional_thousandth" to 1 would be a commitment to not set the "channel_fee_proportional_millionth" larger than 1000 (for the duration of the lease)

@@ -359,6 +384,12 @@ to be ordered in ascending order, unknown ones can be safely ignored.
Additional fields beyond `addresses` may also be added in the future—with
optional padding within `addresses`, if they require certain alignment.

If a node signals `option_will_fund`, they are signaling that they
will provide funding to a node at the stated terms. They also commit
to a feerate they will charge for transmitting funds over the channel
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "to a maximum feerate"

opens are only possible on the v2 channel protocol.

Any advertised liquidity lease is for a duration of 4032 blocks, or
approximately 28 days.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it'd be better to keep this configurable. Is there any reason not to?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the more variables you have the harder it is to computationally compare rates. It's actually pretty easy to add a parameter for this later, if it proves popular, but I think we need to see what it looks like in practice (should the fee guaranty be separately configurable, for example).

Copy link
Collaborator

@rustyrussell rustyrussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording nitpicks and terminology to make things clearer (esp definitions of lessor and active lease or whatever), and naming fixes. Only two actual spec changes:

  1. Request to use the same subtype in both init tlv and announce.
  2. Maybe have them only pay for what they requested?
  3. Make signature commit to end not start of lease.

- if they decide to accept the offer:
- MUST include a `will_fund` tlv
- MUST set `funding_satoshis` to a value greater than 0msat
- MAY provide a `funding_satoshis` of any amount.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this imply WUMBO?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not intentionally; I mean to signal that you MAY provide less (or more) than requested_sats.

Will update to MAY send less...

- MUST set `funding_fee_proportional_basis` to the amount
(in thousandths of satoshi) it will charge per `funding_satoshi`
- MUST set `funding_weight` to the weight they
will contribute to this channel, to fund the request.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite, this is the weight that the initiating node will pay for (I suspect this language predates that change)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will update.

the rate funds are routed through the channel as this provides the opener
the opportunity to move the funds elsewhere (and renders the
lease fairly useless). A sensible policy here depends on the motivation of the
opener in acquiring the leased funds.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes a weird case, where Alice requests 1M sats, Bob has 1M+dust sats available. If Bob puts that dust into the channel, Alice pays a bit extra for it, which is weird. Is this worth fixing? Maybe not? The only fix I can think of is to use min(accept_channel2.funding_satoshis, open_channel2.request_funds.requested_sats) * funding_fee_proportional_basis instead of accept_channel2.funding_satoshis * funding_fee_proportional_basis but that's also a bit messy?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Min sounds reasonable to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is sensible. 👍

- the `funding_fee_base_sat` is too high
- the `funding_weight` is too high
- the `channel_fee_proportional_basis_max` is too high
- the `channel_fee_base_max_msat` is too high
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe - SHOULD accept a funding_satoshi in excess of requested_sats ?

Comment on lines 1995 to 1996
- if `blockheight` is more than 504 blocks behind the current blockheight:
- SHOULD fail the channel
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite right, this needs a separate section (hmm, may need an intro which defines lease nomenclature? Lessor, end of lease, etc?)

A node which offered a lease on a channel, which is still being enforced:

  • if the last received blockheight is more than 504 blocks behind the current blockheight:
    • SHOULD fail the channel.

With rationale:

The lessor's lease is only shortened when a new blockheight is committed, otherwise it's always 4032 blocks in the future. If the peer doesn't update the blockheight, at some point it has to start the closing process to retrieve its funds. 504 blocks seems a reasonable (but not compulsory!) answer here.

- MUST set `funding_fee_base_sat` to the base fee (in satoshi) it will charge
for any reciprocated funding.
- MUST set `funding_fee_proportional_basis` to the amount (in
thousandths of a satoshi) it will charge per contributed satoshi.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... names don't match fields, particularly missing _max?

07-routing-gossip.md Show resolved Hide resolved
02-peer-protocol.md Outdated Show resolved Hide resolved

In a 'leased' channel, the `to_remote` output that pays the `accepter` node
is modified so that it is equal to the greater of the
1 or the `lease_end` - `blockheight`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, yeah. If we define lease nomenclature it's easier to change this to say why:

If a lease applies to the channel, the to_remoteoutput of theinitiator ensures the lessor won't get their funds until after lease expiry.

##### Leased channel (`option_will_fund`)
In a leased channel, the `to_local` output that pays the `accepter` node
is modified so that its CSV is equal to the greater of the
`to_self_delay` or the `lease_end` - `blockheight`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below on explaining why, not what?

02-peer-protocol.md Outdated Show resolved Hide resolved
(in millisatoshi) it will charge for any HTLC during the funding period.
- MUST set `channel_fee_proportional_basis_max` to the max amount (in
thousandths of a satoshi) it will charge per transferred satoshi during
the funding period.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking would it make sense to advertise a minimum required contribution to the channel capacity from the taker?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the rationale here?

Including this would add another 4-8 bytes to the node_announcement, which we're attempting to keep as small as possible.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without it one could open a channel with e.g. 100 sats and request 10M of liquidity. This is not a problem if the node charges for liquidity sufficiently but perhaps the node would be willing to charge less if there's liquidity in the opposite direction.

Maybe we could represent it as a percentage of taker contribution which would fit into one byte.

Alternatively, maybe instead of putting it into channel announcement there could be a way to get details by connecting to the node directly and thus not causing load on the rest of the network?

Copy link
Collaborator Author

@niftynei niftynei Jun 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, maybe instead of putting it into channel announcement there could be a way to get details by connecting to the node directly and thus not causing load on the rest of the network?

the init message would fit this description; adding the minimum required channel size to the init TLV is a superb idea. if you want to take a stab at drafting a PR for it, i can help get it added to clightning :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad that you like the idea! Unfortunately I'm both incredibly overwhelmed by other stuff and not familiar with C-lightning codebase. I'm mainly interested in the protocol because I believe it may solve some high-level problems I observe as a node operator.

funds to an open channel request. Due to the nature of the protocol, these
opens are only possible on the v2 channel protocol.

Any advertised liquidity lease is for a duration of 4032 blocks, or
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fixed lease duration that is also used in Lightning Pool really the best model? If I want to keep the channel after 28 days, but the lessor thinks differently because the funding fee has been collected already anyway, what can I do? I'd need to hit the chain again to restore my connection to the network.

An alternative could be to stay closer to the actual cost of keeping the channel open. For example: the lessee periodically pays interest calculated over the lessor's channel balance. These payments could be integrated as a channel update message with the channel being failed automatically if the contract is not followed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale for presenting channel leases as fixed time block entities is to create a standard contract of liquidity leases that makes it easy to compare offers. In other words, it makes it easier to evaluate what certain liquidity offers are worth across the network.

what can I do? I'd need to hit the chain again to restore my connection to the network.

I think what you're proposing here is a lease extension negotiation protocol -- this something that we can add on top of this existing initial protocol, and perhaps should be something to consider for splices (though that's also an on-chain operation). Which is to say, I think this is a really important point to make and that negotiations for "extending a channel lease" would be very welcome!

the lessee periodically pays interest calculated over the lessor's channel balance.

Yes, as currently proposed, the period of the payment is 4032 blocks; a renewed interest payment would need to be made at the 4033'd block to keep the channel open. This deserves further investigation, but shouldn't be a blocker for shipping this initial lease proposal imo.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could work like that indeed. After each 4032 blocks, the lessor will demand an extension fee based on their average channel balance over the past period.

This could be a protocol that is totally independent from dual funding. Because the counterparty of a single-funder channel that is spent down will probably also consider closing unless they are compensated somehow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a protocol that is totally independent from dual funding.

Totally, though I think we might be able to reuse the CSV locks presented here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSV locks indeed look useful to keep the lessor honest. Although there are still other ways to play unfair. One thing I thought about is for the lessor to lease out a channel and then push out all balance and stop routing. That way they collect the lease fee without having to commit capital for the duration of the channel. The CSV lock probably doesn't hurt that much anymore at that point. This obviously doesn't work if the lessee is not a routing node.

Maybe it is worth to already think through (not implement yet) what that lease extension protocol could look like. Only on the message level. With the goal to identify anything that must already be taken into account for openchannel2.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to have the lessor and lessee switch roles during the lifetime of a channel? Initially A charges B for the liquidity, but over time, A gets low on liquidity and B now wants to charge A to keep the same channel open. Maybe they even mutually charge each other based on their average local channel balances?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and then push out all balance and stop routing

You could push out up to 99% of the channel balance, the reserve would still be locked in. Assuming the channel fee is <1% of the total channel balance (which is .. likely?) then you'd never be able to push out more than you'd originally locked in. Maybe this is fine?

It's recommended that lessors implement routing policies for the capital that they've leased -- e.g. if you're leasing the capacity with the aim of accepting payments, you may reconsider any routed payment that comes in via that channel.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially A charges B for the liquidity, but over time, A gets low on liquidity and B now wants to charge A to keep the same channel open. Maybe they even mutually charge each other based on their average local channel balances?

This is interesting. Here's a few thoughts.

First, having a balance on your side of the channel represents an opportunity to earn routing fees. By charging your peer for the maintenance of a balance on your side of the channel, you're effectively charging them for inbound capacity. A fee for capital maintenance would drive up routing fees, as you're essentially trading off future cash from your channel partner for routing income. Plus there's the potential that routing a payment would then tip the scales in their favor such that you'd then be paying that routing income back over to them (given a long enough time scale).

Assuming zero routing takes place and over a long enough time frame, a policy which charges a fee on the un-capitalized side of the channel would mean that the non-dominant side of the channel would slowly leak funds to the larger side, resulting in an increasingly unbalanced channel. The only way to avoid this would be by maintaining 50-50 balanced channels. So basically, either you allocate a full 50% of a channel's capacity or you slowly lose balance in that channel.

One interesting side effect of this is that it'd incentivize a "mean reversion" on every channel -- e.g. there's an incentive to keep your channel balances at 50/50 to avoid losing fees to your peer. This may not be possible on every channel and would give a strong deadline for channel closure, as opposed to now, where most closures seem to be predicated around either a need to reallocate funds, a total usage of all channel funds, or whimsy.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming the channel fee is <1% of the total channel balance (which is .. likely?) then you'd never be able to push out more than you'd originally locked in

I am thinking of dual-funding where the leasor funds (almost) the complete channel. Then the leasor pushes out 99% and frees up their capital to repeat the same trick with the next lessee, and the next. You sell 100 channels and collect channel fees with the capital that you could normally only use to sell a single channel.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it matters. Let's look at various cases:

  • The user has no prior channels - circularly routing is impossible - the lessor can't do that
  • The user (U) already has a channel to another, higher-quality node (A) than the quality of lessor (L). L -> U -> A -> L causes improvement in quality aside from A becoming SPOF (likely the reason why U wanted another channel). If U charges sufficient fees, it can cover the cost of opening third channel. Note that if B -> L -> U is possible then B -> L -> A -> U is also possible even though a bit worse.
  • A has same strategy as L and will also attempt to push out - will end up in balanced situation while U gets free money or one node routes and the other gives up, still U getting money. This scenario may be even incentive not to attempt it in the first place.
  • A is lower quality than L and doesn't employ this strategy. The question is: is this a contradiction? A node that automatically destroys your ability to receive over its channel should probably be considered worse. Again, if the fee is sufficient it may pay for another channel.

Copy link
Contributor

@ariard ariard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really cool proposal :) Still thinking more about timelocks/channel policies interactions.

# Penalty transaction
<revocationpubkey>
OP_ELSE
MAX(`to_self_delay`, `lease_end` - `blockheight`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to encumber leasor (iiuc the counterparty offering the lease?)'s HTLC transactions outputs with OP_MAX too ?

Otherwise I think as a leasor I can send all my balance (minus max_htlc_value_in_flight_msat) in an offered HTLC output, of which I'm never going to give you the preimage. At expiration broadcast commitment + HTLC-timeout, wait for the CSV delay, and withdraw the balance to a single-owned output ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this a hole in the protocol at the moment. We chose not to update the HTLC contracts, as that was a much larger change for the first draft. iiuc it also impacts the lockup for failed payments across the entire payment chain, which seems suboptimal as well.

You can limit your exposure to this today by limiting the max htlc inflight for this channel.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the multisig + secondary transaction trick we already use with HTLC-success and HTLC-timeout to avoid cltv_expiry lockup across the payment chain. But yes this would complicate the proposal a bit.

Basically for HTLCs where the lessor is remote, the remote_htlcpubkey branch would become a 2 <local_htlcpubkey> <remote_htlcpubkey> 2 OP_CHECKMULTISIG, and the lessor would hold signatures for a new HTLC-lease-lock transaction that locks their funds up for the remainder of the lease.

For HTLCs where the lessor is local, we'd simply adjust the to_self timelock in HTLC-timeout and HTLC-success transactions to enforce the lease lock (identical to the commitment transaction changes).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that LND already adjusts to_self in the HTLC-timeout and HTLC-success transactions for Pool. This partially plugs the hole, but the lessor can still evade the timelock by routing an HTLC to itself and then goading the lessee into force closing.

The solution I described above should block this case as well, but I'm thinking just having HTLC-success and HTLC-timeout lease locks may be enough for the first protocol. It would be nice to avoid the more complex HTLC surgery.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the multisig + secondary transaction trick we already use with HTLC-success and HTLC-timeout to avoid cltv_expiry lockup across the payment chain. But yes this would complicate the proposal a bit.

I think I'm going to propose doing exactly this (creating second stage txs for the leasor in their remote's commitment tx HTLC outputs), as it's a complete patch for this hole. It is more surgery, but I think worth it in terms of eradicating a potential hole in the protocol.

I'll also add HTLC locks on the to-self HTLC second stage txs, as you've suggested (and LND does!)

are modified. They now incorporate the funding lease and time since
the channel has been opened. As blocks are published, the CSV
for the accepter's outputs is decremented. Once the funding lease
has passed, the CSV value outputs return to the 'normal', non-leased values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should introduce a liveliness duty on the leavee ? I.e if you're not online every X, the leasor is allowed to close the channel, otherwise as the lease enforcement is relying on relative timelocks they're never going to be triggered and expire at the expected height of the end lease ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, iiuc this is encapsulated in the suggestion to close the channel if they haven't updated the blockheight in 1008 blocks.

- MUST NOT send `update_blockheight`.

A receiving node:
- if the `update_blockheight` is less than the last received `blockheight`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"if the lease_end - blockheight is less than the receiver announced to_self_delay MUST fail the channel?" to avoid justice delays downgrads ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think I'm missing something about your point here. Anywhere there's a to_self_delay, the csv is max(to_self_delay, lease_end - blockheight).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, you're using OP_MAX directly in the script? So should be good with concern of justice delay downgrades, but still you can save few bytes of witnessScript by checking this during message reception or transaction construction ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The max is done prior to the construction of the script, the actual onchain script is unchanged.

Copy link
Collaborator

@t-bast t-bast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool stuff, this is way smarter than the naive version I had in mind, thanks for putting this together!

02-peer-protocol.md Show resolved Hide resolved
02-peer-protocol.md Show resolved Hide resolved
07-routing-gossip.md Show resolved Hide resolved
@@ -1862,6 +1968,57 @@ it's simplest to only allow it to set fee levels; however, as the same
fee rate applies to HTLC transactions, the receiving node must also
care about the reasonableness of the fee.

### Updating Block Height: `update_blockheight` (`option_will_fund`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: update_lease_blockheight?

ensures the `leasor` funds are not spendable until the lease expires.

In a leased channel, the `to_local` output that pays the `accepter` node
is modified so that its CSV is equal to the greater of the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the lease expires at an absolute block, why do you use a CSV and not a CLTV (which would remove the need for the update_blockheight mechanism)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used CSV since that was already introduced to the scripts for option_anchor_outputs, and therefore required less surgery to the onchain scripts. 👼

I could further make the argument that if your peer isn't actively updating the CSV lock, then it's a good signal to go ahead and shut the channel with them but that's not exactly a water-tight argument for keeping it as a CSV ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the lease expires at an absolute block, why do you use a CSV and not a CLTV (which would remove the need for the update_blockheight mechanism)?

I really like this suggestion!! the update_blockheight hack feels very hacky imo haha. But I do like how cleanly it reuses the CSV lock that anchors introduced. hmm

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trade-offs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One benefit of CLTV is less commitment transaction churn. i.e. we have ~4032 extra revoked commitments to remember if update_blockheight happens every block.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I plan on prototyping the CLTV version soon(ish). I'm not 100% sure it will work, as we're already using the nLockTime of commitment txs to encode data, I'll report back when it's done.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI - It looks like LND already uses CLTV leases for Pool. They also have CLTVs added to HTLC-success and HTLC-timeout transactions, which partially plugs the HTLC hole @ariard pointed out.

I think this is a much simpler approach.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I plan on prototyping the CLTV version soon(ish). I'm not 100% sure it will work, as we're already using the nLockTime of commitment txs to encode data, I'll report back when it's done.

I don't know what I was smoking when I wrote that message, using CLTV of course works, we don't care about the nLockTime of the commitment tx, the nLockTime that matters is the one of the transaction that spends the leasor's main output.

@niftynei niftynei marked this pull request as ready for review September 1, 2021 16:11

## Overview

This proposal adds a TLV to the `node_announcement`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth splitting this part off into its own PR? Extensible gossip probably has uses beyond liquidity ads.

@t-bast
Copy link
Collaborator

t-bast commented Nov 16, 2023

As discussed offline, I think we should add the request_funds TLV to tx_init_rbf and will_fund TLV to tx_ack_rbf. This is especially useful when combining liquidity ads with splicing, because we will heavily use tx_init_rbf to replace an unconfirmed splice with a new one that potentially represents a different set of user actions, and it would be beneficial to be able to include a liquidity request in such a replacement.

This would enable the following flow:

  • Alice initiates a splice-out with Bob
  • That splice-out has a low feerate and doesn't confirm
  • Bob decides that they want to buy liquidity from Alice
  • Bob sends tx_init_rbf with a request_funds TLV
  • Alice finds that offer more interesting than getting her funds out of the channel, so she sends back will_fund
  • They build a competing splice transaction, where instead of splicing out, Alice is now splicing in (for a fee paid by Bob)
  • This transaction has a higher feerate and is likely to confirm and replace the splice-out transaction

Note that there is an interesting "reverse" flow, where the seller can cancel a pending liquidity purchase using RBF:

  • Alice initiates a splice to buy liquidity from Bob
  • The splice transaction has a low feerate and doesn't confirm
  • Bob is running low on liquidity and would like to use it elsewhere: Bob sends tx_init_rbf (note that he was not the initiator of the splice) to splice funds out instead of splice funds in
  • Alice now has the following choices:
    • be antisocial and reject the RBF attempt because she really wants that liquidity, but doesn't want to pay more: it may work, but Bob is likely to ban her in the future
    • pay a higher fee for Bob's liquidity: she rejects Bob's RBF attempt but immediately follows up with her own tx_init_rbf, giving Bob the opportunity to update its lease_rates
    • accept Bob's RBF attempt: this removes the inbound liquidity

Similarly to the way tx_init_rbf and tx_ack_rbf already allow changing the funding contributions on each side, this makes sure each RBF attempt negotiates a potential liquidity lease independently of previous RBF attempts.

- MUST set `lease_fee_base_sat` to the base fee
(in satoshi) it will charge for the `funding_satoshis`
- MUST set `lease_fee_basis` to the amount
(in thousandths of satoshi) it will charge per `funding_satoshi`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was basis points, thus ten thousandths of satoshi? Same for channel_fee_basis_max below. The formula used in the examples below uses 1/10_000.

* [`u16`:`funding_weight`]
* [`u16`:`lease_fee_basis`]
* [`u16`:`channel_fee_basis_max`]
* [`u32`:`lease_fee_base_sat`]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep channel base fees in a way where we can omit it thanks to truncated ints:

Suggested change
* [`u32`:`lease_fee_base_sat`]
* [`u32`:`lease_fee_base_sat`]
* [`tu32`:`channel_fee_base_msat_max`]

@@ -1263,6 +1332,49 @@ Instead, the channel reserve is fixed at 1% of the total channel balance
rounded down to the nearest whole satoshi or the `dust_limit_satoshis`,
whichever is greater.

If the opener requested a `option_will_fund` peer to contribute funds to
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option_will_fund doesn't exist, this doesn't need a feature bit since it instead relies on a TLV in node_announcement.

* `lease_fee_basis` / 10_000 + `funding_feerate_perkw` *
`funding_weight` / 1000, rounded down to the nearest satoshi. See [Appendix A: The lease fee](#appendix-a-the-lease-fee).

The `channel_fee_max_base_msat` is an implicit limit set
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this section once we restore channel_fee_max_base_msat in lease_rates.

@@ -2283,6 +2395,12 @@ be trimmed at the updated feerate, this could overflow the configured
`max_dust_htlc_exposure_msat`. Whether to close the channel preemptively
or not is left as a matter of node policy.

The lessor's lease is only shortened when a new `blockheight` is committed,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is obsolete, it only made sense when using CSVs?

@@ -223,6 +271,45 @@ If a revoked commitment transaction is published, the remote node can spend this

The sending node can use the HTLC-timeout transaction to timeout the HTLC once the HTLC is expired, as shown below. This is the only way that the local node can timeout the HTLC, and this branch requires `<remotehtlcsig>`, which ensures that the local node cannot prematurely timeout the HTLC since the HTLC-timeout transaction has `cltv_expiry` as its specified `locktime`. The local node must also wait `to_self_delay` before accessing these funds, allowing for the remote node to claim these funds if the transaction has been revoked.

### Leasee Offered HTLC Outputs (`option_will_fund`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on the mailing list, I think we should remove this, because we have no way of actually exchanging signatures for those transactions unless we add a round-trip, which isn't desirable. The leasee should adapt their max_accepted_htlc and max_htlc_value_in_flight_msat if they're afraid that the seller will cheat.

@t-bast
Copy link
Collaborator

t-bast commented Dec 1, 2023

After thinking about this more, I have more feedback 😇.

As mentioned in my mailing list post, we can probably use a separate output for the leased amount, which would be much safer for the seller. I initially argued for an option to remove the CLTV lease mechanism entirely, but after discussing it during the last spec meeting, I'm not sure we should have that option: sellers can instead offer a short lease duration, while providing an out-of-band (e.g. in a wallet's UI) soft commitment that they will do their best to provide inbound liquidity longer than the official lease duration.

I also think we should take the duration into account in the lease rates to make the result more predictable for the buyer before they send request_funds. Here is what I think the types should be:

  • lease_rate:
    • [u16:lease_duration] -> in blocks (up to a year-ish, note that I used a u16, I don't think we should use a u32 and allow longer values?)
    • [u16:funding_weight]
    • [u16:lease_fee_basis]
    • [u32:lease_fee_base_sat]
    • [u16:max_channel_fee_basis]
    • [u32:max_channel_fee_base_msat]
  • lease_rates: included in node_announcement and init, default rates that the node will use, but there's no guarantee that this is what will be offered to you when you actually send request_funds:
    • [...*16*byte:rates] -> we offer multiple rates depending on the duration (e.g. 1 week = 0,5% with 50 sat flat, 1 month = 1% with 75 sat flat, 1 year = 5% with 2000 sat flat)
  • request_funds:
    • [u32:requested_sats]
    • [u16:lease_duration] -> must exactly match one of the lease_durations proposed by the seller
    • [u32:lease_expiry]
  • will_fund:
    • [64*byte:signature] -> signature of the lease_witness
    • [u16:funding_weight]
    • [u16:lease_fee_basis]
    • [u32:lease_fee_base_sat]
    • [u16:max_channel_fee_basis]
    • [u32:max_channel_fee_base_msat]
  • lease_witness:
    • [u16:funding_script_size]
    • [funding_script_size:funding_script] -> to be compatible with taproot channels, we commit to the script of the funding output instead of just the funding_pubkey, does that make sense?
    • [u16:lease_duration]
    • [u32:lease_expiry]
    • [u16:max_channel_fee_basis]
    • [u32:max_channel_fee_base_msat]

t-bast added a commit to ACINQ/eclair that referenced this pull request Dec 6, 2023
Send those rates in `node_announcement` and `init`, and update codecs
accordingly.

This matches the proposal in lightning/bolts#878 (comment)
t-bast added a commit to ACINQ/eclair that referenced this pull request Dec 6, 2023
Send those rates in `node_announcement` and `init`, and update codecs
accordingly.

This matches the proposal in lightning/bolts#878 (comment)
t-bast added a commit to ACINQ/lightning-kmp that referenced this pull request Dec 8, 2023
Those rates are sent in `node_announcement` and `init`. We select the
rate in the remote `init` message for a lease duration of `0` (no strict
lease enforcement through CLTV in the commitment transaction).

This matches the proposal in lightning/bolts#878 (comment)
t-bast added a commit to ACINQ/lightning-kmp that referenced this pull request Dec 11, 2023
Those rates are sent in `node_announcement` and `init`. We select the
rate in the remote `init` message for a lease duration of `0` (no strict
lease enforcement through CLTV in the commitment transaction).

This matches the proposal in lightning/bolts#878 (comment)
pm47 pushed a commit to ACINQ/lightning-kmp that referenced this pull request Dec 13, 2023
Implement a prototype for liquidity ads, compatible with ACINQ/eclair#2550
Note that we only implement the buyer side, which limits testing.
The specification is available here: lightning/bolts#878

We currently don't add CLTV locks to the commitment transactions, for simplicity's sake.
Draft of option_will_fund, to allow for liquidity ads
Re-write the spec to use CLTV instead of CSVs for liquidity ads.

This does a number of wonderful things.

First, it decouples us from option_anchor, which is returned to being
optional again.

Second, it allows us to have a static, lifetime value for the lease
which doesn't need to be removed at lease end (it is automatically moot
just by passing the required blockheight)

Thirdly, we add a new 'second stage' transaction type for HTLC output
clauses which pay out to the remote node. These are only added to the
*lessor*'s commitment transactions, as we can simply add the required
CLTV locks to HTLC transaction outputs in the lessor's commitment.

There's no such affordance in the commitment transaction of the leassor,
unfortuantely, in which case we must add them.

As written, note that these live for the lifetime of the channel.

We could optionally remove them upon request of the lessor at some point
after the CLTV has passed. This is left as an exercise to the future
spec writers.
t-bast added a commit to ACINQ/eclair that referenced this pull request Mar 29, 2024
Send those rates in `node_announcement` and `init`, and update codecs
accordingly.

This matches the proposal in lightning/bolts#878 (comment)
t-bast added a commit to ACINQ/eclair that referenced this pull request Mar 29, 2024
The initiator of `open_channel2`, `tx_init_rbf` and `splice_init` can
request funding from the remote node. The non-initiator node will:

- let the open-channel-interceptor plugin decide whether to lease
  liquidity for new channels or not, and how much
- always honor liquidity requests on existing channels (RBF and splice)

We currently don't modify commitment transactions to enforce the lease.
This is different from lightning/bolts#878 and
instead matches lightning/bolts#1145.

We currently use the temporary tlv tag 1337 while we're waiting for
feedback on our spec proposal.

Liquidity ads are included in the `node_announcement` message, which
lets buyers compare sellers and connect to sellers that provide rates
they are comfortable with.

We store every liquidity purchase (whether we're buyer or seller) in the
audit DB. This is important information when choosing which peers are
worth keeping channels with.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants