tempo/overrides.rs
1use tempo_node::TempoNode;
2
3/// Function used to modify the [`TempoNode`] before launch.
4pub type TempoNodeMapper = dyn FnOnce(TempoNode) -> TempoNode + Send + 'static;
5
6/// Optional programmatic overrides for [`tempo_main_with`](crate::tempo_main_with).
7///
8/// These hooks are applied during startup after CLI arguments have been parsed.
9/// Empty overrides are a no-op, so embedding binaries can start from
10/// [`TempoOverrides::default`] or [`TempoOverrides::new`] and opt into only the
11/// hooks they need.
12///
13/// The initial one-shot hook surface maps the [`TempoNode`] produced from CLI arguments
14/// before it is passed to Reth's node launcher. This is useful for settings that
15/// are intentionally not exposed as CLI flags, such as additional transaction
16/// pool validation.
17///
18/// # Example
19///
20/// This rejects externally sourced transactions whose encoded size exceeds a
21/// local policy limit while preserving the rest of the CLI-derived node
22/// configuration.
23///
24/// ```no_run
25/// use tempo::{
26/// InvalidPoolTransactionError, PoolTransaction, TempoOverrides, tempo_main_with,
27/// };
28///
29/// fn main() -> eyre::Result<()> {
30/// const MAX_EXTERNAL_TX_ENCODED_LEN: usize = 128 * 1024;
31///
32/// let overrides = TempoOverrides::new().map_tempo_node(|node| {
33/// node.map_pool_builder(|pool| {
34/// pool.with_additional_stateless_validation(|origin, tx| {
35/// let size = tx.encoded_length();
36/// if origin.is_external() && size > MAX_EXTERNAL_TX_ENCODED_LEN {
37/// return Err(InvalidPoolTransactionError::OversizedData {
38/// size,
39/// limit: MAX_EXTERNAL_TX_ENCODED_LEN,
40/// });
41/// }
42///
43/// Ok(())
44/// })
45/// })
46/// });
47///
48/// tempo_main_with(overrides)
49/// }
50/// ```
51#[derive(Default)]
52pub struct TempoOverrides {
53 pub(crate) tempo_node_mapper: Option<Box<TempoNodeMapper>>,
54}
55
56impl TempoOverrides {
57 /// Creates empty overrides.
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 /// Adds a mapper for the [`TempoNode`] built from CLI arguments.
63 ///
64 /// Multiple mappers are applied in the order they were added.
65 pub fn map_tempo_node<F>(mut self, mapper: F) -> Self
66 where
67 F: FnOnce(TempoNode) -> TempoNode + Send + 'static,
68 {
69 self.tempo_node_mapper = Some(match self.tempo_node_mapper.take() {
70 Some(previous) => Box::new(move |node| mapper(previous(node))),
71 None => Box::new(mapper),
72 });
73 self
74 }
75
76 pub(crate) fn apply_tempo_node(&mut self, node: TempoNode) -> TempoNode {
77 match self.tempo_node_mapper.take() {
78 Some(mapper) => mapper(node),
79 None => node,
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use std::sync::{
87 Arc, Mutex,
88 atomic::{AtomicBool, Ordering},
89 };
90
91 use super::TempoOverrides;
92 use tempo_node::TempoNode;
93
94 #[test]
95 fn maps_tempo_node() {
96 let called = Arc::new(AtomicBool::new(false));
97 let called_clone = called.clone();
98
99 let mut overrides = TempoOverrides::new().map_tempo_node(move |node| {
100 called_clone.store(true, Ordering::Relaxed);
101 node
102 });
103
104 let _ = overrides.apply_tempo_node(TempoNode::default());
105
106 assert!(called.load(Ordering::Relaxed));
107 }
108
109 #[test]
110 fn applies_tempo_node_mappers_in_order() {
111 let calls = Arc::new(Mutex::new(Vec::new()));
112 let first = calls.clone();
113 let second = calls.clone();
114
115 let mut overrides = TempoOverrides::new()
116 .map_tempo_node(move |node| {
117 first.lock().unwrap().push(1);
118 node
119 })
120 .map_tempo_node(move |node| {
121 second.lock().unwrap().push(2);
122 node
123 });
124
125 let _ = overrides.apply_tempo_node(TempoNode::default());
126
127 assert_eq!(*calls.lock().unwrap(), vec![1, 2]);
128 }
129}