From 040fad3f778b5e53c385cf437434223e25c3cd7d Mon Sep 17 00:00:00 2001 From: tmcgroul Date: Mon, 30 Sep 2024 21:11:40 +0700 Subject: [PATCH] starknet support --- .../fixtures/starknet/chunk/blocks.parquet | 3 + .../fixtures/starknet/chunk/events.parquet | 3 + .../starknet/chunk/transactions.parquet | 3 + .../starknet/queries/basic/query.json | 25 ++ .../starknet/queries/basic/result.json | 3 + crates/query/src/query/mod.rs | 9 + crates/query/src/query/starknet.rs | 234 ++++++++++++++++++ 7 files changed, 280 insertions(+) create mode 100644 crates/query/fixtures/starknet/chunk/blocks.parquet create mode 100644 crates/query/fixtures/starknet/chunk/events.parquet create mode 100644 crates/query/fixtures/starknet/chunk/transactions.parquet create mode 100644 crates/query/fixtures/starknet/queries/basic/query.json create mode 100644 crates/query/fixtures/starknet/queries/basic/result.json create mode 100644 crates/query/src/query/starknet.rs diff --git a/crates/query/fixtures/starknet/chunk/blocks.parquet b/crates/query/fixtures/starknet/chunk/blocks.parquet new file mode 100644 index 0000000..9088285 --- /dev/null +++ b/crates/query/fixtures/starknet/chunk/blocks.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:58168f20a0832931ad69332a2be82fb230a89c035775ee1362b1426f891424cc +size 93815 diff --git a/crates/query/fixtures/starknet/chunk/events.parquet b/crates/query/fixtures/starknet/chunk/events.parquet new file mode 100644 index 0000000..0ba19dc --- /dev/null +++ b/crates/query/fixtures/starknet/chunk/events.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cd18d7d3f0f76c13737eb776e4fc5466ccccbeb0a1f9e1378500c5137e6cab4 +size 18843544 diff --git a/crates/query/fixtures/starknet/chunk/transactions.parquet b/crates/query/fixtures/starknet/chunk/transactions.parquet new file mode 100644 index 0000000..be84a31 --- /dev/null +++ b/crates/query/fixtures/starknet/chunk/transactions.parquet @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a8fb1adfe20fed07fc23f17561836a79b1809bf3b37c248631e68178a3946c6 +size 20263389 diff --git a/crates/query/fixtures/starknet/queries/basic/query.json b/crates/query/fixtures/starknet/queries/basic/query.json new file mode 100644 index 0000000..2fb63cb --- /dev/null +++ b/crates/query/fixtures/starknet/queries/basic/query.json @@ -0,0 +1,25 @@ +{ + "type": "starknet", + "fromBlock": 0, + "fields": { + "block": { + "parentHash": true, + "timestamp": true + }, + "transaction": { + "transactionHash": true, + "signature": true + }, + "event": { + "keys": true, + "data": true + } + }, + "events": [ + { + "fromAddress": ["0x22e45d94d5c6c477d9efd440aad71b2c02a5cd5bed9a4d6da10bb7c19fd93ba"], + "key0": ["0xe316f0d9d2a3affa97de1d99bb2aac0538e2666d0d8545545ead241ef0ccab"], + "transaction": true + } + ] +} diff --git a/crates/query/fixtures/starknet/queries/basic/result.json b/crates/query/fixtures/starknet/queries/basic/result.json new file mode 100644 index 0000000..15c357f --- /dev/null +++ b/crates/query/fixtures/starknet/queries/basic/result.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56d69e21866229a4226800daa0f8e5aa224d12b79d76d699a093a70084aaac0e +size 1668 diff --git a/crates/query/src/query/mod.rs b/crates/query/src/query/mod.rs index 77cfc82..1315570 100644 --- a/crates/query/src/query/mod.rs +++ b/crates/query/src/query/mod.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; pub mod eth; pub mod solana; pub mod substrate; +pub mod starknet; pub mod fuel; mod util; @@ -19,6 +20,8 @@ pub enum Query { Solana(solana::SolanaQuery), #[serde(rename = "substrate")] Substrate(substrate::SubstrateQuery), + #[serde(rename = "starknet")] + Starknet(starknet::StarknetQuery), #[serde(rename = "fuel")] Fuel(fuel::FuelQuery) } @@ -50,6 +53,7 @@ impl Query { Query::Eth(q) => q.validate(), Query::Solana(q) => q.validate(), Query::Substrate(q) => q.validate(), + Query::Starknet(q) => q.validate(), Query::Fuel(q) => q.validate(), } } @@ -59,6 +63,7 @@ impl Query { Query::Eth(q) => q.from_block, Query::Solana(q) => q.from_block, Query::Substrate(q) => q.from_block, + Query::Starknet(q) => q.from_block, Query::Fuel(q) => q.from_block, } } @@ -68,6 +73,7 @@ impl Query { Query::Eth(q) => q.from_block = block_number, Query::Solana(q) => q.from_block = block_number, Query::Substrate(q) => q.from_block = block_number, + Query::Starknet(q) => q.from_block = block_number, Query::Fuel(q) => q.from_block = block_number, } } @@ -77,6 +83,7 @@ impl Query { Query::Eth(q) => q.to_block, Query::Solana(q) => q.to_block, Query::Substrate(q) => q.to_block, + Query::Starknet(q) => q.to_block, Query::Fuel(q) => q.to_block, } } @@ -86,6 +93,7 @@ impl Query { Query::Eth(q) => q.to_block = block_number, Query::Solana(q) => q.to_block = block_number, Query::Substrate(q) => q.to_block = block_number, + Query::Starknet(q) => q.to_block = block_number, Query::Fuel(q) => q.to_block = block_number, } } @@ -95,6 +103,7 @@ impl Query { Query::Eth(q) => q.compile(), Query::Solana(q) => q.compile(), Query::Substrate(q) => q.compile(), + Query::Starknet(q) => q.compile(), Query::Fuel(q) => q.compile(), } } diff --git a/crates/query/src/query/starknet.rs b/crates/query/src/query/starknet.rs new file mode 100644 index 0000000..9e18842 --- /dev/null +++ b/crates/query/src/query/starknet.rs @@ -0,0 +1,234 @@ +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; + +use crate::json::exp::Exp; +use crate::json::lang::*; +use crate::plan::{Plan, ScanBuilder, TableSet}; +use crate::primitives::BlockNumber; +use crate::query::util::{compile_plan, ensure_block_range, ensure_item_count, field_selection, item_field_selection, request, PredicateBuilder}; + + +lazy_static! { + static ref TABLES: TableSet = { + let mut tables = TableSet::new(); + + tables.add_table("blocks", vec![ + "number" + ]); + + tables.add_table("transactions", vec![ + "block_number", + "transaction_index" + ]) + .set_weight_column("calldata", "calldata_size") + .set_weight_column("signature", "signature_size") + .set_weight_column("constructor_calldata", "constructor_calldata_size"); + + tables.add_table("events", vec![ + "block_number", + "transaction_index", + "event_index" + ]) + .set_weight_column("key0", "keys_size") + .set_weight("key1", 0) + .set_weight("key2", 0) + .set_weight("key3", 0) + .set_weight("rest_keys", 0) + .set_weight_column("data", "data_size"); + + tables + }; +} + + +field_selection! { + block: BlockFieldSelection, + transaction: TransactionFieldSelection, + event: EventFieldSelection, +} + + +item_field_selection! { + BlockFieldSelection { + parent_hash, + status, + new_root, + timestamp, + sequencer_address, + } + + project(this) json_object! {{ + number, + hash, + [this.parent_hash], + [this.status], + [this.new_root], + [this.sequencer_address], + : TimestampSecond, + }} +} + + +item_field_selection! { + TransactionFieldSelection { + transaction_hash, + contract_address, + entry_point_selector, + calldata, + max_fee, + r#type, + sender_address, + version, + signature, + nonce, + class_hash, + compiled_class_hash, + contract_address_salt, + constructor_calldata, + } + + project(this) json_object! {{ + transaction_index, + [this.transaction_hash], + [this.contract_address], + [this.entry_point_selector], + [this.calldata], + [this.max_fee], + [this.r#type], + [this.sender_address], + [this.version], + [this.signature], + [this.nonce], + [this.class_hash], + [this.compiled_class_hash], + [this.contract_address_salt], + [this.constructor_calldata], + }} +} + + +item_field_selection! { + EventFieldSelection { + from_address, + keys, + data, + } + + project(this) json_object! {{ + transaction_index, + event_index, + [this.from_address], + [this.data], + |obj| { + if this.keys { + obj.add("keys", roll(Exp::Value, vec![ + "key0", + "key1", + "key2", + "key3", + "rest_keys" + ])); + } + } + }} +} + + +type Bytes = String; + + +request! { + pub struct EventRequest { + pub from_address: Option>, + pub key0: Option>, + pub key1: Option>, + pub key2: Option>, + pub key3: Option>, + pub transaction: bool, + } +} + + +impl EventRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_in_list("from_address", self.from_address.clone()); + p.col_in_list("key0", self.key0.clone()); + p.col_in_list("key1", self.key1.clone()); + p.col_in_list("key2", self.key2.clone()); + p.col_in_list("key3", self.key3.clone()); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.transaction { + scan.join( + "transactions", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"] + ); + } + } +} + + +request! { + pub struct TransactionRequest { + pub contract_address: Option>, + pub sender_address: Option>, + pub r#type: Option>, + pub first_nonce: Option, + pub last_nonce: Option, + pub events: bool, + } +} + + +impl TransactionRequest { + fn predicate(&self, p: &mut PredicateBuilder) { + p.col_in_list("contract_address", self.contract_address.clone()); + p.col_in_list("sender_address", self.sender_address.clone()); + p.col_in_list("type", self.r#type.clone()); + p.col_gt_eq("nonce", self.first_nonce); + p.col_lt_eq("nonce", self.last_nonce); + } + + fn relations(&self, scan: &mut ScanBuilder) { + if self.events { + scan.join( + "events", + vec!["block_number", "transaction_index"], + vec!["block_number", "transaction_index"] + ); + } + } +} + + +request! { + pub struct StarknetQuery { + pub from_block: Option, + pub to_block: Option, + pub fields: FieldSelection, + pub include_all_blocks: bool, + pub transactions: Vec, + pub events: Vec, + } +} + + +impl StarknetQuery { + pub fn validate(&self) -> anyhow::Result<()> { + ensure_block_range!(self); + ensure_item_count!(self, transactions, events); + Ok(()) + } + + pub fn compile(&self) -> Plan { + compile_plan!(self, &TABLES, + [blocks: self.fields.block.project()], + [transactions: self.fields.transaction.project()], + [events: self.fields.event.project()], + transactions, + events, + ) + } +} \ No newline at end of file