Skip to content

Commit 29a0e9b

Browse files
authored
chore(tracex): Tracex Crate (#252)
* chore(tracex): rename transaction-tracing to tracex * chore(tracex): touchup crate docs and formatting
1 parent 0976790 commit 29a0e9b

File tree

14 files changed

+386
-323
lines changed

14 files changed

+386
-323
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ base-reth-cli = { path = "crates/cli" }
5555
base-reth-test-utils = { path = "crates/test-utils" }
5656
base-reth-flashblocks = { path = "crates/flashblocks" }
5757
base-reth-rpc = { path = "crates/rpc" }
58-
base-reth-transaction-tracing = { path = "crates/transaction-tracing" }
58+
base-tracex = { path = "crates/tracex" }
5959
base-reth-runner = { path = "crates/runner" }
6060

6161
# base/tips
@@ -136,6 +136,7 @@ jsonrpsee-types = "0.26.0"
136136

137137
# misc
138138
url = "2.5.7"
139+
lru = "0.16.2"
139140
rand = "0.9.2"
140141
clap = "4.5.53"
141142
eyre = "0.6.12"
@@ -151,6 +152,6 @@ itertools = "0.14.0"
151152
serde_json = "1.0.145"
152153
metrics-derive = "0.1.0"
153154

154-
derive_more = { version = "2", default-features = false }
155+
derive_more = { version = "2.1.0", default-features = false }
155156
uuid = { version = "1.18.1", features = ["serde", "v5", "v4"] }
156157
time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] }

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Base Reth Node is an implementation of a Reth Ethereum node, specifically tailor
5555
├── README.md # This file
5656
├── crates/
5757
│ ├── node/ # Main node application logic
58-
│ ├── transaction-tracing/ # Transaction tracing utilities
58+
│ ├── tracex/ # Transaction tracing utilities
5959
│ ├── flashblocks-rpc/ # RPC server for Flashblocks integration
6060
│ └── metering/ # Simulation-based resource metering
6161
├── justfile # Command runner for development tasks

crates/runner/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ workspace = true
1515
# internal
1616
base-reth-flashblocks.workspace = true
1717
base-reth-rpc.workspace = true
18-
base-reth-transaction-tracing.workspace = true
18+
base-tracex.workspace = true
1919

2020
# reth
2121
reth.workspace = true

crates/runner/src/extensions/tracing.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
//! Contains the [TransactionTracingExtension] which wires up the `transaction-tracing`
1+
//! Contains the [TransactionTracingExtension] which wires up the `tracex`
22
//! execution extension on the Base node builder.
33
4-
use base_reth_transaction_tracing::transaction_tracing_exex;
4+
use base_tracex::tracex_exex;
55

66
use crate::{TracingConfig, extensions::OpBuilder};
77

