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}