Skip to content

Commit e10098e

Browse files
committed
feat(starknet): firehose filter
1 parent ea636c5 commit e10098e

6 files changed

Lines changed: 141 additions & 1 deletion

File tree

chain/starknet/src/adapter.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,20 @@ use std::collections::{hash_map::Entry, HashMap};
33
use graph::{
44
blockchain::{EmptyNodeCapabilities, TriggerFilter as TriggerFilterTrait},
55
components::store::BlockNumber,
6+
firehose::{
7+
BlockHeaderOnly as FirehoseFilterBlockHeaderOnly, BlockRange as FirehoseFilterBlockRange,
8+
ContractEventFilter as FirehoseFilterContractEventFilter,
9+
TopicWithRanges as FirehoseFilterTopicWithRanges,
10+
TransactionEventFilter as FirehoseFilterTransactionEventFilter,
11+
},
612
};
13+
use prost::Message;
14+
use prost_types::Any;
15+
16+
const BLOCK_HEADER_ONLY_TYPE_URL: &str =
17+
"type.googleapis.com/zklend.starknet.transform.v1.BlockHeaderOnly";
18+
const TRANSACTION_EVENT_FILTER_TYPE_URL: &str =
19+
"type.googleapis.com/zklend.starknet.transform.v1.TransactionEventFilter";
720

821
use crate::{
922
data_source::{DataSource, DataSourceTemplate},
@@ -50,7 +63,43 @@ impl TriggerFilterTrait<Chain> for TriggerFilter {
5063
}
5164

5265
fn to_firehose_filter(self) -> Vec<prost_types::Any> {
53-
todo!()
66+
// An empty event filter list means that the subgraph is not interested in events at all.
67+
// So we can stream just header-only blocks.
68+
if self.event.is_empty() {
69+
return vec![Any {
70+
type_url: BLOCK_HEADER_ONLY_TYPE_URL.into(),
71+
value: FirehoseFilterBlockHeaderOnly {}.encode_to_vec(),
72+
}];
73+
}
74+
75+
let event_filters = self
76+
.event
77+
.contract_addresses
78+
.iter()
79+
.map(
80+
|(contract_address, topic_with_ranges)| FirehoseFilterContractEventFilter {
81+
contract_address: contract_address.into(),
82+
topics: topic_with_ranges
83+
.iter()
84+
.map(|(topic, ranges)| FirehoseFilterTopicWithRanges {
85+
topic: topic.into(),
86+
block_ranges: ranges
87+
.iter()
88+
.map(|range| FirehoseFilterBlockRange {
89+
start_block: range.start_block as u64,
90+
end_block: range.end_block.unwrap_or_default() as u64,
91+
})
92+
.collect(),
93+
})
94+
.collect(),
95+
},
96+
)
97+
.collect();
98+
99+
vec![Any {
100+
type_url: TRANSACTION_EVENT_FILTER_TYPE_URL.into(),
101+
value: FirehoseFilterTransactionEventFilter { event_filters }.encode_to_vec(),
102+
}]
54103
}
55104
}
56105

chain/starknet/src/felt.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ impl FromStr for Felt {
6363
}
6464
}
6565

