feat(l1): enforce minimum priority-fee floor at mempool admission#6604
feat(l1): enforce minimum priority-fee floor at mempool admission#6604ilitteri wants to merge 10 commits into
Conversation
Promotes MIN_GAS_TIP (1 Mwei, defined in crates/common/types/constants.rs)
from an RPC-estimator-only constant into a configurable admission gate
on the mempool. Zero-tip transactions, which today silently sit in the
pool until block-build time, are now rejected at ingress.
- `BlockchainOptions` gains `min_tip_wei: u64` (default
`MIN_GAS_TIP = 1_000_000`) plus a `DEFAULT_MIN_TIP_WEI` constant.
- `Blockchain::validate_transaction` computes the effective tip
(`max_priority_fee_per_gas` for typed txs, `gas_price - base_fee`
for legacy) and rejects with `MempoolError::TipBelowMinimum
{ actual, limit }` when below the configured floor. A floor of 0
disables the check, matching the same off-switch path as devnet
configurations and `eth_sendRawTransaction` smoke tests.
- New CLI flag `--mempool.min-tip` (env `ETHREX_MEMPOOL_MIN_TIP`),
plumbed through both L1 and L2 initializers.
- Updates the `MIN_GAS_TIP` doc comment to reflect its dual role
(admission floor + RPC estimator floor), replacing the prior
"NOT a mempool admission gate" wording.
- `Blockchain::default_with_store` (the test helper used across the
blockchain, rpc, and ethrex-test crates) now explicitly sets
`min_tip_wei = 0` so unrelated tests stay decoupled from this rule.
- Five integration tests in `test/tests/blockchain/mempool_tests.rs`:
zero-tip 1559 rejected at default, at-floor 1559 passes the tip
check, floor of zero admits zero-tip, legacy effective tip below
floor rejected, and `BlockchainOptions::min_tip_wei` is correctly
consulted in `validate_transaction`.
🤖 Kimi Code ReviewThe PR introduces a configurable mempool minimum tip floor with correct implementation and comprehensive test coverage. No critical issues found. Minor observations:
Security check: The admission check correctly handles:
The validation ordering (after fee cap check, before intrinsic gas) is correct. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewNow I have enough context to write a thorough review. PR #6604 —
|
Lines of code reportTotal lines added: Detailed view |
There was a problem hiding this comment.
Pull request overview
This PR promotes the “minimum tip” concept into a configurable mempool admission rule by adding a min_tip_wei floor to BlockchainOptions and enforcing it in Blockchain::validate_transaction, with CLI plumbing and new integration tests to validate admission behavior.
Changes:
- Add
BlockchainOptions::min_tip_weiwith a new defaultDEFAULT_MIN_TIP_WEI = 1and wire it through L1/L2 initializers and CLI (--mempool.min-tip/ETHREX_MEMPOOL_MIN_TIP). - Enforce the configured minimum effective tip during
Blockchain::validate_transaction, returningMempoolError::TipBelowMinimum { actual, limit }on rejection. - Add integration tests covering zero-tip and under-floor tx admission behavior, and update docs/comments to clarify
MIN_GAS_TIPscope.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/tests/blockchain/mempool_tests.rs | Adds integration tests and helpers for min-tip admission floor behavior. |
| docs/CLI.md | Documents the new --mempool.min-tip CLI option and default. |
| crates/common/types/constants.rs | Updates MIN_GAS_TIP docs to clarify it’s for RPC estimators, not mempool admission. |
| crates/blockchain/error.rs | Introduces MempoolError::TipBelowMinimum { actual, limit }. |
| crates/blockchain/blockchain.rs | Adds min_tip_wei option, a default constant, and enforces the floor in validate_transaction; adjusts test helper constructor. |
| cmd/ethrex/l2/initializers.rs | Plumbs CLI min-tip option into L2 BlockchainOptions. |
| cmd/ethrex/initializers.rs | Plumbs CLI min-tip option into L1 BlockchainOptions. |
| cmd/ethrex/cli.rs | Adds the new CLI flag/env var and defaults for mempool_min_tip. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Admission-time minimum tip floor. For typed (1559-family) txs, the | ||
| // effective tip is `max_priority_fee_per_gas`. For legacy / EIP-2930 | ||
| // it's `gas_price.saturating_sub(base_fee)`. A floor of 0 disables | ||
| // the check. Saturating arithmetic guards against pre-London headers | ||
| // where `base_fee_per_gas` is `None`. | ||
| if self.options.min_tip_wei > 0 { | ||
| let effective_tip = match tx.max_priority_fee() { | ||
| Some(tip) => tip, | ||
| None => { | ||
| let base_fee = header.base_fee_per_gas.unwrap_or(0); | ||
| let gas_price_u64 = u64::try_from(tx.gas_price()).unwrap_or(u64::MAX); | ||
| gas_price_u64.saturating_sub(base_fee) | ||
| } | ||
| }; |
| [default: 10000] | ||
|
|
||
| --mempool.min-tip <MIN_TIP_WEI> | ||
| Minimum effective priority fee (in wei) required for a transaction to be admitted into the mempool. For typed transactions this is `max_priority_fee_per_gas`; for legacy transactions it is `gas_price - base_fee`. Set to 0 to disable the floor. |
| #[arg( | ||
| help = "Minimum effective priority fee (in wei) required for a transaction to be admitted into the mempool. For typed transactions this is `max_priority_fee_per_gas`; for legacy transactions it is `gas_price - base_fee`. Set to 0 to disable the floor.", | ||
| long = "mempool.min-tip", | ||
| default_value_t = DEFAULT_MIN_TIP_WEI, | ||
| value_name = "MIN_TIP_WEI", | ||
| help_heading = "Node options", | ||
| env = "ETHREX_MEMPOOL_MIN_TIP" | ||
| )] |
| assert!(!matches!(res, Err(MempoolError::TipBelowMinimum { .. }))); | ||
| } | ||
|
|
||
| #[tokio::test] |
Greptile SummaryThis PR adds a configurable minimum effective-tip floor to the mempool admission path, enforcing
Confidence Score: 4/5Safe to merge; the admission gate is well-placed, the per-transaction-type tip computation is correct, and PrivilegedL2Transaction correctly bypasses the check via the existing early return. The core logic is correct across all transaction types. Two minor points: the DEFAULT_MIN_TIP_WEI constant is placed after the impl Default block that references it (style), and the two negative-assertion tests pass even when an unrelated error fires, which could let a silent tip-check bypass go undetected. The negative-assertion tests in test/tests/blockchain/mempool_tests.rs are worth a second look to ensure they actually catch a tip-check regression.
|
| Filename | Overview |
|---|---|
| crates/blockchain/blockchain.rs | Adds min_tip_wei to BlockchainOptions, computes effective tip correctly per transaction type in validate_transaction, and sets min_tip_wei = 0 in default_with_store to decouple existing tests. |
| crates/blockchain/error.rs | Adds TipBelowMinimum { actual: u64, limit: u64 } variant to MempoolError with a clear human-readable message. |
| test/tests/blockchain/mempool_tests.rs | Five new tests cover zero-tip rejection, at-floor acceptance, floor=0 bypass, legacy tip calculation, and option-plumbing; negative-assertion tests pass even on unrelated errors, which is intentional but slightly masks regressions. |
| cmd/ethrex/cli.rs | Adds mempool_min_tip: u64 CLI arg with --mempool.min-tip / ETHREX_MEMPOOL_MIN_TIP, defaulting to DEFAULT_MIN_TIP_WEI. |
| cmd/ethrex/initializers.rs | Plumbs opts.mempool_min_tip into BlockchainOptions::min_tip_wei for the L1 path. |
| cmd/ethrex/l2/initializers.rs | Plumbs opts.node_opts.mempool_min_tip into BlockchainOptions::min_tip_wei for the L2 path. |
| crates/common/types/constants.rs | Updates doc comment on MIN_GAS_TIP to clarify its scope is only the RPC gas-price estimator, pointing to DEFAULT_MIN_TIP_WEI for the mempool concern. |
| docs/CLI.md | Documents the new --mempool.min-tip option with its default value and env var. |
Sequence Diagram
sequenceDiagram
participant Caller as RPC / P2P
participant BC as Blockchain::validate_transaction
participant Opts as BlockchainOptions
Caller->>BC: send raw transaction
BC->>BC: early-return if PrivilegedL2Transaction
BC->>BC: check init-code size, data size, gas limits
BC->>BC: "check max_priority_fee <= max_fee_per_gas"
BC->>Opts: read min_tip_wei
alt "min_tip_wei > 0"
BC->>BC: compute effective_tip
alt "effective_tip < min_tip_wei"
BC-->>Caller: Err(TipBelowMinimum)
else "effective_tip >= min_tip_wei"
BC->>BC: continue remaining checks
BC-->>Caller: Ok(Option)
end
else "min_tip_wei == 0"
BC->>BC: skip tip floor check
BC-->>Caller: Ok(Option)
end
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
crates/blockchain/blockchain.rs:240-256
The `DEFAULT_MIN_TIP_WEI` constant is declared after the `impl Default for BlockchainOptions` block that already references it. Rust resolves this at compile time without issue, but by convention constants should be defined before the types and impls that use them. Moving it above `BlockchainOptions` improves readability.
```suggestion
/// Default min-tip floor (wei). Matches geth's mempool `PriceLimit = 1 wei`.
/// Effectively just rejects zero-tip transactions at admission.
pub const DEFAULT_MIN_TIP_WEI: u64 = 1;
impl Default for BlockchainOptions {
fn default() -> Self {
Self {
max_mempool_size: MAX_MEMPOOL_SIZE_DEFAULT,
perf_logs_enabled: false,
r#type: BlockchainType::default(),
max_blobs_per_block: None,
precompute_witnesses: false,
precompile_cache_enabled: true,
min_tip_wei: DEFAULT_MIN_TIP_WEI,
}
}
}
```
### Issue 2 of 2
test/tests/blockchain/mempool_tests.rs:504-511
**Negative assertions mask unrelated validation failures**
`at_floor_eip1559_passes_tip_check` and `floor_of_zero_admits_zero_tip` both use `assert!(!matches!(res, Err(MempoolError::TipBelowMinimum { .. })))`. This passes when `validate_transaction` returns `Ok(_)` OR when it returns any other `Err` variant. If a future refactor reorders checks and the tip guard is accidentally skipped while another error fires, these tests would still pass, providing false confidence. The real fix is to assert `Ok(_)` or to explicitly document that a different error is acceptable here.
Reviews (1): Last reviewed commit: "feat(l1): use DEFAULT_MIN_TIP_WEI consta..." | Re-trigger Greptile
| @@ -242,10 +246,15 @@ impl Default for BlockchainOptions { | |||
| max_blobs_per_block: None, | |||
| precompute_witnesses: false, | |||
| precompile_cache_enabled: true, | |||
| min_tip_wei: DEFAULT_MIN_TIP_WEI, | |||
| } | |||
| } | |||
| } | |||
|
|
|||
| /// Default min-tip floor (wei). Matches geth's mempool `PriceLimit = 1 wei`. | |||
| /// Effectively just rejects zero-tip transactions at admission. | |||
| pub const DEFAULT_MIN_TIP_WEI: u64 = 1; | |||
There was a problem hiding this comment.
The
DEFAULT_MIN_TIP_WEI constant is declared after the impl Default for BlockchainOptions block that already references it. Rust resolves this at compile time without issue, but by convention constants should be defined before the types and impls that use them. Moving it above BlockchainOptions improves readability.
| /// Default min-tip floor (wei). Matches geth's mempool `PriceLimit = 1 wei`. | |
| /// Effectively just rejects zero-tip transactions at admission. | |
| pub const DEFAULT_MIN_TIP_WEI: u64 = 1; | |
| impl Default for BlockchainOptions { | |
| fn default() -> Self { | |
| Self { | |
| max_mempool_size: MAX_MEMPOOL_SIZE_DEFAULT, | |
| perf_logs_enabled: false, | |
| r#type: BlockchainType::default(), | |
| max_blobs_per_block: None, | |
| precompute_witnesses: false, | |
| precompile_cache_enabled: true, | |
| min_tip_wei: DEFAULT_MIN_TIP_WEI, | |
| } | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/blockchain/blockchain.rs
Line: 240-256
Comment:
The `DEFAULT_MIN_TIP_WEI` constant is declared after the `impl Default for BlockchainOptions` block that already references it. Rust resolves this at compile time without issue, but by convention constants should be defined before the types and impls that use them. Moving it above `BlockchainOptions` improves readability.
```suggestion
/// Default min-tip floor (wei). Matches geth's mempool `PriceLimit = 1 wei`.
/// Effectively just rejects zero-tip transactions at admission.
pub const DEFAULT_MIN_TIP_WEI: u64 = 1;
impl Default for BlockchainOptions {
fn default() -> Self {
Self {
max_mempool_size: MAX_MEMPOOL_SIZE_DEFAULT,
perf_logs_enabled: false,
r#type: BlockchainType::default(),
max_blobs_per_block: None,
precompute_witnesses: false,
precompile_cache_enabled: true,
min_tip_wei: DEFAULT_MIN_TIP_WEI,
}
}
}
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| Err(MempoolError::TipBelowMinimum { | ||
| actual: 0, | ||
| limit: 1_000_000, | ||
| }), | ||
| )); | ||
| } | ||
|
|
||
| #[tokio::test] |
There was a problem hiding this comment.
Negative assertions mask unrelated validation failures
at_floor_eip1559_passes_tip_check and floor_of_zero_admits_zero_tip both use assert!(!matches!(res, Err(MempoolError::TipBelowMinimum { .. }))). This passes when validate_transaction returns Ok(_) OR when it returns any other Err variant. If a future refactor reorders checks and the tip guard is accidentally skipped while another error fires, these tests would still pass, providing false confidence. The real fix is to assert Ok(_) or to explicitly document that a different error is acceptable here.
Prompt To Fix With AI
This is a comment left during a code review.
Path: test/tests/blockchain/mempool_tests.rs
Line: 504-511
Comment:
**Negative assertions mask unrelated validation failures**
`at_floor_eip1559_passes_tip_check` and `floor_of_zero_admits_zero_tip` both use `assert!(!matches!(res, Err(MempoolError::TipBelowMinimum { .. })))`. This passes when `validate_transaction` returns `Ok(_)` OR when it returns any other `Err` variant. If a future refactor reorders checks and the tip guard is accidentally skipped while another error fires, these tests would still pass, providing false confidence. The real fix is to assert `Ok(_)` or to explicitly document that a different error is acceptable here.
How can I resolve this? If you propose a fix, please make it concise.
🤖 Codex Code ReviewFindings
No other independent correctness/security issues stood out in the option plumbing. I couldn’t run Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Replaces the inline match-with-fallback that computed the effective tip differently for typed (raw max_priority_fee_per_gas) vs legacy (gas_price - base_fee) transactions. The previous code was asymmetric: legacy subtracted base_fee while typed did not, so a typed tx with max_priority_fee = 5 gwei but max_fee = base_fee + 0 (i.e. effective inclusion tip of 0) would pass the floor. Defers to `Transaction::effective_gas_tip(base_fee)` (already in the codebase at `crates/common/types/transaction.rs:1504`), which implements the EIP-1559 definition `min(max_priority_fee_per_gas, max_fee_per_gas - base_fee)` for every transaction type. `None` from the helper means the tx cannot pay for inclusion at the current base fee; treat that as effective tip = 0 for the floor comparison. Tests: - `shipped_default_floor_rejects_zero_tip_admits_one` pins the actual shipped default (`DEFAULT_MIN_TIP_WEI = 1`, matching geth's `PriceLimit`). Previous suite only exercised manually-set floors (1 Mwei / 0 / 5 gwei). - `blob_tx_under_floor_rejected` confirms the EIP-4844 path also goes through the helper.
Cross-client audit found that geth, reth, and erigon all apply their min-tip floor (PriceLimit / minimum_priority_fee) to the **raw** priority fee cap — `tx.GasTipCap()` in geth, which returns `max_priority_fee_per_gas` for typed txs and `gas_price` for legacy. Only nethermind's `MinGasPriceTxFilter` (which lives in the block producer, not txpool admission) uses an effective-tip computation against the current base fee. The previous implementation used `Transaction::effective_gas_tip(base_fee)` = `min(max_priority_fee_per_gas, max_fee_per_gas - base_fee)`. With a non-trivial floor (e.g. 1 gwei), this would reject otherwise-valid txs at ingress as base fee oscillates upward and `fee_cap - base_fee` falls below the floor. With the 1-wei shipped default this difference is academic, but the semantics should match peers so operators tuning the flag don't see surprising base-fee-dependent rejections. Switch to `tx.gas_tip_cap()` — already U256, already returns gas_price for legacy via Transaction's existing accessor. The check is now independent of the current base fee. Tests: - All existing assertions still hold (every test built the tx with base_fee = None on the header, so effective_gas_tip already returned gas_tip_cap unchanged). - Renamed `legacy_effective_tip_below_floor_rejected` → `legacy_gas_price_below_floor_rejected` to reflect the actual measured quantity. - Updated comments on the blob and legacy test cases.
After the cross-client fix switched the floor from effective tip (`min(tip_cap, fee_cap - base_fee)`) to raw `gas_tip_cap()`, the help text in cli.rs and docs/CLI.md still described the old semantics, and the error message still said "Effective tip". Updated all three to match: the floor is now compared against `max_priority_fee_per_gas` for typed txs and `gas_price` for legacy, independent of current base fee. Also reworded the error variant to "Tip cap … below the configured minimum". Addresses @copilot's review comments on cli.rs:193 and docs/CLI.md:95.
…ses it @greptile: by convention constants should be defined before the types and impls that reference them. Moved `DEFAULT_MIN_TIP_WEI` above `BlockchainOptions` so the reading order matches the dependency order. No behavior change.
@greptile (P2): the three min-tip tests using `assert!(!matches!(res,
Err(TipBelowMinimum {..})))` accidentally pass on ANY non-tip error
(NotEnoughBalance, StoreError, etc.), so a future refactor that
skips the tip check entirely would not be caught.
Replaced with positive assertions: at the floor / floor-of-zero / 1-wei
default, the tip check must pass and the next-stage account-lookup
error (`NotEnoughBalance` or `StoreError` when state isn't seeded)
must fire. If the tip check is accidentally skipped, the assertion
still requires a specific downstream error — that's the regression
guard greptile asked for.
Phase 2 review (greptile + Copilot) flagged three related issues: - `_locals_exempt` was a dead binding silenced by an underscore prefix; if PR #6604 forgot to remove the underscore when wiring the gate, the exemption would silently never fire. - `--mempool.nolocals` CLI flag and `docs/CLI.md` entry implied the flag changes behavior, but with no origin-gated rule on main it's a no-op — an operator-visible knob that does nothing. - `BlockchainOptions::nolocals` field had no consumers. Strip the scaffold: keep the `TxOrigin` enum and the `origin` parameter threading through `validate_transaction` (those are real plumbing, not dead code), but drop the dead variable, the unused option, the no-op CLI flag, and the two docs entries. The `TODO(#6604)` comment in `validate_transaction` records exactly how the exemption should be wired (and that the flag should land in the same PR).
…floor # Conflicts: # crates/blockchain/error.rs
|
| EIP | Bucket | Count |
|---|---|---|
| EIP-7702 | set_code_txs |
24 |
| EIP-7702 | set_code_txs_2 |
15 |
| EIP-7702 | gas |
1 |
| EIP-8037 | state_gas_set_code |
17 |
| EIP-8037 | state_gas_pricing |
1 |
| EIP-8037 | state_gas_sstore |
1 |
| EIP-7928 | block_access_lists_eip7702 |
8 |
| EIP-7928 | block_access_lists |
1 |
| EIP-7778 | gas_accounting |
3 |
| EIP-7708 | transfer_logs |
1 |
| EIP-7976 | refunds |
1 |
| EIP-1344 | chainid (Amsterdam fork-transition fixture) |
1 |
| Total | 74 |
Re-enable once we either:
- (a) bump fixtures to a snobal-devnet-7 release that locks in the new
accounting; or - (b) revert the bal-devnet-7-prep subtraction for bal-devnet-6
compatibility.
Full test list (74)
EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/
delegation_clearingdelegation_clearing_and_setdelegation_clearing_failing_txdelegation_clearing_tx_toeoa_tx_after_set_codeext_code_on_chain_delegating_set_codeext_code_on_self_delegating_set_codeext_code_on_self_set_codeext_code_on_set_codemany_delegationsnonce_overflow_after_first_authorizationnonce_validityreset_codeself_code_on_set_codeself_sponsored_set_codeset_code_multiple_valid_authorization_tuples_same_signer_increasing_nonceset_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce_self_sponsoredset_code_to_logset_code_to_non_empty_storage_non_zero_nonceset_code_to_self_destructset_code_to_self_destructing_account_deployed_in_same_txset_code_to_sstoreset_code_to_sstore_then_sloadset_code_to_system_contract
EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/
call_pointer_to_created_from_create_after_oog_call_againcall_to_precompile_in_pointer_contextcontract_storage_to_pointer_with_storagedelegation_replacement_call_previous_contractdouble_authpointer_measurementspointer_normalpointer_reentrypointer_resets_an_empty_code_account_with_storagepointer_revertspointer_to_pointerpointer_to_precompilepointer_to_staticpointer_to_static_reentrystatic_to_pointer
EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/gas/
account_warming
EIP-8037 — for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/
auth_refund_block_gas_accountingauth_refund_bypasses_one_fifth_capauth_with_calldata_and_access_listauth_with_multiple_sstoresauthorization_exact_state_gas_boundaryauthorization_to_precompile_addressauthorization_with_sstoreduplicate_signer_authorizationsexisting_account_auth_header_gas_used_uses_worst_caseexisting_account_refundexisting_account_refund_enables_sstoreexisting_auth_with_reverted_execution_preserves_intrinsicmany_authorizations_state_gasmixed_auths_header_gas_used_uses_worst_casemixed_new_and_existing_authsmixed_valid_and_invalid_authsmulti_tx_block_auth_refund_and_sstore
EIP-8037 — state_gas_pricing/
auth_state_gas_scales_with_cpsb
EIP-8037 — state_gas_sstore/
sstore_state_gas_all_tx_types
EIP-7928 — for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/
bal_7702_delegation_clearbal_7702_delegation_createbal_7702_delegation_updatebal_7702_double_auth_resetbal_7702_double_auth_swapbal_7702_null_address_delegation_no_code_changebal_selfdestruct_to_7702_delegationbal_withdrawal_to_7702_delegation
EIP-7928 — block_access_lists/
bal_all_transaction_types
EIP-7778 — for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/
multiple_refund_types_in_one_txsimple_gas_accountingvarying_calldata_costs
EIP-7708 — for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/
transfer_with_all_tx_types
EIP-7976 — for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/refunds/
gas_refunds_from_data_floor
EIP-1344 — for_amsterdam/istanbul/eip1344_chainid/chainid/
chainid(Amsterdam fork-transition fixture)
The reorgs simulator generates EIP-1559 transactions with max_priority_fee_per_gas = 0, which would be rejected by the new mempool min-tip floor (default 1 wei). Pass --mempool.min-tip=0 to the spawned ethrex so reorg tests continue to exercise low-fee tx flows independent of the admission floor.
Motivation
MIN_GAS_TIP = 1_000_000(1 Mwei) has lived incrates/common/types/constants.rssince before PR #6509, where it was explicitly documented as not a mempool admission gate — only the RPC gas-price estimator consults it. Today, zero-tip transactions enter the mempool and only get filtered out at block-build time. This change promotes the concept into a configurable admission gate so under-floor transactions are rejected at ingress.Description
BlockchainOptionsgainsmin_tip_wei: u64plus a newDEFAULT_MIN_TIP_WEI = 1constant. The default matches geth's mempoolPriceLimit = 1 wei— effectively just rejects zero-tip transactions while not interfering with any realistic real-world tip.Blockchain::validate_transactioncompares the raw gas tip cap (tx.gas_tip_cap()— returnsmax_priority_fee_per_gasfor typed txs,gas_pricefor legacy) againstmin_tip_wei, matching geth'sPriceLimitcheck ontx.GasTipCap(), reth's check onmax_priority_fee_per_gas, and erigon's check onTxnSlot.GetTipCap(). Under-floor txs are rejected withMempoolError::TipBelowMinimum { actual, limit }. A floor of 0 disables the check entirely.MinGasPriceTxFilteris the only peer that uses effective-tip semantics, and it lives in the block producer, not txpool admission.)--mempool.min-tip(envETHREX_MEMPOOL_MIN_TIP), plumbed through both L1 and L2 initializers.MIN_GAS_TIPkeeps its original scope as the RPC gas-price-estimator floor; the doc comment is updated to point atDEFAULT_MIN_TIP_WEIfor the mempool admission concern.Blockchain::default_with_store(the test helper used across the blockchain, rpc, and ethrex-test crates) explicitly setsmin_tip_wei = 0so unrelated tests stay decoupled from this rule.Behavioral change
Admission becomes stricter: zero-tip transactions submitted via
eth_sendRawTransactionor received over P2P are now rejected at ingress. Operators that want to admit zero-tip transactions (local devnets, internal sequencer flows) can pass--mempool.min-tip 0.Tests
Integration tests in
test/tests/blockchain/mempool_tests.rs: zero-tip 1559 rejected under a 1 Mwei floor, at-floor 1559 passes the tip check, floor of zero admits zero-tip, legacygas_pricebelow floor rejected, default floor admits 1-wei tip and rejects 0-wei, blob tx under floor rejected, options field consulted invalidate_transaction.