/ starknet / src / core.rs
core.rs
  1  use std::fmt::{Debug, Display};
  2  
  3  use apibara_core::{node::v1alpha2::Cursor, starknet::v1alpha2};
  4  use starknet::core::types::{FieldElement, FromByteArrayError};
  5  
  6  #[derive(Debug, Copy, Clone, PartialEq)]
  7  pub struct BlockHash([u8; 32]);
  8  
  9  /// Global identifier for blocks.
 10  #[derive(Copy, Clone, PartialEq)]
 11  pub struct GlobalBlockId(u64, BlockHash);
 12  
 13  pub type IngestionMessage = apibara_node::stream::IngestionMessage<GlobalBlockId>;
 14  
 15  #[derive(Debug, thiserror::Error)]
 16  #[error("invalid block hash size")]
 17  pub struct InvalidBlockHashSize {
 18      pub expected: usize,
 19      pub actual: usize,
 20  }
 21  
 22  #[derive(Debug, thiserror::Error)]
 23  pub enum InvalidBlock {
 24      #[error("missing block header")]
 25      MissingHeader,
 26      #[error("missing block hash")]
 27      MissingHash,
 28      #[error(transparent)]
 29      InvalidHash(#[from] InvalidBlockHashSize),
 30  }
 31  
 32  impl BlockHash {
 33      pub fn zero() -> Self {
 34          BlockHash([0; 32])
 35      }
 36  
 37      pub fn is_zero(&self) -> bool {
 38          *self == Self::zero()
 39      }
 40  
 41      pub fn from_slice(b: &[u8]) -> Result<Self, InvalidBlockHashSize> {
 42          if b.len() != 32 {
 43              return Err(InvalidBlockHashSize {
 44                  expected: 32,
 45                  actual: b.len(),
 46              });
 47          }
 48          let mut out = [0; 32];
 49          out.copy_from_slice(b);
 50          Ok(BlockHash(out))
 51      }
 52  
 53      pub fn as_bytes(&self) -> &[u8] {
 54          &self.0
 55      }
 56  
 57      pub fn into_bytes(self) -> [u8; 32] {
 58          self.0
 59      }
 60  }
 61  
 62  impl GlobalBlockId {
 63      pub fn new(number: u64, hash: BlockHash) -> Self {
 64          GlobalBlockId(number, hash)
 65      }
 66  
 67      pub fn from_u64(number: u64) -> Self {
 68          Self::new(number, BlockHash::zero())
 69      }
 70  
 71      pub fn from_cursor(cursor: &Cursor) -> Result<Self, InvalidBlockHashSize> {
 72          let hash = if cursor.unique_key.is_empty() {
 73              BlockHash::zero()
 74          } else {
 75              BlockHash::from_slice(&cursor.unique_key)?
 76          };
 77          Ok(Self::new(cursor.order_key, hash))
 78      }
 79  
 80      pub fn from_block(block: &v1alpha2::Block) -> Result<Self, InvalidBlock> {
 81          let header = block.header.as_ref().ok_or(InvalidBlock::MissingHeader)?;
 82          Self::from_block_header(header)
 83      }
 84  
 85      pub fn from_block_header(header: &v1alpha2::BlockHeader) -> Result<Self, InvalidBlock> {
 86          let hash = header
 87              .block_hash
 88              .as_ref()
 89              .ok_or(InvalidBlock::MissingHash)?;
 90          let hash = hash.into();
 91          Ok(Self::new(header.block_number, hash))
 92      }
 93  
 94      pub fn from_block_header_parent(header: &v1alpha2::BlockHeader) -> Result<Self, InvalidBlock> {
 95          let hash = header
 96              .parent_block_hash
 97              .as_ref()
 98              .ok_or(InvalidBlock::MissingHash)?;
 99          let hash = hash.into();
100          Ok(Self::new(header.block_number - 1, hash))
101      }
102  
103      pub fn number(&self) -> u64 {
104          self.0
105      }
106  
107      pub fn hash(&self) -> &BlockHash {
108          &self.1
109      }
110  
111      /// Returns a cursor corresponding to the block id.
112      pub fn to_cursor(&self) -> Cursor {
113          Cursor {
114              order_key: self.number(),
115              unique_key: self.hash().as_bytes().to_vec(),
116          }
117      }
118  }
119  
120  impl From<v1alpha2::FieldElement> for BlockHash {
121      fn from(felt: v1alpha2::FieldElement) -> Self {
122          (&felt).into()
123      }
124  }
125  
126  impl From<&v1alpha2::FieldElement> for BlockHash {
127      fn from(felt: &v1alpha2::FieldElement) -> Self {
128          BlockHash(felt.to_bytes())
129      }
130  }
131  
132  impl TryFrom<&BlockHash> for FieldElement {
133      type Error = FromByteArrayError;
134  
135      fn try_from(value: &BlockHash) -> Result<Self, Self::Error> {
136          FieldElement::from_bytes_be(&value.0)
137      }
138  }
139  
140  impl From<&BlockHash> for v1alpha2::FieldElement {
141      fn from(hash: &BlockHash) -> Self {
142          Self::from_bytes(&hash.0)
143      }
144  }
145  
146  impl Display for GlobalBlockId {
147      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148          let hash = hex::encode(self.hash().as_bytes());
149          write!(f, "{}/0x{}", self.number(), hash)
150      }
151  }
152  
153  impl Debug for GlobalBlockId {
154      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155          write!(f, "GBI({})", self)
156      }
157  }
158  
159  impl Default for GlobalBlockId {
160      fn default() -> Self {
161          Self::new(0, BlockHash::zero())
162      }
163  }
164  
165  impl apibara_node::core::Cursor for GlobalBlockId {
166      fn from_proto(cursor: &Cursor) -> Option<Self> {
167          GlobalBlockId::from_cursor(cursor).ok()
168      }
169  
170      fn to_proto(&self) -> Cursor {
171          self.to_cursor()
172      }
173  }