1use std::borrow::Cow;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use super::EthereumError;
6use super::network::block_info::{EthereumBlockInfo, EthereumBlockInfoOrderBy};
7use super::types::H256;
8use crate::prelude::*;
9use cido::__internal::chrono::{DateTime, Utc};
10use cido::__internal::{
11 BlockId, GraphqlMetaExtension, GraphqlOrderBy, Network, async_graphql, tracing,
12};
13
14impl async_graphql::OutputType for EthereumBlockNumber {
15 fn type_name() -> std::borrow::Cow<'static, str> {
16 <Self as async_graphql::InputType>::type_name()
17 }
18
19 fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
20 <Self as async_graphql::InputType>::create_type_info(registry)
21 }
22
23 async fn resolve(
24 &self,
25 _ctx: &async_graphql::ContextSelectionSet<'_>,
26 _field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
27 ) -> async_graphql::ServerResult<async_graphql::Value> {
28 Ok(async_graphql::Value::Number(self.number.into()))
29 }
30}
31
32impl async_graphql::InputType for EthereumBlockNumber {
33 type RawValueType = Self;
34
35 fn type_name() -> std::borrow::Cow<'static, str> {
36 "BlockNumber".into()
37 }
38
39 fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
40 registry.create_input_type::<Self, _>(async_graphql::registry::MetaTypeId::Scalar, |_| {
41 async_graphql::registry::MetaType::Scalar {
42 name: Self::type_name().to_string(),
43 description: Some("8 byte signed integer".into()),
44 visible: None,
45 is_valid: None,
46 specified_by_url: None,
47 inaccessible: false,
48 tags: vec![],
49 directive_invocations: vec![],
50 }
51 })
52 }
53
54 fn parse(value: Option<async_graphql::Value>) -> async_graphql::InputValueResult<Self> {
55 if let Some(val) = value {
56 match val {
57 async_graphql::Value::Number(num) => {
58 Ok(Self::new(num.as_u64().ok_or_else(|| {
59 async_graphql::InputValueError::custom("expect u64")
60 })?))
61 }
62 async_graphql::Value::String(val) => val.parse::<u64>().map(Self::new).map_err(|_| {
63 async_graphql::InputValueError::expected_type(async_graphql::Value::String(val))
64 }),
65 val => async_graphql::Result::Err(async_graphql::InputValueError::expected_type(val)),
66 }
67 } else {
68 async_graphql::Result::Err(async_graphql::InputValueError::expected_type(
69 value.unwrap_or_default(),
70 ))
71 }
72 }
73
74 fn to_value(&self) -> async_graphql::Value {
75 async_graphql::Value::Number(self.number.into())
76 }
77
78 fn as_raw_value(&self) -> Option<&Self::RawValueType> {
79 Some(self)
80 }
81}
82
83#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
87#[non_exhaustive]
88pub enum EthereumBlockId {
89 Number(EthereumBlockNumber),
90 Hash(H256),
91 Latest,
92}
93
94#[crate::_impls(keep)]
95impl BlockId for EthereumBlockId {
96 type Network = EthereumNetwork;
97
98 async fn latest() -> Result<Self, <Self::Network as cido::prelude::Network>::Error> {
99 Ok(Self::Latest)
100 }
101
102 async fn to_block_number(
103 self,
104 network: &EthereumNetwork,
105 ) -> Result<EthereumBlockNumber, EthereumError> {
106 match self {
107 Self::Number(n) => Ok(n),
108 _ => {
109 let header = network.block_info_lookup(self).await?;
110 Ok(header.block_number)
111 }
112 }
113 }
114}
115
116impl Default for EthereumBlockId {
117 fn default() -> Self {
118 Self::Latest
119 }
120}
121
122impl core::fmt::Display for EthereumBlockId {
123 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
124 match self {
125 Self::Number(num) => write!(f, "{}", num),
126 Self::Hash(hash) => write!(f, "{:?}", hash),
127 Self::Latest => write!(f, "Latest"),
128 }
129 }
130}
131
132impl core::fmt::Debug for EthereumBlockId {
133 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
134 match self {
135 Self::Number(num) => write!(f, "EthereumBlockId::Number({})", num),
136 Self::Hash(hash) => write!(f, "EthereumBlockId::Hash({})", hash),
137 Self::Latest => write!(f, "EthereumBlockId::Latest"),
138 }
139 }
140}
141
142impl async_graphql::InputType for EthereumBlockId {
143 type RawValueType = Self;
144
145 fn type_name() -> Cow<'static, str> {
146 Cow::Borrowed("Block_height")
147 }
148
149 fn create_type_info(
150 registry: &mut ::cido::__internal::async_graphql::registry::Registry,
151 ) -> String {
152 registry.create_input_type::<Self, _>(
153 ::cido::__internal::async_graphql::registry::MetaTypeId::InputObject,
154 |registry| ::cido::__internal::async_graphql::registry::MetaType::InputObject {
155 name: Self::type_name().to_string(),
156 description: None,
157 input_fields: {
158 let mut map = async_graphql::indexmap::IndexMap::new();
159 map.insert("number".into(), async_graphql::registry::MetaInputValue {
160 name: "number".into(),
161 description: None,
162 default_value: None,
163 ty: <Option<u64> as async_graphql::InputType>::create_type_info(registry),
164 visible: None,
165 inaccessible: false,
166 tags: vec![],
167 directive_invocations: vec![],
168 is_secret: false,
169 deprecation: async_graphql::registry::Deprecation::NoDeprecated,
170 });
171
172 map.insert(
173 "number_gte".into(),
174 async_graphql::registry::MetaInputValue {
175 name: "number_gte".into(),
176 description: None,
177 default_value: None,
178 ty: <Option<u64> as async_graphql::InputType>::create_type_info(registry),
179 visible: None,
180 inaccessible: false,
181 tags: vec![],
182 directive_invocations: vec![],
183 is_secret: false,
184 deprecation: async_graphql::registry::Deprecation::NoDeprecated,
185 },
186 );
187
188 map.insert("hash".into(), async_graphql::registry::MetaInputValue {
189 name: "hash".into(),
190 description: None,
191 default_value: None,
192 ty: <Option<H256> as async_graphql::InputType>::create_type_info(registry),
193 visible: None,
194 inaccessible: false,
195 tags: vec![],
196 directive_invocations: vec![],
197 is_secret: false,
198 deprecation: async_graphql::registry::Deprecation::NoDeprecated,
199 });
200
201 map
202 },
203 visible: None,
204 inaccessible: false,
205 tags: vec![],
206 directive_invocations: vec![],
207 rust_typename: Some(::std::any::type_name::<Self>()),
208 oneof: false,
209 },
210 )
211 }
212
213 fn parse(value: Option<async_graphql::Value>) -> async_graphql::InputValueResult<Self> {
214 match value {
215 Some(val) => match val {
216 async_graphql::Value::Null => Ok(Self::Latest),
217 async_graphql::Value::Number(num) => match num.as_u64() {
218 Some(n) => Ok(Self::Number(EthereumBlockNumber::new(n))),
219 None => async_graphql::Result::Err(async_graphql::InputValueError::custom(format!(
220 "unexpected value {}",
221 num
222 ))),
223 },
224 async_graphql::Value::String(v) => <H256 as FromStr>::from_str(&v)
225 .map(Self::Hash)
226 .or_else(|e| {
227 if v.to_lowercase().eq("latest") {
228 Ok(Self::Latest)
229 } else {
230 Err(e)
231 }
232 })
233 .map_err(async_graphql::InputValueError::custom),
234 async_graphql::Value::Binary(bytes) => H256::try_from(&*bytes)
235 .map(Self::Hash)
236 .map_err(async_graphql::InputValueError::custom),
237 async_graphql::Value::Object(obj) => {
238 let number: Option<EthereumBlockNumber> =
239 async_graphql::InputType::parse(obj.get("number").cloned())
240 .map_err(async_graphql::InputValueError::propagate)?;
241 let hash: Option<H256> = async_graphql::InputType::parse(obj.get("hash").cloned())
242 .map_err(async_graphql::InputValueError::propagate)?;
243
244 if number.is_some() && hash.is_some() {
245 return async_graphql::Result::Err(async_graphql::InputValueError::custom(
246 "cannot travel number and hash simultaneously",
247 ));
248 }
249
250 if let Some(number) = number {
251 return async_graphql::Result::Ok(Self::Number(number));
252 }
253
254 if let Some(hash) = hash {
255 return async_graphql::Result::Ok(Self::Hash(hash));
256 }
257
258 async_graphql::Result::Err(async_graphql::InputValueError::expected_type(
259 async_graphql::Value::Object(obj),
260 ))
261 }
262 x => async_graphql::Result::Err(async_graphql::InputValueError::expected_type(x)),
263 },
264 None => async_graphql::Result::Err(async_graphql::InputValueError::expected_type(
265 value.unwrap_or_default(),
266 )),
267 }
268 }
269
270 fn to_value(&self) -> async_graphql::Value {
271 match self {
272 Self::Number(number) => <EthereumBlockNumber as async_graphql::InputType>::to_value(number),
273 Self::Hash(hash) => <H256 as async_graphql::InputType>::to_value(hash),
274 Self::Latest => async_graphql::Value::String("latest".into()),
275 }
276 }
277
278 fn as_raw_value(&self) -> Option<&Self::RawValueType> {
279 Some(self)
280 }
281}
282
283impl From<EthereumBlockNumber> for EthereumBlockId {
284 fn from(block_number: EthereumBlockNumber) -> Self {
285 EthereumBlockId::Number(block_number)
286 }
287}
288
289impl From<H256> for EthereumBlockId {
290 fn from(block_hash: H256) -> Self {
291 EthereumBlockId::Hash(block_hash)
292 }
293}
294
295#[derive(Default)]
297pub struct FullBlockQuery;
298
299#[crate::_impls(keep)]
300#[async_graphql::Object]
301impl FullBlockQuery {
302 #[allow(clippy::too_many_arguments)]
303 pub async fn full_blocks<'ctx>(
304 &self,
305 ctx: &async_graphql::Context<'ctx>,
306 block: Option<EthereumBlockId>,
307 first: Option<u64>,
308 skip: Option<u64>,
309 timestamp_from: Option<DateTime<Utc>>,
310 timestamp_to: Option<DateTime<Utc>>,
311 order_by: Option<GraphqlOrderBy<EthereumBlockInfoOrderBy>>,
312 order_direction: Option<::cido::__internal::GraphqlOrderDirection>,
313 ) -> async_graphql::Result<Vec<Arc<<EthereumNetwork as Network>::FullBlock>>> {
314 ctx
315 .data::<Arc<EthereumNetwork>>()?
316 .fetch_full_blocks(
317 block,
318 timestamp_from,
319 timestamp_to,
320 order_by,
321 order_direction,
322 first,
323 skip,
324 )
325 .await
326 .map_err(|e| {
327 tracing::error!(err=?e);
328 async_graphql::Error::new("Error fetching full blocks")
329 })
330 }
331}
332
333#[derive(Default)]
335pub struct BlockQuery;
336
337#[crate::_impls(keep)]
338#[async_graphql::Object]
339impl BlockQuery {
340 #[allow(clippy::too_many_arguments)]
341 pub async fn blocks<'ctx>(
342 &self,
343 ctx: &async_graphql::Context<'ctx>,
344 block: Option<EthereumBlockId>,
345 first: Option<u64>,
346 skip: Option<u64>,
347 timestamp_from: Option<DateTime<Utc>>,
348 timestamp_to: Option<DateTime<Utc>>,
349 timestamp_epoch_from: Option<i64>,
351 timestamp_epoch_to: Option<i64>,
353 order_by: Option<GraphqlOrderBy<EthereumBlockInfoOrderBy>>,
354 order_direction: Option<::cido::__internal::GraphqlOrderDirection>,
355 ) -> async_graphql::Result<Vec<EthereumBlockInfo>> {
356 ctx
357 .data::<Arc<EthereumNetwork>>()?
358 .fetch_blocks(
359 block,
360 timestamp_from
361 .or_else(|| timestamp_epoch_from.and_then(|t| DateTime::from_timestamp(t, 0))),
362 timestamp_to.or_else(|| timestamp_epoch_to.and_then(|t| DateTime::from_timestamp(t, 0))),
363 order_by,
364 order_direction,
365 first,
366 skip,
367 )
368 .await
369 .map_err(|e| {
370 tracing::error!(err=?e);
371 async_graphql::Error::new("Error fetching blocks")
372 })
373 }
374}
375
376pub struct EthereumMetaBlock {
377 number: EthereumBlockNumber,
378 hash: H256,
379 timestamp: u64,
380}
381
382#[async_graphql::Object(name = "_Block_")]
383impl EthereumMetaBlock {
384 async fn number(&self) -> i32 {
386 self.number.as_u64().try_into().unwrap()
387 }
388 async fn hash(&self) -> Option<H256> {
390 (self.hash != H256::default()).then_some(self.hash)
391 }
392 async fn timestamp(&self) -> Option<u64> {
394 (self.timestamp != 0).then_some(self.timestamp)
395 }
396}
397
398pub struct EthereumMetaExtension {
399 block: EthereumBlockNumber,
400}
401
402impl GraphqlMetaExtension<EthereumNetwork> for EthereumMetaExtension {
403 fn from_block(block: EthereumBlockNumber) -> Self {
404 Self { block }
405 }
406}
407
408#[crate::_impls(keep)]
409#[async_graphql::Object]
410impl EthereumMetaExtension {
411 pub async fn block(
416 &self,
417 ctx: &async_graphql::Context<'_>,
418 ) -> async_graphql::Result<EthereumMetaBlock> {
419 let hash_and_timestamp =
420 ctx.look_ahead().field("hash").exists() || ctx.look_ahead().field("timestamp").exists();
421
422 if !hash_and_timestamp {
423 return Ok(EthereumMetaBlock {
424 number: self.block,
425 hash: H256::default(),
426 timestamp: 0,
427 });
428 }
429
430 ctx
431 .data::<Arc<EthereumNetwork>>()?
432 .block_info_lookup(self.block.into())
433 .await
434 .map(|info| EthereumMetaBlock {
435 number: self.block,
436 hash: info.block_hash,
437 timestamp: info.timestamp.timestamp() as u64,
438 })
439 .map_err(Into::into)
440 }
441}
442
443#[test]
444fn test_block_id() {
445 use async_graphql::{InputType, value};
446 let number = EthereumBlockId::parse(Some(value!(123))).unwrap();
447 assert_eq!(
448 number,
449 EthereumBlockId::Number(EthereumBlockNumber::new(123))
450 );
451
452 let number = EthereumBlockId::parse(Some(value!({ "number": 123 }))).unwrap();
453 assert_eq!(
454 number,
455 EthereumBlockId::Number(EthereumBlockNumber::new(123))
456 );
457
458 let hash = EthereumBlockId::parse(Some(value!(
459 "0x0101010101010101010101010101010101010101010101010101010101010101"
460 )))
461 .unwrap();
462 assert_eq!(
463 hash,
464 EthereumBlockId::Hash(
465 H256::from_str("0x0101010101010101010101010101010101010101010101010101010101010101").unwrap()
466 )
467 );
468
469 let hash = EthereumBlockId::parse(Some(
470 value!({ "hash": "0x0101010101010101010101010101010101010101010101010101010101010101" }),
471 ))
472 .unwrap();
473 assert_eq!(
474 hash,
475 EthereumBlockId::Hash(
476 H256::from_str("0x0101010101010101010101010101010101010101010101010101010101010101").unwrap()
477 )
478 );
479}