cido 0.2.0

Core traits and implementations for indexing with cido
Documentation
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
//! Core traits/types for building a [`Cidomap`]
use core::fmt::Debug;
use core::fmt::Display;
use core::hash::Hash;
use core::ops::ControlFlow;
use core::str::FromStr;
use std::sync::Arc;

use chrono::{DateTime, Utc};
use futures::future::{BoxFuture, LocalBoxFuture};

use crate::context::{Context, GeneratorContext, PreprocessingContext};
use crate::graphql::{GraphqlInputType, GraphqlOutputType, LoaderConfig};
use crate::network::{EventOrder, Network};
use crate::prelude::CidomapError;
use crate::{DynError, stable_hash};

/// Trait for identifying a type primarily used for Transformers.
pub trait Identifiable: Debug {
  type Id: Debug
    + Ord
    + Hash
    + Clone
    + Sized
    + Send
    + Sync
    + Unpin
    + 'static
    + stable_hash::StableHash
    + sqlx::Type<sqlx::Postgres>
    + for<'a> sqlx::Encode<'a, sqlx::Postgres>
    + for<'a> sqlx::Decode<'a, sqlx::Postgres>
    + crate::filter::ToDbFilter;

  fn id(&self) -> &Self::Id;
}

// This trait is only to get around having a where clause on the BlockNumber
// trait so that we can enforce that FromStr::Err impls Display
pub trait FromStrErrDisplay: FromStr {
  type Error: Display;
  fn _from_str(s: &str) -> Result<Self, Self::Error>;
}

impl<T> FromStrErrDisplay for T
where
  T: FromStr,
  <T as FromStr>::Err: Display,
{
  type Error = <T as FromStr>::Err;
  fn _from_str(s: &str) -> Result<Self, Self::Error> {
    <Self as FromStr>::from_str(s)
  }
}

#[derive(
  Debug,
  Copy,
  Clone,
  PartialEq,
  Eq,
  PartialOrd,
  Ord,
  Hash,
  array_map::Indexable,
  sqlx::Type,
  serde::Serialize,
  serde::Deserialize,
)]
#[repr(i16)]
#[serde(rename_all = "lowercase")]
/// Tracks processing order as events flow through the system
pub enum ProcessingOrder {
  One = 1,
  Two = 2,
  Three = 3,
}

pub trait ProofOfIndexingImpl: stable_hash::StableHash + Identifiable {}

impl<T> ProofOfIndexingImpl for T
where
  T: Transformer + stable_hash::StableHash + Identifiable,
  <T as Identifiable>::Id: stable_hash::StableHash,
{
}

/// The main trait for interacting with the indexer for different types
///
/// Transformers (name could use some bikeshedding) are either entities or events and they implement
/// the ability to be stored to the database, read from the database, and all other traits for
/// interacting with cido internals.
pub trait Transformer:
  Sized
  + PartialEq
  + Hash
  + Send
  + Sync
  + Unpin
  + CachePolicy<Transformer = Self>
  + Clone
  + Debug
  + Default
  + Identifiable<Id: CacheKey<Self>>
  + ProofOfIndexingImpl
  + LoaderConfig
  + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>
  + crate::filter::ToDbFilter
  + 'static
{
  /// The cidomap this is tied to
  type Cidomap: Cidomap;
  /// For internal use - the graphql input type generated
  type GraphqlInput: GraphqlInputType<Transformer = Self>;
  /// For internal use - the graphql output type generated
  type GraphqlOutput: Default
    + TryFrom<Self, Error = DynError>
    + GraphqlOutputType<Transformer = Self>;

  /// Level that this struct is tracked at
  const TRACKING_LEVEL: TrackingLevel;
  /// Entity or Event
  const TRANSFORM_TYPE: RecordType;

  /// Ability store an iterator of these types efficiently to postgres
  fn bind_array<'q>(
    array: impl Iterator<Item = &'q Self> + Clone + Send + 'q,
    query: &mut sqlx::query_builder::Separated<'_, 'q, sqlx::Postgres, &'static str>,
  );

  /// Static descriptor used internally for interacting with the database
  fn table_descriptor() -> &'static crate::persistence::TableDescriptor<Self>;

  /// The name of this transformer
  fn name() -> &'static str;
}

/// Whether to track all different events, or only the last state in a block
///
/// Only Block is supported for now
#[non_exhaustive]
pub enum TrackingLevel {
  Block,
  Event,
}

/// Entity or Event
#[derive(Clone, Copy, sqlx::Type, PartialEq, Eq, Debug)]
#[repr(i32)]
#[non_exhaustive]
pub enum RecordType {
  Entity = 1,
  Event = 2,
}

