cido 0.2.0

Core traits and implementations for indexing with cido
Documentation
use crate::cidomap::FromStrErrDisplay;
use crate::graphql::GraphqlMetaExtension;
use chrono::{DateTime, Utc};
use core::fmt::{Debug, Display};
use core::hash::Hash;
use core::ops::Range;
use serde::Deserialize;
use serde::{Serialize, de::DeserializeOwned};
use sqlx::PgPool;
use std::sync::Arc;

pub(crate) mod sealed {
  // this can't be exported
  pub trait Sealed {}
}

/// A single identifier for a block.
///
/// This will generally be something like a u64, but each network implementation is able to determine
/// what semantics this has.
pub trait BlockNumber:
  Send
  + Sync
  + Unpin
  + Copy
  + Ord
  + Display
  + Debug
  + Hash
  + Sized
  + async_graphql::InputType
  + async_graphql::OutputType
  + sqlx::Type<sqlx::Postgres>
  + for<'a> sqlx::Decode<'a, sqlx::Postgres>
  + for<'a> sqlx::Encode<'a, sqlx::Postgres>
  + sqlx::postgres::PgHasArrayType
  // should saturate to Self::MAX
  + core::ops::Add<u16, Output = Self>
  // should saturate to Self::ZERO
  + core::ops::Sub<u16, Output = Self>
  + FromStrErrDisplay
  + Serialize
  + DeserializeOwned
  + 'static
  // TODO: see if we can remove this
  + Into<u64>
{
  const ZERO: Self;
  const MAX: Self;
  type EventOrder: EventOrder<BlockNumber = Self>;
  type Range: BlockNumberRange<BlockNumber = Self>;
  fn event_order(self) -> Self::EventOrder;
}

/// A range of block numbers.
///
/// The lower bound is inclusive while the upper bound is exclusive `[)`
pub trait BlockNumberRange:
  Send
  + Sync
  + Sized
  + Unpin
  + Copy
  + Debug
  + Eq
  + Default
  + 'static
  + sqlx::Type<sqlx::Postgres>
  + for<'a> sqlx::Encode<'a, sqlx::Postgres>
  + for<'a> sqlx::Decode<'a, sqlx::Postgres>
  + sqlx::postgres::PgHasArrayType
{
  type BlockNumber: BlockNumber<Range = Self>;
  /// (Lower bound of the range inclusive, upper bound of the range exclusive)
  fn bounds(self) -> (Self::BlockNumber, Option<Self::BlockNumber>);
  fn new(lower: Self::BlockNumber, upper: Option<Self::BlockNumber>) -> Self;
}

/// Any of several unique identifiers for a block
///
/// This should include a blocknumber, but can also include a hash, parent hash, timestamp, etc.
/// as long as they are unique
pub trait BlockId:
  Send
  + Sync
  + Unpin
  + Display
  + Debug
  + Default
  + Ord
  + Hash
  + Sized
  + async_graphql::InputType
  + 'static
{
  type Network: Network;

  fn latest() -> impl Future<Output = Result<Self, <Self::Network as Network>::Error>> + Send;

  fn to_block_number(
    self,
    network: &Self::Network,
  ) -> impl Future<
    Output = Result<<Self::Network as Network>::BlockNumber, <Self::Network as Network>::Error>,
  > + Send;
}

/// Like `AsRef` this is used to cast a type or trait to a block number
pub trait AsBlockNumber {
  type BlockNumber: BlockNumber;
  fn as_block_number(&self) -> Self::BlockNumber;
}

/// Like `AsRef` this is used to cast a type or trait to an event order
pub trait AsEventOrder {
  type EventOrder: EventOrder;
  fn as_event_order(&self) -> Self::EventOrder;
}

/// Represents order of events within a blockchain
///
/// All events are processed according to this ordering
///
/// A blocknumber is representable, while other events like transactions, calls, and logs are should
/// have a single deterministic ordering within the blockchain
pub trait EventOrder:
  Copy
  + Ord
  + Display
  + Debug
  + Hash
  + From<Self::BlockNumber>
  + sqlx::Type<sqlx::Postgres>
  + for<'a> sqlx::Decode<'a, sqlx::Postgres>
  + for<'a> sqlx::Encode<'a, sqlx::Postgres>
  + Sized
  + Unpin
  + Send
  + Sync
  + 'static
{
  type BlockNumber: BlockNumber;
  fn block_number(self) -> Self::BlockNumber;
}

/// Represents a block in the blockchain
pub trait Block: Debug + Sized + Send + Sync + 'static {
  type BlockNumber: BlockNumber;

  fn to_timestamp(&self) -> DateTime<Utc>;
  fn block_number(&self) -> Self::BlockNumber;
}

/// Generates events from the blockchain
pub trait BlockGenerator: Sized + Send + 'static {
  type Network: Network;

  fn new(
    network: Arc<Self::Network>,
  ) -> impl Future<Output = Result<Self, <Self::Network as Network>::Error>> + Send;

  fn get_blocks(
    &mut self,
    request: BlockRequest<<Self::Network as Network>::BlockNumber>,
  ) -> impl Future<
    Output = Result<
      Vec<Arc<<Self::Network as Network>::FullBlock>>,
      <Self::Network as Network>::Error,
    >,
  > + Send;

  fn clear_cache(
    &mut self,
    before: <Self::Network as Network>::BlockNumber,
  ) -> impl Future<Output = Result<(), <Self::Network as Network>::Error>> + Send;

  fn rollback_to_block(
    &mut self,
    block_number: <Self::Network as Network>::BlockNumber,
  ) -> impl Future<Output = Result<(), <Self::Network as Network>::Error>> + Send;
}

/// Used in the TriggerGenerator
pub trait TriggerFilterStorage<N: Network>: Send + Sync + 'static + sealed::Sealed + Debug {
  fn id(&self) -> uuid::Uuid;
  fn block_number(&self) -> N::BlockNumber;
  fn filter(&self) -> &N::TriggerFilter;
}

/// Generates events that are used in processing the blockchain
pub trait TriggerGenerator<S: TriggerFilterStorage<Self::Network>>:
  Send + 'static + Sized + Debug
{
  type Network: Network;

  fn new(
    network: Arc<Self::Network>,
  ) -> impl Future<Output = Result<Self, <Self::Network as Network>::Error>> + Send;

  fn new_trigger_filter(&mut self, filter: Arc<S>)
  -> Result<(), <Self::Network as Network>::Error>;

  fn remove_trigger_filter(&mut self, filter: &S) -> Result<(), <Self::Network as Network>::Error>;

  fn get_triggers(
    &mut self,
    range: BlockRange<<Self::Network as Network>::BlockNumber>,
  ) -> impl Future<
    Output = Result<
      Vec<(<Self::Network as Network>::Trigger, Arc<S>)>,
      <Self::Network as Network>::Error,
    >,
  > + Send;
}

/// Trait that ties all network interactions with a Cidomap together.
pub trait Network: Send + Sync + 'static {
  type Error: Send + Sync + std::error::Error + 'static;
  type InitConfig: Send + Sync + 'static;
  type FullBlock: Block<BlockNumber = Self::BlockNumber>;
  type BlockId: BlockId<Network = Self>;
  type BlockNumber: BlockNumber<Range = Self::BlockNumberRange, EventOrder = Self::EventOrder>;
  type BlockNumberRange: BlockNumberRange<BlockNumber = Self::BlockNumber>;
  type BlockGenerator: BlockGenerator<Network = Self>;
  type TriggerGenerator<S: TriggerFilterStorage<Self>>: TriggerGenerator<S, Network = Self>;
  type TriggerFilter: Send + Sync + Clone + Debug + Serialize + DeserializeOwned + 'static;
  type Trigger: Send
    + Sync
    + Debug
    + AsEventOrder<EventOrder = Self::EventOrder>
    + AsBlockNumber<BlockNumber = Self::BlockNumber>
    + 'static;
  type EventOrder: EventOrder<BlockNumber = Self::BlockNumber>;
  // todo: make an empty type for this
  type GraphqlMetaExtension: GraphqlMetaExtension<Self>;

  fn network_identifier() -> &'static str;

  /// Amount of concurrent calls allowed to the network
  fn network_concurrency(&self) -> usize;

  /// Called everytime the executable starts up
  fn create(
    config: Self::InitConfig,
    db_pool: PgPool,
  ) -> impl Future<Output = Result<Arc<Self>, Self::Error>> + Send;

  /// Only called once at cidomap creation
  fn init(
    &self,
    transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
  ) -> impl Future<Output = Result<(), Self::Error>>;

  /// Should return the latest block as quickly as possible
  fn chain_head_block(&self)
  -> impl Future<Output = Result<Self::BlockNumber, Self::Error>> + Send;

  /// Should only be called when you want to wait for the latest block to change from the provided block.
  fn wait_for_latest_block(
    &self,
    current_block: Self::BlockNumber,
  ) -> impl Future<Output = Result<LatestBlock<Self::BlockNumber>, Self::Error>> + Send;

  /// persist values up to and excluding `block`
  fn persist_to_block(
    &self,
    block: Self::BlockNumber,
  ) -> impl Future<Output = Result<(), Self::Error>> + Send;

  /// delete values back to and excluding `block`
  fn rollback_to_block(
    &self,
    block: Self::BlockNumber,
  ) -> impl Future<Output = Result<(), Self::Error>> + Send;
}

