alloy_rpc_types_mev/stats.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
use crate::{u256_numeric_string, ConsideredByBuildersAt, SealedByBuildersAt};
use alloy_primitives::U256;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
// TODO(@optimiz-r): Revisit after <https://github.com/flashbots/flashbots-docs/issues/424> is closed.
/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getbundlestatsv2>
///
/// Timestamp format: "2022-10-06T21:36:06.322Z"
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum BundleStats {
/// The relayer has not yet seen the bundle.
#[default]
Unknown,
/// The relayer has seen the bundle, but has not simulated it yet.
Seen(StatsSeen),
/// The relayer has seen the bundle and has simulated it.
Simulated(StatsSimulated),
}
impl Serialize for BundleStats {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Unknown => serde_json::json!({"isSimulated": false}).serialize(serializer),
Self::Seen(stats) => stats.serialize(serializer),
Self::Simulated(stats) => stats.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for BundleStats {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let map = serde_json::Map::deserialize(deserializer)?;
if map.get("receivedAt").is_none() {
Ok(Self::Unknown)
} else if map["isSimulated"] == false {
StatsSeen::deserialize(serde_json::Value::Object(map))
.map(BundleStats::Seen)
.map_err(serde::de::Error::custom)
} else {
StatsSimulated::deserialize(serde_json::Value::Object(map))
.map(BundleStats::Simulated)
.map_err(serde::de::Error::custom)
}
}
}
/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getbundlestatsv2>
///
/// Timestamp format: "2022-10-06T21:36:06.322Z
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatsSeen {
/// boolean representing if this searcher has a high enough reputation to be in the high
/// priority queue
pub is_high_priority: bool,
/// representing whether the bundle gets simulated. All other fields will be omitted except
/// simulated field if API didn't receive bundle
pub is_simulated: bool,
/// time at which the bundle API received the bundle
pub received_at: String,
}
/// Response for `flashbots_getBundleStatsV2` represents stats for a single bundle
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getbundlestatsv2>
///
/// Timestamp format: "2022-10-06T21:36:06.322Z
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatsSimulated {
/// boolean representing if this searcher has a high enough reputation to be in the high
/// priority queue
pub is_high_priority: bool,
/// representing whether the bundle gets simulated. All other fields will be omitted except
/// simulated field if API didn't receive bundle
pub is_simulated: bool,
/// time at which the bundle gets simulated
pub simulated_at: String,
/// time at which the bundle API received the bundle
pub received_at: String,
/// indicates time at which each builder selected the bundle to be included in the target
/// block
#[serde(default = "Vec::new")]
pub considered_by_builders_at: Vec<ConsideredByBuildersAt>,
/// indicates time at which each builder sealed a block containing the bundle
#[serde(default = "Vec::new")]
pub sealed_by_builders_at: Vec<SealedByBuildersAt>,
}
/// Response for `flashbots_getUserStatsV2` represents stats for a searcher.
///
/// Note: this is V2: <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint#flashbots_getuserstatsv2>
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserStats {
/// Represents whether this searcher has a high enough reputation to be in the high priority
/// queue.
pub is_high_priority: bool,
/// The total amount paid to validators over all time.
#[serde(with = "u256_numeric_string")]
pub all_time_validator_payments: U256,
/// The total amount of gas simulated across all bundles submitted to Flashbots.
/// This is the actual gas used in simulations, not gas limit.
#[serde(with = "u256_numeric_string")]
pub all_time_gas_simulated: U256,
/// The total amount paid to validators the last 7 days.
#[serde(with = "u256_numeric_string")]
pub last_7d_validator_payments: U256,
/// The total amount of gas simulated across all bundles submitted to Flashbots in the last 7
/// days. This is the actual gas used in simulations, not gas limit.
#[serde(with = "u256_numeric_string")]
pub last_7d_gas_simulated: U256,
/// The total amount paid to validators the last day.
#[serde(with = "u256_numeric_string")]
pub last_1d_validator_payments: U256,
/// The total amount of gas simulated across all bundles submitted to Flashbots in the last
/// day. This is the actual gas used in simulations, not gas limit.
#[serde(with = "u256_numeric_string")]
pub last_1d_gas_simulated: U256,
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
use crate::SealedByBuildersAt;
#[test]
fn can_serialize_deserialize_bundle_stats() {
let fixtures = [
(
r#"{
"isSimulated": false
}"#,
BundleStats::Unknown,
),
(
r#"{
"isHighPriority": false,
"isSimulated": false,
"receivedAt": "476190476193"
}"#,
BundleStats::Seen(StatsSeen {
is_high_priority: false,
is_simulated: false,
received_at: "476190476193".to_string(),
}),
),
(
r#"{
"isHighPriority": true,
"isSimulated": true,
"simulatedAt": "111",
"receivedAt": "222",
"consideredByBuildersAt":[],
"sealedByBuildersAt": [
{
"pubkey": "333",
"timestamp": "444"
},
{
"pubkey": "555",
"timestamp": "666"
}
]
}"#,
BundleStats::Simulated(StatsSimulated {
is_high_priority: true,
is_simulated: true,
simulated_at: String::from("111"),
received_at: String::from("222"),
considered_by_builders_at: vec![],
sealed_by_builders_at: vec![
SealedByBuildersAt {
pubkey: String::from("333"),
timestamp: String::from("444"),
},
SealedByBuildersAt {
pubkey: String::from("555"),
timestamp: String::from("666"),
},
],
}),
),
];
let strip_whitespaces =
|input: &str| input.chars().filter(|&c| !c.is_whitespace()).collect::<String>();
for (serialized, deserialized) in fixtures {
// Check de-serialization
let deserialized_expected = serde_json::from_str::<BundleStats>(serialized).unwrap();
assert_eq!(deserialized, deserialized_expected);
// Check serialization
let serialized_expected = &serde_json::to_string(&deserialized).unwrap();
assert_eq!(strip_whitespaces(serialized), strip_whitespaces(serialized_expected));
}
}
}