-
Notifications
You must be signed in to change notification settings - Fork 108
Add base_meterBundle RPC for transaction bundle metering #142
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
f4d7835
Add base_meterBundle RPC for transaction bundle metering
niran a9574f0
Set up tests for metering
niran 4592da2
Add RPC integration tests for metering
niran b33e325
Avoid including overhead outside of the EVM in execution times
niran 0fdc2d5
Integrate tips-core Bundle types for metering RPC
niran ad00fa3
Add --enable-metering flag to control metering RPC
niran 406fbf0
Use MeterBundleResponse and TransactionResult from tips-core
niran 3deb676
Use latest block state for metering instead of bundle.block_number
niran c3b940c
Fix formatting issues in metering crate
niran File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| [package] | ||
| name = "base-reth-metering" | ||
| version.workspace = true | ||
| edition.workspace = true | ||
| rust-version.workspace = true | ||
| license.workspace = true | ||
| homepage.workspace = true | ||
| repository.workspace = true | ||
| description = "Transaction Metering RPC Support" | ||
|
|
||
| [lints] | ||
| workspace = true | ||
|
|
||
| [dependencies] | ||
| # base/tips | ||
| tips-core.workspace = true | ||
|
|
||
| # reth | ||
| reth.workspace = true | ||
| reth-provider.workspace = true | ||
| reth-primitives.workspace = true | ||
| reth-primitives-traits.workspace = true | ||
| reth-evm.workspace = true | ||
| reth-optimism-evm.workspace = true | ||
| reth-optimism-chainspec.workspace = true | ||
| reth-optimism-primitives.workspace = true | ||
| reth-transaction-pool.workspace = true | ||
| reth-optimism-cli.workspace = true # Enables serde & codec traits for OpReceipt/OpTxEnvelope | ||
|
|
||
| # alloy | ||
| alloy-primitives.workspace = true | ||
| alloy-consensus.workspace = true | ||
| alloy-eips.workspace = true | ||
|
|
||
| # op-alloy | ||
| op-alloy-consensus.workspace = true | ||
|
|
||
| # revm | ||
| revm.workspace = true | ||
|
|
||
| # rpc | ||
| jsonrpsee.workspace = true | ||
|
|
||
| # misc | ||
| tracing.workspace = true | ||
| serde.workspace = true | ||
| eyre.workspace = true | ||
|
|
||
| [dev-dependencies] | ||
| alloy-genesis.workspace = true | ||
| alloy-rpc-client.workspace = true | ||
| rand.workspace = true | ||
| reth-db = { workspace = true, features = ["test-utils"] } | ||
| reth-db-common.workspace = true | ||
| reth-e2e-test-utils.workspace = true | ||
| reth-optimism-node.workspace = true | ||
| reth-testing-utils.workspace = true | ||
| reth-tracing.workspace = true | ||
| reth-transaction-pool = { workspace = true, features = ["test-utils"] } | ||
| serde_json.workspace = true | ||
| tokio.workspace = true | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Transaction Metering RPC | ||
|
|
||
| RPC endpoints for simulating and metering transaction bundles on Optimism. | ||
|
|
||
| ## `base_meterBundle` | ||
|
|
||
| Simulates a bundle of transactions, providing gas usage and execution time metrics. The response format is derived from `eth_callBundle`, but the request uses the [TIPS Bundle format](https://github.com/base/tips) to support TIPS's additional bundle features. | ||
|
|
||
| **Parameters:** | ||
|
|
||
| The method accepts a Bundle object with the following fields: | ||
|
|
||
| - `txs`: Array of signed, RLP-encoded transactions (hex strings with 0x prefix) | ||
| - `block_number`: Target block number for bundle validity (note: simulation always uses the latest available block state) | ||
| - `min_timestamp` (optional): Minimum timestamp for bundle validity (also used as simulation timestamp if provided) | ||
| - `max_timestamp` (optional): Maximum timestamp for bundle validity | ||
| - `reverting_tx_hashes` (optional): Array of transaction hashes allowed to revert | ||
| - `replacement_uuid` (optional): UUID for bundle replacement | ||
| - `flashblock_number_min` (optional): Minimum flashblock number constraint | ||
| - `flashblock_number_max` (optional): Maximum flashblock number constraint | ||
| - `dropping_tx_hashes` (optional): Transaction hashes to exclude from bundle | ||
|
|
||
| **Returns:** | ||
| - `bundleGasPrice`: Average gas price | ||
| - `bundleHash`: Bundle identifier | ||
| - `coinbaseDiff`: Total gas fees paid | ||
| - `ethSentToCoinbase`: ETH sent directly to coinbase | ||
| - `gasFees`: Total gas fees | ||
| - `stateBlockNumber`: Block number used for state (always the latest available block) | ||
| - `totalGasUsed`: Total gas consumed | ||
| - `totalExecutionTimeUs`: Total execution time (μs) | ||
| - `results`: Array of per-transaction results: | ||
| - `txHash`, `fromAddress`, `toAddress`, `value` | ||
| - `gasUsed`, `gasPrice`, `gasFees`, `coinbaseDiff` | ||
| - `ethSentToCoinbase`: Always "0" currently | ||
| - `executionTimeUs`: Transaction execution time (μs) | ||
|
|
||
| **Example:** | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "id": 1, | ||
| "method": "base_meterBundle", | ||
| "params": [{ | ||
| "txs": ["0x02f8...", "0x02f9..."], | ||
| "blockNumber": 1748028, | ||
| "minTimestamp": 1234567890, | ||
| "revertingTxHashes": [] | ||
| }] | ||
| } | ||
| ``` | ||
|
|
||
| Note: While some fields like `revertingTxHashes` are part of the TIPS Bundle format, they are currently ignored during simulation. The metering focuses on gas usage and execution time measurement. | ||
|
|
||
| ## Implementation | ||
|
|
||
| - Executes transactions sequentially using Optimism EVM configuration | ||
| - Tracks microsecond-precision execution time per transaction | ||
| - Stops on first failure | ||
| - Automatically registered in `base` namespace | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| mod meter; | ||
| mod rpc; | ||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| pub use meter::meter_bundle; | ||
| pub use rpc::{MeteringApiImpl, MeteringApiServer}; | ||
| pub use tips_core::types::{Bundle, MeterBundleResponse, TransactionResult}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| use alloy_consensus::{transaction::SignerRecoverable, BlockHeader, Transaction as _}; | ||
| use alloy_primitives::{B256, U256}; | ||
| use eyre::{eyre, Result as EyreResult}; | ||
| use reth::revm::db::State; | ||
| use reth_evm::execute::BlockBuilder; | ||
| use reth_evm::ConfigureEvm; | ||
| use reth_optimism_chainspec::OpChainSpec; | ||
| use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; | ||
| use reth_primitives_traits::SealedHeader; | ||
| use std::sync::Arc; | ||
| use std::time::Instant; | ||
|
|
||
| use crate::TransactionResult; | ||
|
|
||
| const BLOCK_TIME: u64 = 2; // 2 seconds per block | ||
|
|
||
| /// Simulates and meters a bundle of transactions | ||
| /// | ||
| /// Takes a state provider, chain spec, decoded transactions, block header, and bundle metadata, | ||
| /// and executes transactions in sequence to measure gas usage and execution time. | ||
| /// | ||
| /// Returns a tuple of: | ||
| /// - Vector of transaction results | ||
| /// - Total gas used | ||
| /// - Total gas fees paid | ||
| /// - Bundle hash | ||
| /// - Total execution time in microseconds | ||
| pub fn meter_bundle<SP>( | ||
| state_provider: SP, | ||
| chain_spec: Arc<OpChainSpec>, | ||
| decoded_txs: Vec<op_alloy_consensus::OpTxEnvelope>, | ||
| header: &SealedHeader, | ||
| bundle_with_metadata: &tips_core::types::BundleWithMetadata, | ||
| ) -> EyreResult<(Vec<TransactionResult>, u64, U256, B256, u128)> | ||
| where | ||
| SP: reth_provider::StateProvider, | ||
| { | ||
| // Get bundle hash from BundleWithMetadata | ||
| let bundle_hash = bundle_with_metadata.bundle_hash(); | ||
|
|
||
| // Create state database | ||
| let state_db = reth::revm::database::StateProviderDatabase::new(state_provider); | ||
| let mut db = State::builder() | ||
| .with_database(state_db) | ||
| .with_bundle_update() | ||
| .build(); | ||
|
|
||
| // Set up next block attributes | ||
| // Use bundle.min_timestamp if provided, otherwise use header timestamp + BLOCK_TIME | ||
| let timestamp = bundle_with_metadata | ||
| .bundle() | ||
| .min_timestamp | ||
| .unwrap_or_else(|| header.timestamp() + BLOCK_TIME); | ||
| let attributes = OpNextBlockEnvAttributes { | ||
| timestamp, | ||
| suggested_fee_recipient: header.beneficiary(), | ||
| prev_randao: header.mix_hash().unwrap_or(B256::random()), | ||
| gas_limit: header.gas_limit(), | ||
| parent_beacon_block_root: header.parent_beacon_block_root(), | ||
| extra_data: header.extra_data().clone(), | ||
| }; | ||
|
|
||
| // Execute transactions | ||
| let mut results = Vec::new(); | ||
| let mut total_gas_used = 0u64; | ||
| let mut total_gas_fees = U256::ZERO; | ||
|
|
||
| let execution_start = Instant::now(); | ||
| { | ||
| let evm_config = OpEvmConfig::optimism(chain_spec); | ||
| let mut builder = evm_config.builder_for_next_block(&mut db, header, attributes)?; | ||
|
|
||
| builder.apply_pre_execution_changes()?; | ||
|
|
||
| for tx in decoded_txs { | ||
| let tx_start = Instant::now(); | ||
| let tx_hash = tx.tx_hash(); | ||
| let from = tx.recover_signer()?; | ||
| let to = tx.to(); | ||
| let value = tx.value(); | ||
| let gas_price = tx.max_fee_per_gas(); | ||
|
|
||
| let recovered_tx = | ||
| alloy_consensus::transaction::Recovered::new_unchecked(tx.clone(), from); | ||
|
|
||
| let gas_used = builder | ||
| .execute_transaction(recovered_tx) | ||
| .map_err(|e| eyre!("Transaction {} execution failed: {}", tx_hash, e))?; | ||
|
|
||
| let gas_fees = U256::from(gas_used) * U256::from(gas_price); | ||
| total_gas_used = total_gas_used.saturating_add(gas_used); | ||
| total_gas_fees = total_gas_fees.saturating_add(gas_fees); | ||
|
|
||
| let execution_time = tx_start.elapsed().as_micros(); | ||
|
|
||
| results.push(TransactionResult { | ||
| coinbase_diff: gas_fees.to_string(), | ||
| eth_sent_to_coinbase: "0".to_string(), | ||
| from_address: from, | ||
| gas_fees: gas_fees.to_string(), | ||
| gas_price: gas_price.to_string(), | ||
| gas_used, | ||
| to_address: to, | ||
| tx_hash, | ||
| value: value.to_string(), | ||
| execution_time_us: execution_time, | ||
| }); | ||
| } | ||
| } | ||
| let total_execution_time = execution_start.elapsed().as_micros(); | ||
|
|
||
| Ok(( | ||
| results, | ||
| total_gas_used, | ||
| total_gas_fees, | ||
| bundle_hash, | ||
| total_execution_time, | ||
| )) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.