Skip to main content

flashbots_sdk/
validator.rs

1use crate::FlashbotsError;
2use crate::FlashbotsResult;
3use crate::types::Bundle;
4use ethers::types::H256;
5use ethers::types::U64;
6use ethers::utils;
7
8/// Validator for Flashbots bundles
9pub struct BundleValidator;
10
11impl BundleValidator {
12    /// Validates a bundle for correctness and Flashbots compatibility
13    ///
14    /// # Params
15    /// bundle - The bundle to validate
16    ///
17    /// # Returns
18    /// FlashbotsResult<()> - Ok if valid, Err with validation error otherwise
19    ///
20    /// # Example
21    /// ```
22    /// let bundle = Bundle {
23    ///     txs: vec!["0x02f87401843b9aca00843b9aca0082520894...".to_string()],
24    ///     block_number: Some(12345678.into()),
25    ///     min_timestamp: None,
26    ///     max_timestamp: None,
27    ///     reverting_tx_hashes: None,
28    /// };
29    ///
30    /// match BundleValidator::validate_bundle(&bundle) {
31    ///     Ok(()) => println!("Bundle is valid"),
32    ///     Err(e) => println!("Bundle validation failed: {}", e),
33    /// }
34    /// ```
35    pub fn validate_bundle(bundle: &Bundle) -> FlashbotsResult<()> {
36        if bundle.txs.is_empty() {
37            return Err(FlashbotsError::ValidationError(
38                "Bundle must contain at least one transaction".to_string(),
39            ));
40        }
41        if let (Some(min), Some(max)) = (bundle.min_timestamp, bundle.max_timestamp) {
42            if min > max {
43                return Err(FlashbotsError::ValidationError(
44                    "min_timestamp cannot be greater than max_timestamp".to_string(),
45                ));
46            }
47        }
48        for tx in &bundle.txs {
49            Self::validate_transaction_format(tx)?;
50        }
51        Ok(())
52    }
53
54    fn validate_transaction_format(tx: &str) -> FlashbotsResult<()> {
55        if tx.len() < 20 {
56            return Err(FlashbotsError::ValidationError(
57                "Transaction data too short".to_string(),
58            ));
59        }
60        if !tx.starts_with("0x") {
61            return Err(FlashbotsError::ValidationError(
62                "Transaction must start with 0x".to_string(),
63            ));
64        }
65        if let Err(e) = hex::decode(&tx[2..]) {
66            return Err(FlashbotsError::ValidationError(format!(
67                "Invalid hex in transaction: {}",
68                e
69            )));
70        }
71        Ok(())
72    }
73
74    /// Validates that the target block number is in the future and within acceptable range
75    ///
76    /// # Params
77    /// current_block - The current block number
78    /// target_block - The target block number for bundle inclusion
79    ///
80    /// # Returns
81    /// FlashbotsResult<()> - Ok if valid, Err with validation error otherwise
82    ///
83    /// # Example
84    /// ```
85    /// let current_block = U64::from(12345678);
86    /// let target_block = U64::from(12345680);
87    ///
88    /// match BundleValidator::validate_block_number(current_block, target_block) {
89    ///     Ok(()) => println!("Block number validation passed"),
90    ///     Err(e) => println!("Block number validation failed: {}", e),
91    /// }
92    /// ```
93    pub fn validate_block_number(current_block: U64, target_block: U64) -> FlashbotsResult<()> {
94        if target_block <= current_block {
95            return Err(FlashbotsError::ValidationError(
96                "Target block must be in the future".to_string(),
97            ));
98        }
99        if target_block - current_block > U64::from(25) {
100            return Err(FlashbotsError::ValidationError(
101                "Target block too far in the future".to_string(),
102            ));
103        }
104        Ok(())
105    }
106
107    /// Calculates the Keccak-256 hash of a serialized bundle
108    ///
109    /// # Params
110    /// bundle - The bundle to hash
111    ///
112    /// # Returns
113    /// FlashbotsResult<H256> - The bundle hash if successful
114    ///
115    /// # Example
116    /// ```
117    /// let bundle = Bundle {
118    ///     txs: vec!["0x02f87401843b9aca00843b9aca0082520894...".to_string()],
119    ///     block_number: Some(12345678.into()),
120    ///     min_timestamp: None,
121    ///     max_timestamp: None,
122    ///     reverting_tx_hashes: None,
123    /// };
124    ///
125    /// match BundleValidator::calculate_bundle_hash(&bundle) {
126    ///     Ok(hash) => println!("Bundle hash: {:?}", hash),
127    ///     Err(e) => println!("Failed to calculate bundle hash: {}", e),
128    /// }
129    /// ```
130    pub fn calculate_bundle_hash(bundle: &Bundle) -> FlashbotsResult<H256> {
131        let serialized = serde_json::to_vec(bundle)
132            .map_err(|e| FlashbotsError::ValidationError(e.to_string()))?;
133        Ok(utils::keccak256(serialized).into())
134    }
135}