@@ -21,8 +21,8 @@ impl TransactionTracingExtension {
2121
/// Applies the extension to the supplied builder.
2222
pub fn apply(&self, builder: OpBuilder) -> OpBuilder {
2323
let tracing = self.config;
24-
builder.install_exex_if(tracing.enabled, "transaction-tracing", move |ctx| async move {
25-
Ok(transaction_tracing_exex(ctx, tracing.logs_enabled))
24+
builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move {
25+
Ok(tracex_exex(ctx, tracing.logs_enabled))
2626
})
2727
}
2828
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[package]
2-
name = "base-reth-transaction-tracing"
2+
name = "base-tracex"
3+
description = "A transaction tracing execution extension for reth"
34
version.workspace = true
45
edition.workspace = true
56
rust-version.workspace = true
@@ -20,12 +21,12 @@ reth-tracing.workspace = true
2021
alloy-primitives.workspace = true
2122

2223
# async
23-
futures.workspace = true
2424
tokio.workspace = true
25+
futures.workspace = true
2526

2627
# misc
27-
derive_more = { workspace = true, features = ["display"] }
28+
lru.workspace = true
2829
eyre.workspace = true
29-
lru = "0.16.1"
30-
metrics.workspace = true
3130
chrono.workspace = true
31+
metrics.workspace = true
32+
derive_more = { workspace = true, features = ["display"] }

crates/tracex/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# `base-tracex`
2+
3+
<a href="https://github.com/base/node-reth/actions/workflows/ci.yml"><img src="https://github.com/base/node-reth/actions/workflows/ci.yml/badge.svg?label=ci" alt="CI"></a>
4+
<a href="https://github.com/base/node-reth/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-d1d1f6.svg?label=license&labelColor=2a2f35" alt="MIT License"></a>
5+
6+
Base's transaction tracing execution extension for `reth`. It subscribes to mempool events and chain notifications to track how long a transaction spends in each stage of the lifecycle before it is included, dropped, or replaced.
7+
8+
## Overview
9+
10+
- Hooks into the Base node as an execution extension (`ExEx`) so tracing runs alongside normal block production.
11+
- Records pending/queued transitions, replacements, drops, and block inclusion for every tracked transaction.
12+
- Keeps an event log per transaction with optional `info`-level logging to surface lifecycle transitions.
13+
- Emits histogram metrics for mempool residency by event type to help spot latency regressions.
14+
15+
## Usage
16+
17+
Enable transaction tracing on the Base node CLI:
18+
19+
```bash
20+
cargo run -p node --release -- \
21+
--enable-transaction-tracing \
22+
--enable-transaction-tracing-logs # optional: emit per-tx lifecycle logs
23+
```
24+
25+
From code, wire the ExEx into the node builder:
26+
27+
```rust,ignore
28+
use base_reth_runner::{TracingConfig, extensions::TransactionTracingExtension};
29+
30+
let tracing = TracingConfig { enabled: true, logs_enabled: true };
31+
let builder = TransactionTracingExtension::new(tracing).apply(builder);
32+
```
33+
34+
## Metrics
35+
36+
The extension records a histogram named `reth_transaction_tracing_tx_event` with an `event` label for each lifecycle event (`pending`, `queued`, `replaced`, `dropped`, `block_inclusion`, etc.). Values are the milliseconds a transaction spent in the mempool up to that event. When the in-memory log reaches its limit (20,000 transactions), an `overflowed` event is recorded so dashboards can alert on data loss.
37+
38+
## License
39+
40+
Licensed under the MIT license, as found in [`LICENSE`](../../LICENSE).

crates/tracex/src/events.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Domain types describing tracex events and pools.
2+
3+
use std::time::Instant;
4+
5+
use chrono::{DateTime, Local};
6+
use derive_more::Display;
7+
8+
/// Types of transaction events to track.
9+
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq)]
10+
pub enum TxEvent {
11+
/// Transaction dropped from the pool.
12+
#[display("dropped")]
13+
Dropped,
14+
/// Transaction replaced by a higher priced version.
15+
#[display("replaced")]
16+
Replaced,
17+
/// Transaction was observed in the pending pool.
18+
#[display("pending")]
19+
Pending,
20+
/// Transaction was queued due to dependencies (nonce gap, etc).
21+
#[display("queued")]
22+
Queued,
23+
/// Transaction included on chain.
24+
#[display("block_inclusion")]
25+
BlockInclusion,
26+
/// Transaction moved from pending -> queued.
27+
#[display("pending_to_queued")]
28+
PendingToQueued,
29+
/// Transaction moved from queued -> pending.
30+
#[display("queued_to_pending")]
31+
QueuedToPending,
32+
/// Transaction overflowed our tracking buffers.
33+
#[display("overflowed")]
34+
Overflowed,
35+
}
36+
37+
/// Types of pools a transaction can be in.
38+
#[derive(Debug, Clone, PartialEq, Eq)]
39+
pub enum Pool {
40+
/// Pending pool.
41+
Pending,
42+
/// Queued pool.
43+
Queued,
44+
}
45+
46+
/// History of events for a transaction.
47+
#[derive(Debug, Clone)]
48+
pub struct EventLog {
49+
pub(crate) mempool_time: Instant,
50+
pub(crate) events: Vec<(DateTime<Local>, TxEvent)>,
51+
pub(crate) limit: usize,
52+
}
53+
54+
impl EventLog {
55+
/// Create a new log seeded with the first event.
56+
pub fn new(t: DateTime<Local>, event: TxEvent) -> Self {
57+
Self { mempool_time: Instant::now(), events: vec![(t, event)], limit: 10 }
58+
}
59+
60+
/// Append a new `(timestamp, event)` tuple to the log.
61+
pub fn push(&mut self, t: DateTime<Local>, event: TxEvent) {
62+
self.events.push((t, event));
63+
}
64+
65+
/// Render all events into human readable strings.
66+
pub fn to_vec(&self) -> Vec<String> {
67+
self.events
68+
.iter()
69+
.map(|(t, event)| {
70+
// example: 08:57:37.979 pm - Pending
71+
format!("{} - {}", t.format("%H:%M:%S%.3f"), event)
72+
})
73+
.collect::<Vec<_>>()
74+
}
75+
}