/// Used in `Network::wait_for_latest_block`
///
/// Represents the latest block available in the blockchain, along with a block to rollback to if
/// a fork was detected.
#[derive(Debug, Clone)]
pub struct LatestBlock<B: BlockNumber> {
  /// If present, the consumer of this chain should rollback up to the specified block (included).
  rollback_to: Option<B>,
  /// the block that the should be advanced to. This may or may not be the actual latest block
  /// depending on where the indexer is
  advance_to: B,
}

impl<B: BlockNumber> LatestBlock<B> {
  pub fn new(advance_to: B) -> Self {
    Self {
      advance_to,
      rollback_to: None,
    }
  }
  pub fn with_rollback(mut self, rollback_to: B) -> Self {
    assert!(
      rollback_to <= self.advance_to,
      "rollback_to {rollback_to} is <= advance_to {}",
      self.advance_to
    );
    self.rollback_to = Some(rollback_to);
    self
  }
  pub fn advance_to(&self) -> B {
    self.advance_to
  }
  pub fn rollback_to(&self) -> Option<B> {
    self.rollback_to
  }
}

/// Used in `BlockGenerator::get_blocks` to specify either a contiguous range or specific blocks
#[derive(Debug, Clone)]
pub enum BlockRequest<B: BlockNumber> {
  Range(BlockRange<B>),
  Sparse(Vec<B>),
}

impl<B: BlockNumber> From<Range<B>> for BlockRequest<B> {
  fn from(r: Range<B>) -> Self {
    Self::Range(r.into())
  }
}

impl<B: BlockNumber> From<BlockRange<B>> for BlockRequest<B> {
  fn from(r: BlockRange<B>) -> Self {
    Self::Range(r)
  }
}

impl<B: BlockNumber> From<Vec<B>> for BlockRequest<B> {
  fn from(s: Vec<B>) -> Self {
    Self::Sparse(s)
  }
}

/// A range of block numbers
///
/// Differs from BlockNumberRange in that it doesn't have to be [inclusive, exclusive)
#[derive(Clone, PartialEq, Eq)]
pub struct BlockRange<B: BlockNumber>(pub Range<B>);

impl<B: BlockNumber> BlockRange<B> {
  pub fn to_block_number_range(&self) -> B::Range {
    <B::Range as BlockNumberRange>::new(self.start, Some(self.end))
  }
}

impl<B: BlockNumber> From<Range<B>> for BlockRange<B> {
  fn from(r: Range<B>) -> Self {
    Self(r)
  }
}

impl<B: BlockNumber> core::fmt::Debug for BlockRange<B> {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    self.0.fmt(f)
  }
}

impl<B: BlockNumber> core::fmt::Display for BlockRange<B> {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    self.0.fmt(f)
  }
}

impl<B: BlockNumber> core::ops::Deref for BlockRange<B> {
  type Target = Range<B>;
  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

#[derive(Deserialize, Serialize, Debug)]
pub struct ChainTrackerResponse<T: Block> {
  /// Last available stored block.
  pub last_block_stored: T,
  /// Last block available from the source of truth.
  ///
  /// Usually the block number of `last_block_stored` should be equal to this number
  /// unless the tracker is lagging the blockchain for some reason.
  pub last_block_in_chain: T::BlockNumber,
}