Skip to content

[amount] Add support for negative fee rates - #7796

Merged
laanwj merged 3 commits into
bitcoin:masterfrom
maflcko:Mf1604-amountNeg64
Apr 14, 2016
Merged

[amount] Add support for negative fee rates#7796
laanwj merged 3 commits into
bitcoin:masterfrom
maflcko:Mf1604-amountNeg64

Conversation

@maflcko

@maflcko maflcko commented Apr 3, 2016

Copy link
Copy Markdown
Member

Currently negative fee rates are not supported on archs of 64-bit or more

@laanwj

laanwj commented Apr 3, 2016

Copy link
Copy Markdown
Member

Negative fee rates imply that you pay a smaller, more negative fee the larger the transaction? What do negative fees even mean, steal from the miner? Or is this so that anti-transactions of negative size pay a positive fee?

I'd say this is an edge case better to get rid of (e.g. assert or throw an error).

@sipa

sipa commented Apr 3, 2016 via email

Copy link
Copy Markdown
Member

@laanwj

laanwj commented Apr 3, 2016

Copy link
Copy Markdown
Member

According to @MarcoFalke they already don't work on 64-bit architectures, which is probably 90%+ of all nodes. Better to remove it for 32 bit as well.

@laanwj laanwj added the Tests label Apr 3, 2016
@maflcko

maflcko commented Apr 3, 2016

Copy link
Copy Markdown
Member Author

@sipa is correct

@laanwj I have pushed the fix as a second commit, so the travis result can be compared. The fix is probably a lot smaller than the fix which removes support.

Comment thread src/amount.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of the temporary variable, why not just:

nSatoshisPerK = nFeePaid * 1000 / static_cast<int64_t>(nSize);

Also at least theoretically you should handle the case where nSize is outside the range of int64_t, though I doubt someone will ever care about transactions that large.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If it is used more than once, you'd rather want to do the cast only once. I guess an alternative would be to change it in the constructor arguments, but that is less verbose, imo.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess an alternative would be to change it in the constructor arguments

I thought of that, but didn't mention it at second thought. Before you know it people will want full support for negative sizes as well. The appropriate type is size_t.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This would require a cast in 4 places instead of two (see diff below) but I am happy to apply the diff an squash. Please let me know how to proceed so we can finalize this.

