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

fix(node): harden transaction inclusion logic to account for fee market #1239

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

raulk
Copy link
Contributor

@raulk raulk commented Dec 19, 2024

This PR improves correctness of tx inclusion logic on two fronts.

  1. Begins rejecting messages with invalid limits at CheckTx time:
    • Gas fee cap below minimal base fee.
    • Gas limit above maximum block gas limit.

Some type shenanigans via downcasts were necessary to deal with #1241. Tested by extending a materializer test.

  1. Exclude non-includable messages at proposal time.
    • I removed the MessagesSelector trait because it was unusable. The vector holding selectors cannot used boxed objects because the trait is not object-safe as it has methods with generic parameters. Also, there's no point over-engineering this prematurely.
    • Also removed the decreasing sorting by gas limit; we prefer to randomize for security (otherwise an attacker could drip brutally overestimated transactions just below the block gas limit to DoS the chain).

@raulk raulk requested a review from a team as a code owner December 19, 2024 15:11
@raulk raulk marked this pull request as draft December 19, 2024 15:11
@raulk raulk marked this pull request as ready for review January 2, 2025 16:00
- gas fee cap below minimal base fee.
- gas limit above maximum block gas limit.
@raulk raulk force-pushed the raulk/fix/reject-below-base-fee branch from 05c6790 to cd07cec Compare January 2, 2025 16:35
@raulk raulk changed the title fix: reject block if it includes messages below the base fee. fix(node): reject block if it includes messages below the base fee. Jan 2, 2025
// It should be rejected at submission time via CheckTx (which will also reject it at block validation time).
let invalid = Eip1559TransactionRequest::new()
.to(to)
.max_fee_per_gas(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

What about .max_fee_per_gas(min_base_fee - 1)? it will also help to document how that value can be retrieved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We just don't have access to the min base fee here; nor is it available through an API. We could technically fetch the block header of the genesis block and take that base fee as the min base fee, but I'm sure it would buy us anything.

@@ -228,6 +230,14 @@ where
}
}
ChainMessage::Signed(signed) => {
// We do not need to check against the minimium base fee because the gas market
// is guaranteed to be capped at it anyway.
Copy link
Contributor

Choose a reason for hiding this comment

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

Where/how is it guaranteed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point; I think we should check the min base fee is satisfied here since CheckTx won't be invoked for transactions we never saw but that got included by another validator in a proposal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I was mistaken. This is code is correct. The current gas market conditions are determined by our own state transitions. So here we validate message's gas fee cap is above or equal to the current gas market base fee, as determined by our own state. The EIP1559 gas market actor floors the base fee to the minimal base fee here:

self.constants.minimal_base_fee.clone()

@@ -228,6 +230,14 @@ where
}
}
ChainMessage::Signed(signed) => {
// We do not need to check against the minimium base fee because the gas market
// is guaranteed to be capped at it anyway.
if signed.message.gas_fee_cap < gas_market.base_fee {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about doing that in tx selection for block inclusion (as a leader)? When checking for mempool inclusion only the min fee is verified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cryptoAtwill do we not already exclude transactions whose gas fee cap is below the network's base fee at block production time? I was strangely not able to find that logic here: https://github.com/consensus-shipyard/ipc/blob/702d49b619623915772fe935a86e59a89744c3b1/fendermint/vm/interpreter/src/selector.rs

(Do we need a second selector?)

Copy link
Contributor

Choose a reason for hiding this comment

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

@raulk I dont remember discussing that in prepare. I think we were mostly discussing the cometbft side of things.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean with "the CometBFT side of things"? We agreed that we'd assign negative priorities to non-includable transactions in CheckTX, to instruct CometBFT to prefer sending us includable transactions, but we'd still have to filter out non-includable transactions in prepare.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll fix it in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

if we need to enforce below base fee exclusion, we probably should add the other selector to enforce in fendermint just to be sure. Cometbft should have done one round of selection based on priority.

.context("inner state not downcastable to FvmExecState")?;
let gas_market = fvm_exec_state.block_gas_tracker().current_gas_market();
if msg.message.gas_limit > gas_market.block_gas_limit
|| msg.message.gas_fee_cap < gas_market.min_base_fee
Copy link
Contributor

Choose a reason for hiding this comment

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

A quick question, why using min_base_fee here? Currently min_base_fee is not updated so I assume it's updated in a system upgrade? Is it a fixed value that can be smaller than base_fee?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to reject transactions whose base fee cap is below the minimum acceptable base fee. Otherwise those transactions would sit forever in the mempool because the base fee would never go low enough to include them.

Copy link
Contributor

Choose a reason for hiding this comment

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

I misunderstood, should have no issue here.

@raulk raulk requested a review from cryptoAtwill January 3, 2025 15:14
@raulk raulk changed the title fix(node): reject block if it includes messages below the base fee. fix(node): harden transaction inclusion logic to account for fee market Jan 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Backlog
Development

Successfully merging this pull request may close these issues.

3 participants