From dde848ae44aa36faf490ddc43281f924930eab93 Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 5 Dec 2025 20:06:22 +0300 Subject: [PATCH 1/6] wip --- Cargo.lock | 290 +++- Cargo.toml | 2 + networks/services/postgres.nix | 11 +- .../plugins/event-source/starknet/Cargo.toml | 31 + .../plugins/event-source/starknet/src/call.rs | 34 + .../event-source/starknet/src/ibc_events.rs | 110 ++ .../plugins/event-source/starknet/src/main.rs | 1266 +++++++++++++++++ 7 files changed, 1721 insertions(+), 23 deletions(-) create mode 100644 voyager/plugins/event-source/starknet/Cargo.toml create mode 100644 voyager/plugins/event-source/starknet/src/call.rs create mode 100644 voyager/plugins/event-source/starknet/src/ibc_events.rs create mode 100644 voyager/plugins/event-source/starknet/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 258b14be58..891a8e3953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1006,7 +1006,7 @@ dependencies = [ "tokio", "tower 0.4.13", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -2909,6 +2909,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cainome-cairo-serde" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ae2d4c21db23c7730a85187c2e9d73fe00c123171839185fb13f31550f3240" +dependencies = [ + "num-bigint 0.4.6", + "serde", + "serde_with", + "starknet", + "thiserror 2.0.12", +] + [[package]] name = "camellia" version = "0.1.0" @@ -4655,7 +4668,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "uuid", + "uuid 1.16.0", ] [[package]] @@ -5516,6 +5529,28 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac 0.12.1", + "pbkdf2", + "rand 0.8.5", + "scrypt", + "serde", + "serde_json", + "sha2 0.10.9", + "sha3", + "thiserror 1.0.69", + "uuid 0.8.2", +] + [[package]] name = "ethabi" version = "18.0.0" @@ -5527,6 +5562,19 @@ dependencies = [ "sha3", ] +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde 0.4.0", + "tiny-keccak", +] + [[package]] name = "ethereum-light-client" version = "0.0.0" @@ -5608,7 +5656,10 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ + "ethbloom", "fixed-hash 0.8.0", + "impl-rlp", + "impl-serde 0.4.0", "primitive-types 0.12.2", "uint", ] @@ -7387,6 +7438,15 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.3" @@ -8132,27 +8192,24 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.13.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" +checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ "lambdaworks-math", - "rand 0.8.5", - "rand_chacha 0.3.1", + "serde", "sha2 0.10.9", "sha3", ] [[package]] name = "lambdaworks-math" -version = "0.13.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" dependencies = [ - "getrandom 0.2.16", - "num-bigint 0.4.6", - "num-traits", - "rand 0.8.5", + "serde", + "serde_json", ] [[package]] @@ -8765,7 +8822,7 @@ dependencies = [ "smallvec", "tagptr", "thiserror 1.0.69", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -9320,7 +9377,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -10705,7 +10762,7 @@ checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash 0.7.0", "impl-codec 0.5.1", - "impl-serde", + "impl-serde 0.3.2", "uint", ] @@ -10718,6 +10775,7 @@ dependencies = [ "fixed-hash 0.8.0", "impl-codec 0.6.0", "impl-rlp", + "impl-serde 0.4.0", "uint", ] @@ -12092,6 +12150,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -12243,6 +12310,18 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "salsa20", + "sha2 0.10.9", +] + [[package]] name = "sct" version = "0.7.1" @@ -12543,6 +12622,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_json_pythonic" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62212da9872ca2a0cad0093191ee33753eddff9266cbbc1b4a602d13a3a768db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_jsonc" version = "1.0.108" @@ -13303,6 +13393,86 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "starknet" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ed01c14136e56dcdf21385d20c4a6fdd3509947cb56cca45fc765ef5809add" +dependencies = [ + "starknet-accounts", + "starknet-contract", + "starknet-core", + "starknet-core-derive", + "starknet-crypto", + "starknet-macros", + "starknet-providers", + "starknet-signers", +] + +[[package]] +name = "starknet-accounts" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f7c118729bcdcfa1610844047cbdb23090fb1d4172a36bb97a663be8d022d1a" +dependencies = [ + "async-trait", + "auto_impl", + "starknet-core", + "starknet-crypto", + "starknet-providers", + "starknet-signers", + "thiserror 1.0.69", +] + +[[package]] +name = "starknet-contract" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb64331b72caf51c0d8b684b62012f9a771015b4cf5e52cba9bf61be8384ad3" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "starknet-accounts", + "starknet-core", + "starknet-providers", + "thiserror 1.0.69", +] + +[[package]] +name = "starknet-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb7212226769766c1c7d79b70f9242ffbd213290a41604ecc7e78faa0ed0deb" +dependencies = [ + "base64 0.21.7", + "crypto-bigint 0.5.5", + "flate2", + "foldhash 0.1.5", + "hex", + "indexmap 2.9.0", + "num-traits", + "serde", + "serde_json", + "serde_json_pythonic", + "serde_with", + "sha3", + "starknet-core-derive", + "starknet-crypto", + "starknet-types-core", +] + +[[package]] +name = "starknet-core-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08520b7d80eda7bf1a223e8db4f9bb5779a12846f15ebf8f8d76667eca7f5ad" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "starknet-crypto" version = "0.8.1" @@ -13310,6 +13480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a16c25dc6113c19d4f9d0c19ff97d85804829894bba22c0d0e9e7b249812" dependencies = [ "crypto-bigint 0.5.5", + "hex", "hmac 0.12.1", "num-bigint 0.4.6", "num-integer", @@ -13330,6 +13501,54 @@ dependencies = [ "starknet-types-core", ] +[[package]] +name = "starknet-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d59e1eb22f4366385b132ba7016faa5a6457f1f23f896f737a06da626455e7b" +dependencies = [ + "starknet-core", + "syn 2.0.101", +] + +[[package]] +name = "starknet-providers" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15fc3d94cc008cea64e291b261e8349065424ee7491e5dd0fa9bd688818bece1" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "getrandom 0.2.16", + "log", + "reqwest 0.12.15", + "serde", + "serde_json", + "serde_with", + "starknet-core", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "starknet-signers" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d839b06d899ef3a0de11b1e9a91a14c118b1ed36830ec8e59d9fbc9a1e51976b" +dependencies = [ + "async-trait", + "auto_impl", + "crypto-bigint 0.5.5", + "eth-keystore", + "getrandom 0.2.16", + "rand 0.8.5", + "starknet-core", + "starknet-crypto", + "thiserror 1.0.69", +] + [[package]] name = "starknet-storage-verifier" version = "0.0.0" @@ -13343,9 +13562,9 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.2.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d23b1bc014ee4cce40056ab3114bcbcdc2dbc1e845bbfb1f8bd0bab63507d4" +checksum = "5fa3d91e38f091dbc543d33589eb7716bed2a8eb1c20879e484561977832b60a" dependencies = [ "blake2", "digest 0.10.7", @@ -13354,6 +13573,8 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "serde", + "zeroize", ] [[package]] @@ -15020,7 +15241,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -15885,6 +16106,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.16", + "serde", +] + [[package]] name = "uuid" version = "1.16.0" @@ -16878,6 +17109,29 @@ dependencies = [ "voyager-sdk", ] +[[package]] +name = "voyager-event-source-plugin-starknet" +version = "0.0.0" +dependencies = [ + "cainome-cairo-serde", + "clap", + "embed-commit", + "enumorph", + "ibc-union-spec", + "jsonrpsee 0.25.1", + "macros", + "serde", + "serde-utils", + "serde_json", + "sha2 0.10.9", + "starknet", + "thiserror 2.0.12", + "tokio", + "tracing", + "unionlabs", + "voyager-sdk", +] + [[package]] name = "voyager-event-source-plugin-sui" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 12f4131de9..f79d1a5a60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -210,6 +210,7 @@ members = [ "voyager/plugins/event-source/evm", # "voyager/plugins/event-source/movement", "voyager/plugins/event-source/sui", + "voyager/plugins/event-source/starknet", "voyager/plugins/transaction/cosmos", "voyager/plugins/transaction/evm", @@ -528,6 +529,7 @@ serde_with = { version = "3.12.0", default-features = false, featu sha2 = { version = "0.10.9", default-features = false } sha3 = { version = "0.10.8", default-features = false } sqlx = { version = "0.7.4", default-features = false } +starknet = { version = "0.17.0", default-features = false } starknet-core = { version = "0.16.0", default-features = false } starknet-crypto = { version = "0.8.1", default-features = false } static_assertions = { git = "https://github.com/nvzqz/static-assertions" } # https://github.com/nvzqz/static-assertions/pull/28 diff --git a/networks/services/postgres.nix b/networks/services/postgres.nix index eda26c447d..6c15fcd249 100644 --- a/networks/services/postgres.nix +++ b/networks/services/postgres.nix @@ -1,11 +1,12 @@ { lib, pkgs, ... }: let postgres = pkgs.dockerTools.pullImage { - imageName = "timescale/timescaledb"; - imageDigest = "sha256:eb8a3142384e8fd93ebd311783b297a04398ca61902b41233912a1a115279b69"; - sha256 = "sha256-zJ6HTYhxO7h+brEQOoJgDbHp74JfFe0Jcsfnz8MCFHM="; - finalImageName = "timescaledb"; - finalImageTag = "2.14.1-pg16"; + imageName = "arm64v8/postgres"; + imageDigest = "sha256:1b6ca2021138a093566ef47bd851c3ad2c52a665bd8486609fc23cdc44563a4b"; + sha256 = "sha256-SNmjeAyMv1cxC3Qr3MZKHoWXsLMrrAEVWrhf/n13Y3U="; + finalImageName = "arm64v8/postgres"; + finalImageTag = "18.1"; + arch = "arm64"; }; in { diff --git a/voyager/plugins/event-source/starknet/Cargo.toml b/voyager/plugins/event-source/starknet/Cargo.toml new file mode 100644 index 0000000000..93bf591d54 --- /dev/null +++ b/voyager/plugins/event-source/starknet/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "voyager-event-source-plugin-starknet" +version = "0.0.0" + +authors = { workspace = true } +edition = { workspace = true } +license-file = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[dependencies] +cainome-cairo-serde = "0.4.1" +clap = { workspace = true, features = ["derive"] } +embed-commit = { workspace = true } +enumorph = { workspace = true } +ibc-union-spec = { workspace = true, features = ["tracing", "bincode", "serde"] } +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +macros = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde-utils = { workspace = true } +serde_json = { workspace = true } +sha2 = { workspace = true, features = ["std"] } +starknet = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +unionlabs = { workspace = true, features = ["bincode"] } +voyager-sdk = { workspace = true } diff --git a/voyager/plugins/event-source/starknet/src/call.rs b/voyager/plugins/event-source/starknet/src/call.rs new file mode 100644 index 0000000000..d1cf68963f --- /dev/null +++ b/voyager/plugins/event-source/starknet/src/call.rs @@ -0,0 +1,34 @@ +use std::collections::BTreeSet; + +use enumorph::Enumorph; +use macros::model; +use unionlabs::primitives::H256; + +#[model] +#[derive(Enumorph)] +#[allow(clippy::large_enum_variant)] +pub enum ModuleCall { + FetchBlocks(FetchBlocks), + FetchBlock(FetchBlock), + MakeChainEvent(MakeChainEvent), +} + +/// Fetch a block at the specified height, requeuing a seq(wait(H+1), fetch(H+1)). +#[model] +pub struct FetchBlocks { + pub height: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub until: Option, +} + +#[model] +pub struct FetchBlock { + pub height: u64, +} + +#[model] +pub struct MakeChainEvent { + pub height: u64, + pub tx_hash: H256, + // pub event: crate::ibc_events::IbcEvent, +} diff --git a/voyager/plugins/event-source/starknet/src/ibc_events.rs b/voyager/plugins/event-source/starknet/src/ibc_events.rs new file mode 100644 index 0000000000..9eb45177cf --- /dev/null +++ b/voyager/plugins/event-source/starknet/src/ibc_events.rs @@ -0,0 +1,110 @@ +use cainome_cairo_serde::{ByteArray, ContractAddress, NonZero}; + +#[derive(Debug)] +pub enum CairoIbcEvent { + RegisterClient { + /// `#[key]` + client_type: ByteArray, + client_address: ContractAddress, + }, + CreateClient { + /// `#[key]` + client_type: ByteArray, + /// `#[key]` + client_id: NonZero, + /// `#[key]` + counterparty_chain_id: ByteArray, + }, + UpdateClient { + /// `#[key]` + client_id: NonZero, + /// `#[key]` + height: u64, + }, + ConnectionOpenInit { + /// `#[key]` + connection_id: NonZero, + /// `#[key]` + client_id: NonZero, + counterparty_client_id: NonZero, + }, + ConnectionOpenTry { + /// `#[key]` + connection_id: NonZero, + /// `#[key]` + client_id: NonZero, + counterparty_client_id: NonZero, + counterparty_connection_id: NonZero, + }, + ConnectionOpenAck { + /// `#[key]` + connection_id: NonZero, + /// `#[key]` + client_id: NonZero, + counterparty_client_id: NonZero, + counterparty_connection_id: NonZero, + }, + ConnectionOpenConfirm { + /// `#[key]` + connection_id: NonZero, + /// `#[key]` + client_id: NonZero, + counterparty_client_id: NonZero, + counterparty_connection_id: NonZero, + }, + ChannelOpenInit { + /// `#[key]` + port_id: ContractAddress, + /// `#[key]` + channel_id: NonZero, + counterparty_port_id: ByteArray, + connection_id: NonZero, + /// `#[key]` + version: ByteArray, + }, + ChannelOpenTry { + /// `#[key]` + port_id: ContractAddress, + /// `#[key]` + channel_id: NonZero, + counterparty_port_id: ByteArray, + counterparty_channel_id: NonZero, + connection_id: NonZero, + /// `#[key]` + counterparty_version: ByteArray, + }, + ChannelOpenAck { + /// `#[key]` + port_id: ContractAddress, + /// `#[key]` + channel_id: NonZero, + counterparty_port_id: ByteArray, + counterparty_channel_id: NonZero, + connection_id: NonZero, + }, + ChannelOpenConfirm { + /// `#[key]` + port_id: ContractAddress, + /// `#[key]` + channel_id: NonZero, + counterparty_port_id: ByteArray, + counterparty_channel_id: NonZero, + connection_id: NonZero, + }, + ChannelCloseInit { + /// `#[key]` + port_id: ContractAddress, + /// `#[key]` + channel_id: NonZero, + counterparty_port_id: ByteArray, + counterparty_channel_id: NonZero, + }, + ChannelCloseConfirm { + /// `#[key]` + port_id: ContractAddress, + /// `#[key]` + channel_id: NonZero, + counterparty_port_id: ByteArray, + counterparty_channel_id: NonZero, + }, +} diff --git a/voyager/plugins/event-source/starknet/src/main.rs b/voyager/plugins/event-source/starknet/src/main.rs new file mode 100644 index 0000000000..31f788ef93 --- /dev/null +++ b/voyager/plugins/event-source/starknet/src/main.rs @@ -0,0 +1,1266 @@ +// #![warn(clippy::unwrap_used)] + +use core::slice; +use std::{ + cmp::Ordering, + collections::{BTreeMap, BTreeSet, VecDeque, btree_map::Entry}, + num::{NonZeroU8, NonZeroU32, ParseIntError}, +}; + +use cainome_cairo_serde::CairoSerde; +use ibc_union_spec::{ + IbcUnion, MustBeZero, Packet, + event::{ + BatchSend, ChannelMetadata, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, + ChannelOpenTry, ConnectionMetadata, ConnectionOpenAck, ConnectionOpenConfirm, + ConnectionOpenInit, ConnectionOpenTry, CounterpartyChannelMetadata, CreateClient, + PacketAck, PacketMetadata, PacketRecv, PacketSend, UpdateClient, WriteAck, + }, + path::ChannelPath, + query::PacketByHash, +}; +use jsonrpsee::{Extensions, core::async_trait, types::ErrorObject}; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use starknet::{ + core::types::{BlockId, EventFilter, Felt}, + macros::selector, + providers::{JsonRpcClient, Provider, Url, jsonrpc::HttpTransport}, +}; +use tracing::{debug, error, info, info_span, instrument, trace, warn}; +use unionlabs::{ErrorReporter, ibc::core::client::height::Height, never::Never, primitives::H256}; +use voyager_sdk::{ + ExtensionsExt, VoyagerClient, + anyhow::{self, bail}, + hook::simple_take_filter, + into_value, + message::{ + PluginMessage, VoyagerMessage, + call::{Call, WaitForHeight}, + data::{ChainEvent, Data, EventProvableHeight}, + }, + plugin::Plugin, + primitives::{ChainId, ClientType, QueryHeight}, + rpc::{PluginServer, RpcError, RpcResult, types::PluginInfo}, + vm::{Op, call, conc, data, noop, pass::PassResult, seq}, +}; + +use crate::{ + call::{FetchBlock, FetchBlocks, MakeChainEvent, ModuleCall}, + ibc_events::CairoIbcEvent, +}; + +pub mod ibc_events; + +pub mod call; + +const PER_PAGE_LIMIT: NonZeroU8 = NonZeroU8::new(100).unwrap(); + +#[tokio::main] +async fn main() { + Module::run().await +} + +#[derive(Debug, Clone)] +pub struct Module { + pub chain_id: ChainId, + + pub client: JsonRpcClient, + + pub chunk_block_fetch_size: u64, + + pub index_trivial_events: bool, + + pub ibc_host_contract_address: Felt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Config { + pub chain_id: ChainId, + + pub rpc_url: String, + + #[serde(default = "default_chunk_block_fetch_size")] + pub chunk_block_fetch_size: u64, + + /// Whether or not to fully index events that do not produce a counterparty action (packet_recv, packet_acknowledgement, packet_timeout, update_client). + #[serde(default)] + pub index_trivial_events: bool, + + #[serde(default)] + pub ibc_host_contract_address: Felt, +} + +fn default_chunk_block_fetch_size() -> u64 { + 10 +} + +fn default_refetch_delay() -> u64 { + 120 +} + +#[derive(clap::Subcommand)] +pub enum Cmd { + /// Return an op to fetch the events from a single block from the chain. + FetchSingleBlock { height: u64 }, +} + +impl Plugin for Module { + type Call = ModuleCall; + type Callback = Never; + + type Config = Config; + type Cmd = Cmd; + + async fn new(config: Self::Config) -> anyhow::Result { + let client = JsonRpcClient::new(HttpTransport::new(Url::parse(&config.rpc_url)?)); + + let chain_id = ChainId::new(client.chain_id().await?.to_string()); + + if chain_id != config.chain_id { + bail!( + "incorrect chain id: expected `{}`, but found `{}`", + config.chain_id, + chain_id + ); + } + + Ok(Self { + client, + chain_id, + chunk_block_fetch_size: config.chunk_block_fetch_size, + index_trivial_events: config.index_trivial_events, + ibc_host_contract_address: config.ibc_host_contract_address, + }) + } + + fn info(config: Self::Config) -> PluginInfo { + PluginInfo { + name: plugin_name(&config.chain_id), + interest_filter: simple_take_filter(format!( + r#"[.. | (."@type"? == "index" or ."@type"? == "index_range") and ."@value".chain_id == "{}"] | any"#, + config.chain_id + )), + } + } + + async fn cmd(config: Self::Config, cmd: Self::Cmd) { + match cmd { + Cmd::FetchSingleBlock { height } => { + print!( + "{}", + into_value(call::(PluginMessage::new( + plugin_name(&config.chain_id), + ModuleCall::from(FetchBlock { height }) + ))) + ) + } + } + } +} + +fn plugin_name(chain_id: &ChainId) -> String { + pub const PLUGIN_NAME: &str = env!("CARGO_PKG_NAME"); + + format!("{PLUGIN_NAME}/{}", chain_id) +} + +impl Module { + fn plugin_name(&self) -> String { + plugin_name(&self.chain_id) + } + + #[must_use] + pub fn make_height(&self, height: u64) -> Height { + Height::new(height) + } +} + +#[async_trait] +impl PluginServer for Module { + #[instrument(skip_all, fields(chain_id = %self.chain_id))] + async fn run_pass( + &self, + _: &Extensions, + msgs: Vec>, + ) -> RpcResult> { + Ok(PassResult { + optimize_further: vec![], + ready: msgs + .into_iter() + .map(|op| match op { + Op::Call(Call::Index(fetch)) if fetch.chain_id == self.chain_id => { + call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchBlocks { + height: fetch.start_height.height(), + until: None, + }), + )) + } + Op::Call(Call::IndexRange(fetch)) if fetch.chain_id == self.chain_id => { + call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchBlocks { + height: fetch.range.from_height().height(), + until: Some(fetch.range.to_height().height()), + }), + )) + } + op => op, + }) + .enumerate() + .map(|(i, op)| (vec![i], op)) + .collect(), + }) + } + + #[instrument(skip_all, fields(chain_id = %self.chain_id))] + async fn callback( + &self, + _: &Extensions, + cb: Never, + _data: VecDeque, + ) -> RpcResult> { + match cb {} + } + + #[instrument(skip_all, fields(chain_id = %self.chain_id))] + async fn call(&self, e: &Extensions, msg: ModuleCall) -> RpcResult> { + match msg { + ModuleCall::FetchBlocks(FetchBlocks { height, until }) => { + self.fetch_blocks(e.voyager_client()?, height, until).await + } + ModuleCall::FetchBlock(FetchBlock { height }) => self.fetch_block(height).await, + ModuleCall::MakeChainEvent(MakeChainEvent { + height, + tx_hash, + // event, + }) => { + self.make_chain_event(e.voyager_client()?, height, tx_hash /* , event */) + .await + } + } + } +} + +impl Module { + #[instrument(skip_all, fields(%height))] + async fn fetch_blocks( + &self, + voyager_client: &VoyagerClient, + height: u64, + until: Option, + ) -> RpcResult> { + if let Some(until) = until { + if height > until { + return Err(RpcError::fatal_from_message(format!( + "height {height} cannot be greater than the until height {until}" + ))); + } else if height == until { + // if this is a ranged fetch, we need to fetch the upper bound of the range individually since FetchBlocks is exclusive on the upper bound + return Ok(call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchBlock { height }), + ))); + } + } + + let latest_height = voyager_client + .query_latest_height(self.chain_id.clone(), true) + .await? + .height(); + + info!(%latest_height, %height, ?until, "fetching blocks"); + + let continuation = |next_height: u64| { + seq([ + // TODO: Make this a config param + call(WaitForHeight { + chain_id: self.chain_id.clone(), + height: Height::new(next_height), + finalized: true, + }), + call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchBlocks { + height: next_height, + until, + }), + )), + ]) + }; + + match height.cmp(&latest_height) { + // height < latest_height + // fetch transactions on all blocks height..next_height (*exclusive* on the upper bound!) + // and then queue the continuation starting at next_height + Ordering::Equal | Ordering::Less => { + let next_height = + (latest_height - height).clamp(1, self.chunk_block_fetch_size) + height; + + let next_height = next_height.min(until.map_or(next_height, |until| until)); + + info!( + from_height = height, + to_height = next_height, + ?until, + "batch fetching blocks in range {height}..{next_height}" + ); + + Ok(conc( + (height..next_height) + .map(|h| { + call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(FetchBlock { height: h }), + )) + }) + .chain([continuation(next_height)]), + )) + } + Ordering::Greater => { + warn!( + "the latest finalized height ({latest_height}) \ + is less than the requested height ({height})" + ); + + Ok(continuation(height)) + } + } + } + + #[instrument(skip_all, fields(height))] + async fn fetch_block(&self, block_number: u64) -> RpcResult> { + info!(%block_number, "fetching events in block"); + + // list of MakeChainEvent ops that will be queued in a conc + let mut make_chain_event_ops: Vec> = vec![]; + + let mut page = const { NonZeroU32::new(1).unwrap() }; + + let mut total_count = 0; + + let mut continuation_token = None::; + + loop { + info!(%block_number, %page, "fetching page {page}"); + + let response = self + .client + .get_events( + EventFilter { + from_block: Some(BlockId::Number(block_number)), + to_block: Some(BlockId::Number(block_number)), + address: Some(self.ibc_host_contract_address), + keys: None, + }, + continuation_token, + // https://github.com/eqlabs/pathfinder/blob/a34566b9a9f6ea6d7eb3889130d62c8f3fe6a499/crates/rpc/src/method/get_events.rs#L15 + 1024, + ) + .await + .map_err(RpcError::retryable(format_args!( + "error fetching events for block {block_number}" + )))?; + + for emitted_event in response.events { + use cainome_cairo_serde::{ByteArray, ContractAddress, NonZero}; + + if emitted_event.keys[0] == selector!("ConnectionOpenInit") { + let (connection_id, client_id) = + <(NonZero, NonZero)>::cairo_deserialize(&emitted_event.keys, 1) + .unwrap(); + + let counterparty_client_id = + >::cairo_deserialize(&emitted_event.data, 0).unwrap(); + + let event = CairoIbcEvent::ConnectionOpenInit { + connection_id, + client_id, + counterparty_client_id, + }; + + dbg!(event, emitted_event); + } else if emitted_event.keys[0] == selector!("ChannelOpenTry") { + let (port_id, channel_id, counterparty_version) = + <(ContractAddress, NonZero, ByteArray)>::cairo_deserialize( + &emitted_event.keys, + 1, + ) + .unwrap(); + + let (counterparty_port_id, counterparty_channel_id, connection_id) = + <(ByteArray, NonZero, NonZero)>::cairo_deserialize( + &emitted_event.data, + 0, + ) + .unwrap(); + + let event = CairoIbcEvent::ChannelOpenTry { + port_id, + channel_id, + counterparty_port_id, + counterparty_channel_id, + connection_id, + counterparty_version, + }; + + dbg!(event, emitted_event); + } + } + + if response.continuation_token.is_none() { + break; + } + + continuation_token = response.continuation_token; + } + + Ok(conc(make_chain_event_ops.into_iter())) + } + + #[instrument(level = "info", skip_all, fields(%height, %tx_hash))] + async fn make_chain_event( + &self, + voyager_client: &VoyagerClient, + height: u64, + tx_hash: H256, + // event: IbcEvent, + ) -> RpcResult> { + // events at height N are provable at height N+k where k>0 + let provable_height = EventProvableHeight::Min(Height::new(height + 1)); + + // debug!(?event, "raw event"); + + // match event { + // IbcEvent::WasmCreateClient { + // client_id, + // client_type, + // } => { + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::(self.chain_id.clone(), height.into(), client_id) + // .await?; + + // let event = CreateClient { + // client_id, + // client_type: ClientType::new(client_type), + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmUpdateClient { + // client_id, + // counterparty_height, + // } => { + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::(self.chain_id.clone(), height.into(), client_id) + // .await?; + + // let event = UpdateClient { + // client_id, + // client_type: client_info.client_type.clone(), + // height: counterparty_height, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info.clone(), + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmConnectionOpenInit { + // connection_id, + // client_id, + // counterparty_client_id, + // } => { + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::(self.chain_id.clone(), height.into(), client_id) + // .await?; + + // let event = ConnectionOpenInit { + // client_id, + // connection_id, + // counterparty_client_id, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmConnectionOpenTry { + // connection_id, + // client_id, + // counterparty_client_id, + // counterparty_connection_id, + // } => { + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::(self.chain_id.clone(), height.into(), client_id) + // .await?; + + // let event = ConnectionOpenTry { + // connection_id, + // counterparty_connection_id, + // client_id, + // counterparty_client_id, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmConnectionOpenAck { + // connection_id, + // client_id, + // counterparty_client_id, + // counterparty_connection_id, + // } => { + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::(self.chain_id.clone(), height.into(), client_id) + // .await?; + + // let event = ConnectionOpenAck { + // connection_id, + // counterparty_connection_id, + // client_id, + // counterparty_client_id, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmConnectionOpenConfirm { + // connection_id, + // client_id, + // counterparty_client_id, + // counterparty_connection_id, + // } => { + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::(self.chain_id.clone(), height.into(), client_id) + // .await?; + + // let event = ConnectionOpenConfirm { + // connection_id, + // counterparty_connection_id, + // client_id, + // counterparty_client_id, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmChannelOpenInit { + // port_id, + // channel_id, + // counterparty_port_id, + // connection_id, + // version, + // } => { + // let connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { connection_id }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // connection.client_id, + // ) + // .await?; + + // let event = ChannelOpenInit { + // port_id: port_id.to_string().into_bytes().into(), + // channel_id, + // counterparty_port_id: counterparty_port_id.into_encoding(), + // connection, + // version, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmChannelOpenTry { + // port_id, + // channel_id, + // counterparty_port_id, + // counterparty_channel_id, + // connection_id, + // counterparty_version, + // } => { + // let connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { connection_id }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // connection.client_id, + // ) + // .await?; + + // let event = ChannelOpenTry { + // port_id: port_id.to_string().into_bytes().into(), + // channel_id, + // counterparty_port_id: counterparty_port_id.into_encoding(), + // counterparty_channel_id, + // connection, + // version: counterparty_version, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmChannelOpenAck { + // port_id, + // channel_id, + // counterparty_port_id, + // counterparty_channel_id, + // connection_id, + // } => { + // let connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { connection_id }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // connection.client_id, + // ) + // .await?; + + // let channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ChannelPath { channel_id }, + // ) + // .await?; + + // let event = ChannelOpenAck { + // port_id: port_id.to_string().into_bytes().into(), + // channel_id, + // counterparty_port_id: counterparty_port_id.into_encoding(), + // counterparty_channel_id, + // connection, + // version: channel.version, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + + // IbcEvent::WasmChannelOpenConfirm { + // port_id, + // channel_id, + // counterparty_port_id, + // counterparty_channel_id, + // connection_id, + // } => { + // let channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ChannelPath { channel_id }, + // ) + // .await?; + + // let connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { connection_id }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // connection.client_id, + // ) + // .await?; + + // let event = ChannelOpenConfirm { + // port_id: port_id.to_string().into_bytes().into(), + // channel_id, + // counterparty_port_id: counterparty_port_id.into_encoding(), + // counterparty_channel_id, + // connection, + // version: channel.version, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmPacketSend { + // packet_source_channel_id, + // packet_destination_channel_id, + // packet_data, + // packet_timeout_height: _, + // packet_timeout_timestamp, + // channel_id: _, + // packet_hash: _, + // } => { + // let packet = Packet { + // source_channel_id: packet_source_channel_id, + // destination_channel_id: packet_destination_channel_id, + // data: packet_data, + // timeout_height: MustBeZero, + // timeout_timestamp: packet_timeout_timestamp, + // }; + + // let state = voyager_client + // .maybe_query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Latest, + // ibc_union_spec::path::BatchPacketsPath::from_packets(slice::from_ref( + // &packet, + // )), + // ) + // .await?; + + // if state.state.is_none() { + // info!("packet already acknowledged"); + // return Ok(noop()); + // } + + // let source_channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ChannelPath { + // channel_id: packet.source_channel_id, + // }, + // ) + // .await?; + + // let source_connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { + // connection_id: source_channel.connection_id, + // }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), source_connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // source_connection.client_id, + // ) + // .await?; + + // let event = PacketSend { + // packet_data: packet.data, + // packet: PacketMetadata { + // source_channel: ChannelMetadata { + // channel_id: packet.source_channel_id, + // version: source_channel.version.clone(), + // connection: ConnectionMetadata { + // client_id: source_connection.client_id, + // connection_id: source_channel.connection_id, + // }, + // }, + // destination_channel: CounterpartyChannelMetadata { + // channel_id: packet.destination_channel_id, + // connection: ConnectionMetadata { + // client_id: source_connection.counterparty_client_id, + // connection_id: source_connection + // .counterparty_connection_id + // .unwrap(), + // }, + // }, + // timeout_timestamp: packet.timeout_timestamp, + // }, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmBatchSend { + // channel_id, + // packet_hash: _, + // batch_hash, + // } => { + // let source_channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ChannelPath { channel_id }, + // ) + // .await?; + + // let source_connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { + // connection_id: source_channel.connection_id, + // }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), source_connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // source_connection.client_id, + // ) + // .await?; + + // let event = BatchSend { + // batch_hash, + // source_channel: ChannelMetadata { + // channel_id, + // version: source_channel.version.clone(), + // connection: ConnectionMetadata { + // client_id: source_connection.client_id, + // connection_id: source_channel.connection_id, + // }, + // }, + // destination_channel: CounterpartyChannelMetadata { + // channel_id: source_channel + // .counterparty_channel_id + // .expect("channel is open"), + // connection: ConnectionMetadata { + // client_id: source_connection.counterparty_client_id, + // connection_id: source_connection.counterparty_connection_id.unwrap(), + // }, + // }, + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmPacketAck { + // acknowledgement, + // channel_id, + // packet_hash, + // } => { + // let packet = voyager_client + // .query( + // self.chain_id.clone(), + // PacketByHash { + // channel_id, + // packet_hash, + // }, + // ) + // .await? + // .packet; + + // let source_channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ChannelPath { + // channel_id: packet.source_channel_id, + // }, + // ) + // .await?; + + // let source_connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { + // connection_id: source_channel.connection_id, + // }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::(self.chain_id.clone(), source_connection.client_id) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // source_connection.client_id, + // ) + // .await?; + + // let event = PacketAck { + // packet_data: packet.data, + // packet: PacketMetadata { + // source_channel: ChannelMetadata { + // channel_id: packet.source_channel_id, + // version: source_channel.version.clone(), + // connection: ConnectionMetadata { + // client_id: source_connection.client_id, + // connection_id: source_channel.connection_id, + // }, + // }, + // destination_channel: CounterpartyChannelMetadata { + // channel_id: packet.destination_channel_id, + // connection: ConnectionMetadata { + // client_id: source_connection.counterparty_client_id, + // connection_id: source_connection + // .counterparty_connection_id + // .unwrap(), + // }, + // }, + // timeout_timestamp: packet.timeout_timestamp, + // }, + // acknowledgement: acknowledgement.into_encoding(), + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmPacketRecv { + // maker: _, + // maker_msg, + // channel_id, + // packet_hash, + // } => { + // let destination_channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ChannelPath { channel_id }, + // ) + // .await?; + + // let destination_connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { + // connection_id: destination_channel.connection_id, + // }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::( + // self.chain_id.clone(), + // destination_connection.client_id, + // ) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // destination_connection.client_id, + // ) + // .await?; + + // let packet = voyager_client + // .query( + // client_state_meta.counterparty_chain_id.clone(), + // PacketByHash { + // channel_id: destination_channel.counterparty_channel_id.unwrap(), + // packet_hash, + // }, + // ) + // .await? + // .packet; + + // let event = PacketRecv { + // packet_data: packet.data, + // packet: PacketMetadata { + // source_channel: CounterpartyChannelMetadata { + // channel_id: packet.source_channel_id, + // connection: ConnectionMetadata { + // client_id: destination_connection.counterparty_client_id, + // connection_id: destination_connection + // .counterparty_connection_id + // .unwrap(), + // }, + // }, + // destination_channel: ChannelMetadata { + // channel_id: packet.destination_channel_id, + // version: destination_channel.version.clone(), + // connection: ConnectionMetadata { + // client_id: destination_connection.client_id, + // connection_id: destination_channel.connection_id, + // }, + // }, + // timeout_timestamp: packet.timeout_timestamp, + // }, + // maker_msg: maker_msg.into_encoding(), + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // IbcEvent::WasmWriteAck { + // acknowledgement, + // channel_id, + // packet_hash, + // } => { + // let destination_channel = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ChannelPath { channel_id }, + // ) + // .await?; + + // let destination_connection = voyager_client + // .query_ibc_state( + // self.chain_id.clone(), + // QueryHeight::Specific(height), + // ibc_union_spec::path::ConnectionPath { + // connection_id: destination_channel.connection_id, + // }, + // ) + // .await?; + + // let client_info = voyager_client + // .client_info::( + // self.chain_id.clone(), + // destination_connection.client_id, + // ) + // .await?; + + // let client_state_meta = voyager_client + // .client_state_meta::( + // self.chain_id.clone(), + // height.into(), + // destination_connection.client_id, + // ) + // .await?; + + // let packet = voyager_client + // .query( + // client_state_meta.counterparty_chain_id.clone(), + // PacketByHash { + // channel_id: destination_channel.counterparty_channel_id.unwrap(), + // packet_hash, + // }, + // ) + // .await? + // .packet; + + // let event = WriteAck { + // packet_data: packet.data, + // packet: PacketMetadata { + // source_channel: CounterpartyChannelMetadata { + // channel_id: packet.source_channel_id, + // connection: ConnectionMetadata { + // client_id: destination_connection.counterparty_client_id, + // connection_id: destination_connection + // .counterparty_connection_id + // .unwrap(), + // }, + // }, + // destination_channel: ChannelMetadata { + // channel_id: packet.destination_channel_id, + // version: destination_channel.version.clone(), + // connection: ConnectionMetadata { + // client_id: destination_connection.client_id, + // connection_id: destination_channel.connection_id, + // }, + // }, + // timeout_timestamp: packet.timeout_timestamp, + // }, + // acknowledgement: acknowledgement.into_encoding(), + // } + // .into(); + + // ibc_union_spec::log_event(&event, &self.chain_id); + + // Ok(data(ChainEvent::new::( + // self.chain_id.clone(), + // client_info, + // client_state_meta.counterparty_chain_id, + // tx_hash, + // provable_height, + // event, + // ))) + // } + // } + + todo!() + } +} From d38ebdff15fb3d4214679331c05475491c804109 Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 5 Dec 2025 22:27:42 +0300 Subject: [PATCH 2/6] wip --- Cargo.lock | 30 ++++ lib/starknet-storage-verifier/Cargo.toml | 4 +- lib/starknet-storage-verifier/src/lib.rs | 165 +++++++++++++++++- .../plugins/event-source/starknet/src/main.rs | 33 +--- 4 files changed, 200 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 891a8e3953..bdfb9aebb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4879,6 +4879,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "devnet-compose" version = "0.0.0" @@ -5774,6 +5780,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fake" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d391ba4af7f1d93f01fcf7b2f29e2bc9348e109dfdbf4dcbdc51dfa38dab0b6" +dependencies = [ + "deunicode", + "rand 0.8.5", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -10310,6 +10326,18 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathfinder-crypto" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a208532e37e570178e12004616041b1aa6930c7eb4c9bba2460d192fbeb90a62" +dependencies = [ + "bitvec 1.0.1", + "fake", + "rand 0.8.5", + "serde", +] + [[package]] name = "pausable" version = "0.0.0" @@ -13553,7 +13581,9 @@ dependencies = [ name = "starknet-storage-verifier" version = "0.0.0" dependencies = [ + "bitvec 1.0.1", "hex-literal 0.4.1", + "pathfinder-crypto", "serde", "serde-utils", "serde_json", diff --git a/lib/starknet-storage-verifier/Cargo.toml b/lib/starknet-storage-verifier/Cargo.toml index fb5f728d09..188cb3cb6b 100644 --- a/lib/starknet-storage-verifier/Cargo.toml +++ b/lib/starknet-storage-verifier/Cargo.toml @@ -12,9 +12,11 @@ repository = { workspace = true } workspace = true [dependencies] +bitvec = { workspace = true } +pathfinder-crypto = "0.21.3" serde = { workspace = true, features = ["derive"] } serde-utils = { workspace = true } -starknet-crypto = { workspace = true } +starknet-crypto = { workspace = true, features = ["alloc"] } [dev-dependencies] hex-literal = { workspace = true } diff --git a/lib/starknet-storage-verifier/src/lib.rs b/lib/starknet-storage-verifier/src/lib.rs index 22ed6b2e3e..d774e4491d 100644 --- a/lib/starknet-storage-verifier/src/lib.rs +++ b/lib/starknet-storage-verifier/src/lib.rs @@ -1,5 +1,16 @@ +use std::collections::BTreeMap; + +use bitvec::{order::Msb0, view::BitView}; +use pathfinder_crypto::{Felt, hash::pedersen_hash}; use serde::{Deserialize, Serialize}; -use starknet_crypto::Felt; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Node { + node: MerkleNode, + #[serde(with = "felt")] + node_hash: Felt, +} /// A node in the Merkle-Patricia tree, can be a leaf, binary node, or an edge node. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -11,6 +22,19 @@ pub enum MerkleNode { EdgeNode(EdgeNode), } +impl MerkleNode { + pub fn hash(&self) -> Felt { + match self { + MerkleNode::BinaryNode(BinaryNode { left, right }) => pedersen_hash(*left, *right), + MerkleNode::EdgeNode(EdgeNode { + path, + length, + child, + }) => pedersen_hash(*child, *path) + Felt::from_u64(*length), + } + } +} + /// An internal node whose both children are non-zero. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -39,8 +63,8 @@ pub struct EdgeNode { } pub mod felt { + use pathfinder_crypto::Felt; use serde::{Deserializer, Serialize, Serializer, de::Deserialize}; - use starknet_crypto::Felt; pub fn serialize(data: &Felt, serializer: S) -> Result where @@ -49,7 +73,7 @@ pub mod felt { if serializer.is_human_readable() { serializer.collect_str(&data) } else { - data.to_bytes_be().serialize(serializer) + data.to_be_bytes().serialize(serializer) } } @@ -59,9 +83,140 @@ pub mod felt { { if deserializer.is_human_readable() { String::deserialize(deserializer) - .and_then(|s| Felt::from_hex(&s).map_err(serde::de::Error::custom)) + .and_then(|s| Felt::from_hex_str(&s).map_err(serde::de::Error::custom)) } else { - <[u8; 32]>::deserialize(deserializer).map(|bz| Felt::from_bytes_be(&bz)) + <[u8; 32]>::deserialize(deserializer) + .and_then(|bz| Felt::from_be_bytes(bz).map_err(serde::de::Error::custom)) } } } + +#[test] +fn test() { + let proof: Vec = serde_json::from_str( + r#" + [ + { + "node": { + "left": "0x4528b483169d0c4bba2171d487dc24da7371020f1cf4d47054038af05e88654", + "right": "0x5ed7e88e1116bae625a84e2f7dcb637bcd061d6ccb4eb921c6e207d52db510a" + }, + "node_hash": "0x2f26113a475400d1bc8dd0e9b2ea2fd548b5abe22e158568a9395780a58e2c1" + }, + { + "node": { + "child": "0x5dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b", + "length": 247, + "path": "0x263e9f85527e19d3f50301aab0505ad28496620b5484760e64f4fc6673e1ee" + }, + "node_hash": "0x5ed7e88e1116bae625a84e2f7dcb637bcd061d6ccb4eb921c6e207d52db510a" + }, + { + "node": { + "left": "0x2f26113a475400d1bc8dd0e9b2ea2fd548b5abe22e158568a9395780a58e2c1", + "right": "0x310643b32d81e4ee4cf0723859775500280e40ebef3e3458ffec38d16911607" + }, + "node_hash": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" + }, + { + "node": { + "child": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22", + "length": 1, + "path": "0x0" + }, + "node_hash": "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa" + }, + { + "node": { + "left": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd", + "right": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" + }, + "node_hash": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22" + } + ] +"#, + ) + .unwrap(); + + dbg!(&proof); + + let mut proof = proof + .into_iter() + .map(|n| (n.node_hash, n.node)) + .collect::>(); + + dbg!(&proof); + + let key = + Felt::from_hex_str("0x02a63e9f85527e19d3f50301aab0505ad28496620b5484760e64f4fc6673e1ee") + .unwrap(); + let value = + Felt::from_hex_str("0x5dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b").unwrap(); + + // 0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e + + // contracts_proof.contract_leaves_data.storage_root + let mut expected_hash = + Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); + + // https://github.com/eqlabs/pathfinder/blob/a34566b9a9f6ea6d7eb3889130d62c8f3fe6a499/crates/crypto/src/algebra/field/felt.rs#L176 + let mut remaining_path = key.view_bits(); + + while let Some(proof_node) = proof.remove(&expected_hash) { + // Hash mismatch? Return None. + // if proof_node.hash() != expected_hash { + // return None; + // } + + // eprintln!("{remaining_path:b}"); + + match proof_node { + MerkleNode::BinaryNode(BinaryNode { left, right }) => { + // Set the next hash to be the left or right hash, + // depending on the direction + // https://github.com/eqlabs/pathfinder/blob/a34566b9a9f6ea6d7eb3889130d62c8f3fe6a499/crates/merkle-tree/src/merkle_node.rs#L81 + expected_hash = match remaining_path[0] { + false => left, + true => right, + }; + + // Advance by a single bit + remaining_path = &remaining_path[1..]; + } + MerkleNode::EdgeNode(EdgeNode { + path, + length, + child, + }) => { + let path_view = &path.view_bits()[(251 - length) as usize..251]; + let remaining_path_view = &remaining_path[..length as usize]; + + eprintln!("length: {length}"); + eprintln!("path: {path:x}"); + eprintln!("path_view: {path_view:b}"); + eprintln!("remaining_path_view: {remaining_path_view:b}"); + + if path_view != remaining_path_view { + // If paths don't match, we've found a proof of non membership because + // we: + // 1. Correctly moved towards the target insofar as is possible, and + // 2. hashing all the nodes along the path does result in the root hash, + // which means + // 3. the target definitely does not exist in this tree + // return Some(Membership::NonMember); + dbg!("non-membership"); + break; + } + + // Set the next hash to the child's hash + expected_hash = child; + + // Advance by the whole edge path + remaining_path = &remaining_path[length as usize..]; + } + } + } + + dbg!(proof, expected_hash, value); +} diff --git a/voyager/plugins/event-source/starknet/src/main.rs b/voyager/plugins/event-source/starknet/src/main.rs index 31f788ef93..cdeb6353cf 100644 --- a/voyager/plugins/event-source/starknet/src/main.rs +++ b/voyager/plugins/event-source/starknet/src/main.rs @@ -1,34 +1,17 @@ // #![warn(clippy::unwrap_used)] -use core::slice; -use std::{ - cmp::Ordering, - collections::{BTreeMap, BTreeSet, VecDeque, btree_map::Entry}, - num::{NonZeroU8, NonZeroU32, ParseIntError}, -}; +use std::{cmp::Ordering, collections::VecDeque, num::NonZeroU32}; use cainome_cairo_serde::CairoSerde; -use ibc_union_spec::{ - IbcUnion, MustBeZero, Packet, - event::{ - BatchSend, ChannelMetadata, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, - ChannelOpenTry, ConnectionMetadata, ConnectionOpenAck, ConnectionOpenConfirm, - ConnectionOpenInit, ConnectionOpenTry, CounterpartyChannelMetadata, CreateClient, - PacketAck, PacketMetadata, PacketRecv, PacketSend, UpdateClient, WriteAck, - }, - path::ChannelPath, - query::PacketByHash, -}; -use jsonrpsee::{Extensions, core::async_trait, types::ErrorObject}; +use jsonrpsee::{Extensions, core::async_trait}; use serde::{Deserialize, Serialize}; -use serde_json::json; use starknet::{ core::types::{BlockId, EventFilter, Felt}, macros::selector, providers::{JsonRpcClient, Provider, Url, jsonrpc::HttpTransport}, }; -use tracing::{debug, error, info, info_span, instrument, trace, warn}; -use unionlabs::{ErrorReporter, ibc::core::client::height::Height, never::Never, primitives::H256}; +use tracing::{info, instrument, warn}; +use unionlabs::{ibc::core::client::height::Height, never::Never, primitives::H256}; use voyager_sdk::{ ExtensionsExt, VoyagerClient, anyhow::{self, bail}, @@ -37,12 +20,12 @@ use voyager_sdk::{ message::{ PluginMessage, VoyagerMessage, call::{Call, WaitForHeight}, - data::{ChainEvent, Data, EventProvableHeight}, + data::{Data, EventProvableHeight}, }, plugin::Plugin, - primitives::{ChainId, ClientType, QueryHeight}, + primitives::ChainId, rpc::{PluginServer, RpcError, RpcResult, types::PluginInfo}, - vm::{Op, call, conc, data, noop, pass::PassResult, seq}, + vm::{Op, call, conc, pass::PassResult, seq}, }; use crate::{ @@ -54,8 +37,6 @@ pub mod ibc_events; pub mod call; -const PER_PAGE_LIMIT: NonZeroU8 = NonZeroU8::new(100).unwrap(); - #[tokio::main] async fn main() { Module::run().await From df6481518a1bdacd34bf57dd2cec4967c65f5519 Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 5 Dec 2025 22:32:10 +0300 Subject: [PATCH 3/6] wip --- lib/starknet-storage-verifier/src/lib.rs | 80 +++++++++--------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/lib/starknet-storage-verifier/src/lib.rs b/lib/starknet-storage-verifier/src/lib.rs index d774e4491d..e2077b7335 100644 --- a/lib/starknet-storage-verifier/src/lib.rs +++ b/lib/starknet-storage-verifier/src/lib.rs @@ -95,45 +95,31 @@ pub mod felt { fn test() { let proof: Vec = serde_json::from_str( r#" - [ - { - "node": { - "left": "0x4528b483169d0c4bba2171d487dc24da7371020f1cf4d47054038af05e88654", - "right": "0x5ed7e88e1116bae625a84e2f7dcb637bcd061d6ccb4eb921c6e207d52db510a" - }, - "node_hash": "0x2f26113a475400d1bc8dd0e9b2ea2fd548b5abe22e158568a9395780a58e2c1" - }, - { - "node": { - "child": "0x5dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b", - "length": 247, - "path": "0x263e9f85527e19d3f50301aab0505ad28496620b5484760e64f4fc6673e1ee" - }, - "node_hash": "0x5ed7e88e1116bae625a84e2f7dcb637bcd061d6ccb4eb921c6e207d52db510a" - }, - { - "node": { - "left": "0x2f26113a475400d1bc8dd0e9b2ea2fd548b5abe22e158568a9395780a58e2c1", - "right": "0x310643b32d81e4ee4cf0723859775500280e40ebef3e3458ffec38d16911607" - }, - "node_hash": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" - }, - { - "node": { - "child": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22", - "length": 1, - "path": "0x0" - }, - "node_hash": "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa" - }, - { - "node": { - "left": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd", - "right": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" - }, - "node_hash": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22" - } - ] + [ + { + "node": { + "left": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd", + "right": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" + }, + "node_hash": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22" + }, + { + "node": { + "child": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22", + "length": 1, + "path": "0x0" + }, + "node_hash": "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa" + }, + { + "node": { + "child": "0x1611612cfc15e76d48f227e845073c85f4f55c3ef35921f169f8c475f6a819f", + "length": 1, + "path": "0x1" + }, + "node_hash": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd" + } + ] "#, ) .unwrap(); @@ -147,11 +133,8 @@ fn test() { dbg!(&proof); - let key = - Felt::from_hex_str("0x02a63e9f85527e19d3f50301aab0505ad28496620b5484760e64f4fc6673e1ee") - .unwrap(); - let value = - Felt::from_hex_str("0x5dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b").unwrap(); + let key = Felt::from_hex_str("0x0").unwrap(); + let value = Felt::from_hex_str("0x0").unwrap(); // 0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e @@ -165,11 +148,7 @@ fn test() { while let Some(proof_node) = proof.remove(&expected_hash) { // Hash mismatch? Return None. - // if proof_node.hash() != expected_hash { - // return None; - // } - - // eprintln!("{remaining_path:b}"); + assert!(proof_node.hash() == expected_hash); match proof_node { MerkleNode::BinaryNode(BinaryNode { left, right }) => { @@ -218,5 +197,6 @@ fn test() { } } - dbg!(proof, expected_hash, value); + assert!(proof.is_empty()); + assert_eq!(expected_hash, value); } From 2c1b73ef5913afe9e48e14cf2e148b255f1b7563 Mon Sep 17 00:00:00 2001 From: benluelo Date: Sat, 6 Dec 2025 04:06:54 +0300 Subject: [PATCH 4/6] wip --- Cargo.lock | 96 +++- Cargo.toml | 3 + flake.nix | 14 +- lib/starknet-light-client-types/Cargo.toml | 35 ++ .../src/client_state.rs | 27 + .../src/consensus_state.rs | 47 ++ lib/starknet-light-client-types/src/header.rs | 135 +++++ lib/starknet-light-client-types/src/main.rs | 4 + .../src/storage_proof.rs | 0 lib/starknet-storage-verifier/Cargo.toml | 15 +- lib/starknet-storage-verifier/src/lib.rs | 531 ++++++++++++++---- 11 files changed, 778 insertions(+), 129 deletions(-) create mode 100644 lib/starknet-light-client-types/Cargo.toml create mode 100644 lib/starknet-light-client-types/src/client_state.rs create mode 100644 lib/starknet-light-client-types/src/consensus_state.rs create mode 100644 lib/starknet-light-client-types/src/header.rs create mode 100644 lib/starknet-light-client-types/src/main.rs create mode 100644 lib/starknet-light-client-types/src/storage_proof.rs diff --git a/Cargo.lock b/Cargo.lock index bdfb9aebb9..8b1c2f201a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,7 +578,7 @@ dependencies = [ "keccak-asm", "paste", "proptest", - "rand 0.9.1", + "rand 0.9.2", "ruint", "rustc-hash 2.1.1", "serde", @@ -7925,7 +7925,7 @@ dependencies = [ "jsonrpsee-types 0.25.1", "parking_lot", "pin-project", - "rand 0.9.1", + "rand 0.9.2", "rustc-hash 2.1.1", "serde", "serde_json", @@ -8212,7 +8212,21 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ - "lambdaworks-math", + "lambdaworks-math 0.10.0", + "serde", + "sha2 0.10.9", + "sha3", +] + +[[package]] +name = "lambdaworks-crypto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" +dependencies = [ + "lambdaworks-math 0.13.0", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "sha2 0.10.9", "sha3", @@ -8228,6 +8242,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "lambdaworks-math" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" +dependencies = [ + "getrandom 0.2.16", + "num-bigint 0.4.6", + "num-traits", + "rand 0.8.5", + "serde", + "serde_json", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -9953,7 +9981,7 @@ dependencies = [ "glob", "opentelemetry", "percent-encoding", - "rand 0.9.1", + "rand 0.9.2", "thiserror 2.0.12", ] @@ -11118,7 +11146,7 @@ dependencies = [ "bytes", "getrandom 0.3.2", "lru-slab", - "rand 0.9.1", + "rand 0.9.2", "ring", "rustc-hash 2.1.1", "rustls 0.23.26", @@ -11227,9 +11255,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -11881,7 +11909,7 @@ dependencies = [ "primitive-types 0.12.2", "proptest", "rand 0.8.5", - "rand 0.9.1", + "rand 0.9.2", "rlp", "ruint-macro", "serde_core", @@ -13487,7 +13515,7 @@ dependencies = [ "sha3", "starknet-core-derive", "starknet-crypto", - "starknet-types-core", + "starknet-types-core 0.2.0", ] [[package]] @@ -13516,7 +13544,7 @@ dependencies = [ "rfc6979 0.4.0", "sha2 0.10.9", "starknet-curve", - "starknet-types-core", + "starknet-types-core 0.2.0", "zeroize", ] @@ -13526,7 +13554,22 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22c898ae81b6409532374cf237f1bd752d068b96c6ad500af9ebbd0d9bb712f6" dependencies = [ - "starknet-types-core", + "starknet-types-core 0.2.0", +] + +[[package]] +name = "starknet-light-client-types" +version = "0.0.0" +dependencies = [ + "alloy", + "bincode 2.0.1", + "ethereum-light-client-types", + "hex-literal 0.4.1", + "ibc-union-spec", + "serde", + "starknet-core", + "starknet-types-core 1.0.0", + "unionlabs", ] [[package]] @@ -13582,12 +13625,10 @@ name = "starknet-storage-verifier" version = "0.0.0" dependencies = [ "bitvec 1.0.1", - "hex-literal 0.4.1", "pathfinder-crypto", "serde", - "serde-utils", "serde_json", - "starknet-crypto", + "starknet-storage-verifier", ] [[package]] @@ -13598,11 +13639,30 @@ checksum = "5fa3d91e38f091dbc543d33589eb7716bed2a8eb1c20879e484561977832b60a" dependencies = [ "blake2", "digest 0.10.7", - "lambdaworks-crypto", - "lambdaworks-math", + "lambdaworks-crypto 0.10.0", + "lambdaworks-math 0.10.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "serde", + "zeroize", +] + +[[package]] +name = "starknet-types-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a12690813e587969cb4a9e7d8ebdb069d4bb7ec8d03275c5f719310c8e1f07c" +dependencies = [ + "blake2", + "digest 0.10.7", + "generic-array 0.14.7", + "lambdaworks-crypto 0.13.0", + "lambdaworks-math 0.13.0", "num-bigint 0.4.6", "num-integer", "num-traits", + "rand 0.9.2", "serde", "zeroize", ] @@ -15520,7 +15580,7 @@ dependencies = [ "http 1.3.1", "httparse", "log", - "rand 0.9.1", + "rand 0.9.2", "rustls 0.23.26", "rustls-pki-types", "sha1", @@ -16153,7 +16213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ "getrandom 0.3.2", - "rand 0.9.1", + "rand 0.9.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f79d1a5a60..41e9d3b350 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -296,6 +296,7 @@ members = [ "cosmwasm/pausable", "cosmwasm/gatekeeper", "cosmwasm/proxy-account-factory", + "lib/starknet-light-client-types", ] [workspace.package] @@ -420,6 +421,8 @@ serde-utils = { path = "lib/serde-utils", default- solidity-slot = { path = "lib/solidity-slot", default-features = false } ssz = { path = "lib/ssz", default-features = false } ssz-derive = { path = "lib/ssz-derive", default-features = false } +starknet-light-client-types = { path = "lib/starknet-light-client-types", default-features = false } +starknet-storage-verifier = { path = "lib/starknet-storage-verifier", default-features = false } state-lens-ics23-ics23-light-client-types = { path = "lib/state-lens-ics23-ics23-light-client-types", default-features = false } state-lens-ics23-mpt-light-client = { path = "cosmwasm/ibc-union/lightclient/state-lens-ics23-mpt", default-features = false } state-lens-ics23-mpt-light-client-types = { path = "lib/state-lens-ics23-mpt-light-client-types", default-features = false } diff --git a/flake.nix b/flake.nix index 7a2f9c0513..d58ae05815 100644 --- a/flake.nix +++ b/flake.nix @@ -373,7 +373,12 @@ mv $out/bin/cast $out/bin/cast-cursed cat <> $out/bin/cast - export LD_LIBRARY_PATH=${lib.makeLibraryPath [ super.stdenv.cc.cc.lib ]} + export LD_LIBRARY_PATH=${ + lib.makeLibraryPath [ + super.gcc14.cc.lib + # super.stdenv.cc.cc.lib + ] + } $out/bin/cast-cursed "\$@" unset LD_LIBRARY_PATH EOF @@ -385,7 +390,12 @@ mv $out/bin/forge $out/bin/forge-cursed cat <> $out/bin/forge - export LD_LIBRARY_PATH=${lib.makeLibraryPath [ super.stdenv.cc.cc.lib ]} + export LD_LIBRARY_PATH=${ + lib.makeLibraryPath [ + super.gcc14 + # super.stdenv.cc.cc.lib + ] + } $out/bin/forge-cursed "\$@" unset LD_LIBRARY_PATH EOF diff --git a/lib/starknet-light-client-types/Cargo.toml b/lib/starknet-light-client-types/Cargo.toml new file mode 100644 index 0000000000..7d2d9056fb --- /dev/null +++ b/lib/starknet-light-client-types/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "starknet-light-client-types" +version = "0.0.0" + +authors = { workspace = true } +edition = { workspace = true } +license-file = "LICENSE" +publish = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[dependencies] +alloy = { workspace = true, features = ["sol-types"], optional = true } +bincode = { workspace = true, features = ["alloc", "derive"], optional = true } +ethereum-light-client-types = { workspace = true } +ibc-union-spec = { workspace = true } +serde = { workspace = true, optional = true, features = ["derive"] } +starknet-core = { workspace = true } # REVIEW: Do we want to use this crate in the public api of this client's types? +starknet-types-core = { version = "1.0.0", features = ["hash"] } +unionlabs = { workspace = true } + +[features] +bincode = [ + "dep:bincode", + "unionlabs/bincode", + "ethereum-light-client-types/bincode", + "ibc-union-spec/bincode", +] +ethabi = ["dep:alloy", "ethereum-light-client-types/ethabi", "ibc-union-spec/ethabi"] +serde = ["dep:serde", "ethereum-light-client-types/serde", "ibc-union-spec/serde"] + +[dev-dependencies] +hex-literal = { workspace = true } diff --git a/lib/starknet-light-client-types/src/client_state.rs b/lib/starknet-light-client-types/src/client_state.rs new file mode 100644 index 0000000000..ca5b49da04 --- /dev/null +++ b/lib/starknet-light-client-types/src/client_state.rs @@ -0,0 +1,27 @@ +use starknet_core::types::Felt; +use unionlabs::primitives::H160; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "version", content = "data", rename_all = "snake_case") +)] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub enum ClientState { + V1(ClientStateV1), +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub struct ClientStateV1 { + pub chain_id: Felt, + pub latest_height: u64, + pub ibc_contract_address: Felt, + /// https://docs.starknet.io/learn/cheatsheets/chain-info#important-addresses + /// + /// Mainnet: `0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4` + /// Sepolia: `0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057` + pub l1_contract_address: H160, +} diff --git a/lib/starknet-light-client-types/src/consensus_state.rs b/lib/starknet-light-client-types/src/consensus_state.rs new file mode 100644 index 0000000000..36f139edf7 --- /dev/null +++ b/lib/starknet-light-client-types/src/consensus_state.rs @@ -0,0 +1,47 @@ +use ibc_union_spec::Timestamp; +use unionlabs::primitives::H256; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ConsensusState { + pub global_root: H256, + pub ibc_storage_root: H256, + pub timestamp: Timestamp, +} + +#[cfg(feature = "ethabi")] +pub mod ethabi { + use unionlabs::impl_ethabi_via_try_from_into; + + use super::*; + + impl_ethabi_via_try_from_into!(ConsensusState => SolConsensusState); + + alloy::sol! { + struct SolConsensusState { + bytes32 global_root; + bytes32 ibc_storage_root; + uint64 timestamp; + } + } + + impl From for SolConsensusState { + fn from(value: ConsensusState) -> Self { + Self { + global_root: value.global_root.get().into(), + ibc_storage_root: value.storage_root.get().into(), + timestamp: value.timestamp.as_nanos(), + } + } + } + + impl From for ConsensusState { + fn from(value: SolConsensusState) -> Self { + Self { + global_root: H256::new(value.global_root.0), + storage_root: H256::new(value.ibc_storage_root.0), + timestamp: Timestamp::from_nanos(value.timestamp), + } + } + } +} diff --git a/lib/starknet-light-client-types/src/header.rs b/lib/starknet-light-client-types/src/header.rs new file mode 100644 index 0000000000..485bf39e34 --- /dev/null +++ b/lib/starknet-light-client-types/src/header.rs @@ -0,0 +1,135 @@ +use ethereum_light_client_types::StorageProof; +use starknet_types_core::{ + felt::Felt, + hash::{Pedersen, Poseidon, StarkHash as CoreStarkHash}, +}; +use unionlabs::primitives::H256; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub struct Header { + pub l1_height: u64, + pub l1_block_hash_proof: StorageProof, + pub l2_block: L2Block, +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub struct L2Block { + block_number: u64, + parent_block_hash: H256, + global_state_root: H256, + sequencer_address: H256, + // SECONDS + block_timestamp: u64, + transaction_count: u32, + events_count: u32, + state_diff_length: u32, + state_diff_commitment: H256, + transactions_commitment: H256, + events_commitment: H256, + receipts_commitment: H256, + l1_gas_price: (u128, u128), + l1_data_gas_price: (u128, u128), + l2_gas_price: (u128, u128), + l1_da_mode: String, + protocol_version: String, +} + +impl L2Block { + /// + /// + pub fn hash(&self) -> H256 { + Poseidon::hash_array(&[ + Felt::from_bytes_be_slice(b"STARKNET_BLOCK_HASH1".as_slice()), + self.block_number.into(), + Felt::from_bytes_be(self.global_state_root.get()), + Felt::from_bytes_be(self.sequencer_address.get()), + self.block_timestamp.into(), + // https://github.com/starkware-libs/sequencer/blob/079ed26ce95b3b10de40c9916ffa332aaecd9f06/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L230 + Felt::from_bytes_be_slice( + [ + (self.transaction_count as u64).to_be_bytes(), + (self.events_count as u64).to_be_bytes(), + (self.state_diff_length as u64).to_be_bytes(), + match &*self.l1_da_mode { + // 0b0000_0000 ++ 7 bytes 0 padding + "CALLDATA" => 0_u64, + // 0b1000_0000 ++ 7 bytes 0 padding + "BLOB" => 1 << 63, + _ => panic!(), + } + .to_be_bytes(), + ] + .as_flattened(), + ), + Felt::from_bytes_be(self.state_diff_commitment.get()), + Felt::from_bytes_be(self.transactions_commitment.get()), + Felt::from_bytes_be(self.events_commitment.get()), + Felt::from_bytes_be(self.receipts_commitment.get()), + Poseidon::hash_array(&[ + Felt::from_bytes_be_slice(b"STARKNET_GAS_PRICES0".as_slice()), + self.l1_gas_price.0.into(), + self.l1_gas_price.1.into(), + self.l1_data_gas_price.0.into(), + self.l1_data_gas_price.1.into(), + self.l2_gas_price.0.into(), + self.l2_gas_price.1.into(), + ]), + Felt::from_bytes_be_slice(self.protocol_version.as_bytes()), + Felt::ZERO, + Felt::from_bytes_be(self.parent_block_hash.get()), + ]) + .to_bytes_be() + .into() + } +} + +#[test] +fn l2_block_hash() { + use hex_literal::hex; + + let block = L2Block { + block_number: 3996475, + parent_block_hash: hex!("07488afa914e19281d6a859f1673d91f84b124576677bc90790954934bcf6a90") + .into(), + global_state_root: hex!("000b977d63eeb59fda732ff60c6b956a91bd1c30784b2a25829f3a5fd882b0f8") + .into(), + sequencer_address: hex!("01176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8") + .into(), + block_timestamp: 1764693045, + transaction_count: 8, + events_count: 14 + 7 + 104 + 5 + 3 + 7 + 5 + 5, + state_diff_length: 108, + state_diff_commitment: hex!( + "000d69e24d96773a920991dcd7f86fea0526acb3dae9bb3955caf840c71b54f6" + ) + .into(), + transactions_commitment: hex!( + "01df3ce5acd86d8c2d7f1155997a70a004ee0a0c36c67c9baafe87ace22f30d9" + ) + .into(), + events_commitment: hex!("030a53d5d62958b18f1094b66c4ad4c3bcee8dd2a36666fc5fc8b46ddaa5b37c") + .into(), + receipts_commitment: hex!( + "0494e30696606f6208ac02b701f2350460c35b0be17cdf23e4017c79a6a69f2f" + ) + .into(), + l1_gas_price: (0x6df5cf40, 0x27d11e1709d4), + l1_data_gas_price: (0x1, 0x5cb2), + l2_gas_price: (0x1edd2, 0xb2d05e00), + l1_da_mode: "BLOB".to_owned(), + protocol_version: "0.14.0".to_owned(), + }; + + dbg!(&block); + + assert_eq!( + block.hash(), + ::new(hex!( + "0366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675" + )) + ); +} diff --git a/lib/starknet-light-client-types/src/main.rs b/lib/starknet-light-client-types/src/main.rs new file mode 100644 index 0000000000..1c5f13099f --- /dev/null +++ b/lib/starknet-light-client-types/src/main.rs @@ -0,0 +1,4 @@ +pub mod client_state; +pub mod consensus_state; +pub mod header; +pub mod storage_proof; diff --git a/lib/starknet-light-client-types/src/storage_proof.rs b/lib/starknet-light-client-types/src/storage_proof.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/starknet-storage-verifier/Cargo.toml b/lib/starknet-storage-verifier/Cargo.toml index 188cb3cb6b..31c77c69fc 100644 --- a/lib/starknet-storage-verifier/Cargo.toml +++ b/lib/starknet-storage-verifier/Cargo.toml @@ -12,12 +12,15 @@ repository = { workspace = true } workspace = true [dependencies] -bitvec = { workspace = true } +bitvec = { workspace = true } pathfinder-crypto = "0.21.3" -serde = { workspace = true, features = ["derive"] } -serde-utils = { workspace = true } -starknet-crypto = { workspace = true, features = ["alloc"] } +serde = { workspace = true, optional = true, features = ["derive"] } [dev-dependencies] -hex-literal = { workspace = true } -serde_json = { workspace = true } +serde_json = { workspace = true } +starknet-storage-verifier = { workspace = true, features = ["serde"] } + +[features] +default = [] + +serde = ["dep:serde"] diff --git a/lib/starknet-storage-verifier/src/lib.rs b/lib/starknet-storage-verifier/src/lib.rs index e2077b7335..efa65d8dfb 100644 --- a/lib/starknet-storage-verifier/src/lib.rs +++ b/lib/starknet-storage-verifier/src/lib.rs @@ -1,67 +1,75 @@ use std::collections::BTreeMap; -use bitvec::{order::Msb0, view::BitView}; -use pathfinder_crypto::{Felt, hash::pedersen_hash}; +use pathfinder_crypto::{ + Felt, + hash::{pedersen_hash, poseidon_hash}, +}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Node { - node: MerkleNode, - #[serde(with = "felt")] - node_hash: Felt, +pub trait FeltHash { + fn hash(a: Felt, b: Felt) -> Felt; +} + +pub enum PedersenHash {} + +impl FeltHash for PedersenHash { + fn hash(a: Felt, b: Felt) -> Felt { + pedersen_hash(a, b) + } +} + +pub enum PoseidonHash {} + +impl FeltHash for PoseidonHash { + fn hash(a: Felt, b: Felt) -> Felt { + poseidon_hash(a.into(), b.into()).into() + } } /// A node in the Merkle-Patricia tree, can be a leaf, binary node, or an edge node. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] +#[derive(Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))] pub enum MerkleNode { /// Binary/branch node. - BinaryNode(BinaryNode), + /// + /// An internal node whose both children are non-zero. + BinaryNode { + /// The hash of the left child + #[cfg_attr(feature = "serde", serde(with = "felt"))] + left: Felt, + /// The hash of the right child + #[cfg_attr(feature = "serde", serde(with = "felt"))] + right: Felt, + }, /// Edge/leaf node. - EdgeNode(EdgeNode), + /// + /// Represents a path to the highest non-zero descendant node. + EdgeNode { + /// An unsigned integer whose binary representation represents the path from the current node to + /// its highest non-zero descendant (bounded by 2^251) + #[cfg_attr(feature = "serde", serde(with = "felt"))] + path: Felt, + /// The length of the path (bounded by 251) + length: u8, + /// The hash of the unique non-zero maximal-height descendant node + #[cfg_attr(feature = "serde", serde(with = "felt"))] + child: Felt, + }, } impl MerkleNode { - pub fn hash(&self) -> Felt { + pub fn hash(&self) -> Felt { match self { - MerkleNode::BinaryNode(BinaryNode { left, right }) => pedersen_hash(*left, *right), - MerkleNode::EdgeNode(EdgeNode { + MerkleNode::BinaryNode { left, right } => H::hash(*left, *right), + MerkleNode::EdgeNode { path, length, child, - }) => pedersen_hash(*child, *path) + Felt::from_u64(*length), + } => H::hash(*child, *path) + Felt::from_u64((*length).into()), } } } -/// An internal node whose both children are non-zero. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct BinaryNode { - /// The hash of the left child - #[serde(with = "felt")] - pub left: Felt, - /// The hash of the right child - #[serde(with = "felt")] - pub right: Felt, -} - -/// Represents a path to the highest non-zero descendant node. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct EdgeNode { - /// An unsigned integer whose binary representation represents the path from the current node to - /// its highest non-zero descendant (bounded by 2^251) - #[serde(with = "felt")] - pub path: Felt, - /// The length of the path (bounded by 251) - pub length: u64, - /// The hash of the unique non-zero maximal-height descendant node - #[serde(with = "felt")] - pub child: Felt, -} - pub mod felt { use pathfinder_crypto::Felt; use serde::{Deserializer, Serialize, Serializer, de::Deserialize}; @@ -91,67 +99,35 @@ pub mod felt { } } -#[test] -fn test() { - let proof: Vec = serde_json::from_str( - r#" - [ - { - "node": { - "left": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd", - "right": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" - }, - "node_hash": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22" - }, - { - "node": { - "child": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22", - "length": 1, - "path": "0x0" - }, - "node_hash": "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa" - }, - { - "node": { - "child": "0x1611612cfc15e76d48f227e845073c85f4f55c3ef35921f169f8c475f6a819f", - "length": 1, - "path": "0x1" - }, - "node_hash": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd" - } - ] -"#, - ) - .unwrap(); +#[derive(Debug, PartialEq)] +pub enum Membership { + Membership, + NonMembership, +} - dbg!(&proof); +#[derive(Debug)] +pub enum Error { + UnusedNodes, + ValueMismatch { expected: Felt, found: Felt }, +} +pub fn verify_proof( + proof: impl IntoIterator, + key: Felt, + value: Felt, + mut expected_hash: Felt, +) -> Result { let mut proof = proof .into_iter() - .map(|n| (n.node_hash, n.node)) + .map(|n| (n.hash::(), n)) .collect::>(); - dbg!(&proof); - - let key = Felt::from_hex_str("0x0").unwrap(); - let value = Felt::from_hex_str("0x0").unwrap(); - - // 0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e - - // contracts_proof.contract_leaves_data.storage_root - let mut expected_hash = - Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") - .unwrap(); - // https://github.com/eqlabs/pathfinder/blob/a34566b9a9f6ea6d7eb3889130d62c8f3fe6a499/crates/crypto/src/algebra/field/felt.rs#L176 let mut remaining_path = key.view_bits(); while let Some(proof_node) = proof.remove(&expected_hash) { - // Hash mismatch? Return None. - assert!(proof_node.hash() == expected_hash); - match proof_node { - MerkleNode::BinaryNode(BinaryNode { left, right }) => { + MerkleNode::BinaryNode { left, right } => { // Set the next hash to be the left or right hash, // depending on the direction // https://github.com/eqlabs/pathfinder/blob/a34566b9a9f6ea6d7eb3889130d62c8f3fe6a499/crates/merkle-tree/src/merkle_node.rs#L81 @@ -163,18 +139,18 @@ fn test() { // Advance by a single bit remaining_path = &remaining_path[1..]; } - MerkleNode::EdgeNode(EdgeNode { + MerkleNode::EdgeNode { path, length, child, - }) => { + } => { let path_view = &path.view_bits()[(251 - length) as usize..251]; let remaining_path_view = &remaining_path[..length as usize]; - eprintln!("length: {length}"); - eprintln!("path: {path:x}"); - eprintln!("path_view: {path_view:b}"); - eprintln!("remaining_path_view: {remaining_path_view:b}"); + // eprintln!("length: {length}"); + // eprintln!("path: {path:x}"); + // eprintln!("path_view: {path_view:b}"); + // eprintln!("remaining_path_view: {remaining_path_view:b}"); if path_view != remaining_path_view { // If paths don't match, we've found a proof of non membership because @@ -184,8 +160,7 @@ fn test() { // which means // 3. the target definitely does not exist in this tree // return Some(Membership::NonMember); - dbg!("non-membership"); - break; + return Ok(Membership::NonMembership); } // Set the next hash to the child's hash @@ -197,6 +172,356 @@ fn test() { } } - assert!(proof.is_empty()); - assert_eq!(expected_hash, value); + if expected_hash != value { + return Err(Error::ValueMismatch { + expected: value, + found: expected_hash, + }); + } + + if !proof.is_empty() { + return Err(Error::UnusedNodes); + } + + Ok(Membership::Membership) } + +#[test] +fn contract_membership() { + // {"jsonrpc":"2.0","method":"starknet_getStorageProof","params":[{"block_number":3996475}, [], ["0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e"], [{"contract_address":"0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e", "storage_keys":["0x03d0f817b2e6b145a39886c95257e1bade33bc907b2125d2b4b93ced393d8e6b"]}]],"id":1} + + let proof: Vec = serde_json::from_str( + r#" +[ + { + "left": "0x30095df0bd831363806a03fecd7e73f1155881353aa0019bf04b4f5d1fea821", + "right": "0x748cd654d464a07a7125dd1f59f3af6440bd1cb91e3eabd00e25bb67e18f47" + }, + { + "left": "0xbc8378d6f911a210f16ccb10bf3dfe70015297467da04ff31a64e8af0171e1", + "right": "0x4929406f1a89910c18c385a255314e9087ee08d2d43bac141629fafe11d32ee" + }, + { + "left": "0x572aad9316302c0b1d289941b21505e5804eb65dc3cd8a4d0f44d93accf4841", + "right": "0x2744655003106431884eca542ea98dc49bf7893b2f91eadc3ee2680ab8ef277" + }, + { + "left": "0x2f78a85e8c9885e3a59d501cdeea93ba0187a1ae01f359707d8afd9f74baee5", + "right": "0x56d529ad090e5e0128d597230161c1c1bce16748c6290c544f8e79d468361a" + }, + { + "left": "0x4fe9a703e6d1058bb0e9877953c94c9c90b86a701c8f200fd9d44a923d1a5c7", + "right": "0x29214ce46be3b69a8df879702e92572a66a2233720ac243ce6e5c7c66611d3" + }, + { + "left": "0x620c91dad524cc9b3373395f796f6da612ed0dca4878522a164d37adea3e360", + "right": "0x7e0297ed3043db28612d7487b759ae291a5da3bb630d8af5aa010927829769e" + }, + { + "left": "0x6c06820feb357e347fd0af611070e8f9f83b046d230cf845adacd22dbc54cff", + "right": "0x7e2b13cc29dfaf25fd8f5e8f18f259da549223dcdb9e8a91cce8178bcd17901" + }, + { + "left": "0x14d8b2f2795217c6ee4ac6a8e3fb9bde9baa4680411ec63f73b887f040fd50e", + "right": "0x8eeb881ead580d16eebc8b3ecebc3816eb4bab2573442668242eb9fd73ac57" + }, + { + "left": "0x6bb29dd06cfca37492631004ea10b670b6ac104f2ccbe40e0889c790ff8faa3", + "right": "0x4b2e6799b800af6b60ee81abb0f27c9fd42bb728b7ad60cdf56b3be4ddc5f4b" + }, + { + "left": "0x1eb10eac884e2d2bde5aaa6bac9adce4ba54f0961a4c00c21676fba0d8852fb", + "right": "0xbce16eef6886a0ae81de15690d53085d74e35d4978305b1089c17fd6a4597d" + }, + { + "left": "0x498835c43eaae60a95bcd4e8e3bc89e524e41cf570d13fad12892ec6ec2ee40", + "right": "0x46abdeb1716e0fc230793bf94fe2493d7699d4d178f6b19d0774a2d1ffbbc7c" + }, + { + "left": "0x5595953da4821e203284a77330b290d121340f93ec829334aacf46e968af880", + "right": "0x50337184a321e3b087bef0290a8749f51a1bde4b07208efbd37cf1bb875cf96" + }, + { + "child": "0x4b6a88ec5cc90586fd5c285ae20224503306e5b304c5ddb26e643a53add589b", + "length": 1, + "path": "0x0" + }, + { + "left": "0x5169bd75bfe69fda75b0540c786db80c94daf757648db7697fad73bc822fc41", + "right": "0x20517e604b1e1a4e4f43218586b02d0008a15e08e95b12d8f84cea400e2a58c" + }, + { + "left": "0x3b8af7a5e04753f361e87b29efc2a381cbb2385db4a23cd0ca369a8836d5046", + "right": "0x6c900eb816f9c72490ed8f4e5d212e4a97f5f455b499d1108073a4fa00e66ad" + }, + { + "left": "0x2402022ec9f57d4a46f8eac414a7f5750b6080a155385e43381b129b7afc809", + "right": "0x3de8920b04a078cb1f4b9a28446fc23ca1b54fa9515e4d9824a47603fedd4a5" + }, + { + "left": "0x26a1526df622c907d8785994112b6883f3aac08990f83280fab885c3b44ea98", + "right": "0x1838240aa374acd6debf6d116ebb94a7263817c4d669ee882065a3b54528124" + }, + { + "child": "0x7ab8b6072e9d0957760c6d309c5ae7f27edaa5a85d1a05ee2ce1383872970ca", + "length": 226, + "path": "0x32c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e" + }, + { + "left": "0x5ea95db2890aab02eac34ee3ec2505449de85bab00bea978be1e29c708fdbbb", + "right": "0x1f57fd6e3d709385f713877097c605a862a7e4bc066972e063700ce159df3da" + }, + { + "left": "0x1ccef54c3f4f8b21b45a0d6e8fb027c8bdd34d6f2ff9f42776a581441b69aaf", + "right": "0x1f3c21a472345b5c074977d15481949b58100a3a51235ceb30263807779983c" + }, + { + "left": "0x708c497a0cc53dad98c127d954232cab3bee84a6bdd03efb57ffd2abb030b6", + "right": "0x394c6c35bf159839dad252ef79150932b351732e2eee91f97486ef6c17e5712" + }, + { + "left": "0x1569dc0213aff99902f23c4a0a3f1b6d22220744f8cc12f6b516266758f9891", + "right": "0x5b46548495f8969e54a116e8eb0b3f49d3fcdec358c3e1ba584c88d6253229d" + }, + { + "left": "0x6546df831ec386564b14284cf4a6f54321d8d719472754f6878dc07d475173e", + "right": "0x33f9aaf486d92e8f5d4dd7c8ca3b7df694c01d31c060dcc58d80454f89b992e" + }, + { + "left": "0x4fb8ace2d0cf884a3a070718b82a2568d16e72cf582b702592e2d553b1885fe", + "right": "0x3759e400e3bf0166eaa83e45494007bff64867c2f383e9b41d953471d62e76e" + }, + { + "left": "0x4257e8c08a26ee33a64622cffff95a83932ac81246c3ffbb2d8361aaae2d2b1", + "right": "0x6e0f0db09122b2b46deb5fd2269b4587947b937cf14874bbac7c15883ca3bd8" + }, + { + "left": "0x5c9a95965d8c446e9bc5e968d33ce077d1e705c0fcdefb4f836254843f63c21", + "right": "0x6cbbf2e225b06c542d324a9c3a55d792a5c95cb98543bfdd9d086e227f015d9" + } +] +"#, + ) + .unwrap(); + + let key = + Felt::from_hex_str("0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e") + .unwrap(); + let value = { + let class_hash = + Felt::from_hex_str("0x69b893a8b6e1bf94740e33d9584a01295510f3b51f024d9833b2acaf1be4045") + .unwrap(); + let nonce = Felt::from_hex_str("0x0").unwrap(); + let storage_root = + Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); + + // https://docs.starknet.io/learn/protocol/state#the-contract-trie + pedersen_hash( + pedersen_hash(pedersen_hash(class_hash, storage_root), nonce), + Felt::ZERO, + ) + }; + + // contracts_proof.contract_leaves_data.storage_root + let expected_hash = + Felt::from_hex_str("0x2c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb") + .unwrap(); + + let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + + assert_eq!(res, Membership::Membership); +} + +#[test] +fn contract_storage_membership() { + // {"jsonrpc":"2.0","method":"starknet_getStorageProof","params":[{"block_number":3996475}, [], ["0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e"], [{"contract_address":"0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e", "storage_keys":["0x03d0f817b2e6b145a39886c95257e1bade33bc907b2125d2b4b93ced393d8e6b"]}]],"id":1} + + let proof: Vec = serde_json::from_str( + r#" +[ + { + "child": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22", + "length": 1, + "path": "0x0" + }, + { + "left": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd", + "right": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" + }, + { + "child": "0x49ff5b3a7d38e2b50198f408fa8281635b5bc81ee49ab87ac36c8324c214427", + "length": 246, + "path": "0x10f817b2e6b145a39886c95257e1bade33bc907b2125d2b4b93ced393d8e6b" + }, + { + "left": "0x2f26113a475400d1bc8dd0e9b2ea2fd548b5abe22e158568a9395780a58e2c1", + "right": "0x310643b32d81e4ee4cf0723859775500280e40ebef3e3458ffec38d16911607" + }, + { + "left": "0x3e817efe680adf2c6072ed9f795191640549196d00a989a168cb96b1a2ffdb7", + "right": "0x7415330dba1c847123bd543bbb684771a5706a03814c4919d437abaf070a169" + }, + { + "left": "0x1673d50ff33986889bd487dc1dcaccae706f620e54d4b7afa9821e1408da49b", + "right": "0x50a62b544461e0d83bac95f26c7e0d906433b3f777ff5df13d074c45237b8c6" + } +] +"#, + ) + .unwrap(); + + let key = + Felt::from_hex_str("0x03d0f817b2e6b145a39886c95257e1bade33bc907b2125d2b4b93ced393d8e6b") + .unwrap(); + let value = + Felt::from_hex_str("0x49ff5b3a7d38e2b50198f408fa8281635b5bc81ee49ab87ac36c8324c214427") + .unwrap(); + + // contracts_proof.contract_leaves_data.storage_root + let expected_hash = + Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); + + let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + + assert_eq!(res, Membership::Membership); +} + +#[test] +fn contract_storage_non_membership() { + // {"jsonrpc":"2.0","method":"starknet_getStorageProof","params":[{"block_number":3996475}, [], ["0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e"], [{"contract_address":"0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e", "storage_keys":["0x0"]}]],"id":1} + + let proof: Vec = serde_json::from_str( + r#" +[ + { + "child": "0x1611612cfc15e76d48f227e845073c85f4f55c3ef35921f169f8c475f6a819f", + "length": 1, + "path": "0x1" + }, + { + "left": "0x778ebcee8874705995f911f4c7edaac1748f5b583c146e9c37dd48e30d11cfd", + "right": "0x219c6c95d8eeee035ffa9bd5d301175569b6151874f157c4f9546f0073710db" + }, + { + "child": "0x56ef8be5dc020f5437e6611ca54e4f78c245c2e49592de3db76abfe0998eb22", + "length": 1, + "path": "0x0" + } +] +"#, + ) + .unwrap(); + + let key = Felt::from_hex_str("0x0").unwrap(); + let value = Felt::from_hex_str("0x0").unwrap(); + + // contracts_proof.contract_leaves_data.storage_root + let expected_hash = + Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); + + let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + + assert_eq!(res, Membership::NonMembership); +} + +// #[test] +// fn class_membership() { +// // {"jsonrpc":"2.0","method":"starknet_getStorageProof","params":[{"block_number":3996475}, [], ["0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e"], [{"contract_address":"0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e", "storage_keys":["0x0"]}]],"id":1} + +// let proof: Vec = serde_json::from_str( +// r#" +// [ +// { +// "left": "0x26b2739055e4802a19ca4bccc676eb1915805b622840d80f2589e81b6242ece", +// "right": "0x3a433590778d3909e3e179e0c4cd0b4484c633dd8bdc37a185e2b9243ac2de5" +// }, +// { +// "left": "0x16dc0a8f88330651d7f3715300c9e066fb71758a14d3373449cd02771975f0e", +// "right": "0x4d006e7521a809d5cbcdcd13478baf939bb2a045742300325928880a9c7d589" +// }, +// { +// "child": "0x64a8b101389497ad8d3aa2a57cb929610fecf2a73e0c48b88945524299bd397", +// "length": 1, +// "path": "0x1" +// }, +// { +// "left": "0x3a48d7fa71fbae5ec931ae9ee7bcc570509f3f57659c2c75ae9137519f5a2b", +// "right": "0x10eb67d6673d276714dc2dd57027cddb4d285c736a7a2bfa1d29ae8b0a89f67" +// }, +// { +// "left": "0x611b50cdba393035653b6a1f57841f6d72ceafee9309747ef9bfcba94aef9d6", +// "right": "0x71df411506e7b929d96b7e8349e504fcf936ab3434065d9063e3c43817a428a" +// }, +// { +// "left": "0x2d58a9c26903f33f15327e0e6282524cb5f0d07397484b762b655db6ab8f0d0", +// "right": "0x10d9f15ceec6d543775137f0ef6d38f263f5156d53bf8abd8bcb34655b14354" +// }, +// { +// "left": "0x43a417180d30647875a1659c9e0a07545af75d2aef2621d919a9806eed86c37", +// "right": "0x75a9d7ff57564c2f562954d359be74d5e61d1976111da06ada132e93cc0709" +// }, +// { +// "left": "0x42db20cc2e93142d8994ab403ab9994a704d1385cdf7852e8553611e134ac9c", +// "right": "0x3a9b7d4ce629aa371a610a023d96271aea2cb6f37c13b4522aa5a6c4d3f8656" +// }, +// { +// "left": "0x748a8d8c1aa1e5b2e93651fc417eb5110965da145356e81aa7bbeeb66179797", +// "right": "0x3231d374fac2f62ae747f799cd7f93cea095dee0d41992f10f757bc146229d9" +// }, +// { +// "left": "0x18679b940a8998d46ffcfbd99012e2cfdf2ec25ac211c40a678a7edcd5b679d", +// "right": "0x5510a179fedda3e54154d17010c9b41e103c90a41e434349bc234b23170291" +// }, +// { +// "left": "0x4fdbbc8483c5847d9fc1254deda500218c5772eb3ac86e189c316b8efed176f", +// "right": "0x5546b5291fda84d8a05b7e00c4a329ae7b65360539a7f6f8be46351a01ac092" +// }, +// { +// "left": "0x754ad9e6ac2d4251d68ba34e4e89dd40d68eaa820174f6123b496ed3a764551", +// "right": "0xd500b04c4d14b9370156c40c1ee43524d6b866a485b833b28a945eb71bfcb7" +// }, +// { +// "left": "0x20493ddcc3e537c03cb85385fb21c513563abbd199f3a992dfb78513df7f5ae", +// "right": "0x3982ec392741d6b5cacc9dce89923712bbeef746c1a964b0e02277ddc1feba1" +// }, +// { +// "left": "0x5aa872c5b3fddb2258cff2bf457b6df819a16db9d9526b2c04beb53ba864cb9", +// "right": "0x45ae2ba84e8feb6f552f4d16e099908327e48dc8c32da1d29b14e8cce3876ff" +// }, +// { +// "left": "0x15da48a83f9b9f2062b7911aa22b4d67429b2eb0fc11d6ee27055c0023053a4", +// "right": "0x38a232e4222e5e9b764f022be0c90a5e81106efcdb0d9c4df58ec0ab19737ba" +// }, +// { +// "left": "0x2dc76ef37ea85d5ff626b572b5a6eae1fdda7242a2eb4416a12c0350ee373d4", +// "right": "0x7e7126adac488ccdc1c6f12c4b5ba6cc9a7d66d1ec172802e25ca1b71b2965b" +// }, +// { +// "child": "0x71e2a6ac164a33d38ff9bb07fef96bbef3ff82b9445ec51d64e4bf01581f268", +// "length": 234, +// "path": "0x13a8b6e1bf94740e33d9584a01295510f3b51f024d9833b2acaf1be4045" +// }, +// { +// "left": "0x1bb32891b44fd0166003032b24e60ae4250d9e11f56367857b13cd1838811c1", +// "right": "0x694cd424f3be20d80a235f92e33c736af50646e39001b61144d1a9fdf477324" +// } +// ] +// "#, +// ) +// .unwrap(); + +// let key = +// Felt::from_hex_str("0x69b893a8b6e1bf94740e33d9584a01295510f3b51f024d9833b2acaf1be4045") +// .unwrap(); +// let value = Felt::from_hex_str("0x0").unwrap(); + +// // contracts_proof.contract_leaves_data.storage_root +// let expected_hash = Felt::from_hex_str("").unwrap(); + +// let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + +// assert_eq!(res, Membership::NonMembership); +// } From 212bb91f89a0b42e61300634df796fab1218b013 Mon Sep 17 00:00:00 2001 From: benluelo Date: Sat, 6 Dec 2025 04:25:15 +0300 Subject: [PATCH 5/6] wip --- lib/starknet-light-client-types/src/header.rs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/starknet-light-client-types/src/header.rs b/lib/starknet-light-client-types/src/header.rs index 485bf39e34..a54e9e5ea7 100644 --- a/lib/starknet-light-client-types/src/header.rs +++ b/lib/starknet-light-client-types/src/header.rs @@ -1,7 +1,7 @@ use ethereum_light_client_types::StorageProof; use starknet_types_core::{ felt::Felt, - hash::{Pedersen, Poseidon, StarkHash as CoreStarkHash}, + hash::{Poseidon, StarkHash as _}, }; use unionlabs::primitives::H256; @@ -34,16 +34,29 @@ pub struct L2Block { l1_gas_price: (u128, u128), l1_data_gas_price: (u128, u128), l2_gas_price: (u128, u128), - l1_da_mode: String, + l1_da_mode: L1DaMode, protocol_version: String, } +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub enum L1DaMode { + Blob, + Calldata, +} + impl L2Block { /// /// pub fn hash(&self) -> H256 { Poseidon::hash_array(&[ - Felt::from_bytes_be_slice(b"STARKNET_BLOCK_HASH1".as_slice()), + // hex(b"STARKNET_BLOCK_HASH1") + const { Felt::from_hex_unwrap("0x535441524b4e45545f424c4f434b5f4841534831") }, self.block_number.into(), Felt::from_bytes_be(self.global_state_root.get()), Felt::from_bytes_be(self.sequencer_address.get()), @@ -51,18 +64,17 @@ impl L2Block { // https://github.com/starkware-libs/sequencer/blob/079ed26ce95b3b10de40c9916ffa332aaecd9f06/crates/starknet_api/src/block_hash/block_hash_calculator.rs#L230 Felt::from_bytes_be_slice( [ - (self.transaction_count as u64).to_be_bytes(), - (self.events_count as u64).to_be_bytes(), - (self.state_diff_length as u64).to_be_bytes(), - match &*self.l1_da_mode { + (self.transaction_count as u64), + (self.events_count as u64), + (self.state_diff_length as u64), + match self.l1_da_mode { // 0b0000_0000 ++ 7 bytes 0 padding - "CALLDATA" => 0_u64, + L1DaMode::Calldata => 0_u64, // 0b1000_0000 ++ 7 bytes 0 padding - "BLOB" => 1 << 63, - _ => panic!(), - } - .to_be_bytes(), + L1DaMode::Blob => 1 << 63, + }, ] + .map(u64::to_be_bytes) .as_flattened(), ), Felt::from_bytes_be(self.state_diff_commitment.get()), @@ -70,7 +82,8 @@ impl L2Block { Felt::from_bytes_be(self.events_commitment.get()), Felt::from_bytes_be(self.receipts_commitment.get()), Poseidon::hash_array(&[ - Felt::from_bytes_be_slice(b"STARKNET_GAS_PRICES0".as_slice()), + // hex(b"STARKNET_GAS_PRICES0") + const { Felt::from_hex_unwrap("0x535441524b4e45545f4741535f50524943455330") }, self.l1_gas_price.0.into(), self.l1_gas_price.1.into(), self.l1_data_gas_price.0.into(), @@ -91,6 +104,7 @@ impl L2Block { fn l2_block_hash() { use hex_literal::hex; + // https://feeder.alpha-mainnet.starknet.io/feeder_gateway/get_block?blockNumber=3996475 let block = L2Block { block_number: 3996475, parent_block_hash: hex!("07488afa914e19281d6a859f1673d91f84b124576677bc90790954934bcf6a90") @@ -120,7 +134,7 @@ fn l2_block_hash() { l1_gas_price: (0x6df5cf40, 0x27d11e1709d4), l1_data_gas_price: (0x1, 0x5cb2), l2_gas_price: (0x1edd2, 0xb2d05e00), - l1_da_mode: "BLOB".to_owned(), + l1_da_mode: L1DaMode::Blob, protocol_version: "0.14.0".to_owned(), }; From b0258fbb277635e144d2060d6b512e2a6d9b042b Mon Sep 17 00:00:00 2001 From: benluelo Date: Sat, 6 Dec 2025 04:34:32 +0300 Subject: [PATCH 6/6] wip --- lib/starknet-light-client-types/src/header.rs | 20 ++++++++++++---- lib/starknet-light-client-types/src/lib.rs | 11 +++++++++ lib/starknet-light-client-types/src/main.rs | 4 ---- .../src/storage_proof.rs | 24 +++++++++++++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 lib/starknet-light-client-types/src/lib.rs delete mode 100644 lib/starknet-light-client-types/src/main.rs diff --git a/lib/starknet-light-client-types/src/header.rs b/lib/starknet-light-client-types/src/header.rs index a54e9e5ea7..e5d0f6e325 100644 --- a/lib/starknet-light-client-types/src/header.rs +++ b/lib/starknet-light-client-types/src/header.rs @@ -1,21 +1,32 @@ -use ethereum_light_client_types::StorageProof; +use ethereum_light_client_types::StorageProof as L1StorageProof; use starknet_types_core::{ felt::Felt, hash::{Poseidon, StarkHash as _}, }; use unionlabs::primitives::H256; +use crate::StorageProof; + #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(deny_unknown_fields, rename_all = "snake_case") +)] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct Header { pub l1_height: u64, - pub l1_block_hash_proof: StorageProof, + pub l1_block_hash_proof: L1StorageProof, pub l2_block: L2Block, + pub l2_ibc_contract_proof: StorageProof, } #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(deny_unknown_fields, rename_all = "snake_case") +)] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct L2Block { block_number: u64, @@ -53,6 +64,7 @@ pub enum L1DaMode { impl L2Block { /// /// + // TODO: Handle different versions pub fn hash(&self) -> H256 { Poseidon::hash_array(&[ // hex(b"STARKNET_BLOCK_HASH1") diff --git a/lib/starknet-light-client-types/src/lib.rs b/lib/starknet-light-client-types/src/lib.rs new file mode 100644 index 0000000000..9620501a2a --- /dev/null +++ b/lib/starknet-light-client-types/src/lib.rs @@ -0,0 +1,11 @@ +pub mod client_state; +pub mod consensus_state; +pub mod header; +pub mod storage_proof; + +pub use crate::{ + client_state::{ClientState, ClientStateV1}, + consensus_state::ConsensusState, + header::Header, + storage_proof::StorageProof, +}; diff --git a/lib/starknet-light-client-types/src/main.rs b/lib/starknet-light-client-types/src/main.rs deleted file mode 100644 index 1c5f13099f..0000000000 --- a/lib/starknet-light-client-types/src/main.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod client_state; -pub mod consensus_state; -pub mod header; -pub mod storage_proof; diff --git a/lib/starknet-light-client-types/src/storage_proof.rs b/lib/starknet-light-client-types/src/storage_proof.rs index e69de29bb2..c92ce77cf8 100644 --- a/lib/starknet-light-client-types/src/storage_proof.rs +++ b/lib/starknet-light-client-types/src/storage_proof.rs @@ -0,0 +1,24 @@ +use unionlabs::primitives::H256; + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(deny_unknown_fields, rename_all = "snake_case") +)] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub struct StorageProof { + nodes: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(deny_unknown_fields, rename_all = "snake_case") +)] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub enum MerkleNode { + BinaryNode { left: H256, right: H256 }, + EdgeNode { path: H256, length: u8, child: H256 }, +}