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

Fee Estimation: Ignore all transactions with an in-block child #30079

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

ismaelsadeeq
Copy link
Member

@ismaelsadeeq ismaelsadeeq commented May 10, 2024

Another attempt at #25380 with an alternate approach

This PR updates CBlockPolicyEstimator to ignore all transactions with an in-block child when a new block is received.
This fixes the assumption that all transactions are confirmed solely because of their fee rate.
As some transactions are confirmed due to a CPFP by some child.

The downside of this approach is that transactions that are not CPFP’d will also be ignored.

The correct approach is to linearize all transactions removed from the mempool because of the new block, and only ignore transactions whose mining score is not the same with their the fee rate.

I have a branch that implements this using a mini miner:

The implication of the approach in this branch is that the constructor used and the linearization code will be removed post-cluster mempool implementation:

Rework mini_miner to utilize cluster mempool features

Another approach I contemplated was to replicate the linearization code into policy/fees.cpp and use it but this means having duplicate code in policy/fees.cpp and node/mini_miner.cpp. Considering that post-cluster mempool, we will transition to tracking chunks rather than individual transactions and the specification for this is already under discussion here.

This PR is an interim solution, focusing on ignoring transactions with in-block children this is safe to use considering that the majority of transactions are individual transactions. Upon implementing the cluster mempool, we will just track chunks and make the fee estimator package aware.

@DrahtBot
Copy link
Contributor

DrahtBot commented May 10, 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 rkrux
Concept ACK instagibbs, josibake

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

@glozow
Copy link
Member

glozow commented May 10, 2024

cc @josibake @instagibbs @naumenkogs @darosior @murchandamus since you reviewed the last time

@instagibbs
Copy link
Member

concept ACK

strikes me as the right idea, keep it simple for now, focusing on correctness. Do we have charts anywhere tracking % of transactions that are in a cluster size of 1?

@ismaelsadeeq
Copy link
Member Author

Do we have charts anywhere tracking % of transactions that are in a cluster size of 1?

I will analyze the percentage of cluster size 1 transactions mined in previous blocks.

@josibake
Copy link
Member

Concept ACK

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 c12a677

Make successful, all functional tests successful.
Mentioned few nits and asked few questions for my clarity.

The downside of this approach is that transactions that are not CPFP’d will aslo be ignored.

Related to this^, one specific case I can think of that will be affected is of layered transactions that are meant to be confirmed together, example: fanout transactions to increase the utxo count in the wallet and make the utxo value distribution more even, but Idk how frequently these transactions happen.

test/functional/feature_fee_estimation.py Outdated Show resolved Hide resolved
test/functional/feature_fee_estimation.py Outdated Show resolved Hide resolved
test/functional/feature_fee_estimation.py Outdated Show resolved Hide resolved
test/functional/feature_fee_estimation.py Show resolved Hide resolved
test/functional/feature_fee_estimation.py Show resolved Hide resolved
test/functional/feature_fee_estimation.py Show resolved Hide resolved
src/policy/fees.cpp Outdated Show resolved Hide resolved
seen_transactions.insert(tx.info.m_tx->GetHash());
for (const auto& input : tx.info.m_tx->vin) {
const Txid& parentId = input.prevout.hash;
if (seen_transactions.count(parentId)) {
Copy link

Choose a reason for hiding this comment

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

I'm assuming the transactions are sorted from parents to children. So in the case A -> B -> C -> D, all except D would be removed, right?

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'm assuming the transactions are sorted from parents to children.

Yes, they are.

The fee estimator does not take all transaction with parents in the mempool into account already see

const bool validForFeeEstimation = !tx.m_mempool_limit_bypassed && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;

Assuming A->B->C->D are topologically sorted from parent to last descendant.

In this case B, C, D are already not considered for fee estimation, After this PR A will also be ignored.

Copy link

Choose a reason for hiding this comment

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

Hmm I see, as per this comment it says "not dependent on any other transactions in the mempool" - does this also apply to other transactions in the block?

// - the transaction is not dependent on any other transactions in the mempool

Copy link
Member Author

Choose a reason for hiding this comment

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

does this also apply to other transactions in the block?

I don't fully understand your question.
The comment you highlighted refers to all incoming transactions into the mempool.

After a new block arrives, the fee estimator listens for notifications of all transactions removed from mempool because of the new block, and updates their tracking stats. All transactions with mempool dependencies that are mined are not tracked initially, they are just going to be skipped.

test/functional/feature_fee_estimation.py Show resolved Hide resolved
test/functional/feature_fee_estimation.py Show resolved Hide resolved
@ismaelsadeeq ismaelsadeeq force-pushed the 05-2023-ignore-transactions-with-parents branch from c12a677 to 2563305 Compare May 14, 2024 17:03
@ismaelsadeeq
Copy link
Member Author

Do we have charts anywhere tracking % of transactions that are in a cluster size of 1?

I will analyze the percentage of cluster size 1 transactions mined in previous blocks.

I tracked recent 1000 blocks from block 842457 to 843457

~61% of transactions in the last 1000 blocks were confirmed in a cluster size > 1.
~38% of transactions in the last 1000 blocks were confirmed in a cluster size 1

Transactions: 3974143
Cluster size 1 transactions: 1516505
Approximate percentage of Cluster size 1 transactions: ~38
Transactions in a cluster size > 1 transactions: 2457638
Approximate percentage of transactions in a cluster size > 1: ~61

The data is available here here https://docs.google.com/spreadsheets/d/1QTE2EQeQlamA123pIn0SY4F-9jtnzS7e7CV1etT6ytM/edit?usp=sharing

I used this script to collect the data https://gist.github.com/ismaelsadeeq/e2767040b720f65986976b3f8c6b7b20

@ismaelsadeeq
Copy link
Member Author

Thanks for review @rkrux

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.

reACK 2563305

Thanks for the quick turnaround on this @ismaelsadeeq!

@@ -383,6 +401,79 @@ def test_acceptstalefeeestimates_option(self):
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)


def send_and_mine_child_tx(self, broadcaster, miner, parent_tx, feerate):
u = {"txid": parent_tx["txid"], "vout": 0, "value": Decimal(parent_tx["tx"].vout[0].nValue) / COIN}
Copy link

Choose a reason for hiding this comment

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

Decimal(parent_tx["tx"].vout[0].nValue) / COIN

Is there not a satToBtc() already? WDYT about introducing one? I've noticed this conversion in this diff thrice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Happy to modify if there is need to retouch, else this is a good idea for a follow-up @rkrux

Copy link
Member

Choose a reason for hiding this comment

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

yeah, would recommend this be a separate PR, since there are likely multiple instances in other tests where we could replace the satToBtc conversion with a utility function in the test framework.

Copy link

Choose a reason for hiding this comment

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

Yeah, I can pick this up in a separate PR.

Copy link
Member

@josibake josibake left a comment

Choose a reason for hiding this comment

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

Looks good, although unclear to me why we don't also remove the child transaction from fee estimation to avoid overestimating. Would recommend also removing the children, or at least documenting why it doesn't matter in a comment.

@@ -662,6 +662,22 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const Remo
return true;
}

