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

wallet: notify when preset + automatic inputs exceed max weight #30309

Merged
merged 1 commit into from
Jun 26, 2024

Conversation

furszy
Copy link
Member

@furszy furszy commented Jun 19, 2024

Found it while finishing my review on #29523. This does not interfere with it.

Basically, we are erroring out early when the automatic coin selection process exceeds the maximum weight, but we are not doing so when the user-preselected inputs combined with the wallet-selected inputs exceed the maximum weight.
This change avoids signing all inputs before erroring out and introduces test coverage for fundrawtransaction.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 19, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK tdb3, rkrux, ismaelsadeeq, achow101

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Copy link
Contributor

@tdb3 tdb3 left a comment

Choose a reason for hiding this comment

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

ACK 3f50276
Helpful addition to help cover edge cases.
Anything that should be added to unit tests?


# 3) Pre-select some inputs and let the wallet fill-up the remaining amount
inputs = input_weights[0:1000]
assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
Copy link
Contributor

Choose a reason for hiding this comment

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

Tested that this would fail if the return util::Error... line was commented out in source/wallet/spend.cpp. It did (as expected).

Copy link
Contributor

Choose a reason for hiding this comment

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

Re-ran this test (comment out return util::Error...) for 72b2268. Failed as expected.

@furszy
Copy link
Member Author

furszy commented Jun 21, 2024

Anything that should be added to unit tests?

The behavior change is tested on a functional test. No need to duplicate the same test twice.
Functional tests are portable across releases due to our API stability requirement. Unit tests are harder to maintain due to internal code changes. Thus when possible, functional tests are preferred.

Copy link

@rkrux rkrux left a comment

Choose a reason for hiding this comment

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

tACK 3f50276

make, make check, test/functional are successful.

Thanks @furszy for this improvement, and keeping the PR short and to the point, easy to review! Asked few questions for my understanding.

src/wallet/spend.cpp Outdated Show resolved Hide resolved
test/functional/wallet_fundrawtransaction.py Show resolved Hide resolved
src/wallet/spend.cpp Show resolved Hide resolved
Copy link
Member

@ismaelsadeeq ismaelsadeeq left a comment

Choose a reason for hiding this comment

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

Approach ACK

This also avoids signing all inputs prior to erroring out.
Copy link
Contributor

@tdb3 tdb3 left a comment

Choose a reason for hiding this comment

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

re ACK for 72b2268
Re-ran tests locally (exercising induced failures and received failures as expected).
test_weight_limits() implementations are almost identical between wallet_fundrawtransaction and wallet_spend. While this feels a bit redundant, these are separate functional tests so an alternative (like combining) wouldn't really make sense to do.


# 3) Pre-select some inputs and let the wallet fill-up the remaining amount
inputs = input_weights[0:1000]
assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
Copy link
Contributor

Choose a reason for hiding this comment

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

Re-ran this test (comment out return util::Error...) for 72b2268. Failed as expected.


# 3) Pre-select some inputs and let the wallet fill-up the remaining amount
inputs = inputs[0:1000]
assert_raises_rpc_error(-4, "The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
Copy link
Contributor

Choose a reason for hiding this comment

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

Saw that the same expected failure occurs here when commenting out return util::Error... for 72b2268.

Copy link

@rkrux rkrux left a comment

Choose a reason for hiding this comment

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

tACK 72b2268

Reran make and all functional tests. Thanks @furszy for addressing my comments quickly!

Basically, we are erroring out early when the automatic coin selection process exceeds the maximum weight

I ended up going through the coin selection algorithms and found all of them honouring the max weight criteria in case of automatic coin selection in coinselection:

test/functional/wallet_fundrawtransaction.py Show resolved Hide resolved
test/functional/wallet_send.py Show resolved Hide resolved
@rkrux
Copy link

rkrux commented Jun 24, 2024

tACK 72b2268

Reran make and all functional tests. Thanks @furszy for addressing my comments quickly!

Basically, we are erroring out early when the automatic coin selection process exceeds the maximum weight

I ended up going through the coin selection algorithms and found all of them honouring the max weight criteria in case of automatic coin selection in coinselection:

Oh interesting, as per this commit: 79f3aff, this max_weight means the maximum allowed weight of the selected UTXOs in the transaction, not of the whole transaction, which I now believe transitively translates to the whole transaction weight criteria as well.

Copy link
Member

@ismaelsadeeq ismaelsadeeq left a comment

Choose a reason for hiding this comment

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

utACK 72b2268

@achow101
Copy link
Member

ACK 72b2268

@achow101 achow101 merged commit 1d00601 into bitcoin:master Jun 26, 2024
16 checks passed
@furszy furszy deleted the 2024_wallet_max_weight branch June 26, 2024 18:47
input_weights = []
for i in range(1471):
input_weights.append({"txid": txid, "vout": i, "weight": 273})
assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights)
Copy link
Member

