Skip to content

test: Add functional test for replacement relay fee check #22310

Merged
maflcko merged 1 commit into
bitcoin:masterfrom
ariard:2021-06-add-rbf5-test
Jul 1, 2021
Merged

test: Add functional test for replacement relay fee check #22310
maflcko merged 1 commit into
bitcoin:masterfrom
ariard:2021-06-add-rbf5-test

Conversation

@ariard
Copy link
Copy Markdown

@ariard ariard commented Jun 22, 2021

This PR adds rename the reject_reason of our implementation of BIP125 rule 4 and adds missing functional test coverage. Note, insufficient fee is already the reject_reason of few others PreChecks replacement checks and as such might be confusing.

The replacement transaction must also pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting. For example, if the minimum relay fee is 1 satoshi/byte and the replacement transaction is 500 bytes total, then the replacement must pay a fee at least 500 satoshis higher than the sum of the originals.

        // Finally in addition to paying more fees than the conflicts the
        // new transaction must pay for its own bandwidth.
        CAmount nDeltaFees = nModifiedFees - nConflictingFees;
        if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
        {
            return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee",
                    strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
                        hash.ToString(),
                        FormatMoney(nDeltaFees),
                        FormatMoney(::incrementalRelayFee.GetFee(nSize))));
        }

Comment thread src/validation.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.

any reason to change the reject string? This will just cause a headache for downstream project (if they use the reject string) and might even be controversial due to the change "fee" -> "penalty".

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.

Also the commit title is wrong? Mentions rule 5, but this is rule 4?

Copy link
Copy Markdown
Author

@ariard ariard Jun 22, 2021

Choose a reason for hiding this comment

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

Pushed new commit, updating to rule 4.

W.r.t to headache for downstream project, it's motivated by the fact that this same reject reason is already used L897 for the "higher fee" check and L833 for the "higher feerate" check.

Do we want to dissipate the confusion ? I would say it's better but can just drop it if you think it's too bothering for downstream.

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.

If you really want to change it, a separate pull request with clear rationale to motivate the change (and the hassle it causes) would be better. Bundling it with a change that is adding a test might not be the best approach.

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.

Obviously it needs release notes etc. Though, I'd rather avoid breaking downstream via reject reasons, which might break silently because they are not an enum. #19339 (comment)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ah okay, well I'll let the confusion for now and just happy to have test coverage. Updated the branch.

@ariard ariard force-pushed the 2021-06-add-rbf5-test branch from d19c7cc to cdb84f1 Compare June 22, 2021 17:42
@ariard ariard force-pushed the 2021-06-add-rbf5-test branch 4 times, most recently from 8854ad6 to 8014964 Compare June 22, 2021 18:18
Comment thread test/functional/feature_rbf.py 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.

how is this test different from the one above? # Should fail because we haven't changed the fee

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.

It is different because the newly added test adds coverage for previously uncovered code https://marcofalke.github.io/btc_cov/total.coverage/src/validation.cpp.gcov.html

Comment thread test/functional/feature_rbf.py 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.

The fee and fee per KB are lower though?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think the replacement_transaction output value is CTxOut(int(1 * COIN - 1)) so it has a higher fee/fee per KB, as everything else is constant.

You can verify with the following diff :

@@ -892,6 +894,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
         // The replacement must pay greater fees than the transactions it
         // replaces - if we did the bandwidth used by those conflicting
         // transactions would not be paid for.
+        LogPrintf("nModifiedFees %d\n", nModifiedFees);
+        LogPrintf("nConflictingFees %d\n", nConflictingFees);
         if (nModifiedFees < nConflictingFees)
         {
             return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee",

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.

Sorry, brainfart.

@DrahtBot DrahtBot added the Tests label Jun 22, 2021
@maflcko
Copy link
Copy Markdown
Member

maflcko commented Jun 23, 2021

Nice catch. Concept ACK

Comment thread test/functional/feature_rbf.py 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.

any reason to not use miniwallet, like in the previous test?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Well I think it makes the test a bit longer because it doesn't give you an outpoint but an utxo ? Though I took it anyway.

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.

heh, the point is to create the tx with the miniwallet. In that case the test should be three lines or so.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Okay I did drop the miniwallet usage as AFAICT its fee_rate argument doesn't allow the feerate granularity (sat per KvB) required to hit the yet-uncovered branch ? Or at least without modifying node's default incrementalrelayfee

@ariard ariard changed the title test: Add functional test for replacement penalty check test: Add functional test for replacement relay fee check Jun 23, 2021
@ariard ariard force-pushed the 2021-06-add-rbf5-test branch from 8014964 to 4bcb8d1 Compare June 23, 2021 16:31
Comment thread test/functional/feature_rbf.py Outdated
Comment on lines 635 to 650
Copy link
Copy Markdown
Member

@darosior darosior Jun 23, 2021

Choose a reason for hiding this comment

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

So now this is the very same test as in test_simple_doublespend:

tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1a_hex = txToHex(tx1a)
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
self.sync_all()
# Should fail because we haven't changed the fee
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)]
tx1b_hex = txToHex(tx1b)
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)

I think (untested) it would be caught in:

bitcoin/src/validation.cpp

Lines 830 to 838 in 7317e14

CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
if (newFeeRate <= oldFeeRate)
{
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee",
strprintf("rejecting replacement %s; new feerate %s <= old feerate %s",
hash.ToString(),
newFeeRate.ToString(),
oldFeeRate.ToString()));
}

Which is already a covered branch.

You need to keep the lower higher feerate to get to the yet-uncovered branch of:

bitcoin/src/validation.cpp

Lines 892 to 900 in 7317e14