diff --git a/src/amount.cpp b/src/amount.cpp
index 7b8618d..3966c5f 100644
--- a/src/amount.cpp
+++ b/src/amount.cpp
@@ -13,8 +13,7 @@ CFeeRate::CFeeRate(const CAmount& nFeePaid, size_t nBytes_)
 {
     assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
-    int64_t nSize = int64_t(nBytes_);

-    if (nSize > 0)
-        nSatoshisPerK = nFeePaid * 1000 / nSize;
+    if (int64_t(nBytes_) > 0)
+        nSatoshisPerK = nFeePaid * 1000 / int64_t(nBytes_);
     else
         nSatoshisPerK = 0;
@@ -24,9 +23,8 @@ CAmount CFeeRate::GetFee(size_t nBytes_) const
 {
     assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
-    int64_t nSize = int64_t(nBytes_);

-    CAmount nFee = nSatoshisPerK * nSize / 1000;
+    CAmount nFee = nSatoshisPerK * int64_t(nBytes_) / 1000;

-    if (nFee == 0 && nSize != 0) {
+    if (nFee == 0 && int64_t(nBytes_) != 0) {
         if (nSatoshisPerK > 0)
             nFee = CAmount(1);

@laanwj

laanwj commented Apr 3, 2016

Copy link
Copy Markdown
Member

Ok, at least document this then, for example in the doc comment of the constructor. It's extremely unintuitive to me and probably to others reading this code as well.

@maflcko

maflcko commented Apr 3, 2016

Copy link
Copy Markdown
Member Author

Ok, will add the doc later...

@morcos

morcos commented Apr 3, 2016

Copy link
Copy Markdown
Contributor

ping @sdaftuar

@sdaftuar

sdaftuar commented Apr 5, 2016

Copy link
Copy Markdown
Member

I think it's probably better to fix support for negative fee rates on 64-bit platforms, because (a) fixing the prioritisetransaction RPC to disallow negative feerates would be difficult/annoying because fee deltas can be stored prior to transaction acceptance, (b) it would be counterintuitive if you called prioritisetransaction with -X and then later with X but didn't get back to where you started, and (c) I think there might be legitimate reasons to want to keep an ordering of transactions you don't want to mine right now (which you'd lose if you tried to floor everything at zero, which is the easiest reasonable alternative I can come up with).

But to be fair, I'm surprised that no one has complained about this before, because as far as I can tell, up until 0.12, if you used prioritisetransaction to apply a negative feerate, then the mining code would immediately select it as a super-high-fee transaction. (I stumbled upon this behavior while working on #7063.)

So, concept ACK. I agree with @laanwj's comment about the cast. Also, I think it probably makes most sense to enforce the property that CFeeRate(X).GetFee(size) == -1 * CFeeRate(-X).GetFee(size); if everyone agrees, then we should make the special case rounding recently introduced (where we return CFeeRate(1)) also apply in the negative fee rate case.

Also, we should add a unit test that exercises the constructor CFeeRate(const CAmount& nFeePaid, size_t nSize_) that is changed in this PR.

@maflcko
maflcko force-pushed the Mf1604-amountNeg64 branch from 7777bd2 to fadd138 Compare April 8, 2016 16:42
@maflcko

maflcko commented Apr 8, 2016

Copy link
Copy Markdown
Member Author

I think it probably makes most sense to enforce the property that CFeeRate(X).GetFee(size) == -1 * CFeeRate(-X).GetFee(size);

Makes sense, done.

add a unit test that exercises the constructor CFeeRate(const CAmount& nFeePaid, size_t nSize_)

Done.

@maflcko
maflcko force-pushed the Mf1604-amountNeg64 branch 2 times, most recently from fa7c3b8 to faf4ef7 Compare April 8, 2016 17:51
MarcoFalke added 2 commits April 8, 2016 19:59
Currently negative fee rates are not supported on archs of 64-bit or
more
@maflcko
maflcko force-pushed the Mf1604-amountNeg64 branch 2 times, most recently from fa2da2c to 11114a6 Compare April 9, 2016 11:32
@maflcko

maflcko commented Apr 9, 2016

Copy link
Copy Markdown
Member Author

Before on 64-bit:

test/amount_tests.cpp(33): error: feeRate.GetFee(1) == -1 has failed [18446744073709550 != -1]
test/amount_tests.cpp(34): error: feeRate.GetFee(121) == -121 has failed [18446744073709430 != -121]
test/amount_tests.cpp(35): error: feeRate.GetFee(999) == -999 has failed [18446744073708552 != -999]
test/amount_tests.cpp(36): error: feeRate.GetFee(1e3) == -1e3 has failed [18446744073708551 != -1000]
test/amount_tests.cpp(37): error: feeRate.GetFee(9e3) == -9e3 has failed [18446744073700551 != -9000]
test/amount_tests.cpp(53): error: feeRate.GetFee(8) == -1 has failed [18446744073709550 != -1]
test/amount_tests.cpp(54): error: feeRate.GetFee(9) == -1 has failed [18446744073709550 != -1]
test/amount_tests.cpp(58): error: CFeeRate(CAmount(-1), 1000) == CFeeRate(-1) has failed

After:
tests pass

@maflcko
maflcko force-pushed the Mf1604-amountNeg64 branch from 1b83a0d to fa2da2c Compare April 9, 2016 12:00
@laanwj

laanwj commented Apr 14, 2016

Copy link
Copy Markdown
Member

utACK facf5a4

@laanwj
laanwj merged commit facf5a4 into bitcoin:master Apr 14, 2016
laanwj added a commit that referenced this pull request Apr 14, 2016
facf5a4 [amount] tests: Fix off-by-one mistake (MarcoFalke)
fa2da2c [amount] Add support for negative fee rates (MarcoFalke)
11114a6 [amount] test negative fee rates and full constructor (MarcoFalke)
Comment thread src/amount.cpp

if (nFee == 0 && nSize != 0 && nSatoshisPerK > 0)
nFee = CAmount(1);
if (nFee == 0 && nSize != 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Couldn't have we just removed this special case from here (ie move this check to the callers that need it)?
I know that would be more disruptive, but it will also make increasing the internal precision easier later.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure!

I am only aware of the wallet using it:

if (nFeeNeeded == 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh, that's great. Moving from nSatoshisPerK to 1 was already and step forward. But I've strong reason to believe that this special case was the most important impediment for not being able to "easily" do more than *2 on internal precision in #7731(in fact, I don't think it would pass all the tests if I rebased this now due to this change).

If you create a PR to move the special case to the wallet only, please ping me for review, I am very interested in seeing that happening. Looking at the code you link to, it looks like that part can actually be simplified as a result, instead of complicating it. But tests will break...

@jtimon

jtimon commented Apr 14, 2016

Copy link
Copy Markdown
Contributor

After this, the following change will fail the new tests in amount_tests.cpp: 5ca2473

This is a mystery to me, can anybody help me understand?
I get the same errors that MarcoFalke gets "Before on 64-bit:", do I have to do something locally on 64 bits linux?

@maflcko
maflcko deleted the Mf1604-amountNeg64 branch April 14, 2016 14:57
codablock pushed a commit to codablock/dash that referenced this pull request Dec 20, 2017
facf5a4 [amount] tests: Fix off-by-one mistake (MarcoFalke)
fa2da2c [amount] Add support for negative fee rates (MarcoFalke)
11114a6 [amount] test negative fee rates and full constructor (MarcoFalke)
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants