Skip to content

Conversation

@glozow
Copy link
Member

@glozow glozow commented Apr 20, 2023

Add an RPC to get prioritised transactions (also tells you whether the tx is in mempool or not), helping users clean up mapDeltas manually. When CTxMemPool::PrioritiseTransaction sets a delta to 0, remove the entry from mapDeltas.

Motivation / Background

  • mapDeltas entries are never removed from mapDeltas except when the tx is mined in a block or conflicted.
    • Mostly it is a feature to allow prioritisetransaction for a tx that isn't in the mempool {yet, anymore}. A user can may resbumit a tx and it retains its priority, or mark a tx as "definitely accept" before it is seen.
    • Since Store mempool and prioritization data to disk #8448, mapDeltas is persisted to mempool.dat and loaded on restart. This is also good, otherwise we lose prioritisation on restart.
    • Note the removal due to block/conflict is only done when removeForBlock is called, i.e. when the block is received. If you load a mempool.dat containing mapDeltas with transactions that were mined already (e.g. the file was saved prior to the last few blocks), you don't delete them.
    • Related: mapDeltas isn't always cleaned up. #4818 and Always clean up manual transaction prioritization #6464.
  • There is no way to query the node for not-in-mempool mapDeltas. If you add a priority and forget what the value was, the only way to get that information is to inspect mempool.dat.
  • Calling prioritisetransaction with an inverse value does not remove it from mapDeltas, it just sets the value to 0. It disappears on a restart (LoadMempool checks if delta is 0), but that might not happen for a while.

Added together, if a user calls prioritisetransaction very regularly and not all those transactions get mined/conflicted, mapDeltas might keep lots of entries of delta=0 around. A user should clean up the not-in-mempool prioritisations, but that's currently difficult without keeping track of what those txids/amounts are.

@DrahtBot
Copy link
Contributor

DrahtBot commented Apr 20, 2023

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

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK theStack, instagibbs, ajtowns, achow101
Concept ACK MarcoFalke, stickies-v, kevkevinpal, svanstaa
Approach ACK ishaanam

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

Conflicts

No conflicts as of last run.

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.

Concept ACK. Wanted to add a deletepriority RPC myself, but I guess it is easy to implement a for loop in python utilising the output of getprioritisationmap

@glozow glozow force-pushed the 2023-04-clear-prioritisation branch from 2c0eee6 to d5069a4 Compare April 20, 2023 15:48
@glozow glozow removed the CI failed label Apr 20, 2023
@dergoegge
Copy link
Member

Maybe add a short description for what mapDeltas is to the PR description under "motivation / background"? afaict mapDeltas also doesn't have any code documentation.

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.

lgtm ACK. Left two nits.

Copy link
Contributor

@stickies-v stickies-v 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

Copy link
Contributor

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

@glozow glozow force-pushed the 2023-04-clear-prioritisation branch 2 times, most recently from 4f6ea69 to ab47d8b Compare May 3, 2023 15:54
Copy link
Member Author

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

Addressed comments, rebased, added a release note

@kevkevinpal
Copy link
Contributor

Concept ACK

Copy link
Contributor

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

Was a bit surprised to see that the fee-delta is returned in BTC as unit rather than Satoshis, I assume our RPC guidelines (saying "Always use AmountFromValue and ValueFromAmount when inputting or outputting monetary values") have higher priority over the idea to simply use the same unit used for prioritisetransaction? Probably miners would be thankful to not need multiply the result by 100000000 first in order to delete mapDelta entries (and it would also make the tests simpler 😅), but happy to ACK either variant.

Left a few small nits below.

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't these messages be returned as an RPC result?

Shouldn't the message here also show the resulting total delta, and perhaps whether the tx was already in the mempool (nTransactionsUpdated > 0)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Would it be API-breaky to change the result of prioritisetransaction?

I've edited the logs though, to show whether the tx is in mempool and what the new delta is. Btw I don't think nTransactionsUpdated starts out at 0 when this is called.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could add a -deprecatedrpc=prioritisetransactionresult flag to allow access to the current format of result.

Copy link
Member Author

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

Addressed review comments and moved it from rpc/mempool.cpp to rpc/mining.cpp.

Probably miners would be thankful to not need multiply the result by 100000000 first in order to delete mapDelta entries (and it would also make the tests simpler 😅), but happy to ACK either variant.

I agree! I've changed it to satoshis to be the same as prioritisetransaction.

Copy link
Member Author

Choose a reason for hiding this comment

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

Would it be API-breaky to change the result of prioritisetransaction?

I've edited the logs though, to show whether the tx is in mempool and what the new delta is. Btw I don't think nTransactionsUpdated starts out at 0 when this is called.

@glozow glozow changed the title mempool / rpc: add getprioritisationmap, delete a mapDeltas entry when delta==0 mempool / rpc: add getprioritisedtransactions, delete a mapDeltas entry when delta==0 May 5, 2023
Copy link
Contributor

@theStack theStack left a comment

Choose a reason for hiding this comment

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

Code looks good to me, also agree with aj that getprioritisedtranactions is a better naming. Note that the first three commits still have the old names in their commit subjects and should be adapted accordingly.

glozow added 5 commits May 10, 2023 21:10
This allows the user to see prioritisation for not-in-mempool
transactions.
It's unnecessary to keep the data around, as it doesn't do anything. If
prioritisetransaction is called again, we'll make a new entry in
mapDeltas.

These entries are only deleted when the transaction is mined or conflicted
from a block (i.e. not in replacement or eviction), are persisted in
mempool.dat, and never expire. If node operators use the RPC to
regularly prioritise/de-prioritise transactions, these (meaningless)
map entries may hang around forever and take up valuable mempool memory.
@glozow glozow force-pushed the 2023-04-clear-prioritisation branch from 3e5c90e to 67b7fec Compare May 10, 2023 20:11
Comment on lines +524 to +525
/** The modified fee (base fee + delta) of this entry. Only present if in_mempool=true. */
std::optional<CAmount> modified_fee;
Copy link
Contributor

Choose a reason for hiding this comment

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

This field is set in GetPrioritisedTransaction, but not read anywhere. Was there a plan to also return that in getprioritisedtransactions or is there any other future use-case? If not, I think it can be removed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, it is not used. I added it to address this comment: #27501 (comment).

Copy link
Member Author

Choose a reason for hiding this comment

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

I could add the modified fee to the RPC result? I don't feel strongly, happy to change however people want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Have no strong opinion on that either, just wanted to make sure that there wasn't some code using modified_fee field around that was maybe forgotten to be pushed or sth alike.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably better to either expose it via the rpc, or not have the useless code at all?

@svanstaa
Copy link

Concept ACK

Copy link
Contributor

@theStack theStack left a comment

Choose a reason for hiding this comment

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

Code-review ACK 67b7fec

@fanquake fanquake requested review from ajtowns and instagibbs June 6, 2023 09:41
Copy link
Member

@instagibbs instagibbs left a comment

Choose a reason for hiding this comment

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

code review ACK 67b7fec

just test suggestions, everything looks good

{
{RPCResult::Type::OBJ, "txid", "", {
{RPCResult::Type::NUM, "fee_delta", "transaction fee delta in satoshis"},
{RPCResult::Type::BOOL, "in_mempool", "whether this transaction is currently in mempool"},
Copy link
Member

Choose a reason for hiding this comment

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

is there a particular reason we're exposing in-mempool-ness here? No strong feelings, just curious.

Copy link
Contributor

Choose a reason for hiding this comment

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

Provides a good excuse to map to an object rather than just a the delta itself, which in turn might make it easier to add other info in future without having a breaking change?

Copy link
Member

Choose a reason for hiding this comment

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

100% for object for future-proofing, either way

assert_equal(raw_before[txid_a], raw_after[txid_a])
assert_equal(raw_before, raw_after)
prioritisation_map_in_mempool = self.nodes[0].getprioritisedtransactions()
assert_equal(prioritisation_map_in_mempool[txid_b], {"fee_delta" : fee_delta_b*COIN, "in_mempool" : True})
Copy link
Member

Choose a reason for hiding this comment

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

just directly construct the entire 2-entry map and assert here? means we wouldn't miss extraneous entries in future regression. Could also keep a running "tally" object that's modified along with the test cases

self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_1 * COIN))
self.nodes[0].prioritisetransaction(txid=txid_c, fee_delta=int(fee_delta_c_2 * COIN))
prioritisation_map_not_in_mempool = self.nodes[0].getprioritisedtransactions()
assert_equal(prioritisation_map_not_in_mempool[txid_b], {"fee_delta" : fee_delta_b*COIN, "in_mempool" : False})
Copy link
Member

Choose a reason for hiding this comment

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

same as above?

assert_equal(raw_before[txid_a], raw_after[txid_a])
assert_equal(raw_before, raw_after)
prioritisation_map_in_mempool = self.nodes[0].getprioritisedtransactions()
assert_equal(prioritisation_map_in_mempool[txid_b], {"fee_delta" : fee_delta_b*COIN, "in_mempool" : True})
Copy link
Member

Choose a reason for hiding this comment

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

same as above?

}
++nTransactionsUpdated;
}
if (delta == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

maybe add a test case that shows prioritising by value of 0 doesn't add an entry, to cover all the bases

Copy link
Contributor

@ajtowns ajtowns left a comment

Choose a reason for hiding this comment

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

ACK 67b7fec code review only, some nits


struct delta_info {
/** Whether this transaction is in the mempool. */
const bool in_mempool;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems strange to make these const. const auto deltas = mempool.GetPrioritisedTransactions() should be enough...

Copy link
Member

Choose a reason for hiding this comment

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

I think const in this context is mainly used to force the designated initializer to initialize the field, because no constructor exists and otherwise this may end up being uninitialized?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like it would be better to explicitly specify defaults (false and 0?) in that case? If you want to delete the default constructor to force aggregate initialization, then delta_info() = delete; seems more direct?

"Returns a map of all user-created (see prioritisetransaction) fee deltas by txid, and whether the tx is present in mempool.",
{},
RPCResult{
RPCResult::Type::OBJ_DYN, "prioritisation-map", "prioritisation keyed by txid",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should just be "" rather than "prioritisation-map" ?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, the string is eaten by the help generator either way

RPCResult{
RPCResult::Type::OBJ_DYN, "prioritisation-map", "prioritisation keyed by txid",
{
{RPCResult::Type::OBJ, "txid", "", {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is "transactionid" in getrawmempool and getmempoolancestors, probably better to be consistent?

{
{RPCResult::Type::OBJ, "txid", "", {
{RPCResult::Type::NUM, "fee_delta", "transaction fee delta in satoshis"},
{RPCResult::Type::BOOL, "in_mempool", "whether this transaction is currently in mempool"},
Copy link
Contributor

Choose a reason for hiding this comment

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

Provides a good excuse to map to an object rather than just a the delta itself, which in turn might make it easier to add other info in future without having a breaking change?

Copy link
Contributor

Choose a reason for hiding this comment

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

Could add a -deprecatedrpc=prioritisetransactionresult flag to allow access to the current format of result.

Comment on lines +524 to +525
/** The modified fee (base fee + delta) of this entry. Only present if in_mempool=true. */
std::optional<CAmount> modified_fee;
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably better to either expose it via the rpc, or not have the useless code at all?

@achow101
Copy link
Member

achow101 commented Jun 7, 2023

ACK 67b7fec

@achow101 achow101 merged commit 1af72e7 into bitcoin:master Jun 7, 2023
@glozow glozow deleted the 2023-04-clear-prioritisation branch June 7, 2023 07:39
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jun 7, 2023
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Aug 16, 2023
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Aug 16, 2023
This allows the user to see prioritisation for not-in-mempool
transactions.

Github-Pull: bitcoin#27501
Rebased-From: 99f8046
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Aug 16, 2023
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Aug 16, 2023
luke-jr pushed a commit to luke-jr/bitcoin that referenced this pull request Aug 16, 2023
It's unnecessary to keep the data around, as it doesn't do anything. If
prioritisetransaction is called again, we'll make a new entry in
mapDeltas.

These entries are only deleted when the transaction is mined or conflicted
from a block (i.e. not in replacement or eviction), are persisted in
mempool.dat, and never expire. If node operators use the RPC to
regularly prioritise/de-prioritise transactions, these (meaningless)
map entries may hang around forever and take up valuable mempool memory.

Github-Pull: bitcoin#27501
Rebased-From: 67b7fec
glozow added a commit that referenced this pull request Jan 12, 2024
…nd delete a mapDeltas entry when delta==0

0eebd6f test: Assert that a new tx with a delta of 0 is never added (kevkevin)
cfdbcd1 rpc: exposing modified_fee in getprioritisedtransactions (kevkevin)
252a867 rpc: renaming txid -> transactionid (kevkevin)
2fca6c2 rpc: changed prioritisation-map -> "" (kevkevin)
3a118e1 test: Directly constructing 2 entry map for getprioritisedtransactions (kevkevin)

Pull request description:

  In this PR I am addressing some comments in #27501 as a followup.
  - changed `prioritisation-map` in the `RPCResult` to `""`
  - Directly constructing 2 entry map for getprioritisedtransactions in functional tests
  - renamed `txid` to `transactionid` in `RPCResult` to be more consistent with naming elsewhere
  - exposed the `modified_fee` field instead of having it be a useless arg
  - Created a new test that asserts when `prioritisedtransaction` is called with a fee_delta of 0 it is not added to mempool

ACKs for top commit:
  glozow:
    reACK 0eebd6f, only change is the doc suggestion

Tree-SHA512: e99056e37a8b1cfc511d87c83edba7c928b50d2cd6c2fd7c038976779850677ad37fddeb2b983e8bc007ca8567eb21ebb78d7eae9b773657c2b297299993ec05
@bitcoin bitcoin locked and limited conversation to collaborators Nov 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.