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 }