impl RecordType {
  pub const fn spans_blocks(self) -> bool {
    matches!(self, Self::Entity)
  }
}

/// Trait used to query potentially all instances of a type from the database
pub trait Query: Send {
  /// Use to make the trait solver happy in the absence of equality in where clauses
  type Cidomap;
  /// The Transformer type this is querying
  type QueryType: Transformer<Cidomap = Self::Cidomap>;
  /// visits a Transformer and returns whether more visits should occur. Any state needs to be saved
  /// on the struct implementing this trait
  fn visit(&mut self, query_type: &Self::QueryType) -> core::ops::ControlFlow<()>;
}

/// Trait implemented by network trigger filter types so they can be spawned
pub trait ProcessingOrderFilter {
  type Network: Network;
  type Param: Send;
  const PROCESSING_ORDER: ProcessingOrder;
  fn new_filter(
    p: Self::Param,
  ) -> Result<<Self::Network as Network>::TriggerFilter, <Self::Network as Network>::Error>;
}

/// Determines if a trigger(event) is compatible with a type
pub trait TryFromTrigger: Sized {
  type Network: Network;

  /// Returns `None` if the provided trigger wasn't for Self and
  /// `Some(Err)` if the provided trigger was for Self but failed to parse
  fn try_from_trigger(
    trigger: &<Self::Network as Network>::Trigger,
    filter: &<Self::Network as Network>::TriggerFilter,
  ) -> Option<Result<Self, <Self::Network as Network>::Error>>;
}

/// Used in the `ParsingContext` trait to get information about the filter and possibly remove it
pub trait ParseContext<'a, C: Cidomap> {
  fn filter(&self) -> &<C::Network as Network>::TriggerFilter;
  fn remove_filter(self) -> impl core::future::Future<Output = Result<(), CidomapError>> + Send;
}

/// Can be implemented to change how errors parsing triggers are handled
pub trait ParsingContext<H: EventHandler>: Default + Send + Sync {
  fn handle_error<'a>(
    &'a mut self,
    ctx: impl ParseContext<'a, H::Cidomap>,
    error: <H::Cidomap as Cidomap>::Error,
  ) -> BoxFuture<'a, ControlFlow<<H::Cidomap as Cidomap>::Error, <H::Cidomap as Cidomap>::Error>>;
}

pub trait EventHandler: Debug + Send + Sync + Sized + 'static {
  type Cidomap: Cidomap;
  // The enum duplicate of this with only a single item per variant
  type Cacheless: Debug + Send + Sync + Sized + 'static;
  type ContextHandler: ParsingContext<Self>;

  /// Parses a trigger into the event enum that will be handed to the other functions
  fn parse_trigger(
    handler: &mut Self::ContextHandler,
    trigger: &<<Self::Cidomap as Cidomap>::Network as Network>::Trigger,
    filter: &<<Self::Cidomap as Cidomap>::Network as Network>::TriggerFilter,
  ) -> Result<Self::Cacheless, <Self::Cidomap as Cidomap>::Error>;

  /// Actually handle an event
  fn event_handler(
    self,
    ctx: Context<'_, Self::Cidomap>,
    meta: MetaEvent<Self::Cidomap>,
  ) -> impl Future<Output = Result<(), <Self::Cidomap as Cidomap>::Error>>;

  /// Spawn off more triggers to look for
  fn event_source<'a>(
    &'a self,
    ctx: GeneratorContext<'a, Self::Cidomap>,
    meta: &'a MetaEvent<Self::Cidomap>,
  ) -> impl Future<Output = Result<(), <Self::Cidomap as Cidomap>::Error>>;

  /// Do network calls before the event is handled by the event_handler
  /// Each future is created serially and done in event order and then they
  /// are all polled simultaneously to complete. This guarantee allows extra
  /// flexibility in using the preprocessor. This ensures that the first time
  /// an entity is encountered that it is the true first time encountering it
  /// and there is no need to worry about concurrent execution ordering.
  fn event_preprocessor<'a>(
    ctx: &'a PreprocessingContext<Self::Cidomap>,
    meta: MetaEvent<Self::Cidomap>,
    event: Self::Cacheless,
  ) -> either::Either<
    BoxFuture<'a, Result<(MetaEvent<Self::Cidomap>, Self), <Self::Cidomap as Cidomap>::Error>>,
    Result<(MetaEvent<Self::Cidomap>, Self), <Self::Cidomap as Cidomap>::Error>,
  >;
}

/// Internal
pub trait TypeIter<C: Cidomap> {
  fn next_type<E: Transformer<Cidomap = C>>(&mut self) -> Result<(), CidomapError>;
}

