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

Funder's balance can dip back below channel reserve due to race #761

Open
SomberNight opened this issue Mar 30, 2020 · 7 comments
Open

Funder's balance can dip back below channel reserve due to race #761

SomberNight opened this issue Mar 30, 2020 · 7 comments

Comments

@SomberNight
Copy link
Contributor

SomberNight commented Mar 30, 2020

(This has been split off from #740 (comment))

BOLT-02 claims:

The channel reserve is specified by the peer's channel_reserve_satoshis: 1% of the channel total is suggested. Each side of a channel maintains this reserve so it always has something to lose if it were to try to broadcast an old, revoked commitment transaction. Initially, this reserve may not be met, as only one side has funds; but the protocol ensures that there is always progress toward meeting this reserve, and once met, it is maintained.

I think that it is not true that once the reserve is met it is invariant that it will stay met.

The key insight is this: the funder, by sending update_fee, can reduce their own balance by x. And the fundee, by sending update_add_htlc, can make x larger. Exploiting this concurrently is the trick to dip the funder back below channel reserves.

(note: I sometimes use "ctx" to mean "commitment transaction")


Consider the following example:

Alice is the fundee

  • reserve: 546 sat
  • dust limit: 546 sat

Bob is the funder

  • reserve: 546 sat
  • dust limit: 546 sat
  1. initial state:
  • feerate: 1 sat/byte
  • Bob balance: 8000 sat
  • (Alice balance does not matter, it is high)
  • both Alice and Bob has balances above channel reserve
  • there are no pending HTLCs
  • ctx size: 724 weight = 181 vbyte
  • Bob's ctx output then pays him: 8000-181=7819 sat (non-dust)
  1. Alice considers sending update_add_htlc (amount: 50000 sat)
    Alice calculates that in the next remote ctx:
  • there will be a corresponding non-dust received HTLC output of 50000 sat
  • Bob can pay for the onchain fees and remain over reserve
  • there will be a to_local output that pays Bob 8000-181-43=7776 sat
  1. Concurrently to (2), Bob considers sending update_fee (new feerate: 41 sat/byte).
    Bob calculates that in the next remote ctx:
  • Bob can pay for the onchain fees and remain over reserve
  • there will be a to_remote output that pays Bob 8000-41*181=579 sat

events from Alice's POV: (scenario 1)

  • Alice sends update_add_htlc
  • Bob sends update_fee
  • Bob sends commitment_signed (only contains fee update)
  • Alice sends revoke_and_ack
  • Alice sends commitment_signed (contains both fee update and new htlc)
    • but hey, in this commitment Alice just signed ("ctxA"), Bob's corresponding output would be:
      8000-41*(181+43)= -1184 sat (of course this will just end up being pruned)

From BOLT-03:

Base commitment transaction fees are extracted from the funder's amount; if that amount is insufficient, the entire amount of the funder's output is used.

So in "ctxA" the to_local output (that would pay Bob) is supposed to have a -1184 sat value,
which is below the dust limit so it gets pruned. The effective feerate hence will be different,
but this is not interesting.

What is interesting is that Bob dipped back below reserve!
That is, there is no incentive for Bob not to try to cheat.


Now that I wrote up this whole example, it occurred to me that maybe what is meant by the invariant is that balance before subtracting the onchain fees will always stay above the reserve.
If this is what is meant then I guess the text is correct.
However, even in that case, it still stands that in the example above Bob has positive expected value for trying to cheat. If such a situation can occur in a way where onchain fees have actually increased and Bob actually wants to pay high fees to have a chance at getting the tx mined at all, he very well might try to cheat.

Also note that Bob can keep sending update_fee messages, low feerate, high feerate, going back and forth, trying to trigger the race.

@t-bast
Copy link
Collaborator

t-bast commented Mar 30, 2020

Now that I wrote up this whole example, it occurred to me that maybe what is meant by the invariant is that balance before subtracting the onchain fees will always stay above the reserve.

That's not exactly it. There was a previous proposal by rusty to indeed allow using the channel reserve to pay for the on-chain fee, but it was rejected because it made the protocol a bit more complex. Maybe @rustyrussell can dig up the discussion? It would probably have exactly the same kind of arguments as yours.

Alice sends commitment_signed (contains both fee update and new htlc)

I think this is were it's incorrect. Alice should not send a commitment_signed with both those changes because she can see that Bob wouldn't be able to pay for the fee.

@SomberNight
Copy link
Contributor Author

SomberNight commented Mar 30, 2020

I think this is were it's incorrect. Alice should not send a commitment_signed with both those changes because she can see that Bob wouldn't be able to pay for the fee.

Note that there are no such checks listed in BOLT-02 for commitment_signed.

So is the channel dead then? Or is Alice supposed to disconnect to "undo" that she had sent update_add_htlc?

And if Alice is indeed supposed to run such checks at commitment_signed and see that Bob cannot pay for the fee then what is the quoted BOLT-03 paragraph about:

Base commitment transaction fees are extracted from the funder's amount; if that amount is insufficient, the entire amount of the funder's output is used.

Is it only regarding the early channel lifecycle before the reserve is met? Note that in that case it could only possibly apply if the funder used a huge push_msat value.

@t-bast
Copy link
Collaborator

t-bast commented Mar 30, 2020

So is the channel dead then? Or is Alice supposed to disconnect to "undo" that she had sent update_add_htlc?

No, she can just send her commit_sig for the new HTLC without taking into account the proposed update_fee.

@SomberNight
Copy link
Contributor Author

No, she can just send her commit_sig for the new HTLC without taking into account the proposed update_fee.

She already sent revoke_and_ack in the example (scenario 1), so it's too late for that at commitment_signed time.
So I guess you are proposing to add some extra logic earlier then.
Ok. Note that if she ACKs the update_fee (i.e. if she ACKs), it's too late to fix.

So let's consider the other case, (scenario 2) Alice's POV:

  • Alice sends update_add_htlc
  • Bob sends update_fee
  • Bob sends commitment_signed (only contains fee update)
  • Alice sends commitment_signed (only contains new htlc)
  • ... now some additional messages (rev_acks and commits) will be exchanged but regardless of order, there will be a commitment_signed where now both updates are included.

If neither party disconnects then either this (below-reserve) commitment tx gets signed or I don't know how the channel can be used further (if either party refuses to progress the state).

@t-bast
Copy link
Collaborator

t-bast commented Mar 30, 2020

Yes it's true that there is a potential race where you can end up in that state (which will trigger a close in the worst case to avoid letting the funder dip into its channel reserve).

To my knowledge, the best you can do as an implementer to avoid running into this too frequently is to avoid sending update_fee for a fee increase when your balance is very low (defer it to when your balance increases). As long as you have a very low balance it's okay-ish if the channel closes with a smaller feerate than you'd like; you don't have much at stake so it's ok to wait longer for on-chain inclusion (but the fundee may be the one to close the channel if he sees that the feerate doesn't match reality anymore).

Obviously if your channel stays inactive like that for a long while and the feerate keeps increasing, the situation is getting worse. But you can't predict the future, so you don't know that maybe an incoming HTLC will give you more balance soon and thus allow you to correctly sign an update_fee, getting you out of this painful situation. That's some uncertainty you'll have to live with.

This is in my opinion one of the most rabbit-hole-y area of the spec. You'll have to choose trade-offs in your implementation depending on the kind of usage you expect (I guess electrum-ln will be used mostly as a wallet, right?).

@SomberNight
Copy link
Contributor Author

SomberNight commented Mar 31, 2020

to avoid running into this too frequently is to avoid sending update_fee for a fee increase when your balance is very low

Yes, of course you are right, from the funder's perspective that is sufficient (or I guess as good as it gets atm :) ).

However, I am also interested in the fundee's perspective.

Yes it's true that there is a potential race where you can end up in that state (which will trigger a close in the worst case to avoid letting the funder dip into its channel reserve).

From your comments I gather that Alice at some point would run some check, and she might decide to force-close the channel (or at least disconnect). What I don't understand yet however, is exactly when and approx what checks is Alice supposed to run to actually accomplish this.

Note that neither commitment_signed, nor revoke_and_ack lists any such checks (about funder's being able to afford onchain fees or remaining above reserve) for either sender or recipient of msg.

Also, as pointed out in a comment above, if you are right and such a commitment tx where the funder dips below reserve is not "valid" and must not be signed, then I don't understand the purpose of this paragraph (BOLT-03):

Base commitment transaction fees are extracted from the funder's amount; if that amount is insufficient, the entire amount of the funder's output is used.

Is it only regarding the niche case of the early channel lifecycle before the reserve is met for a channel where the funder used a huge push_msat value (because without push_msat the funder's reserve is supposed always be met, they have all the funds typically)?
(also note that "MUST set channel_reserve_satoshis greater than or equal to dust_limit_satoshis.", i.e. if there is no output for the funder, the reserve is definitely not met)

When I am in doubt about the spec, I usually just check what implementations are doing. :)
I've looked at eclair master, and AFAICT the reserve is only tested against in sendAdd/receiveAdd/sendFee/receiveFee. Testing at those times only will not behave as you described. If the race happens, the reserve will get violated (which if intended, fine by me, it's a tradeoff, I really just want to know how it is supposed to work, and have it documented).

(I guess electrum-ln will be used mostly as a wallet, right?).

Yes, for the foreseeable future we will likely only create unannounced channels and not forward payments.

@t-bast
Copy link
Collaborator

t-bast commented Mar 31, 2020

then I don't understand the purpose of this paragraph (BOLT-03):

I'm not sure when this paragraph is applicable either. It's not in any bolt 2 requirement, and AFAICT in eclair we can never end up in that situation (except for cases where the reserve isn't initially met)...it would be interesting to have other implementation's feedback on this.

If the race happens, the reserve will get violated

In eclair this will result in a CannotAffordFees which triggers a close. We haven't seen this error much on our node, which is why we haven't implemented yet the delay-until-you-have-more-balance mechanism (but we're monitoring for it to see if that's needed).

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

No branches or pull requests

2 participants