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