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

Implement dynamic amount of tokens for change #223

Merged
merged 1 commit into from
May 23, 2023

Conversation

xphade
Copy link
Collaborator

@xphade xphade commented May 16, 2023

With the recent update to NUT-08, we can ensure that the amount of blank outputs is always enough to cover any overpaid lightning fees. This change implements this functionality for both the wallet and the mint. The mint updateis backwards-compatible with respect to old wallets.

Closes #183

With the recent update to NUT-08, we can ensure that the amount of blank
outputs is always enough to cover any overpaid lightning fees. This
change implements this functionality for both the wallet and the mint.
The mint updateis backwards-compatible with respect to old wallets.
@xphade xphade requested a review from callebtc May 16, 2023 20:22
Comment on lines +48 to +55
def calculate_number_of_blank_outputs(fee_reserve_sat: int):
"""Calculates the number of blank outputs used for returning overpaid fees.

The formula ensures that any overpaid fees can be represented by the blank outputs,
see NUT-08 for details.
"""
assert fee_reserve_sat > 0, "Fee reserve has to be positive."
return max(math.ceil(math.log2(fee_reserve_sat)), 1)
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 sure if I really should put it in here, or rather into the wallet directly.

expected_returned_fees = 1900

n_blank_outputs = calculate_number_of_blank_outputs(fee_reserve)
blinded_msgs = [step1_alice(str(n)) for n in range(n_blank_outputs)]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should we rather use pre-computed blinded messages here? We don't use them for anything in this test, but maybe better to have it as deterministic as possible.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You can use a predetermined blinding_factor when calling step1_alice, then the outcome is determinstic!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, now I'm thinking this might break legacy wallets which always send 4 outputs (or we add an exception to that but that would be extra ugly).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can use a predetermined blinding_factor when calling step1_alice, then the outcome is determinstic!

Yeah, that was my proposal. I guess that's a yes from your side, so I'll add predefined blinding factors. 👍

Actually, now I'm thinking this might break legacy wallets which always send 4 outputs (or we add an exception to that but that would be extra ugly).

Sorry, I'm a bit confused here, can you expand on this? How does it break legacy wallets? As you can see below, I also added a test for it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What I meant: Legacy wallets send 4 outputs, but if our new algo says "we only need 3" then legacy wallets would get an error. We can leave it as you did: just ignore outputs that are unnecessary.

I think it's safe to ignore this for now. There is more than just one place in the code where we can DoS the mint.

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 I meant: Legacy wallets send 4 outputs, but if our new algo says "we only need 3" then legacy wallets would get an error.

Ah, I think I get what you mean now. However, I think it was already the case before that less outputs could be sent back (e.g. if the return output fits into 3). See line 557 and following in the old version:

return_amounts = amount_split(user_paid_fee_sat - ln_fee_sat)
# we only need as many outputs as we have change to return
outputs = outputs[: len(return_amounts)]

At least if I understand that part correctly. 🙂

@xphade
Copy link
Collaborator Author

xphade commented May 16, 2023

I've added tests for the actual equation and the mint implementation. Let me know if we also should do something for the Wallet.pay_lightning. Wasn't sure how hard it is to test that, and the equation is pretty much the only thing that changed.

@codecov
Copy link

codecov bot commented May 16, 2023

Codecov Report

Patch coverage: 78.57% and project coverage change: +0.08 🎉

Comparison is base (d028367) 55.42% compared to head (d9ce2c6) 55.50%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #223      +/-   ##
==========================================
+ Coverage   55.42%   55.50%   +0.08%     
==========================================
  Files          41       41              
  Lines        3284     3326      +42     
==========================================
+ Hits         1820     1846      +26     
- Misses       1464     1480      +16     
Impacted Files Coverage Δ
cashu/wallet/api/router.py 70.18% <0.00%> (ø)
cashu/wallet/cli/cli.py 47.78% <0.00%> (ø)
cashu/wallet/wallet.py 80.81% <66.66%> (ø)
cashu/core/helpers.py 43.75% <100.00%> (+8.03%) ⬆️
cashu/mint/ledger.py 30.73% <100.00%> (+2.68%) ⬆️

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@callebtc
Copy link
Collaborator

Excellent PR! Will review

Comment on lines -551 to -555
if user_paid_fee_sat - ln_fee_sat > 0 and outputs is not None:
# we will only accept at maximum n_return_outputs outputs
assert len(outputs) <= n_return_outputs, Exception(
"too many change outputs provided"
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Although not strictly necessary, for DOS safety, we could add a check here using calculate_number_of_blank_outputs to check whether the user has generated too many blank outputs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, can do. What do you want to happen in that case? Right now, if a wallet sends too many blank outputs, I'm just ignoring the one that are not necessary, see line 558.

Comment on lines +144 to +146
async def test_generate_change_promises_legacy_wallet(ledger: Ledger):
# Check if mint handles a legacy wallet implementation (always sends 4 blank
# outputs) as well.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Excellent! Very thoughtful!

Copy link
Collaborator

@callebtc callebtc left a comment

Choose a reason for hiding this comment

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

Beautiful PR! Pls see if my comments are of any benefit!

Copy link
Collaborator

@AngusP AngusP left a comment

Choose a reason for hiding this comment

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

LGTM, caveat that I'm still familiarizing with the code 😉

cashu/core/helpers.py Show resolved Hide resolved
cashu/mint/ledger.py Show resolved Hide resolved
@callebtc
Copy link
Collaborator

LGTM^2

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.

[Wallet/Mint] Implement update to NUT-08: Dynamic amount of tokens for change
3 participants