66+
impl From<&Felt> for Vec<u8> {
67+
fn from(value: &Felt) -> Self {
68+
value.0.to_vec()
69+
}
70+
}
71+
6672
impl<'de> Deserialize<'de> for Felt {
6773
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6874
where

graph/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ fn main() {
88
"proto/ethereum/transforms.proto",
99
"proto/near/transforms.proto",
1010
"proto/cosmos/transforms.proto",
11+
"proto/starknet/transforms.proto",
1112
],
1213
&["proto"],
1314
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
syntax = "proto3";
2+
3+
package zklend.starknet.transform.v1;
4+
5+
option go_package = "github.com/starknet-graph/firehose-starknet/types/pb/zklend/starknet/type/v1;pbtransform";
6+
7+
// Stream block headers only. The `transactions` field is always empty.
8+
message BlockHeaderOnly {}
9+
10+
// Stream every single block, but each block will only contain transactions that match with `event_filters`.
11+
// A TransactionEventFilter message with an empty `event_filters` is invalid. Do not send any filter instead
12+
// if you wish to receive full blocks.
13+
message TransactionEventFilter {
14+
repeated ContractEventFilter event_filters = 1;
15+
}
16+
17+
// Only include transactions which emit at least one event that *BOTH*
18+
// * is emitted by `contract_address`
19+
// * matches with at least one topic in `topics`
20+
message ContractEventFilter {
21+
bytes contract_address = 1;
22+
repeated TopicWithRanges topics = 2;
23+
}
24+
25+
// Matches events whose `keys[0]` equals `topic`, *AND* in any of the `block_ranges`.
26+
message TopicWithRanges {
27+
bytes topic = 1;
28+
repeated BlockRange block_ranges = 2;
29+
}
30+
31+
// A range of blocks. Both `start_block` and `end_block` are inclusive. When `end_block` is `0`, it means that any
32+
// block height >= `start_block` is matched.
33+
message BlockRange {
34+
uint64 start_block = 1;
35+
uint64 end_block = 2;
36+
}

graph/src/firehose/codec.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ mod pbnear;
1414
#[path = "sf.cosmos.transform.v1.rs"]
1515
mod pbcosmos;
1616

17+
#[rustfmt::skip]
18+
#[path = "zklend.starknet.transform.v1.rs"]
19+
mod pbstarknet;
20+
1721
pub use pbcosmos::*;
1822
pub use pbethereum::*;
1923
pub use pbfirehose::*;
2024
pub use pbnear::*;
25+
pub use pbstarknet::*;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/// Stream block headers only. The `transactions` field is always empty.
2+
#[allow(clippy::derive_partial_eq_without_eq)]
3+
#[derive(Clone, PartialEq, ::prost::Message)]
4+
pub struct BlockHeaderOnly {}
5+
/// Stream every single block, but each block will only contain transactions that match with `event_filters`.
6+
/// A TransactionEventFilter message with an empty `event_filters` is invalid. Do not send any filter instead
7+
/// if you wish to receive full blocks.
8+
#[allow(clippy::derive_partial_eq_without_eq)]
9+
#[derive(Clone, PartialEq, ::prost::Message)]
10+
pub struct TransactionEventFilter {
11+
#[prost(message, repeated, tag = "1")]
12+
pub event_filters: ::prost::alloc::vec::Vec<ContractEventFilter>,
13+
}
14+
/// Only include transactions which emit at least one event that *BOTH*
15+
/// * is emitted by `contract_address`
16+
/// * matches with at least one topic in `topics`
17+
#[allow(clippy::derive_partial_eq_without_eq)]
18+
#[derive(Clone, PartialEq, ::prost::Message)]
19+
pub struct ContractEventFilter {
20+
#[prost(bytes = "vec", tag = "1")]
21+
pub contract_address: ::prost::alloc::vec::Vec<u8>,
22+
#[prost(message, repeated, tag = "2")]
23+
pub topics: ::prost::alloc::vec::Vec<TopicWithRanges>,
24+
}
25+
/// Matches events whose `keys\[0\]` equals `topic`, *AND* in any of the `block_ranges`.
26+
#[allow(clippy::derive_partial_eq_without_eq)]
27+
#[derive(Clone, PartialEq, ::prost::Message)]
28+
pub struct TopicWithRanges {
29+
#[prost(bytes = "vec", tag = "1")]
30+
pub topic: ::prost::alloc::vec::Vec<u8>,
31+
#[prost(message, repeated, tag = "2")]
32+
pub block_ranges: ::prost::alloc::vec::Vec<BlockRange>,
33+
}
34+
/// A range of blocks. Both `start_block` and `end_block` are inclusive. When `end_block` is `0`, it means that any
35+
/// block height >= `start_block` is matched.
36+
#[allow(clippy::derive_partial_eq_without_eq)]
37+
#[derive(Clone, PartialEq, ::prost::Message)]
38+
pub struct BlockRange {
39+
#[prost(uint64, tag = "1")]
40+
pub start_block: u64,
41+
#[prost(uint64, tag = "2")]
42+
pub end_block: u64,
43+
}

0 commit comments

Comments
 (0)