Choose a reason for hiding this comment

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

https://cirrus-ci.com/task/5319226913718272?logs=ci#L1930

 test  2024-06-26T19:28:29.157000Z TestFramework (ERROR): Assertion failed 
                                   Traceback (most recent call last):
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/test_framework/util.py", line 157, in try_rpc
                                       fun(*args, **kwds)
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/test_framework/coverage.py", line 50, in __call__
                                       return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/test_framework/authproxy.py", line 146, in __call__
                                       raise JSONRPCException(response['error'], status)
                                   test_framework.authproxy.JSONRPCException: The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs (-4)
                                   During handling of the above exception, another exception occurred:
                                   Traceback (most recent call last):
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/test_framework/test_framework.py", line 132, in main
                                       self.run_test()
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/wallet_fundrawtransaction.py", line 117, in run_test
                                       self.test_weight_limits()
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/wallet_fundrawtransaction.py", line 1335, in test_weight_limits
                                       assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights)
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/test_framework/util.py", line 148, in assert_raises_rpc_error
                                       assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
                                     File "/ci_container_base/ci/scratch/build/bitcoin-i686-pc-linux-gnu/test/functional/test_framework/util.py", line 163, in try_rpc
                                       raise AssertionError(
                                   AssertionError: Expected substring not found in error message:
                                   substring: 'Transaction too large'
                                   error message: 'The combination of the pre-selected inputs and the wallet automatic inputs selection exceeds the transaction maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs'.

achow101 added a commit that referenced this pull request Jul 11, 2024
…mits test

00b8e26 test: fix inconsistency in fundrawtransaction weight limits test (furszy)

Pull request description:

  Fix #30309 (comment) inconsistency.

  Currently, the test is passing due to a mistake in the test inputs
  selection process. We are selecting the parent transaction change
  output as one of the inputs of the transaction to fund, which
  helps to surpass the target amount when it shouldn't due to the
  fee reduction.

  The failure arises when the test behaves as intended by its coder;
  that is, when it does not select the change output. In this case,
  the pre-selected inputs aren't enough to cover the target amount.

  Fix this by excluding the parent transaction's change output from
  the inputs selection and including an extra input to cover the tx
  fee.

  The CI failure can be replicated with the following patch in master:

  ```diff
  diff --git a/test/functional/wallet_fundrawtransaction.py b/test/functional/wallet_fundrawtransaction.py
  --- a/test/functional/wallet_fundrawtransaction.py(revision 9b480f7)
  +++ b/test/functional/wallet_fundrawtransaction.py(date 1720652934739)
  @@ -1322,7 +1322,7 @@
           outputs = []
           for _ in range(1472):
               outputs.append({wallet.getnewaddress(address_type="legacy"): 0.1})
  -        txid = self.nodes[0].send(outputs=outputs)["txid"]
  +        txid = self.nodes[0].send(outputs=outputs, change_position=0)["txid"]
           self.generate(self.nodes[0], 1)

           # 272 WU per input (273 when high-s); picking 1471 inputs will exceed the max standard tx weight.
  @@ -1330,7 +1330,7 @@

           # 1) Try to fund transaction only using the preset inputs
           input_weights = []
  -        for i in range(1471):
  +        for i in range(1, 1472):  # skip first output as it is the parent tx change output
               input_weights.append({"txid": txid, "vout": i, "weight": 273})
           assert_raises_rpc_error(-4, "Transaction too large", wallet.fundrawtransaction, hexstring=rawtx, input_weights=input_weights)
  ```

ACKs for top commit:
  achow101:
    ACK 00b8e26
  ismaelsadeeq:
    Code review and Tested ACK 00b8e26

Tree-SHA512: 5ef792961b7fad4999fc30aa03366432103ddf672ca5cbb366c9eab4c2e46d5ae1ab0c073dfc4fbb2b4e63203653bc0e54463c731c5f8655140207ba5f8e542e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants