Skip to main content

tempo_payload_builder/
metrics.rs

1use alloy_primitives::{Address, B256, BlockNumber, Bytes, StorageKey, StorageValue};
2use metrics::Gauge;
3use reth_errors::ProviderResult;
4use reth_metrics::{Metrics, metrics::Histogram};
5use reth_primitives_traits::{Account, Bytecode};
6use reth_storage_api::{
7    AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider,
8    StateProvider, StateRootProvider, StorageRootProvider,
9};
10use reth_trie_common::{
11    AccountProof, ExecutionWitnessMode, HashedPostState, HashedStorage, MultiProof,
12    MultiProofTargets, StorageMultiProof, StorageProof, TrieInput, updates::TrieUpdates,
13};
14use std::time::Instant;
15use tracing::debug_span;
16
17#[derive(Metrics, Clone)]
18#[metrics(scope = "tempo_payload_builder")]
19pub(crate) struct TempoPayloadBuilderMetrics {
20    /// Block time in milliseconds.
21    pub(crate) block_time_millis: Histogram,
22    /// Block time in milliseconds.
23    pub(crate) block_time_millis_last: Gauge,
24    /// Number of transactions in the payload.
25    pub(crate) total_transactions: Histogram,
26    /// Number of transactions in the payload.
27    pub(crate) total_transactions_last: Gauge,
28    /// Number of payment transactions in the payload.
29    pub(crate) payment_transactions: Histogram,
30    /// Number of payment transactions in the payload.
31    pub(crate) payment_transactions_last: Gauge,
32    /// Number of pool transactions yielded by the best transactions iterator.
33    pub(crate) pool_transactions_yielded: Histogram,
34    /// Number of pool transactions yielded by the best transactions iterator for the last payload.
35    pub(crate) pool_transactions_yielded_last: Gauge,
36    /// Number of yielded pool transactions included in the payload.
37    pub(crate) pool_transactions_included: Histogram,
38    /// Number of yielded pool transactions included in the last payload.
39    pub(crate) pool_transactions_included_last: Gauge,
40    /// Number of pool transaction execution attempts rejected as invalid.
41    pub(crate) invalid_pool_transaction_execution_attempts: Histogram,
42    /// Ratio of yielded pool transactions that were included in the payload.
43    pub(crate) pool_transactions_inclusion_ratio: Histogram,
44    /// Ratio of yielded pool transactions that were included in the last payload.
45    pub(crate) pool_transactions_inclusion_ratio_last: Gauge,
46    /// Number of subblocks in the payload.
47    pub(crate) subblocks: Histogram,
48    /// Number of subblocks in the payload.
49    pub(crate) subblocks_last: Gauge,
50    /// Number of subblock transactions in the payload.
51    pub(crate) subblock_transactions: Histogram,
52    /// Number of subblock transactions in the payload.
53    pub(crate) subblock_transactions_last: Gauge,
54    /// Amount of gas used in the payload.
55    pub(crate) gas_used: Histogram,
56    /// Amount of gas used in the payload.
57    pub(crate) gas_used_last: Gauge,
58    /// State gas used in the payload (TIP-1016).
59    pub(crate) state_gas_used: Histogram,
60    /// State gas used in the last payload (TIP-1016).
61    pub(crate) state_gas_used_last: Gauge,
62    /// Gas used by general (non-payment) transactions in the payload.
63    pub(crate) general_gas_used_last: Gauge,
64    /// Gas used by payment transactions in the payload.
65    pub(crate) payment_gas_used_last: Gauge,
66    /// General lane gas limit.
67    pub(crate) general_gas_limit_last: Gauge,
68    /// Payment lane gas limit.
69    pub(crate) payment_gas_limit_last: Gauge,
70    /// Shared (subblock) gas limit.
71    pub(crate) shared_gas_limit_last: Gauge,
72    /// Time to create the pool's `BestTransactions` iterator, including lock acquisition and snapshot.
73    pub(crate) pool_fetch_duration_seconds: Histogram,
74    /// Time to acquire the state provider and initialize the state DB.
75    pub(crate) state_setup_duration_seconds: Histogram,
76    /// The time it took to prepare system transactions in seconds.
77    pub(crate) prepare_system_transactions_duration_seconds: Histogram,
78    /// Total wall-clock time spent filling the block with normal pool transactions.
79    pub(crate) total_normal_transaction_fill_duration_seconds: Histogram,
80    /// Time spent waiting for more normal transactions during block fill.
81    pub(crate) normal_transaction_fill_idle_duration_seconds: Histogram,
82    /// Total wall-clock time spent in transaction execution phases.
83    pub(crate) total_transaction_execution_duration_seconds: Histogram,
84    /// The time it took to execute subblock transactions in seconds.
85    pub(crate) total_subblock_transaction_execution_duration_seconds: Histogram,
86    /// Execution time for a single subblock.
87    pub(crate) subblock_execution_duration_seconds: Histogram,
88    /// Number of transactions in a single subblock.
89    pub(crate) subblock_transaction_count: Histogram,
90    /// The time it took to execute system transactions in seconds.
91    pub(crate) system_transactions_execution_duration_seconds: Histogram,
92    /// The time it took to finalize the payload in seconds. Includes merging transitions and calculating the state root.
93    pub(crate) payload_finalization_duration_seconds: Histogram,
94    /// Wall-clock time spent waiting for the shared sparse trie state root.
95    pub(crate) sparse_trie_state_root_wait_duration_seconds: Histogram,
96    /// Wall-clock time spent in `builder.finish()`.
97    pub(crate) builder_finish_duration_seconds: Histogram,
98    /// Total time it took to build the payload in seconds.
99    pub(crate) payload_build_duration_seconds: Histogram,
100    /// Gas per second calculated as gas_used / payload_build_duration.
101    pub(crate) gas_per_second: Histogram,
102    /// Gas per second for the last payload calculated as gas_used / payload_build_duration.
103    pub(crate) gas_per_second_last: Gauge,
104    /// Serialized payload size in bytes, including optional RLP-encoded BAL sidecar bytes.
105    pub(crate) rlp_block_size_bytes: Histogram,
106    /// Serialized payload size in bytes for the last payload.
107    pub(crate) rlp_block_size_bytes_last: Gauge,
108    /// Time to compute the hashed post-state from the bundle state.
109    pub(crate) hashed_post_state_duration_seconds: Histogram,
110    /// Time to compute the state root and trie updates via `state_root_with_updates`.
111    pub(crate) state_root_with_updates_duration_seconds: Histogram,
112}
113
114/// Reason the payload builder stopped adding pool transactions to the block.
115pub(crate) enum BlockBuildStopReason {
116    GasLimit,
117    RlpBlockSizeLimit,
118    TxPoolEmpty,
119    BuildBudget,
120}
121
122impl BlockBuildStopReason {
123    const fn as_str(&self) -> &'static str {
124        match self {
125            Self::GasLimit => "gas_limit",
126            Self::RlpBlockSizeLimit => "rlp_block_size_limit",
127            Self::TxPoolEmpty => "tx_pool_empty",
128            Self::BuildBudget => "build_budget",
129        }
130    }
131}
132
133impl TempoPayloadBuilderMetrics {
134    /// Increments the unified pool transaction skip counter with the given reason label.
135    ///
136    /// Note: `mark_invalid` may also prune descendant transactions from the iterator,
137    /// so the skip count represents skip *events*, not total transactions removed.
138    #[inline]
139    pub(crate) fn inc_pool_tx_skipped(&self, reason: &'static str) {
140        metrics::counter!("tempo_payload_builder_pool_transactions_skipped_total", "reason" => reason)
141            .increment(1);
142    }
143
144    /// Increments the build failure counter for a given reason.
145    #[inline]
146    pub(crate) fn inc_build_failure(&self, reason: &'static str) {
147        metrics::counter!("tempo_payload_builder_build_failures_total", "reason" => reason)
148            .increment(1);
149    }
150
151    /// Increments the counter for why the payload builder stopped adding pool transactions.
152    #[inline]
153    pub(crate) fn inc_block_build_stop_reason(&self, reason: BlockBuildStopReason) {
154        metrics::counter!("tempo_payload_builder_block_build_stop_total", "reason" => reason.as_str())
155            .increment(1);
156    }
157
158    /// Increments the counter for subblocks dropped due to expired transactions.
159    #[inline]
160    pub(crate) fn inc_subblocks_expired(&self) {
161        metrics::counter!("tempo_payload_builder_subblocks_expired_total").increment(1);
162    }
163}
164
165/// Wraps a [`StateProvider`] reference to instrument `hashed_post_state` and
166/// `state_root_with_updates` with tracing spans and histogram metrics during `builder.finish()`.
167pub(crate) struct InstrumentedFinishProvider<'a> {
168    pub(crate) inner: &'a dyn StateProvider,
169    pub(crate) metrics: TempoPayloadBuilderMetrics,
170}
171
172impl<'a> AsRef<dyn StateProvider + 'a> for InstrumentedFinishProvider<'a> {
173    fn as_ref(&self) -> &(dyn StateProvider + 'a) {
174        self.inner
175    }
176}
177
178reth_storage_api::delegate_impls_to_as_ref!(
179    for InstrumentedFinishProvider<'_> =>
180    AccountReader {
181        fn basic_account(&self, address: &Address) -> ProviderResult<Option<Account>>;
182    }
183    BlockHashReader {
184        fn block_hash(&self, number: u64) -> ProviderResult<Option<B256>>;
185        fn canonical_hashes_range(&self, start: BlockNumber, end: BlockNumber) -> ProviderResult<Vec<B256>>;
186    }
187    StateProvider {
188        fn storage(&self, account: Address, storage_key: StorageKey) -> ProviderResult<Option<StorageValue>>;
189    }
190    BytecodeReader {
191        fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult<Option<Bytecode>>;
192    }
193    StorageRootProvider {
194        fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult<B256>;
195        fn storage_proof(&self, address: Address, slot: B256, storage: HashedStorage) -> ProviderResult<StorageProof>;
196        fn storage_multiproof(&self, address: Address, slots: &[B256], storage: HashedStorage) -> ProviderResult<StorageMultiProof>;
197    }
198    StateProofProvider {
199        fn proof(&self, input: TrieInput, address: Address, slots: &[B256]) -> ProviderResult<AccountProof>;
200        fn multiproof(&self, input: TrieInput, targets: MultiProofTargets) -> ProviderResult<MultiProof>;
201        fn witness(&self, input: TrieInput, target: HashedPostState, mode: ExecutionWitnessMode) -> ProviderResult<Vec<Bytes>>;
202    }
203);
204
205impl HashedPostStateProvider for InstrumentedFinishProvider<'_> {
206    fn hashed_post_state(&self, bundle_state: &reth_revm::db::BundleState) -> HashedPostState {
207        let start = Instant::now();
208        let _span = debug_span!(target: "payload_builder", "hashed_post_state").entered();
209        let result = self.inner.hashed_post_state(bundle_state);
210        drop(_span);
211        self.metrics
212            .hashed_post_state_duration_seconds
213            .record(start.elapsed());
214        result
215    }
216}
217
218impl StateRootProvider for InstrumentedFinishProvider<'_> {
219    fn state_root(&self, hashed_state: HashedPostState) -> ProviderResult<B256> {
220        let start = Instant::now();
221        let _span = debug_span!(target: "payload_builder", "state_root").entered();
222        let result = self.inner.state_root(hashed_state);
223        drop(_span);
224        self.metrics
225            .state_root_with_updates_duration_seconds
226            .record(start.elapsed());
227        result
228    }
229
230    fn state_root_from_nodes(&self, input: TrieInput) -> ProviderResult<B256> {
231        self.inner.state_root_from_nodes(input)
232    }
233
234    fn state_root_with_updates(
235        &self,
236        hashed_state: HashedPostState,
237    ) -> ProviderResult<(B256, TrieUpdates)> {
238        let start = Instant::now();
239        let _span = debug_span!(target: "payload_builder", "state_root_with_updates").entered();
240        let result = self.inner.state_root_with_updates(hashed_state);
241        drop(_span);
242        self.metrics
243            .state_root_with_updates_duration_seconds
244            .record(start.elapsed());
245        result
246    }
247
248    fn state_root_from_nodes_with_updates(
249        &self,
250        input: TrieInput,
251    ) -> ProviderResult<(B256, TrieUpdates)> {
252        self.inner.state_root_from_nodes_with_updates(input)
253    }
254}