void CBlockPolicyEstimator::removeParentTxs(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block)
Copy link
Member

Choose a reason for hiding this comment

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

in a6e8133 ("ignore all transaction with in block child"):

Shouldn't we also remove the child? Otherwise we will overestimate fees, unless I'm missing something? Example:

  • Parent pays 1 sat/vb
  • 10 sat/vb per tx is required to be competitive for the next block
  • Child pays 20 sat/vb to try and get them + parent to 10 sat/vb
  • Now in fee estimation, we see 20 sat/vb for a child that could have only paid 10 sat/vb

Copy link
Member

@glozow glozow May 16, 2024

Choose a reason for hiding this comment

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

although unclear to me why we don't also remove the child transaction from fee estimation to avoid overestimating

A child is already ignored, new mempool txns with unconfirmed parents are never added

bitcoin/src/validation.cpp

Lines 1274 to 1279 in dd42a5d

const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(),
args.m_bypass_limits, args.m_package_submission,
IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx));
m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence());

bitcoin/src/policy/fees.cpp

Lines 610 to 615 in dd42a5d

// This transaction should only count for fee estimation if:
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
// - the node is not behind
// - the transaction is not dependent on any other transactions in the mempool
// - it's not part of a package.
const bool validForFeeEstimation = !tx.m_mempool_limit_bypassed && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for your review, Josie. I think you are missing something.

When the child arrives, it will have a mempool parent, and we currently do not consider all transactions with mempool parent for fee estimation.

So when processing a transaction in processBlockTx all transactions with mempool parent, will be missing and , and we will skip them.

Child transactions whose parents aren't in our mempool will also be missing, and we will skip them because orphan transactions aren't added to the mempool and hence won't be tracked.
They are skipped here:

if (!_removeTx(tx.info.m_tx->GetHash(), true)) {

See the discussion with @rkrux:
#30079 (comment)

I recently made a delving on how fix this limitation and track chunks post cluster mempool.
Package-aware fee estimator post

Copy link
Member

Choose a reason for hiding this comment

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

ah, my bad. I had even read that comment that @glozow linked to earlier but it didn't click for me that by the time we get to the block we've already processed transactions and excluded the child transaction. I think a comment explaining what you just explained here would help a lot, maybe at the call site of removeParentTxs?

@@ -303,6 +303,9 @@ class CBlockPolicyEstimator : public CValidationInterface
/** Process a transaction confirmed in a block*/
bool processBlockTx(unsigned int nBlockHeight, const RemovedMempoolTransactionInfo& tx) EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator);

/* Remove transactions with child from fee estimation tracking stats */
Copy link
Member

Choose a reason for hiding this comment

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

in a6e8133 ("ignore all transaction with in block child"):

nit: /** comment */ to match the styling of the surrounding comments. Might also be worth mention this is only for in-block children (CPFP).

@@ -383,6 +401,79 @@ def test_acceptstalefeeestimates_option(self):
assert_equal(self.nodes[0].estimatesmartfee(1)["feerate"], fee_rate)


def send_and_mine_child_tx(self, broadcaster, miner, parent_tx, feerate):
u = {"txid": parent_tx["txid"], "vout": 0, "value": Decimal(parent_tx["tx"].vout[0].nValue) / COIN}
Copy link
Member

Choose a reason for hiding this comment

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

yeah, would recommend this be a separate PR, since there are likely multiple instances in other tests where we could replace the satToBtc conversion with a utility function in the test framework.

@DrahtBot DrahtBot requested a review from josibake May 16, 2024 10:02
@laanwj laanwj added the Mempool label May 16, 2024
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

8 participants