crates/tracex/src/exex.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//! Tracex execution extension wiring.
2+
3+
use alloy_primitives::TxHash;
4+
use eyre::Result;
5+
use futures::StreamExt;
6+
use reth::{
7+
api::{BlockBody, FullNodeComponents},
8+
core::primitives::{AlloyBlockHeader, transaction::TxHashRef},
9+
transaction_pool::{FullTransactionEvent, TransactionPool},
10+
};
11+
use reth_exex::{ExExContext, ExExEvent, ExExNotification};
12+
use reth_tracing::tracing::debug;
13+
14+
use crate::{
15+
events::{Pool, TxEvent},
16+
tracker::Tracker,
17+
};
18+
19+
/// Execution extension that tracks transaction timing from mempool to inclusion.
20+
///
21+
/// Monitors transaction lifecycle events and records timing metrics.
22+
pub async fn tracex_exex<Node: FullNodeComponents>(
23+
mut ctx: ExExContext<Node>,
24+
enable_logs: bool,
25+
) -> Result<()> {
26+
debug!(target: "tracex", "Starting transaction tracking ExEx");
27+
let mut tracker = Tracker::new(enable_logs);
28+
29+
// Subscribe to events from the mempool.
30+
let pool = ctx.pool().clone();
31+
let mut all_events_stream = pool.all_transactions_event_listener();
32+
33+
loop {
34+
tokio::select! {
35+
// Track # of transactions dropped and replaced.
36+
Some(full_event) = all_events_stream.next() => {
37+
match full_event {
38+
FullTransactionEvent::Pending(tx_hash) => {
39+
tracker.transaction_inserted(tx_hash, TxEvent::Pending);
40+
tracker.transaction_moved(tx_hash, Pool::Pending);
41+
}
42+
FullTransactionEvent::Queued(tx_hash, _) => {
43+
tracker.transaction_inserted(tx_hash, TxEvent::Queued);
44+
tracker.transaction_moved(tx_hash, Pool::Queued);
45+
}
46+
FullTransactionEvent::Discarded(tx_hash) => {
47+
tracker.transaction_completed(tx_hash, TxEvent::Dropped);
48+
}
49+
FullTransactionEvent::Replaced{ transaction, replaced_by } => {
50+
let tx_hash = transaction.hash();
51+
tracker.transaction_replaced(*tx_hash, TxHash::from(replaced_by));
52+
}
53+
_ => {
54+
// Other events.
55+
}
56+
}
57+
}
58+
59+
// Use chain notifications to track time to inclusion.
60+
Some(notification) = ctx.notifications.next() => {
61+
match notification {
62+
Ok(ExExNotification::ChainCommitted { new }) => {
63+
// Process all transactions in committed chain.
64+
for block in new.blocks().values() {
65+
for transaction in block.body().transactions() {
66+
tracker.transaction_completed(*transaction.tx_hash(), TxEvent::BlockInclusion);
67+
}
68+
}
69+
ctx.events.send(ExExEvent::FinishedHeight(new.tip().num_hash()))?;
70+
}
71+
Ok(ExExNotification::ChainReorged { old: _, new }) => {
72+
debug!(target: "tracex", tip = ?new.tip().number(), "Chain reorg detected");
73+
for block in new.blocks().values() {
74+
for transaction in block.body().transactions() {
75+
tracker.transaction_completed(*transaction.tx_hash(), TxEvent::BlockInclusion);
76+
}
77+
}
78+
ctx.events.send(ExExEvent::FinishedHeight(new.tip().num_hash()))?;
79+
}
80+
Ok(ExExNotification::ChainReverted { old }) => {
81+
debug!(target: "tracex", old_tip = ?old.tip().number(), "Chain reverted");
82+
ctx.events.send(ExExEvent::FinishedHeight(old.tip().num_hash()))?;
83+
}
84+
Err(e) => {
85+
debug!(target: "tracex", error = %e, "Notification error");
86+
return Err(e);
87+
}
88+
}
89+
}
90+
}
91+
}
92+
}
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
44
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
55

6-
mod tracing;
7-
pub use tracing::transaction_tracing_exex;
6+
mod events;
7+
pub use events::{EventLog, Pool, TxEvent};
88

9-
mod types;
9+
mod exex;
10+
pub use exex::tracex_exex;
11+
12+
mod tracker;
13+
pub use tracker::Tracker;

0 commit comments

Comments
 (0)