Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions crates/consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg))]

use alloy_consensus::{BlockHeader, Transaction, transaction::TxHashRef};
use alloy_consensus::{BlockHeader, Transaction, TxReceipt, transaction::TxHashRef};
use alloy_evm::block::BlockExecutionResult;
use reth_chainspec::EthChainSpec;
use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom};
Expand All @@ -12,7 +12,7 @@ use reth_consensus_common::validation::{
validate_against_parent_gas_limit, validate_against_parent_hash_number,
};
use reth_ethereum_consensus::EthBeaconConsensus;
use reth_primitives_traits::{RecoveredBlock, SealedBlock, SealedHeader};
use reth_primitives_traits::{GotExpected, RecoveredBlock, SealedBlock, SealedHeader};
use std::sync::Arc;
use tempo_chainspec::{
hardfork::TempoHardforks,
Expand Down Expand Up @@ -203,10 +203,39 @@ impl FullConsensus<TempoPrimitives> for TempoConsensus {
result: &BlockExecutionResult<TempoReceipt>,
receipt_root_bloom: Option<ReceiptRootBloom>,
) -> Result<(), ConsensusError> {
// TIP-1016: block header gas_used tracks execution gas only, while receipt
// cumulative_gas_used tracks total gas (execution + storage creation). The
// standard Ethereum check requires strict equality, but TIP-1016 allows
// header gas_used <= last receipt cumulative_gas_used.
let cumulative_gas_used = result
.receipts
.last()
.map(|r| r.cumulative_gas_used())
.unwrap_or(0);
if block.header().gas_used() > cumulative_gas_used {
return Err(ConsensusError::BlockGasUsed {
gas: GotExpected {
got: cumulative_gas_used,
expected: block.header().gas_used(),
},
gas_spent_by_tx: reth_primitives_traits::receipt::gas_spent_by_transactions(
&result.receipts,
),
});
}

// Delegate receipt root, logs bloom, and requests hash validation to the
// inner Ethereum consensus. We construct a temporary result with gas_used
// matching the header so the inner gas check passes, while the actual
// TIP-1016 gas invariant (header <= receipts) is checked above.
let mut patched_result = result.clone();
if let Some(last) = patched_result.receipts.last_mut() {
last.cumulative_gas_used = block.header().gas_used();
}
FullConsensus::<TempoPrimitives>::validate_block_post_execution(
&self.inner,
block,
result,
&patched_result,
receipt_root_bloom,
)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ reth-revm.workspace = true
reth-primitives-traits.workspace = true
reth-rpc-eth-api = { workspace = true, optional = true }

rayon = { workspace = true, optional = true }
alloy-rlp.workspace = true
alloy-evm.workspace = true
alloy-consensus.workspace = true
alloy-primitives.workspace = true

derive_more.workspace = true
rayon = { workspace = true, optional = true }
thiserror.workspace = true
tracing.workspace = true

Expand Down
106 changes: 103 additions & 3 deletions crates/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ impl ConfigureEvm for TempoEvmConfig {
// Apply TIP-1000 gas params for T1 hardfork.
let mut cfg_env = cfg_env.with_spec_and_gas_params(spec, tempo_gas_params(spec));
cfg_env.tx_gas_limit_cap = spec.tx_gas_limit_cap();
// TIP-1016: Enable state gas tracking for T3+
cfg_env.enable_amsterdam_eip8037 = spec.is_t3();

Ok(EvmEnv {
cfg_env,
Expand Down Expand Up @@ -172,6 +174,8 @@ impl ConfigureEvm for TempoEvmConfig {
// Apply TIP-1000 gas params for T1 hardfork.
let mut cfg_env = cfg_env.with_spec_and_gas_params(spec, tempo_gas_params(spec));
cfg_env.tx_gas_limit_cap = spec.tx_gas_limit_cap();
// TIP-1016: Enable state gas tracking for T3+
cfg_env.enable_amsterdam_eip8037 = spec.is_t3();

Ok(EvmEnv {
cfg_env,
Expand Down Expand Up @@ -263,7 +267,7 @@ mod tests {
use alloy_rlp::{Encodable, bytes::BytesMut};
use reth_evm::{ConfigureEvm, NextBlockEnvAttributes};
use std::collections::HashMap;
use tempo_chainspec::hardfork::TempoHardfork;
use tempo_chainspec::{hardfork::TempoHardfork, spec::DEV};
use tempo_primitives::{
BlockBody, SubBlockMetadata, subblock::SubBlockVersion,
transaction::envelope::TEMPO_SYSTEM_TX_SIGNATURE,
Expand Down Expand Up @@ -319,8 +323,6 @@ mod tests {
/// [TIP-1000]: <https://docs.tempo.xyz/protocol/tips/tip-1000>
#[test]
fn test_evm_env_t1_gas_cap() {
use tempo_chainspec::spec::DEV;

// DEV chainspec has T1 activated at timestamp 0
let chainspec = DEV.clone();
let evm_config = TempoEvmConfig::new(chainspec.clone());
Expand Down Expand Up @@ -351,6 +353,104 @@ mod tests {
);
}

/// TIP-1016: enable_amsterdam_eip8037 must be set when T3 hardfork is active.
/// This gates the reservoir model, tx cap bypass, and state gas tracking.
#[test]
fn test_evm_env_t3_enable_amsterdam_eip8037() {
let chainspec = DEV.clone();
let evm_config = TempoEvmConfig::new(chainspec.clone());

let header = TempoHeader {
inner: alloy_consensus::Header {
number: 100,
timestamp: 1000, // After T3 activation in DEV
gas_limit: 30_000_000,
base_fee_per_gas: Some(1000),
..Default::default()
},
general_gas_limit: 10_000_000,
timestamp_millis_part: 0,
shared_gas_limit: 3_000_000,
};

// Verify we're in T3
assert!(chainspec.tempo_hardfork_at(header.timestamp()).is_t3());

let evm_env = evm_config.evm_env(&header).unwrap();

assert!(
evm_env.cfg_env.enable_amsterdam_eip8037,
"TIP-1016: enable_amsterdam_eip8037 must be true when T3 hardfork is active"
);
}

/// TIP-1016: enable_amsterdam_eip8037 must NOT be set for pre-T3 hardforks.
#[test]
fn test_evm_env_pre_t3_no_state_gas() {
let chainspec = DEV.clone();
let _evm_config = TempoEvmConfig::new(chainspec);

// Verify that a T2-only spec does NOT enable state gas
let t2_spec = TempoHardfork::T2;
let gas_params = tempo_gas_params(t2_spec);
let cfg = revm::context::CfgEnv::new_with_spec_and_gas_params(t2_spec, gas_params);

assert!(
!cfg.enable_amsterdam_eip8037,
"enable_amsterdam_eip8037 must be false for pre-T3 hardforks"
);
}

/// TIP-1016: next_evm_env must also set enable_amsterdam_eip8037 for T3.
#[test]
fn test_next_evm_env_t3_enable_amsterdam_eip8037() {
let chainspec = DEV.clone();
let evm_config = TempoEvmConfig::new(chainspec.clone());

let parent = TempoHeader {
inner: alloy_consensus::Header {
number: 99,
timestamp: 900,
gas_limit: 30_000_000,
base_fee_per_gas: Some(1000),
..Default::default()
},
general_gas_limit: 10_000_000,
timestamp_millis_part: 0,
shared_gas_limit: 3_000_000,
};

let attributes = TempoNextBlockEnvAttributes {
inner: NextBlockEnvAttributes {
timestamp: 1000, // After T3 activation
suggested_fee_recipient: Address::repeat_byte(0x02),
prev_randao: B256::repeat_byte(0x03),
gas_limit: 30_000_000,
parent_beacon_block_root: Some(B256::ZERO),
withdrawals: None,
extra_data: Default::default(),
},
general_gas_limit: 10_000_000,
shared_gas_limit: 3_000_000,
timestamp_millis_part: 0,
subblock_fee_recipients: HashMap::new(),
};

// Verify we're in T3
assert!(
chainspec
.tempo_hardfork_at(attributes.inner.timestamp)
.is_t3()
);

let evm_env = evm_config.next_evm_env(&parent, &attributes).unwrap();

assert!(
evm_env.cfg_env.enable_amsterdam_eip8037,
"TIP-1016: next_evm_env must set enable_amsterdam_eip8037 for T3"
);
}

#[test]
fn test_next_evm_env() {
let evm_config = TempoEvmConfig::new(test_chainspec());
Expand Down
1 change: 1 addition & 0 deletions crates/node/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod payment_lane;
mod pool;
mod stablecoin_dex;
mod tempo_transaction;
mod tip1016_storage_gas;
mod tip20;
mod tip20_factory;
mod tip20_gas_fees;
Expand Down
41 changes: 35 additions & 6 deletions crates/payload/builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ where
);

let mut cumulative_gas_used = 0;
let mut cumulative_state_gas_used = 0u64;
let mut non_payment_gas_used = 0;
// initial block size usage - size of withdrawals plus 1Kb of overhead for the block header
let mut block_size_used = attributes
Expand Down Expand Up @@ -392,16 +393,20 @@ where
let execution_start = Instant::now();
let _block_fill_span = debug_span!(target: "payload_builder", "block_fill").entered();
while let Some(pool_tx) = best_txs.next() {
// TIP-1016: State gas does not count toward block gas capacity, so use
// execution_gas_limit (= gas_limit - state_gas) for the block-level checks below.
let tx_execution_gas_limit = pool_tx.transaction.execution_gas_limit();

// Ensure we still have capacity for this transaction within the non-shared gas limit.
// The remaining `shared_gas_limit` is reserved for validator subblocks and must not
// be consumed by proposer's pool transactions.
if cumulative_gas_used + pool_tx.gas_limit() > non_shared_gas_limit {
if cumulative_gas_used + tx_execution_gas_limit > non_shared_gas_limit {
// Mark this transaction as invalid since it doesn't fit
// The iterator will handle lane switching internally when appropriate
best_txs.mark_invalid(
&pool_tx,
&InvalidPoolTransactionError::ExceedsGasLimit(
pool_tx.gas_limit(),
tx_execution_gas_limit,
non_shared_gas_limit - cumulative_gas_used,
),
);
Expand All @@ -413,7 +418,7 @@ where
// If the tx is not a payment and will exceed the general gas limit
// mark the tx as invalid and continue
if !pool_tx.transaction.is_payment()
&& non_payment_gas_used + pool_tx.gas_limit() > general_gas_limit
&& non_payment_gas_used + tx_execution_gas_limit > general_gas_limit
{
best_txs.mark_invalid(
&pool_tx,
Expand Down Expand Up @@ -464,7 +469,13 @@ where

let tx_with_env = pool_tx.transaction.clone().into_with_tx_env();
let tx_execution_start = Instant::now();
let gas_used = match builder.execute_transaction(tx_with_env) {
let mut tx_state_gas = 0u64;
let gas_used = match builder.execute_transaction_with_result_closure(
tx_with_env,
|result| {
tx_state_gas = result.gas().state_gas_spent();
},
) {
Ok(gas_used) => gas_used,
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
error,
Expand Down Expand Up @@ -500,6 +511,7 @@ where
// update and add to total fees
total_fees += calc_gas_balance_spending(gas_used, effective_gas_price);
cumulative_gas_used += gas_used;
cumulative_state_gas_used += tx_state_gas;
if !is_payment {
non_payment_gas_used += gas_used;
}
Expand Down Expand Up @@ -542,7 +554,12 @@ where
let mut subblock_tx_count = 0f64;

for tx in subblock.transactions_recovered() {
if let Err(err) = builder.execute_transaction(tx.cloned()) {
let mut tx_state_gas = 0u64;
if let Err(err) =
builder.execute_transaction_with_result_closure(tx.cloned(), |result| {
tx_state_gas = result.gas().state_gas_spent();
})
{
if let BlockExecutionError::Validation(BlockValidationError::InvalidTx {
..
}) = &err
Expand All @@ -560,6 +577,7 @@ where
}
}

cumulative_state_gas_used += tx_state_gas;
subblock_tx_count += 1.0;
}

Expand Down Expand Up @@ -590,9 +608,13 @@ where
let _system_txs_span =
debug_span!(target: "payload_builder", "execute_system_txs").entered();
for system_tx in system_txs {
let mut tx_state_gas = 0u64;
builder
.execute_transaction(system_tx)
.execute_transaction_with_result_closure(system_tx, |result| {
tx_state_gas = result.gas().state_gas_spent();
})
.map_err(PayloadBuilderError::evm)?;
cumulative_state_gas_used += tx_state_gas;
}
drop(_system_txs_span);
let system_txs_execution_elapsed = system_txs_execution_start.elapsed();
Expand Down Expand Up @@ -634,6 +656,12 @@ where
let gas_used = block.gas_used();
self.metrics.gas_used.record(gas_used as f64);
self.metrics.gas_used_last.set(gas_used as f64);
self.metrics
.state_gas_used
.record(cumulative_state_gas_used as f64);
self.metrics
.state_gas_used_last
.set(cumulative_state_gas_used as f64);
self.metrics
.general_gas_used_last
.set(non_payment_gas_used as f64);
Expand Down Expand Up @@ -681,6 +709,7 @@ where
timestamp = sealed_block.timestamp_millis(),
gas_limit = sealed_block.gas_limit(),
gas_used,
cumulative_state_gas_used,
extra_data = %sealed_block.extra_data(),
subblocks_count,
payment_transactions,
Expand Down
4 changes: 4 additions & 0 deletions crates/payload/builder/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pub(crate) struct TempoPayloadBuilderMetrics {
pub(crate) gas_used: Histogram,
/// Amount of gas used in the payload.
pub(crate) gas_used_last: Gauge,
/// State gas used in the payload (TIP-1016).
pub(crate) state_gas_used: Histogram,
/// State gas used in the last payload (TIP-1016).
pub(crate) state_gas_used_last: Gauge,
/// Gas used by general (non-payment) transactions in the payload.
pub(crate) general_gas_used_last: Gauge,
/// Gas used by payment transactions in the payload.
Expand Down
Loading