Skip to main content

tempo_programmatic_node/
main.rs

1//! Template binary for launching Tempo with programmatic overrides.
2
3use tempo::{
4    InvalidPoolTransactionError, PoolTransaction, PoolTransactionError, TempoOverrides,
5    tempo_main_with,
6};
7
8#[global_allocator]
9static ALLOC: tempo::cli_util::allocator::Allocator = tempo::cli_util::allocator::new_allocator();
10
11fn main() -> Result<(), Box<dyn std::error::Error>> {
12    const MAX_EXTERNAL_TX_ENCODED_LEN: usize = 128 * 1024;
13
14    let overrides = TempoOverrides::new().map_tempo_node(|node| {
15        // Modify the node's transaction pool validation before the CLI launches Tempo.
16        node.map_pool_builder(|pool| {
17            // Reject externally sourced transactions whose encoded size exceeds a local limit.
18            pool.with_additional_stateless_validation(|origin, tx| {
19                let size = tx.encoded_length();
20                if origin.is_external() && size > MAX_EXTERNAL_TX_ENCODED_LEN {
21                    return Err(InvalidPoolTransactionError::OversizedData {
22                        size,
23                        limit: MAX_EXTERNAL_TX_ENCODED_LEN,
24                    });
25                }
26
27                Ok(())
28            })
29            // Reject transactions whose sender state resolves to a zero-balance account.
30            .with_additional_stateful_validation(|_origin, tx, state| {
31                let account = match state.basic_account(tx.sender_ref()) {
32                    Ok(account) => account.unwrap_or_default(),
33                    Err(_err) => {
34                        return Err(InvalidPoolTransactionError::other(
35                            ProgrammaticPoolError::SenderStateUnavailable,
36                        ));
37                    }
38                };
39
40                if account.balance.is_zero() {
41                    return Err(InvalidPoolTransactionError::other(
42                        ProgrammaticPoolError::ZeroBalanceSender,
43                    ));
44                }
45
46                Ok(())
47            })
48        })
49    });
50
51    tempo_main_with(overrides)?;
52    Ok(())
53}
54
55/// Custom pool rejection reasons returned by this programmatic validation layer.
56#[derive(Debug)]
57enum ProgrammaticPoolError {
58    SenderStateUnavailable,
59    ZeroBalanceSender,
60}
61
62impl std::fmt::Display for ProgrammaticPoolError {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            Self::SenderStateUnavailable => f.write_str("failed to read sender account state"),
66            Self::ZeroBalanceSender => f.write_str("sender account has zero balance"),
67        }
68    }
69}
70
71impl std::error::Error for ProgrammaticPoolError {}
72
73impl PoolTransactionError for ProgrammaticPoolError {
74    fn is_bad_transaction(&self) -> bool {
75        // These are local policy rejections, not malformed peer data that should be penalized.
76        false
77    }
78
79    fn as_any(&self) -> &dyn std::any::Any {
80        self
81    }
82}