Skip to content

Conversation

andremralves
Copy link
Contributor

@andremralves andremralves commented Nov 23, 2024

Introduce utility functions satToBtc and btcToSat to simplify and standardize conversions between satoshis and bitcoins in functional tests

#closes #31345

This PR implements the solution proposed in the issue. The goal is to simplify conversions between satoshis and bitcoins in the functional tests by using utility functions. For example, code like fee_rate=Decimal(feerate * 1000) / COIN could become fee_rate=satToBtc(feerate * 1000) in the proposed solution.

An additional benefit of satToBtc() is that it ensures the use of Decimal.

This issue was briefly mentioned in this discussion, where the conversion Decimal(value) / COIN was noted to be repetitive.

While some may prefer to continue using the / COIN and * COIN conversions, I don’t have a strong preference either way. I’d like to hear your opinions and, if needed, implement the approach that works best.

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 23, 2024

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

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31356.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #31371 (doc, test: more ephemeral dust follow-ups by theStack)
  • #30157 (Fee Estimation via Fee rate Forecasters by ismaelsadeeq)
  • #28676 ([WIP] Cluster mempool implementation by sdaftuar)
  • #28121 (include verbose "reject-details" field in testmempoolaccept response by pinheadmz)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/33416833622

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@laanwj
Copy link
Member

laanwj commented Nov 23, 2024

Concept -0, it seems like a huge change to factor out what is just a multiply/division. imo this only makes sense if the intent is to add extra validity checks to the functions.

Could at least make sure that the return value from satsToBtc in BTC is a right-precision Decimal and not a float.

@laanwj laanwj added the Tests label Nov 23, 2024
@@ -234,6 +234,14 @@ def from_binary(cls, stream):
return obj


def satToBtc(sat_value):
return sat_value / COIN
Copy link
Member

@laanwj laanwj Nov 23, 2024

Choose a reason for hiding this comment

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

so something like

def satToBtc(sat_value: int) -> Decimal:
    return Decimal(sat_value) / COIN

def btcToSat(btc_value: Decimal) -> int:
    return int(btc_value * COIN)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think this might be better. I've updated the PR.

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Debug: https://github.com/bitcoin/bitcoin/runs/33430954182

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@andremralves andremralves force-pushed the btc-to-sat branch 2 times, most recently from 32a5b18 to 8aaa4ad Compare November 24, 2024 02:31
Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

Instead of repeating the diff in the pull request description (if someone was interested in the changes in detail, they could just look at the diff themselves), it would be better to mention the benefits/goals and how they are expected to be achieved for new code written after this was merged.

Possibly there is a case to have feerate conversion helpers, or a class to hold feerates. However, any "global" change here would need a solid motivation and long-term upside.

@@ -1159,18 +1159,18 @@ def run_test(self):
self.log.info("Test transaction resurrection during a re-org")
self.move_tip(76)
self.next_block(77)
tx77 = self.create_and_sign_transaction(out[24], 10 * COIN)
tx77 = self.create_and_sign_transaction(out[24], btcToSat(10))
Copy link
Member

Choose a reason for hiding this comment

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

Not sure about this. I think writing 10 * COIN with the meaning of "10 bitcoins" is self-explanatory and couldn't be clearer. btcToSat(10) just seems like an extra step and extra complexity without a benefit?

Also, the "new" style isn't enforced, so you'll end up with both styles being used in the future, increasing the confusing further.

Finally, I had the impression that python code is using snake_case, so btcToSat would be confusing in that regard as well.

Copy link
Contributor

@rkrux rkrux Nov 25, 2024

Choose a reason for hiding this comment

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

While 10 * COIN seems self-explanatory, I find the util function usage in the following diff more readable and easier on the eyes. Instead of having to process 3 things in one go, namely int conversion, sum() with an argument that's being calculated, multiplication with COIN, I have to process only one function call with the said argument.

https://github.com/bitcoin/bitcoin/pull/31356/files#diff-9e79d56c581ca71e62a898ee0d2afda253081118ebcd1744ba9b3d16f0958a80L192

- input_amount = int(sum([utxo['value'] for utxo in utxos_to_spend]) * COIN)
+ input_amount = btcToSat(sum([utxo['value'] for utxo in utxos_to_spend]))

Introduce utility functions `satToBtc` and `btcToSat` to
simplify and standardize conversions between satoshis and bitcoins
in functional tests
Copy link
Contributor