/// Internal
pub trait AsyncTypeIter<C: Cidomap>: Send {
  fn next_type<E: Transformer<Cidomap = C>>(&mut self) -> BoxFuture<'_, Result<(), CidomapError>>;
}

/// Trait for tying everything together.
pub trait Cidomap: 'static + Send + Sync + Sized + core::fmt::Debug + Clone {
  /// The error type that can be returned from handler functions
  type Error: std::error::Error + Send + Sync + 'static;
  /// Network that this cidomap works with
  type Network: crate::network::Network;
  /// Struct that has `#[event_handler]` annotated on it
  type Events: EventHandler<Cidomap = Self>;
  /// Used internally
  type Graphql: 'static + Send + Sync + Default + async_graphql::ObjectType;
  /// Max processing order allowed. This defaults to 1 and must be overridden otherwise
  const MAX_PROCESSING_ORDER: ProcessingOrder;
  /// Block to start indexing from
  const START_BLOCK: <Self::Network as Network>::BlockNumber;
  /// Block to stop indexing at
  const MAX_BLOCK: Option<<Self::Network as Network>::BlockNumber> = None;
  /// Used internally
  fn type_iter<T: TypeIter<Self>>(t: &mut T) -> Result<(), CidomapError>;
  /// Used internally
  fn async_type_iter<T: AsyncTypeIter<Self>>(t: &mut T) -> BoxFuture<'_, Result<(), CidomapError>>;
  /// Initial filters to start the cidomap with
  fn initial_filters()
  -> Result<Vec<<Self::Network as Network>::TriggerFilter>, <Self::Network as Network>::Error>;
  /// Called once at cidomap initialization. Can be used to initialize entities/events that will only be created once
  fn init<'a>(ctx: Context<'a, Self>) -> LocalBoxFuture<'a, Result<(), Self::Error>> {
    let _ = ctx;
    Box::pin(futures::future::ok(()))
  }

  /// Called every time the cidomap starts up or has to do a rollback
  ///
  /// This function needs to be idempotent as it is called anytime there is a rollback so that any extra
  /// state that may be stored can be cleared and re-synced. If this doesn't happen there are subtle bugs
  /// that will arise and they are very difficult to debug.
  fn create<'a>(ctx: Context<'a, Self>) -> LocalBoxFuture<'a, Result<(), Self::Error>> {
    let _ = ctx;
    Box::pin(futures::future::ok(()))
  }
}

/// Defines how caching of entities and events is handled
pub trait CachePolicy {
  /// Related Transformer
  type Transformer: Transformer;
  /// The type of key used when looking up the cache. This will be the same as the `id` field if using a default policy
  type CacheId: Ord + Hash + Sized + Clone + Send + Sync + Debug + 'static;
  /// Struct to use for loading the cache. This could have a custom implementation to only load the rows required from the database
  type CacheLoader: CacheLoader<Self::Transformer>;
  /// Indicates whether the database should be queried on a cache miss
  const QUERY_DATABASE: bool;
  /// Indicates whether the database should be queried on a cache miss if the size of the cache is less than `SIZE`
  /// Only has an effect if `QUERY_DATABASE` is true
  const QUERY_DATABASE_IF_NOT_FULL: bool;
  /// Indicates whether the database should be queried to load the cache initially
  const LOAD_DATABASE: bool;
  /// Whether or not to load most recent values from the database first
  const LOAD_MOST_RECENT_FIRST: bool;
  /// When the cache surpasses `SIZE` it will be reduced back down to `SIZE`
  /// setting this to 0 clears it out after each block group. Defaults to 5000.
  const SIZE: usize;
  /// When the cache surpasses `MAX_SIZE` it will be reduced back down to `SIZE`
  /// setting this to 0 makes it so that nothing is ever cached.
  /// Defaults to SIZE + max(SIZE, 100) / 10; if SIZE is default that would be 5_500.
  const MAX_SIZE: usize;
}

/// Allows effecient cache lookups with minimal cloning
///
/// This is similar to the `Equivalent` trait
///
/// Default implementations have been provided and should be overridden
/// if either to_cache_id() or to_id() are costly
pub trait CacheKey<T: Transformer> {
  /// Determines if this is equivalent to the CacheId
  fn eq_key(&self, cache_id: &<T as CachePolicy>::CacheId) -> bool {
    self.to_cache_id() == *cache_id
  }
  /// Used for hashing the key
  fn hash_cache<H: std::hash::Hasher>(&self, state: &mut H) {
    <<T as CachePolicy>::CacheId as std::hash::Hash>::hash(&self.to_cache_id(), state);
  }
  /// Makes an Id
  fn to_id(&self) -> <T as Identifiable>::Id;
  //// Compares the Id to ensure matching works when the CacheId isn't the same as the Id
  fn eq_id(&self, id: &<T as Identifiable>::Id) -> bool {
    self.to_id() == *id
  }
  /// Makes a CacheId
  fn to_cache_id(&self) -> <T as CachePolicy>::CacheId;
}