// The replacement must pay greater fees than the transactions it
// replaces - if we did the bandwidth used by those conflicting
// transactions would not be paid for.
if (nModifiedFees < nConflictingFees)
{
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee",
strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)));
}

And if you modified that because of my previous brainfart i'm sorry 😶

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You need to keep the lower feerate to get to the yet-uncovered branch of:

Oh yes effectively, do you mean the higher feerate ?

At 58cdabe6, feerates are the following:

 node0 2021-06-24T15:51:23.735515Z [httpworker.0] [validation.cpp:831] [PreChecks] newFeeRate 583.33333345 BTC/kvB 
 node0 2021-06-24T15:51:23.735525Z [httpworker.0] [validation.cpp:832] [PreChecks] oldFeeRate 583.33333333 BTC/kvB 

I think it's a +1 to have different reject_reason :p

@DrahtBot
Copy link
Copy Markdown
Contributor

DrahtBot commented Jun 23, 2021

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

Conflicts

No conflicts as of last run.

Copy link
Copy Markdown
Member

@darosior darosior left a comment

Choose a reason for hiding this comment

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

ACK 58cdabe60e -- nice!

Copy link
Copy Markdown
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.

Left a review comment about a comment.

Also, would be good to use MiniWallet, to that the test doesn't have to be touched again in the future when someone wants to run policy tests without the wallet. (Lightning doesn't run on the Bitcoin Core wallet, so I presume the wallet will generally be disabled)

Comment thread test/functional/feature_rbf.py 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.

Is is "equal fee" if the fee is increased?

Again, I'd recommend to use MiniWallet, which will simplify this test a lot:

    def test_replacement_relay_fee(self):
        wallet = MiniWallet(self.nodes[0])
        wallet.scan_blocks(start=77, num=1)
        tx = wallet.send_self_transfer(from_node=self.nodes[0])['tx']
        tx.vout[0].nValue -= 1
        assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ooops, leftover from the multiple updates of this PR with the converation here.

Copy link
Copy Markdown
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

Concept ACK

@ariard ariard force-pushed the 2021-06-add-rbf5-test branch from 58cdabe to c4ddee6 Compare June 30, 2021 22:53
@ariard
Copy link
Copy Markdown
Author

ariard commented Jun 30, 2021

@MarcoFalke, Fair point, updated at c4ddee6 with MiniWallet and hopefully binding comment, thanks for the code snippet! IMHO, I prefer to use the low-level test framework API to illustrate etchy, confusing case of our mempool logic but I understand for maintainability the usage of the higher API is better.

Note to reviewers, hopefully the following diff should let you exercise the new test coverage, without blurring with other related "insufficient fee" checks:

diff --git a/src/validation.cpp b/src/validation.cpp
index b48e49a10..5c8008d30 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -904,7 +904,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
         CAmount nDeltaFees = nModifiedFees - nConflictingFees;
         if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
         {
-            return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee",
+            return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient relay fee",
                     strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
                         hash.ToString(),
                         FormatMoney(nDeltaFees),
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index ed944274e..9fcdf614a 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -638,7 +638,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
         # Higher fee, higher feerate, different txid, but the replacement does not provide a relay
         # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB.
         tx.vout[0].nValue -= 1
-        assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
+        assert_raises_rpc_error(-26, "insufficient relay fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
 
 if __name__ == '__main__':
     ReplaceByFeeTest().main()

Copy link
Copy Markdown
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

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

ACK c4ddee6, one small suggestion if you retouch.


# Higher fee, higher feerate, different txid, but the replacement does not provide a relay
# fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB.
tx.vout[0].nValue -= 1
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.

(nit) was slightly surprised that 1 was parsed as 1 satoshi and that the tx didn't need a new signature

Suggested change
tx.vout[0].nValue -= 1
# Increase the fee by 1 satoshi. Same signature works because it is an anyone-can-spend.
tx.vout[0].nValue -= Decimal("0.00000001")

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.

The test wouldn't pass with the suggestion applied

@maflcko
Copy link
Copy Markdown
Member

maflcko commented Jul 1, 2021

cr ACK c4ddee6

@maflcko maflcko merged commit a926d6d into bitcoin:master Jul 1, 2021
@maflcko
Copy link
Copy Markdown
Member

maflcko commented Jul 1, 2021

sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jul 3, 2021
…fee check

c4ddee6 test: Add test for replacement relay fee check (Antoine Riard)

Pull request description:

  This PR adds rename the `reject_reason` of our implementation of BIP125 rule 4 and adds missing functional test coverage. Note, `insufficient fee` is already the `reject_reason` of few others `PreChecks` replacement checks and as such might be confusing.

  > The replacement transaction must also pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting. For example, if the minimum relay fee is 1 satoshi/byte and the replacement transaction is 500 bytes total, then the replacement must pay a fee at least 500 satoshis higher than the sum of the originals.

  ```
          // Finally in addition to paying more fees than the conflicts the
          // new transaction must pay for its own bandwidth.
          CAmount nDeltaFees = nModifiedFees - nConflictingFees;
          if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
          {
              return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee",
                      strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
                          hash.ToString(),
                          FormatMoney(nDeltaFees),
                          FormatMoney(::incrementalRelayFee.GetFee(nSize))));
          }
  ```

ACKs for top commit:
  MarcoFalke:
    cr ACK c4ddee6
  glozow:
    ACK c4ddee6, one small suggestion if you retouch.

Tree-SHA512: 7c5d1065db6e6fe57a9f083bf051a7a55eb9892de3a2888679d4a6853491608c93b6e35887ef383a9988d14713fa13a0b1d6134b7354af5fd54765f0d4e98568
gwillen pushed a commit to ElementsProject/elements that referenced this pull request Jun 1, 2022
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 16, 2022
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.

5 participants