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 fn to_capnp(&self) -> crate::hypersync_net_types_capnp::BlockField {
317 match self {
318 BlockField::Number => crate::hypersync_net_types_capnp::BlockField::Number,
319 BlockField::Hash => crate::hypersync_net_types_capnp::BlockField::Hash,
320 BlockField::ParentHash => crate::hypersync_net_types_capnp::BlockField::ParentHash,
321 BlockField::Sha3Uncles => crate::hypersync_net_types_capnp::BlockField::Sha3Uncles,
322 BlockField::LogsBloom => crate::hypersync_net_types_capnp::BlockField::LogsBloom,
323 BlockField::TransactionsRoot => {
324 crate::hypersync_net_types_capnp::BlockField::TransactionsRoot
325 }
326 BlockField::StateRoot => crate::hypersync_net_types_capnp::BlockField::StateRoot,
327 BlockField::ReceiptsRoot => crate::hypersync_net_types_capnp::BlockField::ReceiptsRoot,
328 BlockField::Miner => crate::hypersync_net_types_capnp::BlockField::Miner,
329 BlockField::ExtraData => crate::hypersync_net_types_capnp::BlockField::ExtraData,
330 BlockField::Size => crate::hypersync_net_types_capnp::BlockField::Size,
331 BlockField::GasLimit => crate::hypersync_net_types_capnp::BlockField::GasLimit,
332 BlockField::GasUsed => crate::hypersync_net_types_capnp::BlockField::GasUsed,
333 BlockField::Timestamp => crate::hypersync_net_types_capnp::BlockField::Timestamp,
334 BlockField::MixHash => crate::hypersync_net_types_capnp::BlockField::MixHash,
335 BlockField::Nonce => crate::hypersync_net_types_capnp::BlockField::Nonce,
336 BlockField::Difficulty => crate::hypersync_net_types_capnp::BlockField::Difficulty,
337 BlockField::TotalDifficulty => {
338 crate::hypersync_net_types_capnp::BlockField::TotalDifficulty
339 }
340 BlockField::Uncles => crate::hypersync_net_types_capnp::BlockField::Uncles,
341 BlockField::BaseFeePerGas => {
342 crate::hypersync_net_types_capnp::BlockField::BaseFeePerGas
343 }
344 BlockField::BlobGasUsed => crate::hypersync_net_types_capnp::BlockField::BlobGasUsed,
345 BlockField::ExcessBlobGas => {
346 crate::hypersync_net_types_capnp::BlockField::ExcessBlobGas
347 }
348 BlockField::ParentBeaconBlockRoot => {
349 crate::hypersync_net_types_capnp::BlockField::ParentBeaconBlockRoot
350 }
351 BlockField::WithdrawalsRoot => {
352 crate::hypersync_net_types_capnp::BlockField::WithdrawalsRoot
353 }
354 BlockField::Withdrawals => crate::hypersync_net_types_capnp::BlockField::Withdrawals,
355 BlockField::L1BlockNumber => {
356 crate::hypersync_net_types_capnp::BlockField::L1BlockNumber
357 }
358 BlockField::SendCount => crate::hypersync_net_types_capnp::BlockField::SendCount,
359 BlockField::SendRoot => crate::hypersync_net_types_capnp::BlockField::SendRoot,
360 }
361 }
362
363 pub fn from_capnp(field: crate::hypersync_net_types_capnp::BlockField) -> Self {
365 match field {
366 crate::hypersync_net_types_capnp::BlockField::Number => BlockField::Number,
367 crate::hypersync_net_types_capnp::BlockField::Hash => BlockField::Hash,
368 crate::hypersync_net_types_capnp::BlockField::ParentHash => BlockField::ParentHash,
369 crate::hypersync_net_types_capnp::BlockField::Sha3Uncles => BlockField::Sha3Uncles,
370 crate::hypersync_net_types_capnp::BlockField::LogsBloom => BlockField::LogsBloom,
371 crate::hypersync_net_types_capnp::BlockField::TransactionsRoot => {
372 BlockField::TransactionsRoot
373 }
374 crate::hypersync_net_types_capnp::BlockField::StateRoot => BlockField::StateRoot,
375 crate::hypersync_net_types_capnp::BlockField::ReceiptsRoot => BlockField::ReceiptsRoot,
376 crate::hypersync_net_types_capnp::BlockField::Miner => BlockField::Miner,
377 crate::hypersync_net_types_capnp::BlockField::ExtraData => BlockField::ExtraData,
378 crate::hypersync_net_types_capnp::BlockField::Size => BlockField::Size,
379 crate::hypersync_net_types_capnp::BlockField::GasLimit => BlockField::GasLimit,
380 crate::hypersync_net_types_capnp::BlockField::GasUsed => BlockField::GasUsed,
381 crate::hypersync_net_types_capnp::BlockField::Timestamp => BlockField::Timestamp,
382 crate::hypersync_net_types_capnp::BlockField::MixHash => BlockField::MixHash,
383 crate::hypersync_net_types_capnp::BlockField::Nonce => BlockField::Nonce,
384 crate::hypersync_net_types_capnp::BlockField::Difficulty => BlockField::Difficulty,
385 crate::hypersync_net_types_capnp::BlockField::TotalDifficulty => {
386 BlockField::TotalDifficulty
387 }
388 crate::hypersync_net_types_capnp::BlockField::Uncles => BlockField::Uncles,
389 crate::hypersync_net_types_capnp::BlockField::BaseFeePerGas => {
390 BlockField::BaseFeePerGas
391 }
392 crate::hypersync_net_types_capnp::BlockField::BlobGasUsed => BlockField::BlobGasUsed,
393 crate::hypersync_net_types_capnp::BlockField::ExcessBlobGas => {
394 BlockField::ExcessBlobGas
395 }
396 crate::hypersync_net_types_capnp::BlockField::ParentBeaconBlockRoot => {
397 BlockField::ParentBeaconBlockRoot
398 }
399 crate::hypersync_net_types_capnp::BlockField::WithdrawalsRoot => {
400 BlockField::WithdrawalsRoot
401 }
402 crate::hypersync_net_types_capnp::BlockField::Withdrawals => BlockField::Withdrawals,
403 crate::hypersync_net_types_capnp::BlockField::L1BlockNumber => {
404 BlockField::L1BlockNumber
405 }
406 crate::hypersync_net_types_capnp::BlockField::SendCount => BlockField::SendCount,
407 crate::hypersync_net_types_capnp::BlockField::SendRoot => BlockField::SendRoot,
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use std::str::FromStr;
415
416 use hypersync_format::Hex;
417
418 use super::*;
419 use crate::{query::tests::test_query_serde, Query};
420
421 #[test]
422 fn test_all_fields_in_schema() {
423 let schema = hypersync_schema::block_header();
424 let schema_fields = schema
425 .fields
426 .iter()
427 .map(|f| f.name.clone())
428 .collect::<BTreeSet<_>>();
429 let all_fields = BlockField::all()
430 .into_iter()
431 .map(|f| f.as_ref().to_string())
432 .collect::<BTreeSet<_>>();
433 assert_eq!(schema_fields, all_fields);
434 }
435
436 #[test]
437 fn test_serde_matches_strum() {
438 for field in BlockField::all() {
439 let serialized = serde_json::to_string(&field).unwrap();
440 let strum = serde_json::to_string(&field.as_ref()).unwrap();
441 assert_eq!(serialized, strum, "strum value should be the same as serde");
442 }
443 }
444
445 #[test]
446 fn test_block_filter_serde_with_values() {
447 let block_filter = BlockFilter {
448 hash: vec![Hash::decode_hex(
449 "0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294",
450 )
451 .unwrap()],
452 miner: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
453 };
454 let query = Query::new()
455 .where_blocks(block_filter)
456 .select_block_fields(BlockField::all());
457
458 test_query_serde(query, "block selection with rest defaults");
459 }
460
461 #[test]
462 fn test_as_str() {
463 let block_field = BlockField::Number;
464 let from_str = BlockField::from_str("number").unwrap();
465 assert_eq!(block_field, from_str);
466 }
467}