Skip to content

Commit 1778c42

Browse files
xJonathanLEIincrypto32
authored andcommitted
feat(starknet): move away from FieldElement for data source
Currently, the native `FieldElement` type is used for Starknet data sources, which uses the Montgomery representation internally for fast finite field mulitplication, at the cost of slower (de)serialization. However, in `graph-node`, this type is only used for equality comparison, which calls for a type that's backed by a `u8` array instead. In fact, the `graph::blockchain::DataSource` trait requires the return type of its `address()` method to be `Option<&[u8]>`, which necessitates the use of `u8`-backed types (currently the Starknet data source simply returnes `None`). This commit adds a new type `Felt` that's a wrapper around `[u8; 32]`, and replaces any `FieldElement` occurrence. The `starknet-ff` dependency is also removed.
1 parent ad638b7 commit 1778c42

File tree

6 files changed

+114
-158
lines changed

6 files changed

+114
-158
lines changed

Cargo.lock

Lines changed: 0 additions & 122 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chain/starknet/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ prost = { workspace = true }
1313
prost-types = { workspace = true }
1414
serde = "1.0"
1515
sha3 = "0.10.8"
16-
starknet-ff = "0.3.6"
1716

1817
graph-runtime-wasm = { path = "../../runtime/wasm" }
1918
graph-runtime-derive = { path = "../../runtime/derive" }

chain/starknet/src/data_source.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ use graph::{
77
semver,
88
};
99
use sha3::{Digest, Keccak256};
10-
use starknet_ff::FieldElement;
1110
use std::{collections::HashSet, sync::Arc};
1211

1312
use crate::{
1413
chain::Chain,
1514
codec,
15+
felt::Felt,
1616
trigger::{StarknetEventTrigger, StarknetTrigger},
1717
};
1818

@@ -49,8 +49,8 @@ pub struct UnresolvedDataSource {
4949
pub struct Source {
5050
pub start_block: BlockNumber,
5151
pub end_block: Option<BlockNumber>,
52-
#[serde(default, deserialize_with = "deserialize_address")]
53-
pub address: Option<FieldElement>,
52+
#[serde(default)]
53+
pub address: Option<Felt>,
5454
}
5555

5656
#[derive(Deserialize)]
@@ -71,7 +71,7 @@ pub struct MappingBlockHandler {
7171
#[derive(Clone, PartialEq, Eq, Deserialize)]
7272
pub struct MappingEventHandler {
7373
pub handler: String,
74-
pub event_selector: FieldElement,
74+
pub event_selector: Felt,
7575
}
7676

7777
#[derive(Clone, Deserialize)]
@@ -92,7 +92,7 @@ impl blockchain::DataSource<Chain> for DataSource {
9292
}
9393

9494
fn address(&self) -> Option<&[u8]> {
95-
None
95+
self.source.address.as_ref().map(|addr| addr.as_ref())
9696
}
9797

9898
fn start_block(&self) -> BlockNumber {
@@ -218,11 +218,11 @@ impl DataSource {
218218
/// if event.fromAddr matches the source address. Note this only supports the default
219219
/// Starknet behavior of one key per event.
220220
fn handler_for_event(&self, event: &StarknetEventTrigger) -> Option<MappingEventHandler> {
221-
let event_key = FieldElement::from_byte_slice_be(event.event.keys.first()?).ok()?;
221+
let event_key: Felt = Self::pad_to_32_bytes(event.event.keys.first()?)?.into();
222222

223-
// Always deocding first here seems fine as we expect most sources to define an address
223+
// Always padding first here seems fine as we expect most sources to define an address
224224
// filter anyways. Alternatively we can use lazy init here, which seems unnecessary.
225-
let event_from_addr = FieldElement::from_byte_slice_be(&event.event.from_addr).ok()?;
225+
let event_from_addr: Felt = Self::pad_to_32_bytes(&event.event.from_addr)?.into();
226226

227227
return self
228228
.mapping
@@ -241,6 +241,18 @@ impl DataSource {
241241
})
242242
.cloned();
243243
}
244+
245+
/// We need to pad incoming event selectors and addresses to 32 bytes as our data source uses
246+
/// padded 32 bytes.
247+
fn pad_to_32_bytes(slice: &[u8]) -> Option<[u8; 32]> {
248+
if slice.len() > 32 {
249+
None
250+
} else {
251+
let mut buffer = [0u8; 32];
252+
buffer[(32 - slice.len())..].copy_from_slice(slice);
253+
Some(buffer)
254+
}
255+
}
244256
}
245257

246258
#[async_trait]
@@ -314,16 +326,16 @@ impl blockchain::UnresolvedDataSourceTemplate<Chain> for UnresolvedDataSourceTem
314326

315327
// Adapted from:
316328
// https://github.com/xJonathanLEI/starknet-rs/blob/f16271877c9dbf08bc7bf61e4fc72decc13ff73d/starknet-core/src/utils.rs#L110-L121
317-
fn get_selector_from_name(func_name: &str) -> graph::anyhow::Result<FieldElement> {
329+
fn get_selector_from_name(func_name: &str) -> graph::anyhow::Result<Felt> {
318330
const DEFAULT_ENTRY_POINT_NAME: &str = "__default__";
319331
const DEFAULT_L1_ENTRY_POINT_NAME: &str = "__l1_default__";
320332

321333
if func_name == DEFAULT_ENTRY_POINT_NAME || func_name == DEFAULT_L1_ENTRY_POINT_NAME {
322-
Ok(FieldElement::ZERO)
334+
Ok([0u8; 32].into())
323335
} else {
324336
let name_bytes = func_name.as_bytes();
325337
if name_bytes.is_ascii() {
326-
Ok(starknet_keccak(name_bytes))
338+
Ok(starknet_keccak(name_bytes).into())
327339
} else {
328340
Err(anyhow!("the provided name contains non-ASCII characters"))
329341
}
@@ -332,7 +344,7 @@ fn get_selector_from_name(func_name: &str) -> graph::anyhow::Result<FieldElement
332344

333345
// Adapted from:
334346
// https://github.com/xJonathanLEI/starknet-rs/blob/f16271877c9dbf08bc7bf61e4fc72decc13ff73d/starknet-core/src/utils.rs#L98-L108
335-
fn starknet_keccak(data: &[u8]) -> FieldElement {
347+
fn starknet_keccak(data: &[u8]) -> [u8; 32] {
336348
let mut hasher = Keccak256::new();
337349
hasher.update(data);
338350
let mut hash = hasher.finalize();
@@ -341,12 +353,5 @@ fn starknet_keccak(data: &[u8]) -> FieldElement {
341353
hash[0] &= 0b00000011;
342354

343355
// Because we know hash is always 32 bytes
344-
FieldElement::from_bytes_be(unsafe { &*(hash[..].as_ptr() as *const [u8; 32]) }).unwrap()
345-
}
346-
347-
fn deserialize_address<'de, D>(deserializer: D) -> Result<Option<FieldElement>, D::Error>
348-
where
349-
D: serde::de::Deserializer<'de>,
350-
{
351-
Ok(Some(serde::Deserialize::deserialize(deserializer)?))
356+
*unsafe { &*(hash[..].as_ptr() as *const [u8; 32]) }
352357
}

chain/starknet/src/felt.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::{
2+
fmt::{Debug, Formatter},
3+
str::FromStr,
4+
};
5+
6+
use graph::anyhow;
7+
use serde::{de::Visitor, Deserialize};
8+
9+
#[derive(Clone, PartialEq, Eq)]
10+
pub struct Felt([u8; 32]);
11+
12+
struct FeltVisitor;
13+
14+
impl Debug for Felt {
15+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
16+
write!(f, "0x{}", hex::encode(&self.0))
17+
}
18+
}
19+
20+
impl From<[u8; 32]> for Felt {
21+
fn from(value: [u8; 32]) -> Self {
22+
Self(value)
23+
}
24+
}
25+
26+
impl AsRef<[u8]> for Felt {
27+
fn as_ref(&self) -> &[u8] {
28+
&self.0
29+
}
30+
}
31+
32+
impl FromStr for Felt {
33+
type Err = anyhow::Error;
34+
35+
fn from_str(s: &str) -> Result<Self, Self::Err> {
36+
let hex_str = s.trim_start_matches("0x");
37+
if hex_str.len() % 2 == 0 {
38+
Ok(Felt(decode_even_hex_str(hex_str)?))
39+
} else {
40+
// We need to manually pad it as the `hex` crate does not allow odd hex length
41+
let mut padded_string = String::from("0");
42+
padded_string.push_str(hex_str);
43+
44+
Ok(Felt(decode_even_hex_str(&padded_string)?))
45+
}
46+
}
47+
}
48+
49+
impl<'de> Deserialize<'de> for Felt {
50+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51+
where
52+
D: serde::Deserializer<'de>,
53+
{
54+
deserializer.deserialize_any(FeltVisitor)
55+
}
56+
}
57+
58+
impl<'de> Visitor<'de> for FeltVisitor {
59+
type Value = Felt;
60+
61+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
62+
write!(formatter, "string")
63+
}
64+
65+
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
66+
where
67+
E: serde::de::Error,
68+
{
69+
Felt::from_str(v).map_err(|_| {
70+
serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &"valid Felt value")
71+
})
72+
}
73+
}
74+
75+
/// Attempts to decode a even-length hex string into a padded 32-byte array.
76+
pub fn decode_even_hex_str(hex_str: &str) -> anyhow::Result<[u8; 32]> {
77+
let byte_len = hex_str.len() / 2;
78+
if byte_len > 32 {
79+
anyhow::bail!("length exceeds 32 bytes");
80+
}
81+
82+
let mut buffer = [0u8; 32];
83+
hex::decode_to_slice(hex_str, &mut buffer[(32 - byte_len)..])?;
84+
85+
Ok(buffer)
86+
}

0 commit comments

Comments
 (0)