1use crate::{hypersync_net_types_capnp, types::AnyOf, CapnpBuilder, CapnpReader, Selection};
2use anyhow::Context;
3use hypersync_format::{Address, Hash};
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeSet;
6
7pub type BlockSelection = Selection<BlockFilter>;
8
9impl From<BlockFilter> for AnyOf<BlockFilter> {
10 fn from(filter: BlockFilter) -> Self {
11 Self::new(filter)
12 }
13}
14
15#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
16pub struct BlockFilter {
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
20 pub hash: Vec<Hash>,
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
24 pub miner: Vec<Address>,
25}
26
27impl BlockFilter {
28 pub fn all() -> Self {
34 Default::default()
35 }
36
37 pub fn or(self, other: Self) -> AnyOf<Self> {
63 AnyOf::new(self).or(other)
64 }
65
66 pub fn and_hash<I, A>(mut self, hashes: I) -> anyhow::Result<Self>
104 where
105 I: IntoIterator<Item = A>,
106 A: TryInto<Hash>,
107 A::Error: std::error::Error + Send + Sync + 'static,
108 {
109 let mut converted_hashes: Vec<Hash> = Vec::new();
110 for (idx, hash) in hashes.into_iter().enumerate() {
111 converted_hashes.push(
112 hash.try_into()
113 .with_context(|| format!("invalid block hash value at position {idx}"))?,
114 );
115 }
116 self.hash = converted_hashes;
117 Ok(self)
118 }
119
120 pub fn and_miner<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
163 where
164 I: IntoIterator<Item = A>,
165 A: TryInto<Address>,
166 A::Error: std::error::Error + Send + Sync + 'static,
167 {
168 let mut converted_addresses: Vec<Address> = Vec::new();
169 for (idx, address) in addresses.into_iter().enumerate() {
170 converted_addresses.push(
171 address
172 .try_into()
173 .with_context(|| format!("invalid miner address value at position {idx}"))?,
174 );
175 }
176 self.miner = converted_addresses;
177 Ok(self)
178 }
179}
180
181impl CapnpReader<hypersync_net_types_capnp::block_filter::Owned> for BlockFilter {
182 fn from_reader(
184 reader: hypersync_net_types_capnp::block_filter::Reader,
185 ) -> Result<Self, capnp::Error> {
186 let mut hash = Vec::new();
187
188 if reader.has_hash() {
190 let hash_list = reader.get_hash()?;
191 for i in 0..hash_list.len() {
192 let hash_data = hash_list.get(i)?;
193 if hash_data.len() == 32 {
194 let mut hash_bytes = [0u8; 32];
195 hash_bytes.copy_from_slice(hash_data);
196 hash.push(Hash::from(hash_bytes));
197 }
198 }
199 }
200
201 let mut miner = Vec::new();
202
203 if reader.has_miner() {
205 let miner_list = reader.get_miner()?;
206 for i in 0..miner_list.len() {
207 let addr_data = miner_list.get(i)?;
208 if addr_data.len() == 20 {
209 let mut addr_bytes = [0u8; 20];
210 addr_bytes.copy_from_slice(addr_data);
211 miner.push(Address::from(addr_bytes));
212 }
213 }
214 }
215
216 Ok(Self { hash, miner })
217 }
218}
219
220impl CapnpBuilder<hypersync_net_types_capnp::block_filter::Owned> for BlockFilter {
221 fn populate_builder(
222 &self,
223 builder: &mut hypersync_net_types_capnp::block_filter::Builder,
224 ) -> Result<(), capnp::Error> {
225 if !self.hash.is_empty() {
227 let mut hash_list = builder.reborrow().init_hash(self.hash.len() as u32);
228 for (i, hash) in self.hash.iter().enumerate() {
229 hash_list.set(i as u32, hash.as_slice());
230 }
231 }
232
233 if !self.miner.is_empty() {
235 let mut miner_list = builder.reborrow().init_miner(self.miner.len() as u32);
236 for (i, miner) in self.miner.iter().enumerate() {
237 miner_list.set(i as u32, miner.as_slice());
238 }
239 }
240
241 Ok(())
242 }
243}
244
245#[derive(
246 Debug,
247 Clone,
248 Copy,
249 Serialize,
250 Deserialize,
251 PartialEq,
252 Eq,
253 schemars::JsonSchema,
254 strum_macros::EnumIter,
255 strum_macros::AsRefStr,
256 strum_macros::Display,
257 strum_macros::EnumString,
258)]
259#[serde(rename_all = "snake_case")]
260#[strum(serialize_all = "snake_case")]
261pub enum BlockField {
262 Number,
264 Hash,
265 ParentHash,
266 Sha3Uncles,
267 LogsBloom,
268 TransactionsRoot,
269 StateRoot,
270 ReceiptsRoot,
271 Miner,
272 ExtraData,
273 Size,
274 GasLimit,
275 GasUsed,
276 Timestamp,
277 MixHash,
278
279 Nonce,
281 Difficulty,
282 TotalDifficulty,
283 Uncles,
284 BaseFeePerGas,
285 BlobGasUsed,
286 ExcessBlobGas,
287 ParentBeaconBlockRoot,
288 WithdrawalsRoot,
289 Withdrawals,
290 L1BlockNumber,
291 SendCount,
292 SendRoot,
293}
294
295impl Ord for BlockField {
296 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
297 self.as_ref().cmp(other.as_ref())
298 }
299}
300
301impl PartialOrd for BlockField {
302 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
303 Some(self.cmp(other))
304 }
305}
306
307impl BlockField {
308 pub fn all() -> BTreeSet<Self> {
309 use strum::IntoEnumIterator;
310 Self::iter().collect()
311 }
312
313 pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::BlockField {
315 match self {
316 BlockField::Number => crate::hypersync_net_types_capnp::BlockField::Number,
317 BlockField::Hash => crate::hypersync_net_types_capnp::BlockField::Hash,
318 BlockField::ParentHash => crate::hypersync_net_types_capnp::BlockField::ParentHash,
319 BlockField::Sha3Uncles => crate::hypersync_net_types_capnp::BlockField::Sha3Uncles,
320 BlockField::LogsBloom => crate::hypersync_net_types_capnp::BlockField::LogsBloom,
321 BlockField::TransactionsRoot => {
322 crate::hypersync_net_types_capnp::BlockField::TransactionsRoot
323 }
324 BlockField::StateRoot => crate::hypersync_net_types_capnp::BlockField::StateRoot,
325 BlockField::ReceiptsRoot => crate::hypersync_net_types_capnp::BlockField::ReceiptsRoot,
326 BlockField::Miner => crate::hypersync_net_types_capnp::BlockField::Miner,
327 BlockField::ExtraData => crate::hypersync_net_types_capnp::BlockField::ExtraData,
328 BlockField::Size => crate::hypersync_net_types_capnp::BlockField::Size,
329 BlockField::GasLimit => crate::hypersync_net_types_capnp::BlockField::GasLimit,
330 BlockField::GasUsed => crate::hypersync_net_types_capnp::BlockField::GasUsed,
331 BlockField::Timestamp => crate::hypersync_net_types_capnp::BlockField::Timestamp,
332 BlockField::MixHash => crate::hypersync_net_types_capnp::BlockField::MixHash,
333 BlockField::Nonce => crate::hypersync_net_types_capnp::BlockField::Nonce,
334 BlockField::Difficulty => crate::hypersync_net_types_capnp::BlockField::Difficulty,
335 BlockField::TotalDifficulty => {
336 crate::hypersync_net_types_capnp::BlockField::TotalDifficulty
337 }
338 BlockField::Uncles => crate::hypersync_net_types_capnp::BlockField::Uncles,
339 BlockField::BaseFeePerGas => {
340 crate::hypersync_net_types_capnp::BlockField::BaseFeePerGas
341 }
342 BlockField::BlobGasUsed => crate::hypersync_net_types_capnp::BlockField::BlobGasUsed,
343 BlockField::ExcessBlobGas => {
344 crate::hypersync_net_types_capnp::BlockField::ExcessBlobGas
345 }
346 BlockField::ParentBeaconBlockRoot => {
347 crate::hypersync_net_types_capnp::BlockField::ParentBeaconBlockRoot
348 }
349 BlockField::WithdrawalsRoot => {
350 crate::hypersync_net_types_capnp::BlockField::WithdrawalsRoot
351 }
352 BlockField::Withdrawals => crate::hypersync_net_types_capnp::BlockField::Withdrawals,
353 BlockField::L1BlockNumber => {
354 crate::hypersync_net_types_capnp::BlockField::L1BlockNumber
355 }
356 BlockField::SendCount => crate::hypersync_net_types_capnp::BlockField::SendCount,
357 BlockField::SendRoot => crate::hypersync_net_types_capnp::BlockField::SendRoot,
358 }
359 }
360
361 pub fn from_capnp(field: crate::hypersync_net_types_capnp::BlockField) -> Self {
363 match field {
364 crate::hypersync_net_types_capnp::BlockField::Number => BlockField::Number,
365 crate::hypersync_net_types_capnp::BlockField::Hash => BlockField::Hash,
366 crate::hypersync_net_types_capnp::BlockField::ParentHash => BlockField::ParentHash,
367 crate::hypersync_net_types_capnp::BlockField::Sha3Uncles => BlockField::Sha3Uncles,
368 crate::hypersync_net_types_capnp::BlockField::LogsBloom => BlockField::LogsBloom,
369 crate::hypersync_net_types_capnp::BlockField::TransactionsRoot => {
370 BlockField::TransactionsRoot
371 }
372 crate::hypersync_net_types_capnp::BlockField::StateRoot => BlockField::StateRoot,
373 crate::hypersync_net_types_capnp::BlockField::ReceiptsRoot => BlockField::ReceiptsRoot,
374 crate::hypersync_net_types_capnp::BlockField::Miner => BlockField::Miner,
375 crate::hypersync_net_types_capnp::BlockField::ExtraData => BlockField::ExtraData,
376 crate::hypersync_net_types_capnp::BlockField::Size => BlockField::Size,
377 crate::hypersync_net_types_capnp::BlockField::GasLimit => BlockField::GasLimit,
378 crate::hypersync_net_types_capnp::BlockField::GasUsed => BlockField::GasUsed,
379 crate::hypersync_net_types_capnp::BlockField::Timestamp => BlockField::Timestamp,
380 crate::hypersync_net_types_capnp::BlockField::MixHash => BlockField::MixHash,
381 crate::hypersync_net_types_capnp::BlockField::Nonce => BlockField::Nonce,
382 crate::hypersync_net_types_capnp::BlockField::Difficulty => BlockField::Difficulty,
383 crate::hypersync_net_types_capnp::BlockField::TotalDifficulty => {
384 BlockField::TotalDifficulty
385 }
386 crate::hypersync_net_types_capnp::BlockField::Uncles => BlockField::Uncles,
387 crate::hypersync_net_types_capnp::BlockField::BaseFeePerGas => {
388 BlockField::BaseFeePerGas
389 }
390 crate::hypersync_net_types_capnp::BlockField::BlobGasUsed => BlockField::BlobGasUsed,
391 crate::hypersync_net_types_capnp::BlockField::ExcessBlobGas => {
392 BlockField::ExcessBlobGas
393 }
394 crate::hypersync_net_types_capnp::BlockField::ParentBeaconBlockRoot => {
395 BlockField::ParentBeaconBlockRoot
396 }
397 crate::hypersync_net_types_capnp::BlockField::WithdrawalsRoot => {
398 BlockField::WithdrawalsRoot
399 }
400 crate::hypersync_net_types_capnp::BlockField::Withdrawals => BlockField::Withdrawals,
401 crate::hypersync_net_types_capnp::BlockField::L1BlockNumber => {
402 BlockField::L1BlockNumber
403 }
404 crate::hypersync_net_types_capnp::BlockField::SendCount => BlockField::SendCount,
405 crate::hypersync_net_types_capnp::BlockField::SendRoot => BlockField::SendRoot,
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use std::str::FromStr;
413
414 use hypersync_format::Hex;
415
416 use super::*;
417 use crate::{query::tests::test_query_serde, Query};
418
419 #[test]
420 fn test_all_fields_in_schema() {
421 let schema = hypersync_schema::block_header();
422 let schema_fields = schema
423 .fields
424 .iter()
425 .map(|f| f.name.clone())
426 .collect::<BTreeSet<_>>();
427 let all_fields = BlockField::all()
428 .into_iter()
429 .map(|f| f.as_ref().to_string())
430 .collect::<BTreeSet<_>>();
431 assert_eq!(schema_fields, all_fields);
432 }
433
434 #[test]
435 fn test_serde_matches_strum() {
436 for field in BlockField::all() {
437 let serialized = serde_json::to_string(&field).unwrap();
438 let strum = serde_json::to_string(&field.as_ref()).unwrap();
439 assert_eq!(serialized, strum, "strum value should be the same as serde");
440 }
441 }
442
443 #[test]
444 fn test_block_filter_serde_with_values() {
445 let block_filter = BlockFilter {
446 hash: vec![Hash::decode_hex(
447 "0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294",
448 )
449 .unwrap()],
450 miner: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
451 };
452 let query = Query::new()
453 .where_blocks(block_filter)
454 .select_block_fields(BlockField::all());
455
456 test_query_serde(query, "block selection with rest defaults");
457 }
458
459 #[test]
460 fn test_as_str() {
461 let block_field = BlockField::Number;
462 let from_str = BlockField::from_str("number").unwrap();
463 assert_eq!(block_field, from_str);
464 }
465}