impl<'a, T: Transformer, C: CacheKey<T>> CacheKey<T> for &'a C {
  fn eq_key(&self, cache_id: &<T as CachePolicy>::CacheId) -> bool {
    (*self).eq_key(cache_id)
  }
  fn hash_cache<H: std::hash::Hasher>(&self, state: &mut H) {
    (*self).hash_cache(state)
  }
  fn to_id(&self) -> <T as Identifiable>::Id {
    (*self).to_id()
  }
  fn eq_id(&self, id: &<T as Identifiable>::Id) -> bool {
    (*self).eq_id(id)
  }
  fn to_cache_id(&self) -> <T as CachePolicy>::CacheId {
    (*self).to_cache_id()
  }
}

/// Used internally to interact tag an event order to a record for storing in the database
pub trait RecordEvent<EO: EventOrder, T: Identifiable> {
  fn event_order(&self) -> EO;
  fn record(&self) -> &T;
}

/// Load instances from the database while being flexible enough to stop early.
pub trait CacheLoader<T: Transformer>: Sized + Send {
  fn new(
    first: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> Self;
  // Called before inserting a value into the cache. If this returns `Break` then the value will be added to the cache based on the value of the returned boolean.
  fn continue_loading(
    &mut self,
    t: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> ::core::ops::ControlFlow<bool>;
}

/// Used for `always` cache policy
impl<T: Transformer> CacheLoader<T> for () {
  fn new(
    _: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> Self {
  }

  fn continue_loading(
    &mut self,
    _: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> core::ops::ControlFlow<bool> {
    ::core::ops::ControlFlow::Continue(())
  }
}

/// Used for `never` cache policy
impl<T: Transformer> CacheLoader<T> for ::core::convert::Infallible {
  fn new(
    _: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> Self {
    unreachable!()
  }
  fn continue_loading(
    &mut self,
    _: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> core::ops::ControlFlow<bool> {
    ::core::ops::ControlFlow::Break(false)
  }
}

/// Used for `lru` cache policy
impl<T: Transformer> CacheLoader<T> for usize {
  fn new(
    _: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> Self {
    0
  }
  fn continue_loading(
    &mut self,
    _: &dyn RecordEvent<<<T::Cidomap as Cidomap>::Network as Network>::EventOrder, T>,
  ) -> core::ops::ControlFlow<bool> {
    *self += 1;
    if *self >= <T as CachePolicy>::SIZE {
      core::ops::ControlFlow::Break(true)
    } else {
      core::ops::ControlFlow::Continue(())
    }
  }
}

/// Information about an event like the time it was created, where in the blockchain it was created
/// the block it came from and information about the trigger that matched it
pub struct MetaEvent<C: Cidomap> {
  trigger: <C::Network as Network>::Trigger,
  timestamp: DateTime<Utc>,
  trigger_processing_order: u8,
  order: <C::Network as Network>::EventOrder,
  block: Arc<<C::Network as Network>::FullBlock>,
}

impl<C: Cidomap> MetaEvent<C> {
  pub fn new(
    trigger: <C::Network as Network>::Trigger,
    timestamp: DateTime<Utc>,
    trigger_processing_order: u8,
    order: <C::Network as Network>::EventOrder,
    block: Arc<<C::Network as Network>::FullBlock>,
  ) -> Self {
    Self {
      trigger,
      timestamp,
      trigger_processing_order,
      order,
      block,
    }
  }

  #[inline]
  /// Returns the trigger that matched this event
  pub fn trigger(&self) -> &<C::Network as Network>::Trigger {
    &self.trigger
  }

  pub(crate) fn trigger_processing_order(&self) -> u8 {
    self.trigger_processing_order
  }

  #[inline]
  /// Returns the block number of this event
  pub fn block_number(&self) -> <C::Network as Network>::BlockNumber {
    self.order.block_number()
  }

  #[inline]
  /// Returns the timestamp of this event
  pub fn timestamp(&self) -> DateTime<Utc> {
    self.timestamp
  }

  #[inline]
  /// Returns the event order of this event
  pub fn event_order(&self) -> <C::Network as Network>::EventOrder {
    self.order
  }

  #[inline]
  /// Returns the block related to this event
  pub fn block(&self) -> &Arc<<C::Network as Network>::FullBlock> {
    &self.block
  }
}