@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.

Thanks for picking this up! Left few suggestions.
Can the PR be split into 2 commits for each util function that will make it easier to process piece by piece?

Comment on lines +238 to +243
def satToBtc(sat_value: int) -> Decimal:
return Decimal(sat_value) / COIN


def btcToSat(btc_value: Decimal) -> int:
return int(btc_value * COIN)
Copy link
Contributor

Choose a reason for hiding this comment

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

I see that COIN is defined in this file but not sure if this is the right file to put these functions in, maybe util.py?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for using camelCase in the issue description, snake_case is preferred in functional tests.

Copy link

Choose a reason for hiding this comment

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

this is definitely the wrong file, I couldn't even find this change

Comment on lines 396 to +397
tx_val = 0.001
tx.vout = [CTxOut(int(Decimal(tx_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))]
tx.vout = [CTxOut(btcToSat(Decimal(tx_val)), CScript([OP_RETURN] + [OP_FALSE] * 30))]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this utility function can become more useful if it accepts float too, thereby getting rid of explicit conversion while calling?

Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines -174 to +177
def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None):
def branch(prevout, initial_value, max_txs, tree_width=5, fee=None, _total_txs=None):
if fee is None:
fee = btcToSat(0.00001)
Copy link
Contributor

Choose a reason for hiding this comment

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

- def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None):
+ def branch(prevout, initial_value, max_txs, tree_width=5, fee=btcToSat(0.00001), _total_txs=None):

Seems to work.

@@ -1159,18 +1159,18 @@ def run_test(self):
self.log.info("Test transaction resurrection during a re-org")
self.move_tip(76)
self.next_block(77)
tx77 = self.create_and_sign_transaction(out[24], 10 * COIN)
tx77 = self.create_and_sign_transaction(out[24], btcToSat(10))
Copy link
Contributor

@rkrux rkrux Nov 25, 2024

Choose a reason for hiding this comment

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

While 10 * COIN seems self-explanatory, I find the util function usage in the following diff more readable and easier on the eyes. Instead of having to process 3 things in one go, namely int conversion, sum() with an argument that's being calculated, multiplication with COIN, I have to process only one function call with the said argument.

https://github.com/bitcoin/bitcoin/pull/31356/files#diff-9e79d56c581ca71e62a898ee0d2afda253081118ebcd1744ba9b3d16f0958a80L192

- input_amount = int(sum([utxo['value'] for utxo in utxos_to_spend]) * COIN)
+ input_amount = btcToSat(sum([utxo['value'] for utxo in utxos_to_spend]))

# Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output
tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50
tx.vout.append(CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), SCRIPT_W0_SH_OP_TRUE))
Copy link
Contributor

Choose a reason for hiding this comment

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

Possibly there is a case to have feerate conversion helpers, or a class to hold feerates.

Agree, a fee rate conversion helper here would make reading this far easier.

Copy link
Contributor

Choose a reason for hiding this comment

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

This conversion was not intuitive to me in the first glance: #30079 (comment)

Comment on lines 163 to +164
tx2_val = '20.99'
tx2.vout = [CTxOut(int(Decimal(tx2_val) * COIN), CScript([OP_RETURN] + [OP_FALSE] * 30))]
tx2.vout = [CTxOut(btcToSat(Decimal(tx2_val)), CScript([OP_RETURN] + [OP_FALSE] * 30))]
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe accepting a string with internal string to decimal checking and conversion would also make it more flexible? The explicit Decimal conversion could be removed here then.

Copy link
Member

Choose a reason for hiding this comment

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

An alternative idea would be to add a btcStringToSat function, instead of overloading btcToSat on type, as this makes type checking harder.

Comment on lines +99 to +101
fee_delta_b = satToBtc(Decimal(9999))
fee_delta_c_1 = satToBtc(Decimal(-1234))
fee_delta_c_2 = satToBtc(Decimal(8888))
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't satToBtc accept an int?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, passing sats as a decimal should never be necessary.

@DrahtBot
Copy link
Contributor

🐙 This pull request conflicts with the target branch and needs rebase.

@fanquake
Copy link
Member

fanquake commented Dec 4, 2024

Closing, given the author has said in #31345 that someone else can take it over.

@fanquake fanquake closed this Dec 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add sat_to_btc() and conversely btc_to_sat() util functions in functional tests
7 participants