From 0fe5f2f1300ac7a4a80b9102674c9a9cfac9697f Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 5 Dec 2025 20:06:22 +0300 Subject: [PATCH 1/8] 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 1863416a254..9e1426184e5 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" @@ -4656,7 +4669,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "uuid", + "uuid 1.16.0", ] [[package]] @@ -5517,6 +5530,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" @@ -5528,6 +5563,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" @@ -5609,7 +5657,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", ] @@ -7388,6 +7439,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" @@ -8133,27 +8193,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]] @@ -8767,7 +8824,7 @@ dependencies = [ "smallvec", "tagptr", "thiserror 1.0.69", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -9322,7 +9379,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -10707,7 +10764,7 @@ checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash 0.7.0", "impl-codec 0.5.1", - "impl-serde", + "impl-serde 0.3.2", "uint", ] @@ -10720,6 +10777,7 @@ dependencies = [ "fixed-hash 0.8.0", "impl-codec 0.6.0", "impl-rlp", + "impl-serde 0.4.0", "uint", ] @@ -12094,6 +12152,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" @@ -12245,6 +12312,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" @@ -12545,6 +12624,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" @@ -13311,6 +13401,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" @@ -13318,6 +13488,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", @@ -13338,6 +13509,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" @@ -13351,9 +13570,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", @@ -13362,6 +13581,8 @@ dependencies = [ "num-bigint 0.4.6", "num-integer", "num-traits", + "serde", + "zeroize", ] [[package]] @@ -15028,7 +15249,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -15893,6 +16114,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" @@ -16886,6 +17117,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 12f4131de97..f79d1a5a60d 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 eda26c447d4..6c15fcd2494 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 00000000000..93bf591d548 --- /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 00000000000..d1cf68963f6 --- /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 00000000000..9eb45177cf0 --- /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 00000000000..31f788ef937 --- /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 e09eb2a065554d025114b94d1af958295cfad13c Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 5 Dec 2025 22:27:42 +0300 Subject: [PATCH 2/8] 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 9e1426184e5..51361b5853b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4880,6 +4880,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" @@ -5775,6 +5781,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" @@ -10312,6 +10328,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" @@ -13561,7 +13589,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 fb5f728d09b..188cb3cb6be 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 22ed6b2e3ea..d774e4491de 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 31f788ef937..cdeb6353cf8 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 01ee1d4ccede4f87474d67953a9a68f2bc42d492 Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 5 Dec 2025 22:32:10 +0300 Subject: [PATCH 3/8] 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 d774e4491de..e2077b73355 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 321c400b38e1b25bfd954cff6348485162ce30b5 Mon Sep 17 00:00:00 2001 From: benluelo Date: Sat, 6 Dec 2025 04:06:54 +0300 Subject: [PATCH 4/8] 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 51361b5853b..bd13f7b5302 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", @@ -7926,7 +7926,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", @@ -8213,7 +8213,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", @@ -8229,6 +8243,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" @@ -9955,7 +9983,7 @@ dependencies = [ "glob", "opentelemetry", "percent-encoding", - "rand 0.9.1", + "rand 0.9.2", "thiserror 2.0.12", ] @@ -11120,7 +11148,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", @@ -11229,9 +11257,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", @@ -11883,7 +11911,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", @@ -13495,7 +13523,7 @@ dependencies = [ "sha3", "starknet-core-derive", "starknet-crypto", - "starknet-types-core", + "starknet-types-core 0.2.0", ] [[package]] @@ -13524,7 +13552,7 @@ dependencies = [ "rfc6979 0.4.0", "sha2 0.10.9", "starknet-curve", - "starknet-types-core", + "starknet-types-core 0.2.0", "zeroize", ] @@ -13534,7 +13562,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]] @@ -13590,12 +13633,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]] @@ -13606,11 +13647,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", ] @@ -15528,7 +15588,7 @@ dependencies = [ "http 1.3.1", "httparse", "log", - "rand 0.9.1", + "rand 0.9.2", "rustls 0.23.26", "rustls-pki-types", "sha1", @@ -16161,7 +16221,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 f79d1a5a60d..41e9d3b3506 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 7a2f9c0513d..d58ae058154 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 00000000000..7d2d9056fb8 --- /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 00000000000..ca5b49da046 --- /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 00000000000..36f139edf7e --- /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 00000000000..485bf39e34e --- /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 00000000000..1c5f13099f4 --- /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 00000000000..e69de29bb2d diff --git a/lib/starknet-storage-verifier/Cargo.toml b/lib/starknet-storage-verifier/Cargo.toml index 188cb3cb6be..31c77c69fc8 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 e2077b73355..efa65d8dfbc 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 67969505ad0d36d917241066a2d542e9bbf410c2 Mon Sep 17 00:00:00 2001 From: benluelo Date: Sat, 6 Dec 2025 04:25:15 +0300 Subject: [PATCH 5/8] 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 485bf39e34e..a54e9e5ea70 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 624afc661e58f0c9acd4495c5dfa6a964c7414dc Mon Sep 17 00:00:00 2001 From: benluelo Date: Sat, 6 Dec 2025 04:34:32 +0300 Subject: [PATCH 6/8] 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 a54e9e5ea70..e5d0f6e3250 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 00000000000..9620501a2a2 --- /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 1c5f13099f4..00000000000 --- 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 e69de29bb2d..c92ce77cf89 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 }, +} From f2d17b1a8cd3fca0d769b2966bd3fe6e4be6d4eb Mon Sep 17 00:00:00 2001 From: benluelo Date: Thu, 11 Dec 2025 23:54:43 +0300 Subject: [PATCH 7/8] wip --- Cargo.lock | 26 ++ Cargo.toml | 3 +- lib/starknet-light-client-types/Cargo.toml | 3 +- .../src/client_state.rs | 4 +- lib/starknet-light-client-types/src/header.rs | 205 +++++++---- .../src/storage_proof.rs | 3 +- lib/starknet-light-client/Cargo.toml | 14 + lib/starknet-light-client/src/lib.rs | 14 + lib/starknet-storage-verifier/Cargo.toml | 7 +- lib/starknet-storage-verifier/src/lib.rs | 38 +- lib/starknet-verifier/Cargo.toml | 22 ++ lib/starknet-verifier/src/lib.rs | 336 ++++++++++++++++++ .../plugins/event-source/starknet/Cargo.toml | 32 +- 13 files changed, 592 insertions(+), 115 deletions(-) create mode 100644 lib/starknet-light-client/Cargo.toml create mode 100644 lib/starknet-light-client/src/lib.rs create mode 100644 lib/starknet-verifier/Cargo.toml create mode 100644 lib/starknet-verifier/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index bd13f7b5302..27bf3ab6b41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8176,6 +8176,12 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + [[package]] name = "lalrpop" version = "0.19.12" @@ -13565,6 +13571,10 @@ dependencies = [ "starknet-types-core 0.2.0", ] +[[package]] +name = "starknet-light-client" +version = "0.0.0" + [[package]] name = "starknet-light-client-types" version = "0.0.0" @@ -13574,6 +13584,7 @@ dependencies = [ "ethereum-light-client-types", "hex-literal 0.4.1", "ibc-union-spec", + "pathfinder-crypto", "serde", "starknet-core", "starknet-types-core 1.0.0", @@ -13637,6 +13648,7 @@ dependencies = [ "serde", "serde_json", "starknet-storage-verifier", + "thiserror 2.0.12", ] [[package]] @@ -13675,6 +13687,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "starknet-verifier" +version = "0.0.0" +dependencies = [ + "evm-storage-verifier", + "hex-literal 0.4.1", + "keccak-const", + "rlp", + "starknet-light-client-types", + "starknet-storage-verifier", + "thiserror 2.0.12", + "unionlabs-primitives", +] + [[package]] name = "starlark" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index 41e9d3b3506..2f05863c03b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -297,6 +297,8 @@ members = [ "cosmwasm/gatekeeper", "cosmwasm/proxy-account-factory", "lib/starknet-light-client-types", + "lib/starknet-verifier", + "lib/starknet-light-client", ] [workspace.package] @@ -534,7 +536,6 @@ 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 strum = { version = "0.27", default-features = false } subtle-encoding = { version = "0.5.1", default-features = false } diff --git a/lib/starknet-light-client-types/Cargo.toml b/lib/starknet-light-client-types/Cargo.toml index 7d2d9056fb8..280220ac9fa 100644 --- a/lib/starknet-light-client-types/Cargo.toml +++ b/lib/starknet-light-client-types/Cargo.toml @@ -16,9 +16,10 @@ alloy = { workspace = true, features = ["sol-types"], opti bincode = { workspace = true, features = ["alloc", "derive"], optional = true } ethereum-light-client-types = { workspace = true } ibc-union-spec = { workspace = true } +pathfinder-crypto = "0.21.3" 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"] } +starknet-types-core = { version = "1.0.0", features = ["hash"] } unionlabs = { workspace = true } [features] diff --git a/lib/starknet-light-client-types/src/client_state.rs b/lib/starknet-light-client-types/src/client_state.rs index ca5b49da046..51eeebaa9d3 100644 --- a/lib/starknet-light-client-types/src/client_state.rs +++ b/lib/starknet-light-client-types/src/client_state.rs @@ -1,5 +1,5 @@ -use starknet_core::types::Felt; -use unionlabs::primitives::H160; +use pathfinder_crypto::Felt; +use unionlabs::primitives::{H160, H256}; #[derive(Debug, Clone, PartialEq)] #[cfg_attr( diff --git a/lib/starknet-light-client-types/src/header.rs b/lib/starknet-light-client-types/src/header.rs index e5d0f6e3250..1a7386a9f68 100644 --- a/lib/starknet-light-client-types/src/header.rs +++ b/lib/starknet-light-client-types/src/header.rs @@ -1,11 +1,11 @@ -use ethereum_light_client_types::StorageProof as L1StorageProof; -use starknet_types_core::{ - felt::Felt, - hash::{Poseidon, StarkHash as _}, +pub use ethereum_light_client_types::AccountProof; +use pathfinder_crypto::{ + Felt, + hash::{pedersen_hash, poseidon_hash_many}, }; -use unionlabs::primitives::H256; +use unionlabs::primitives::{Bytes, H256, U256}; -use crate::StorageProof; +use crate::storage_proof::MerkleNode; #[derive(Debug, Clone, PartialEq)] #[cfg_attr( @@ -16,9 +16,10 @@ use crate::StorageProof; #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct Header { pub l1_height: u64, - pub l1_block_hash_proof: L1StorageProof, + pub l1_contract_account_proof: AccountProof, + pub l1_block_hash_proof: Vec, pub l2_block: L2Block, - pub l2_ibc_contract_proof: StorageProof, + pub l2_ibc_contract_proof: ContractProof, } #[derive(Debug, Clone, PartialEq)] @@ -29,24 +30,25 @@ pub struct Header { )] #[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, + pub block_number: u64, + pub parent_block_hash: Felt, + pub contracts_trie_root: Felt, + pub classes_trie_root: Felt, + pub sequencer_address: Felt, // 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: L1DaMode, - protocol_version: String, + pub block_timestamp: u64, + pub transaction_count: u32, + pub events_count: u32, + pub state_diff_length: u32, + pub state_diff_commitment: Felt, + pub transactions_commitment: Felt, + pub events_commitment: Felt, + pub receipts_commitment: Felt, + pub l1_gas_price: (u128, u128), + pub l1_data_gas_price: (u128, u128), + pub l2_gas_price: (u128, u128), + pub l1_da_mode: L1DaMode, + pub protocol_version: String, } #[derive(Debug, Clone, PartialEq)] @@ -61,59 +63,113 @@ pub enum L1DaMode { Calldata, } +#[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 struct ContractProof { + /// The nodes in the union of the paths from the contracts tree root to the requested leaves + pub nodes: Vec, + pub contract_leaf_data: ContractLeafData, +} + +#[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 struct ContractLeafData { + pub nonce: Felt, + pub class_hash: Felt, + pub storage_root: Felt, +} + +impl ContractLeafData { + pub fn hash(&self) -> Felt { + pedersen_hash( + pedersen_hash( + pedersen_hash(self.class_hash, self.storage_root), + self.nonce, + ), + Felt::ZERO, + ) + } +} + impl L2Block { /// /// // TODO: Handle different versions - pub fn hash(&self) -> H256 { - Poseidon::hash_array(&[ - // 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()), - 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), - (self.events_count as u64), - (self.state_diff_length as u64), - match self.l1_da_mode { - // 0b0000_0000 ++ 7 bytes 0 padding - L1DaMode::Calldata => 0_u64, - // 0b1000_0000 ++ 7 bytes 0 padding - L1DaMode::Blob => 1 << 63, - }, - ] - .map(u64::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(&[ - // 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(), - 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() + pub fn hash(&self) -> Felt { + poseidon_hash_many( + &[ + // hex(b"STARKNET_BLOCK_HASH1") + Felt::from_hex_str("0x535441524b4e45545f424c4f434b5f4841534831").unwrap(), + self.block_number.into(), + poseidon_hash_many( + &[ + // hex(b"STARKNET_STATE_V0") + Felt::from_hex_str("0x535441524b4e45545f53544154455f5630").unwrap(), + self.contracts_trie_root, + self.classes_trie_root, + ] + .map(Into::into), + ) + .into(), + self.sequencer_address, + 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_be_slice( + [ + (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 + L1DaMode::Calldata => 0_u64, + // 0b1000_0000 ++ 7 bytes 0 padding + L1DaMode::Blob => 1 << 63, + }, + ] + .map(u64::to_be_bytes) + .as_flattened(), + ) + .unwrap(), + self.state_diff_commitment, + self.transactions_commitment, + self.events_commitment, + self.receipts_commitment, + poseidon_hash_many( + &[ + // hex(b"STARKNET_GAS_PRICES0") + Felt::from_hex_str("0x535441524b4e45545f4741535f50524943455330").unwrap(), + 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(), + ] + .map(Into::into), + ) + .into(), + Felt::from_be_slice(self.protocol_version.as_bytes()).unwrap(), + Felt::ZERO, + self.parent_block_hash, + ] + .map(Into::into), + ) .into() } } #[test] -fn l2_block_hash() { +fn l2_block_hash_3996475() { use hex_literal::hex; // https://feeder.alpha-mainnet.starknet.io/feeder_gateway/get_block?blockNumber=3996475 @@ -121,8 +177,12 @@ fn l2_block_hash() { block_number: 3996475, parent_block_hash: hex!("07488afa914e19281d6a859f1673d91f84b124576677bc90790954934bcf6a90") .into(), - global_state_root: hex!("000b977d63eeb59fda732ff60c6b956a91bd1c30784b2a25829f3a5fd882b0f8") + classes_trie_root: hex!("052dedb4984ca5bde1fa31f46bdedd2462779d7a6db3039be87eb0c532d79470") .into(), + contracts_trie_root: hex!( + "02c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb" + ) + .into(), sequencer_address: hex!("01176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8") .into(), block_timestamp: 1764693045, @@ -154,8 +214,7 @@ fn l2_block_hash() { assert_eq!( block.hash(), - ::new(hex!( - "0366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675" - )) + Felt::from_hex_str("0x366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675") + .unwrap() ); } diff --git a/lib/starknet-light-client-types/src/storage_proof.rs b/lib/starknet-light-client-types/src/storage_proof.rs index c92ce77cf89..9f9f088e8e4 100644 --- a/lib/starknet-light-client-types/src/storage_proof.rs +++ b/lib/starknet-light-client-types/src/storage_proof.rs @@ -8,7 +8,8 @@ use unionlabs::primitives::H256; )] #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct StorageProof { - nodes: Vec, + pub nodes: Vec, + pub root: H256, } #[derive(Debug, Clone, PartialEq)] diff --git a/lib/starknet-light-client/Cargo.toml b/lib/starknet-light-client/Cargo.toml new file mode 100644 index 00000000000..8a4efc2e238 --- /dev/null +++ b/lib/starknet-light-client/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "starknet-light-client" +version = "0.0.0" + +authors = { workspace = true } +edition = { workspace = true } +license-file = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[dependencies] diff --git a/lib/starknet-light-client/src/lib.rs b/lib/starknet-light-client/src/lib.rs new file mode 100644 index 00000000000..b93cf3ffd9c --- /dev/null +++ b/lib/starknet-light-client/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/lib/starknet-storage-verifier/Cargo.toml b/lib/starknet-storage-verifier/Cargo.toml index 31c77c69fc8..c03e29f125a 100644 --- a/lib/starknet-storage-verifier/Cargo.toml +++ b/lib/starknet-storage-verifier/Cargo.toml @@ -12,9 +12,10 @@ repository = { workspace = true } workspace = true [dependencies] -bitvec = { workspace = true } -pathfinder-crypto = "0.21.3" -serde = { workspace = true, optional = true, features = ["derive"] } +bitvec = { workspace = true } +pathfinder-crypto = "0.21.3" +serde = { workspace = true, optional = true, features = ["derive"] } +thiserror.workspace = true [dev-dependencies] serde_json = { workspace = true } diff --git a/lib/starknet-storage-verifier/src/lib.rs b/lib/starknet-storage-verifier/src/lib.rs index efa65d8dfbc..cdaeadddfe4 100644 --- a/lib/starknet-storage-verifier/src/lib.rs +++ b/lib/starknet-storage-verifier/src/lib.rs @@ -1,9 +1,8 @@ use std::collections::BTreeMap; -use pathfinder_crypto::{ - Felt, - hash::{pedersen_hash, poseidon_hash}, -}; +pub use pathfinder_crypto::Felt; +use pathfinder_crypto::hash::{pedersen_hash, poseidon_hash}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; pub trait FeltHash { @@ -70,6 +69,7 @@ impl MerkleNode { } } +#[cfg(feature = "serde")] pub mod felt { use pathfinder_crypto::Felt; use serde::{Deserializer, Serialize, Serializer, de::Deserialize}; @@ -105,17 +105,19 @@ pub enum Membership { NonMembership, } -#[derive(Debug)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum Error { + #[error("unused extra nodes provided")] UnusedNodes, + #[error("value mismatch: expected {expected}, found {found}")] ValueMismatch { expected: Felt, found: Felt }, } pub fn verify_proof( - proof: impl IntoIterator, + mut root: Felt, key: Felt, value: Felt, - mut expected_hash: Felt, + proof: impl IntoIterator, ) -> Result { let mut proof = proof .into_iter() @@ -125,13 +127,13 @@ pub fn verify_proof( // 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) { + while let Some(proof_node) = proof.remove(&root) { match proof_node { 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 - expected_hash = match remaining_path[0] { + root = match remaining_path[0] { false => left, true => right, }; @@ -164,7 +166,7 @@ pub fn verify_proof( } // Set the next hash to the child's hash - expected_hash = child; + root = child; // Advance by the whole edge path remaining_path = &remaining_path[length as usize..]; @@ -172,10 +174,10 @@ pub fn verify_proof( } } - if expected_hash != value { + if root != value { return Err(Error::ValueMismatch { expected: value, - found: expected_hash, + found: root, }); } @@ -324,11 +326,11 @@ fn contract_membership() { }; // contracts_proof.contract_leaves_data.storage_root - let expected_hash = + let root = Felt::from_hex_str("0x2c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb") .unwrap(); - let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + let res = verify_proof::(root, key, value, proof).unwrap(); assert_eq!(res, Membership::Membership); } @@ -379,11 +381,11 @@ fn contract_storage_membership() { .unwrap(); // contracts_proof.contract_leaves_data.storage_root - let expected_hash = + let root = Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") .unwrap(); - let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + let res = verify_proof::(root, key, value, proof).unwrap(); assert_eq!(res, Membership::Membership); } @@ -418,11 +420,11 @@ fn contract_storage_non_membership() { let value = Felt::from_hex_str("0x0").unwrap(); // contracts_proof.contract_leaves_data.storage_root - let expected_hash = + let root = Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") .unwrap(); - let res = verify_proof::(proof, key, value, expected_hash).unwrap(); + let res = verify_proof::(root, key, value, proof).unwrap(); assert_eq!(res, Membership::NonMembership); } diff --git a/lib/starknet-verifier/Cargo.toml b/lib/starknet-verifier/Cargo.toml new file mode 100644 index 00000000000..cee8a5e9fa8 --- /dev/null +++ b/lib/starknet-verifier/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "starknet-verifier" +version = "0.0.0" + +authors = { workspace = true } +edition = { workspace = true } +license-file = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[dependencies] +evm-storage-verifier.workspace = true +hex-literal.workspace = true +keccak-const = "0.2.0" +rlp.workspace = true +starknet-light-client-types = { workspace = true } +starknet-storage-verifier = { workspace = true } +thiserror.workspace = true +unionlabs-primitives.workspace = true diff --git a/lib/starknet-verifier/src/lib.rs b/lib/starknet-verifier/src/lib.rs new file mode 100644 index 00000000000..89a2b420430 --- /dev/null +++ b/lib/starknet-verifier/src/lib.rs @@ -0,0 +1,336 @@ +use starknet_light_client_types::{ClientStateV1, Header, storage_proof::MerkleNode}; +use starknet_storage_verifier::{Felt, Membership, PedersenHash}; +use unionlabs_primitives::{H256, U256}; + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum Error { + #[error("error verifying l1 gps statement verifier account proof")] + L1GpsStatementVerifierAccountProof(evm_storage_verifier::error::Error), + #[error("error verifying l1 block hash proof")] + L1BlockHashProof(evm_storage_verifier::error::Error), + #[error("error verifying l2 ibc account proof")] + L2IbcAccountProof(starknet_storage_verifier::Error), + #[error("expected a membership proof, but found a non-membership proof")] + ExpectedMembershipProof, +} + +/// 1. Verify the L2 block hash in the L1 +/// 2. Verify the L2 IBC account in the L2 contracts root +pub fn verify_header( + client_state: ClientStateV1, + header: Header, + l1_state_root: H256, +) -> Result<(), Error> { + // 1. + evm_storage_verifier::verify_account_storage_root( + l1_state_root, + &client_state.l1_contract_address, + &header.l1_contract_account_proof.proof, + &header.l1_contract_account_proof.storage_root, + ) + .map_err(Error::L1GpsStatementVerifierAccountProof)?; + + evm_storage_verifier::verify_storage_proof( + header.l1_contract_account_proof.storage_root, + L2_BLOCK_HASH_SLOT, + &rlp::encode(&U256::from_be_bytes(*header.l2_block.hash().as_be_bytes())), + &header.l1_block_hash_proof, + ) + .map_err(Error::L1BlockHashProof)?; + + // 2. + let result = starknet_storage_verifier::verify_proof::( + header.l2_block.contracts_trie_root, + client_state.ibc_contract_address, + header.l2_ibc_contract_proof.contract_leaf_data.hash(), + header + .l2_ibc_contract_proof + .nodes + .into_iter() + .map(|node| match node { + MerkleNode::BinaryNode { left, right } => { + starknet_storage_verifier::MerkleNode::BinaryNode { + left: Felt::from_be_bytes(*left.get()).unwrap(), + right: Felt::from_be_bytes(*right.get()).unwrap(), + } + } + MerkleNode::EdgeNode { + path, + length, + child, + } => starknet_storage_verifier::MerkleNode::EdgeNode { + path: Felt::from_be_bytes(*path.get()).unwrap(), + length, + child: Felt::from_be_bytes(*child.get()).unwrap(), + }, + }), + ) + .map_err(Error::L2IbcAccountProof)?; + + match result { + Membership::Membership => {} + Membership::NonMembership => return Err(Error::ExpectedMembershipProof), + } + + Ok(()) +} + +/// ```solidity +/// string internal constant STATE_STRUCT_TAG = "STARKNET_1.0_INIT_STARKNET_STATE_STRUCT"; +/// ``` +pub const STATE_STRUCT_TAG: &str = "STARKNET_1.0_INIT_STARKNET_STATE_STRUCT"; + +/// Slot of the `StarknetState.State.blockHash` value in storage. +// TODO: Once we have U256::from_be_bytes as a const fn, use that instead of this monstrosity +pub const L2_BLOCK_HASH_SLOT: U256 = { + let mut hash = keccak_const::Keccak256::new() + .update(STATE_STRUCT_TAG.as_bytes()) + .finalize(); + + hash.reverse(); + + let mut limbs = unsafe { core::mem::transmute::<[u8; 32], [u64; 4]>(hash) }; + + limbs[0] += 2; + + U256::from_limbs(limbs) +}; + +#[cfg(test)] +mod tests { + use hex_literal::hex; + use starknet_light_client_types::{ + StorageProof, + header::{AccountProof, ContractProof, L1DaMode, L2Block}, + }; + use unionlabs_primitives::H160; + + use super::*; + + #[test] + fn l2_block_hash_slot() { + assert_eq!( + ::new(L2_BLOCK_HASH_SLOT.to_be_bytes()), + ::new(hex!( + "71a8ef1b1265359d77973c3524afac225c0a0d829a0d4da5cac3b34532019fee" + )) + ); + } + + #[test] + fn verify_header_works() { + let felt = |s| -> H256 { Felt::from_hex_str(s).unwrap().to_be_bytes().into() }; + + let l2_block = L2Block { + block_number: 4174049, + parent_block_hash: Felt::from_hex_str( + "6de8c5b146b0127e0a7842ffa97931850339016cc6fa2083f8817a8fef3260e", + ) + .unwrap(), + classes_trie_root: Felt::from_hex_str( + "0x5e4ce3094b99bc894ff49cb80319f494384158834b69fc8b396f28b65386d49", + ) + .unwrap(), + contracts_trie_root: Felt::from_hex_str( + "0x8eb9daf1010e9400d1549fe06ae61e8bb5b52a85b784a26e4353294b69db0f", + ) + .unwrap(), + sequencer_address: Felt::from_hex_str( + "1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", + ) + .unwrap(), + block_timestamp: 1765453275, + transaction_count: 6, + events_count: 4 + 4 + 11 + 5 + 7 + 5, + state_diff_length: 39, + state_diff_commitment: Felt::from_hex_str( + "0x700341e929df7d4cd196e97bfe84b13bc65a981d93229dfe249b2ab80a9be37", + ) + .unwrap(), + transactions_commitment: Felt::from_hex_str( + "0x7646130360d27c6e5de4d09f5e8141301a20aa66fc4c8c17189be631466a264", + ) + .unwrap(), + events_commitment: Felt::from_hex_str( + "0x31571f5901e6081cb7b10c64b44df4407f56b4de12c98d98aba7854c4f99cd4", + ) + .unwrap(), + receipts_commitment: Felt::from_hex_str( + "0x7a1f73a67ca072a6d7e61f80252ab5c1e4ec3fd916398b26e11fc92ce55a1e2", + ) + .unwrap(), + l1_gas_price: (0x42f7e3a4, 0x1eafaf65743a), + l1_data_gas_price: (0xef9bf, 0x6dcb3ada5), + l2_gas_price: (0x410a0, 0x1dcd65000), + l1_da_mode: L1DaMode::Blob, + protocol_version: "0.14.1".to_owned(), + }; + + let nodes = [ + MerkleNode::BinaryNode { + left: felt("0x305ab7d618a9e856a5b187d809a7f306cdf3f3d33d36bc2c2968aeab533cda2"), + right: felt("0x6d1edbfa23c5bf679f08ab9d54d8dad98b628487edd1f5cc5fcea524d90b4f7"), + }, + MerkleNode::BinaryNode { + left: felt("0x33604196e53cc124af3f050167854bb1868fa495ef66c0c90fb2a5fcda2c83c"), + right: felt("0x3de8920b04a078cb1f4b9a28446fc23ca1b54fa9515e4d9824a47603fedd4a5"), + }, + MerkleNode::BinaryNode { + left: felt("0x101ce766e32315b6cc47883ecee2b5a9fd4c7557958aedff4f329d180220d3a"), + right: felt("0xebe1287fb5a25ac632a53fb9e54ce256fcd7533133c4ff0cd70886595d99b7"), + }, + MerkleNode::BinaryNode { + left: felt("0xb6b1c5d7dc2c596942b2a17810156a4d08bc11d448645840d0ab637ae38484"), + right: felt("0x4c76037107f82eba1bd6d9993971f16eff3468b64ee4e9b17d37d1527040896"), + }, + MerkleNode::BinaryNode { + left: felt("0x533017e434b033689f12b06fb9e855ed00c78a8cdfddc903432cee60f849366"), + right: felt("0x6dc53ffe318ab307ae6354335ce35be76530599f7ca3a8ce6fc18dc9d8de74e"), + }, + MerkleNode::BinaryNode { + left: felt("0x4257e8c08a26ee33a64622cffff95a83932ac81246c3ffbb2d8361aaae2d2b1"), + right: felt("0x6e0f0db09122b2b46deb5fd2269b4587947b937cf14874bbac7c15883ca3bd8"), + }, + MerkleNode::BinaryNode { + left: felt("0x70a94c7a5666b3d2148a6bdee5469081798b1adad7c3c4ef8c25c891503a1a6"), + right: felt("0x36584e72c2006e479a87af2ee971d4ae9c7f756b81627105c6f7412bd0db448"), + }, + MerkleNode::BinaryNode { + left: felt("0x5609ed4b81a781af59aefbaf205b61576a29911a7c9a3fa1c1710690d5867f"), + right: felt("0x2da53eab882376d8f48b9e4be19cc7c49b467164f541afe395622938fc94a03"), + }, + MerkleNode::BinaryNode { + left: felt("0x179869a71950d867e9cfdcaad742c46bd7da32a48ed2628679c5ef35dec8fdd"), + right: felt("0x733f7a7ba94681b6622f0c65451bf9233f97d993b28753bd87febfadffae489"), + }, + MerkleNode::BinaryNode { + left: felt("0x7fbba0a33569ad039875cae97dae56ea99c713a63ddfa1537e2e514b776d9e1"), + right: felt("0x104e502581db03927dc9db020f64b599a4baf4cb8da191d55eaabf6a317283d"), + }, + MerkleNode::BinaryNode { + left: felt("0x6d840221616a26a92acb112a1582a85644cbb35d7a3cf603343be089ed00ec9"), + right: felt("0x44e1a78d7acd1bbd66e604288faf9a07bed58964ec6f465c7c5209cd7e8ea92"), + }, + MerkleNode::BinaryNode { + left: felt("0x5eb2d805c3d7d34584572f7b77419fc815139a52a680af56d05bdc738afef18"), + right: felt("0x1811d45d4d14a44857f3a14841f14139acfbc49d08a612acbbebd2563ece92f"), + }, + MerkleNode::BinaryNode { + left: felt("0x6c613354260a00fb7931522f4194ae69159cab032b0299c8c6c7cf328a5617f"), + right: felt("0xaa035333b79d9800b66150936daddda15d98b0dc34ab212e09da12386db907"), + }, + MerkleNode::BinaryNode { + left: felt("0x506ced2ba38897f4cf279202baf072b5973dc31bc53e66344157b6700f17d3c"), + right: felt("0x4fca360db940e4e47e640e336723bd3bd78934ad2af520f15f3e0b52166f5fb"), + }, + MerkleNode::EdgeNode { + child: felt("0x4b6a88ec5cc90586fd5c285ae20224503306e5b304c5ddb26e643a53add589b"), + length: 1, + path: felt("0x0"), + }, + MerkleNode::BinaryNode { + left: felt("0x5e29544c3c9d6c4c972e5ff910354e82a0e7ebe890823784f813c9875a042ce"), + right: felt("0x7866ca74f720092fa6b16c9a9f2c5618c89efa35a29ccc1b472cdafb94d75cb"), + }, + MerkleNode::BinaryNode { + left: felt("0x1eb10eac884e2d2bde5aaa6bac9adce4ba54f0961a4c00c21676fba0d8852fb"), + right: felt("0xbce16eef6886a0ae81de15690d53085d74e35d4978305b1089c17fd6a4597d"), + }, + MerkleNode::BinaryNode { + left: felt("0x5c9a95965d8c446e9bc5e968d33ce077d1e705c0fcdefb4f836254843f63c21"), + right: felt("0x6cbbf2e225b06c542d324a9c3a55d792a5c95cb98543bfdd9d086e227f015d9"), + }, + MerkleNode::BinaryNode { + left: felt("0x4f492e971e246ddbd1c3b0b1357eedb37bf2607c9525f978aa030e22319d9c5"), + right: felt("0x2d0f3a2b0e9fd31188cebe84634636ac6691b03f6174a3c4883b3bc3b041762"), + }, + MerkleNode::BinaryNode { + left: felt("0x3b8af7a5e04753f361e87b29efc2a381cbb2385db4a23cd0ca369a8836d5046"), + right: felt("0x6c900eb816f9c72490ed8f4e5d212e4a97f5f455b499d1108073a4fa00e66ad"), + }, + MerkleNode::BinaryNode { + left: felt("0x6c8e798c2f31a2da9904fb6ed1b8c818daa58fde22811a4b80b6593d285877"), + right: felt("0xb92e98647c0c0bdb3712940643eeb11ca3918a04f7bd9abf2545c9a2fbd340"), + }, + MerkleNode::BinaryNode { + left: felt("0x6bb29dd06cfca37492631004ea10b670b6ac104f2ccbe40e0889c790ff8faa3"), + right: felt("0x4b2e6799b800af6b60ee81abb0f27c9fd42bb728b7ad60cdf56b3be4ddc5f4b"), + }, + MerkleNode::BinaryNode { + left: felt("0x5595953da4821e203284a77330b290d121340f93ec829334aacf46e968af880"), + right: felt("0x50337184a321e3b087bef0290a8749f51a1bde4b07208efbd37cf1bb875cf96"), + }, + MerkleNode::EdgeNode { + child: felt("0x7ab8b6072e9d0957760c6d309c5ae7f27edaa5a85d1a05ee2ce1383872970ca"), + length: 226, + path: felt("0x32c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e"), + }, + MerkleNode::BinaryNode { + left: felt("0x14d8b2f2795217c6ee4ac6a8e3fb9bde9baa4680411ec63f73b887f040fd50e"), + right: felt("0x8eeb881ead580d16eebc8b3ecebc3816eb4bab2573442668242eb9fd73ac57"), + }, + MerkleNode::BinaryNode { + left: felt("0x7beb3f225b68f50c2891b2689a0ee01d2adb69e97f486a6970c803a77531b95"), + right: felt("0x2a50d7e63c0dfe7e90ef2409e0e2cd062d8912b9f58fe9bc72bf1d94e6036ab"), + }, + ] + .to_vec(); + + let header = Header { + l1_height: 23990413, + l1_contract_account_proof: AccountProof { + storage_root: hex!("5091e8afb812f9c63db3d1e54a604d85b57e686edf3086a611ae9974d94abd12").into(), + proof: [ + hex!("f90211a03e595771950b867b55ab9fef4b36f1564b8a4c1ce18830e9f926223c6aa3da21a0bd1afcd3c17f33c7e740adb9261a757fa0dfd8a3a862be0b9f0d870a74eacfdda04da3fa652bf7653a415a611e06ef867c71888b8c37f63c08bfbb9af92fd52b27a01471afe3c98aa82ef03c28f7cc2151ec609fce184391dd813f4c70ec0b3eb9a2a04ea9c9744ba0d4344efd3fa534f79c23f6aec3648942d8925193f3cd66bb2e76a006ed4779d6efc7cd91de110b263a129609b21eb558957c270f372d778f5227dca0dbc0435fa8265d476ad21cd2e58f8a8fa98aab2b45ac8977d17a1bfa2d88568da072b072a897ef4020dd7424f3fd104b316aecea9e293f489a86ae133c5fcdfc72a0304c9fc95e4f723681cdea067ae515e7cdafa2b232bc743ba11fea7f1d4d38b4a0afa2dde73b74db55bcfe7cb19519a7c0b4c6d14ba2fe90680775f22f7a52caeaa00a0ed18f1c3e332f935054aa99e4697b9bdd0da1fb87c7395d27e2f12f872995a094be62452a47f226bae6f4427f5938dfb22fb22412c11eed2c0f9ac1e7eeb678a0fab94bc0bd582753fa4a1ba054a02f8dba4cd1aabcc64c6c9caebebec5713950a0ef45fbeda7ec6a6105602b559a0fbdbaec82a266ba9cf522c525858575eff739a0308624acd791a027048a79937fcbf6d80b59a78b84778a506a5fa250b88565bfa0761ace2d061c764cf1f84c26e072a670846ccdb82cfb687ec1b0907029dcb7ab80").into(), + hex!("f90211a0449b65ba65298468536d892ff17f1cc3d68023d42d7c35b5b07ab60bb0905cbba0197eef034779b1081a12dee6a7992957842b2947452d347f93b13dbee525cd47a0ce11c2fb70a19215d329923c5489b94e9a8719dff730105a7185012f1e5475b6a05a14b21bbbe5125c458af8f8482453825de58f8db8d4ac3a2f9399d206fad8b6a07ab1100d1b227d4fbff4e6256209cf7ab5e5a1939486ea9adf75e451be3404b3a0d38b5356a173e938c17c1414303f4ea503868a7762c0476a76b66022dea3dbefa07388b616e6bf4bb3e57b3b814b18c2fff113400117305bc2972e4c9f4e53859aa0f47dd6321ac87d299ba924279d9922f5359551d54314ffe5d954637bcece2666a0f5f93b2d2eec85f32cf830042eb34065e7a505c91450f6b409d8fa4fabf0331ca08ca18aef4b9af22ae05b1a5428c6b180926a1f93c66bda9619c8b02506f8cd62a067319affbe5c772b3e55e9a8f4e5bc13547212d8a9d4efdc0d6a6ece9a60c5dea0af7d92ddd8b2d2ee6a2d4dcc47930a99f2027967487778602765e45996caaf7ea0c9b89c1adfc4b508b165cdc5d266a580e4f221ecb5031101d25d1a8a38f49e35a085e2cad7ddab7e0d070f7207769c7a062e59407379a428e6f39d78dd3cf7d06aa0c9ed6a341e148984794f703e4c55225083343172c9cf380b74dde012285f2923a03d5da7c4393d57a45a4d949d96ee75d80f6ea6337cd7d2584bd3d92015747c4780").into(), + hex!("f90211a0ee5104fdf2a8b282f7f494a119521928ec2d179b70d3a8cbbbdb3aefd5a147b0a073eca6df5b3b16abb8184d61140af61779f261bd39f91387d6c37889e8717df8a0a5fa40e04b5594149f464ea303608203e980ad75602afd1c1a2435f0644dd986a0710d328bf62d1bb633242a127dd7ed191f7f823d65df75ed529d65d68510f391a0f876c200e95114d4af0eb803ddf97760b3ea9126fc89e180a11fcf5a669eb42fa03cfc9f70295ac53f21a1196abfe7fddf5d702fcb874787bab887fc06a61b1ae5a067ec87474a3a8e842bafeb2c6f3ea0389fa37ed02981401fa7e20e62861aa04ba056819e4d71e68627ea8b07f45695b7a389fe5f93dfca970abba0584d73bb1e58a076585e5ed18c5b1b3f5a458a23c4947ebc2cc31441fb7365ff82d7fae6c890d8a00240b6b07d0f8750d9291124eb1e87cd1828c6aa9f1be38e96de5e2133aa2453a0bd8e613eb8a9c27a851cf687d1ec5d7a5cec89bef103bebff65f1669a49e2231a0f6eda6f0fb6aa954b0bdbd3fb746dd3403c3b0964ae17206abf0bd0ddea4ce8aa0b6af5e19cd1274130fd7dfe98a9a1d8890d3167b909e8523ec6aa6eaeb85a1a6a0409d685159a04dad449c61a2bbabc1d666362ca3280959d5302b7bc62b226192a041339e3b4a77ebffd3e24235e19c676a2766d5974032a0e49c363553d82d1f41a05c8a1c7ad156e8873d874e587c2307258e31f593f1761eb5bec02219469a511880").into(), + hex!("f90211a0ee459789c04f180799bcaf0460d3ea08d03d6046c8c5f4cf1e95033cde484a61a044cff7f31d5f8827bbcb10a2b2091f351de83b1ca058bdb318f2c958e79a154da06771a9490f4a868cc5aafebef6e6425acc2d5210bf3696f0c19c97d0120ba3b4a0a2cc3f7f8fbbfbaa16e75c6293e136d64de868cc5460b6893dd275f439b7613aa07796e5aff768e052d3fc9eba311a3285e061fe6b756bd0690bc041975af3799da05a2e52647cac3f85ddc63e75e55f01ab52b606f9831106da141330dba4094503a014fe992fc9619b545c8bc99cb28cc78301c5d2215447d756e32ff96ff79d65daa0308ca28d6eae2df2329ead707fff9eddd585ececdf975e5c5ba2c35134ad7905a0c054d1f7ef28cc154042f17618e5c16f73b0b55ee63168d495ac82fd2fb12390a02fe2381a7c2861c70dc446cb04f18ae1bb4ed08e3a021227c2edad2ba8e08726a0f62a728ebff2fcc922f022093c2d8d89c1d7fff07ac8a415229e6e18bb1edc9fa0a46375aeec7da0ab831ca221512c3374ece7cdcd3fc3cee4ab42512ffdab1a58a082045f1daf961809941f39020e54d300e6e3e3e35f0373fd7f8a54e97fa6c872a029afeafcfad7e34f920e9021a31bea99dcb84f550e7ae8500415af459e1e1218a06d7714c3e6dc3d110eb2f5ccba8e012e797d813ebeeb59f549fcbe7a4662929aa0245c1f2076eb39161cdfb79ad5f601bd69fe3782aa72224b656aef33aab57d4880").into(), + hex!("f90211a0bc290355b1444639bdb8373b4db36f02eb8e70e40de8b598b52c7e44fec5b5a8a07f100776f18df1230187ca91853d725dbf10a1cedd0660c8465e40a5175e4147a0ec76d99914b03c81fd1e211bcb33ad7234d9b6e5dde334a0d1419b72a03543c4a0153ad31617fa1ea6ed68c348f8ccb1a05155555bca3af86ac8f2ed166abfc62ba05ade8da365cd1507a0b243e0aafa1f070e5e03446550a36b43bda4c5298ed517a030f544886c963f5ded3fd5f30b7c6a9848973bda42f88085fbfa17b13d35c899a05f4140ff406952a0e0b4bfc1cee653436353d903c4a7293423632909d065d9d5a08e5f82a20b88e4b58ef9a9478213907ba783de0d9ea628c2b6931e4c30840cd8a0b6d5d6bb04f97902a7302fc19633c9f2a55959c65c1be57b62e0f9e95e8ec3f9a0e7b2868ba40a93f36c6734b150472066bba7699a17bb1221eb8f0e59e19448e4a094e7b1543b602c8a1ed5d176d5c1f74791cf225c55eb121d3bed769eef511fada0a666f1425eee957fe5c0cc58b07704ad083530b31b4573a9b320cf97b89793e9a0f498c4378bb808353a09c56c07f661e6bf24941c7d6c17aba7f9e529907eae27a02bae967a9ed3737c7f0ff57bcf3ff303dcce14bf7c8066d45629d484fbd6b7eba05f681ab0d9d679ad45c105b41211538fd8e0174f77c0b26d0e4b52d20616c1cfa0f0af938a28a4d9fec146aa2d6564d599f7364ccb0d799fc0cbba634b9c7b06c180").into(), + hex!("f90211a0c2690e05ee400d3821aa8b02f00278b2c914ddd1ee98c3ab08b9a5ed396f2a76a06eb76932643b5522ffe7dee050719ef4c108de2e45dfdb7f46af4b335e953c38a0d9a4856d97ebc8f3f9fe9fc2fa2fd1a1e4e6d6e007de8e41dec557108e9405bca0bf280bfa88ac2e82cd457779b1f75c01e4b20b6e205fc99dfca401cabcb5f771a05ddcfd9420716dbcbe1db6327149e53a4eb994eb1173aba938de7716ac51b8c5a0af58096c6e6b65421582224dac49d2b3233681a314e0c65c9de78c15073731bfa07fdcb5088dc5f44eadf66f282733315f7b6390efc6c55ec9fd0fdeae1df285e8a07a6a0b694b03afe9039901489d6855c1b540a097fbe93766eff2c10c70ec35d8a0192db5cb96512327890d1583fe76f2bd773db0edf92f78fa8fede109d2019794a01b085a612ee42af9c4e744f3d47e367725b5ac53368c743a5e73678d9421ac7ea03a41eab04af9ced63c2e2090e30c8300746193a9934ef87e671b66721b87b3c7a08078971bdb6bfc1c85417018af9cd57c333c65a84b2f9e4ca0dc2a165b54b51ba04e5ba27c81ee80e74ccfb2bfbcfdda19d90daf980423345dba67b7c505a84bf9a0d16aea477c5f47a524848749596d5214a70c5ada4d1399c29718c47c25e49cc4a001eae41eee3289077e693ca1d78bdf713e6c28dd561f7df32810bbc181c4c4b4a0be97e340305c8f56f7be48b38927cf34695e1b95e8ae1f5ec62ac5d7d37d8a2f80").into(), + hex!("f901b1a0c72aeb188d7cd6741489ef5460480de56a13efa12e958d431361cc5dbfd8512da03173fa72da502e38933a7880e0d11ad359c349ed114622b648fc341c1b9db583a04bdeb919619279e2d29216a75984703b0c54acd97a2397779cdaccd63aa469fda004ff6049b5d49cbffcf04eef98d58a01390521a92a6be2eb9a3b202cfc771d97a021c85ae0dc5cd5580412da6382bf056fabbd90b7d4c97b984a41c2f2924d4007a06e3ab8543da8ae335e2e6ecc6f2e002e618eebef0ad6494b5e543dbe20517801a01cc712310d45e087795101c1799a648d0d9f0208734502bdcfdb9aedc52c1dbe80a0940d53ac9828d4798298e408f8e27736b4ff7e0e96bb8e95e72115d2f294c97fa07ff22562db4aa2796bb2c52a2eb7d33d619684ac8f34bedf4e38634fc3ce4fc78080a0f6e5f5264fcb01f565f1893672d545f7e26ba7349cf33e5083de2201192fcceaa0506f354afa7e62f5efd6ae46fd65174fcea9557f1fc352dadd55ed704e0747f8a0d05c26a817a7fbae067e79249c08e8674df5694fc465b3a73887c1166c346237a053e92c2946ce9febc76672c85442bcad290c4f114604ebcc0d98a83d489464e180").into(), + hex!("f871a024c800360ee20aa4e882ea742c38435d6ae2a4dac63d0c54c46ae39ad6dd414a8080a08565b0e0452ab5a5581acd46f18a0c3476f9a77df3eb3ebb41d950ae8605aca98080808080a0e7d7bdf5202403e405168010300e9ae1ad4c6c512d01938664d11cde2d2c963b80808080808080").into(), + hex!("f85180808080808080a05193909176ca6af398a84f40d58e1dd816119381fbef535cbb9d4a7cdae86889808080808080a096948b84dfe566746ca09dbb5609b1462faeafa486307d44260dc25ded12024c8080").into(), + hex!("f86c9c3cac147edcaf01485fc330baaa3a14097ebd75da6e61cffcbe83a07db84df84b01876a124e1747287aa05091e8afb812f9c63db3d1e54a604d85b57e686edf3086a611ae9974d94abd12a04ffd6c414d52ca0bfd8855fe16f448d32a37d72192fb3a72a7a5811935345e5e").into(), + ].to_vec(), + }, + l1_block_hash_proof: [ + hex!("f90211a0583d36368c1f5477f093f33e55d40ad5a118147847b1cd68b3d486907b47d182a07f27ff7f61f08a6ce1c1d217855e731073e9b7d969aec8abe0a184545eab51aca02d3c185f6e08b2394444e836bb4b668ac55baf649442278a0cc57b2bdf4449f5a023a4faabad295e8a4caccc7e00b45303cf2b4e2a0579d4bfb873e58340f36ebea0f8ca7cc363d40801e0b0b2109114004e079da15c114537a57d2d51f3ec3d4e56a0eb23e560048e44611f6a679ca5202bb76bbcf1dc5006e358400be2cf60a96a2ca0a14466ff61f3f4074b38341ebf0dfd493d1f459a71ab9fedbd2ba97b89792d59a0f6b13e60c337f605d58ff143cd9b21d1331e4479502b914aca075c7e7ff14b6ba0a4ca083c4289962304e3e1481645980f88cbda9181290c04d3b0d5b192447be1a0b09a8bee71cbbf6fb53f1e41b964766ef4a969d8b74e84b1f8998cee7e1dbed6a0041a03f5cb048e1fcfa8ff9735eafaa2efe54ebb6e8f823ae41f3f4481b5ec78a09afd43bdc2ba6ea8a7998b9e2e9b6cf773f7e1dfb6bdc72281e619af90a1ad8aa06443f08f5546c17b7e81a2766ef4427683b4ba6f0f7d116cb73cb23196ed6907a0a73fc513909b6b5f5e90cd8aa0e76cb6640eb2703fbecfbcf0d35c305e02d904a051298cd8203ca84cd582097a5993f33fb204e08363a4caada418f0b99e69f3f1a0e423c489919502d593a504411c2bb831612ae1f19358bf39bf2026c169edc32a80").into(), + hex!("f90211a0090c287ea962a21823eea59fd0b6269f05ab109812cd9135ced20cf471c46d54a081f129930a4f1b63fca98154f5eb014810285e02c5d4bf13e6b81e4a9edd5edca084fa7abcb2e99589af47519b95145481cd91b9638bfa63d7ccf6afed560c3e69a0c350cdcb332ba0982b234cabcfbc253e8932605c5e19a33d9fce264640155b90a0d369bdb5164717d3bea735a0f78f2a5ecc488f5dad0118865530d05dd4f0509ea0dfa4641f31de666bad4a7b5d476586321223b8f22db395a111126832fae69af6a06f45b766b761350eb72b1ca252c170ddeab6e87b9b2c4e03cdefe760b0e56626a065270cdc0c448bd6e3fab6002aa582ea2191ae0d2498e7c77d1b9f938c98e02ba089f7d99291980e8ab9549e522591f3de47d82b7a0691889dde258f6219d14e9ea0a7820e4c80ba26c137692ce2ef6853fa7f35203b10ef9aaba067d55fcfef463ca0a1b6582eb71c5880e8b6d35f3dde1a228533a289bd219d84f2604d030c2fff36a0dcdab5f4ab143517f859e97e049a4e3a6b14882df496cfd9761202c56816f193a0698b5819aaf0ed47812c7d016e6c731dc6d477f27ebc5793fd86cfc4814a88caa09914cffd9784b73f23b6f64bf7b8186652ddea93e2811a97a819a05045865f28a0f87562e4410f1d65c10ea9fa638de34e272b71809c3c9c3afb6f6dd1404a2a06a003bc929ce766845cf6aa415d409d1f5853f36bb9f235f789a7852422cbc0d9d680").into(), + hex!("f90211a01cc2de60cf5b8e2bc98964e8e881e25991598b583325eb055ee1fe0a4514eccfa06ffc5ba0c164d5298b5ae21f8a289a65d48bce7a9cbe117efb5638aa82136c32a0fc106db0e99021f818852943c9258b7a8b6c358c8716405a4a7250330ef1e88ea08912c32bc2b9fe163d134aaa4423f497bfcd3954bb8174340bbfc7ef518af4bca0782329cc528964573c1461b5eecdf62737202a5db06d38b1d566bde52fd11be6a0cf087dfa40544d213b8fd50031cbea189fff67fe0090134a926637b5805942b9a0723ab14a8108e2c213a773c6c76dbebed2cf22d98ff830458c67029692705b72a0cc842c1a7171e6bd068d01ae3a5d61a83c784163b4beabc79ce7e8d9295ebbf8a0ceede33b00f71b7bf0f5ca0cfe3679c16f61f3d5ac9deb1ff5b3e2e47273ecffa0b4b1baaa90a2249a06c457b11930871f62b0a98946be60b77cfe9308ad480b9ba0a07e4d1e50ff7a0df4c32ccac682e33198958091f8d0091687a43ab62c150853a05195a228a1eba2a239cd8c326c45ced6d00dc39fb4b1962feaf77f8902b56870a0969467b9cb6be3e6f5392645950d3acc7defbb62cc6d0078aaf10831df685b0aa02f4dd11d173081571593c62baf6ab440c0031e844eafcbffa6b21c59f592fd3ca04d72eb8823c91ad1238f218429514b704df4886cf0f0e4f94121aa469d3bf1f1a0c10a29dd373e05f7cf0c0fad487390dc45c12ddba738aa3e668f3bd069d7b8d680").into(), + hex!("f901d1a09888e7f467aa7102bfb8e12eba680affaec196d4367a604227525bebfc481d55a04cd3bb41d9e1421f18171024fb298ef336956a76d9c1b1b21d0b7f981672c218a016237700134d4b0d2b74aabfc6c87c6c3c041f60d35075a2bed1e59f0556f7c3a086174c6c0236c93805c697e7c95af1412440294f389dd6a9a4b1b335fa4ca1f1a0ee8ddd4b1dab1b067d4e6fbb168e85c0fc965810c6e524d1d204a83000e4a4bda02ff83e3ba14486c08b834b921b5bb1b1d18e1a7073e0ec04a1594f65f20db00980a0d8b5da3b472fa8abb8cc8c7cd615ee46eff77de32ccdfedf00d938ce7b0abd61a057364f7d7897a4809d35f847c959901ada44ada96e451fdd5850ae3d1e15b916a0aa6375dc4677aa7705d05ac753aea9889420826e533b52a6ca713312b04b76c6a0f92395ddae2d93f6539be5834617c59149409d0f648772940551aad834a2db13a0c15e237bc52d93797840492ee364a6346ef3d4b1db6df14024f15e26d823601680a01334bda47273395a27d8b6b55fae2e1e0666d9d711b04ace936830484649d4cea096ed273056ab8f8d2d216629b4121a53214bd02bddd24a21edec45ea49c808a9a093dd5984df02f3dfc9312487e20bd847a8a80308111111475a9089864999841180").into(), + hex!("f8419f205f3c1e0a04583bb4cbd325509f33f4f67e97aec722cca0265adf6f0c1217a09f81b8642c7ee73a7dfdf40963a6d2d2c67ad116504992c1e2d52869f864c12e").into() + ].to_vec(), + l2_block, + l2_ibc_contract_proof: ContractProof { + nodes, + contract_leaf_data: starknet_light_client_types::header::ContractLeafData { + nonce: Felt::ZERO, + class_hash: Felt::from_hex_str( + "0x69b893a8b6e1bf94740e33d9584a01295510f3b51f024d9833b2acaf1be4045" + ).unwrap(), + storage_root: Felt::from_hex_str( + "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa" + ).unwrap(), + }, + }, + }; + + let l1_state_root = ::new(hex!( + "800c716ee21145783ef258df832d9501a02dc1fad1d4c03af08f790ad5548693" + )); + + let client_state = ClientStateV1 { + chain_id: Felt::ZERO, + latest_height: 1, + ibc_contract_address: Felt::from_hex_str( + "0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e", + ) + .unwrap() + .to_be_bytes() + .into(), + l1_contract_address: H160::new(hex!("c662c410C0ECf747543f5bA90660f6ABeBD9C8c4")), + }; + + verify_header(client_state, header, l1_state_root).unwrap(); + } +} diff --git a/voyager/plugins/event-source/starknet/Cargo.toml b/voyager/plugins/event-source/starknet/Cargo.toml index 93bf591d548..31f04ae1713 100644 --- a/voyager/plugins/event-source/starknet/Cargo.toml +++ b/voyager/plugins/event-source/starknet/Cargo.toml @@ -13,19 +13,19 @@ 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 } +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 } From d651496b89f2489e98fdf6ac85332bda4edf5592 Mon Sep 17 00:00:00 2001 From: benluelo Date: Fri, 12 Dec 2025 23:36:51 +0300 Subject: [PATCH 8/8] wip --- Cargo.lock | 97 +++--- Cargo.toml | 11 +- .../ibc-union/lightclient/base/Cargo.toml | 1 - .../ibc-union/lightclient/starknet/Cargo.toml | 33 ++ .../ibc-union/lightclient/starknet/LICENSE | 59 ++++ .../lightclient/starknet/src/client.rs | 162 +++++++++ .../lightclient/starknet/src/contract.rs | 1 + .../lightclient/starknet/src/errors.rs | 33 ++ .../ibc-union/lightclient/starknet/src/lib.rs | 3 + lib/starknet-light-client-types/Cargo.toml | 23 +- .../src/client_state.rs | 6 +- .../src/consensus_state.rs | 18 +- lib/starknet-light-client-types/src/header.rs | 115 +++--- .../src/storage_proof.rs | 15 +- lib/starknet-light-client/Cargo.toml | 14 - lib/starknet-light-client/src/lib.rs | 14 - lib/starknet-storage-verifier/Cargo.toml | 15 +- lib/starknet-storage-verifier/src/lib.rs | 326 ++++++------------ lib/starknet-types/Cargo.toml | 26 ++ lib/starknet-types/src/lib.rs | 189 ++++++++++ lib/starknet-verifier/Cargo.toml | 20 +- lib/starknet-verifier/src/lib.rs | 103 ++---- .../plugins/event-source/starknet/src/call.rs | 2 - 23 files changed, 799 insertions(+), 487 deletions(-) create mode 100644 cosmwasm/ibc-union/lightclient/starknet/Cargo.toml create mode 100644 cosmwasm/ibc-union/lightclient/starknet/LICENSE create mode 100644 cosmwasm/ibc-union/lightclient/starknet/src/client.rs create mode 100644 cosmwasm/ibc-union/lightclient/starknet/src/contract.rs create mode 100644 cosmwasm/ibc-union/lightclient/starknet/src/errors.rs create mode 100644 cosmwasm/ibc-union/lightclient/starknet/src/lib.rs delete mode 100644 lib/starknet-light-client/Cargo.toml delete mode 100644 lib/starknet-light-client/src/lib.rs create mode 100644 lib/starknet-types/Cargo.toml create mode 100644 lib/starknet-types/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 27bf3ab6b41..dc281e7782d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2097,7 +2097,6 @@ dependencies = [ "frissitheto", "ibc-union-light-client", "ibc-union-msg", - "ics23", "serde", "thiserror 2.0.12", "unionlabs", @@ -8219,21 +8218,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" dependencies = [ - "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", + "lambdaworks-math", "serde", "sha2 0.10.9", "sha3", @@ -8249,20 +8234,6 @@ 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" @@ -13529,7 +13500,7 @@ dependencies = [ "sha3", "starknet-core-derive", "starknet-crypto", - "starknet-types-core 0.2.0", + "starknet-types-core", ] [[package]] @@ -13558,7 +13529,7 @@ dependencies = [ "rfc6979 0.4.0", "sha2 0.10.9", "starknet-curve", - "starknet-types-core 0.2.0", + "starknet-types-core", "zeroize", ] @@ -13568,27 +13539,42 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22c898ae81b6409532374cf237f1bd752d068b96c6ad500af9ebbd0d9bb712f6" dependencies = [ - "starknet-types-core 0.2.0", + "starknet-types-core", ] [[package]] name = "starknet-light-client" version = "0.0.0" +dependencies = [ + "cosmwasm-std", + "embed-commit", + "ethereum-light-client", + "frissitheto", + "ibc-union-light-client", + "ibc-union-msg", + "serde", + "starknet-light-client-types", + "starknet-storage-verifier", + "starknet-types", + "starknet-verifier", + "thiserror 2.0.12", + "unionlabs", +] [[package]] name = "starknet-light-client-types" version = "0.0.0" dependencies = [ - "alloy", + "alloy-sol-types", "bincode 2.0.1", "ethereum-light-client-types", "hex-literal 0.4.1", "ibc-union-spec", - "pathfinder-crypto", "serde", - "starknet-core", - "starknet-types-core 1.0.0", - "unionlabs", + "starknet-crypto", + "starknet-types", + "unionlabs-encoding", + "unionlabs-primitives", ] [[package]] @@ -13645,44 +13631,37 @@ version = "0.0.0" dependencies = [ "bitvec 1.0.1", "pathfinder-crypto", - "serde", "serde_json", - "starknet-storage-verifier", + "starknet-crypto", + "starknet-types", "thiserror 2.0.12", ] [[package]] -name = "starknet-types-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa3d91e38f091dbc543d33589eb7716bed2a8eb1c20879e484561977832b60a" +name = "starknet-types" +version = "0.0.0" dependencies = [ - "blake2", - "digest 0.10.7", - "lambdaworks-crypto 0.10.0", - "lambdaworks-math 0.10.0", - "num-bigint 0.4.6", - "num-integer", - "num-traits", + "bincode 2.0.1", + "hex-literal 0.4.1", + "pathfinder-crypto", + "ruint", "serde", - "zeroize", + "starknet-crypto", ] [[package]] name = "starknet-types-core" -version = "1.0.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a12690813e587969cb4a9e7d8ebdb069d4bb7ec8d03275c5f719310c8e1f07c" +checksum = "5fa3d91e38f091dbc543d33589eb7716bed2a8eb1c20879e484561977832b60a" dependencies = [ "blake2", "digest 0.10.7", - "generic-array 0.14.7", - "lambdaworks-crypto 0.13.0", - "lambdaworks-math 0.13.0", + "lambdaworks-crypto", + "lambdaworks-math", "num-bigint 0.4.6", "num-integer", "num-traits", - "rand 0.9.2", "serde", "zeroize", ] @@ -13693,10 +13672,12 @@ version = "0.0.0" dependencies = [ "evm-storage-verifier", "hex-literal 0.4.1", + "ibc-union-spec", "keccak-const", "rlp", "starknet-light-client-types", "starknet-storage-verifier", + "starknet-types", "thiserror 2.0.12", "unionlabs-primitives", ] diff --git a/Cargo.toml b/Cargo.toml index 2f05863c03b..45ea341a8c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ members = [ "cosmwasm/ibc-union/lightclient/parlia", "cosmwasm/ibc-union/lightclient/trusted-mpt", "cosmwasm/ibc-union/lightclient/attested", + "cosmwasm/ibc-union/lightclient/starknet", "cosmwasm/ibc-union/lightclient/state-lens-ics23-mpt", # "cosmwasm/ibc-union/lightclient/state-lens-ics23-smt", "cosmwasm/ibc-union/lightclient/state-lens-ics23-ics23", @@ -296,9 +297,8 @@ members = [ "cosmwasm/pausable", "cosmwasm/gatekeeper", "cosmwasm/proxy-account-factory", - "lib/starknet-light-client-types", "lib/starknet-verifier", - "lib/starknet-light-client", + "lib/starknet-types", ] [workspace.package] @@ -414,6 +414,7 @@ osmosis-tokenfactory-token-minter = { path = "cosmwasm/osmosis-tokenfact parlia-light-client-types = { path = "lib/parlia-light-client-types", default-features = false } parlia-types = { path = "lib/parlia-types", default-features = false } parlia-verifier = { path = "lib/parlia-verifier", default-features = false } +pathfinder-crypto = { version = "0.21.3", default-features = false } pausable = { path = "cosmwasm/pausable", default-features = false } pg-queue = { path = "lib/pg-queue", default-features = false } protos = { path = "generated/rust/protos", default-features = false } @@ -425,6 +426,8 @@ ssz = { path = "lib/ssz", default-features 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 } +starknet-types = { path = "lib/starknet-types", default-features = false } +starknet-verifier = { path = "lib/starknet-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 } @@ -474,7 +477,6 @@ wasm-client-type = { path = "lib/wasm-client-type", def # ===================== # external dependencies # ===================== - alloy = { version = "1.0", default-features = false } alloy-primitives = { version = "1.1", default-features = false } alloy-sol-types = { version = "1.1", default-features = true } @@ -526,6 +528,7 @@ reqwest = { version = "0.11.27", default-features = false } reth-ipc = { git = "https://github.com/paradigmxyz/reth", default-features = false } ripemd = { version = "0.1.3", default-features = false } rlp = { version = "0.5.2", default-features = false } +ruint = { version = "1.17.0", default-features = false } schemars = { version = "0.8.22", default-features = false } serde = { version = "1.0.219", default-features = false } serde-json-wasm = { version = "1.0.1", default-features = false } @@ -535,7 +538,7 @@ 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 strum = { version = "0.27", default-features = false } subtle-encoding = { version = "0.5.1", default-features = false } diff --git a/cosmwasm/ibc-union/lightclient/base/Cargo.toml b/cosmwasm/ibc-union/lightclient/base/Cargo.toml index d878ff90f88..e0ad7504434 100644 --- a/cosmwasm/ibc-union/lightclient/base/Cargo.toml +++ b/cosmwasm/ibc-union/lightclient/base/Cargo.toml @@ -28,7 +28,6 @@ evm-storage-verifier = { workspace = true } frissitheto = { workspace = true } ibc-union-light-client = { workspace = true } ibc-union-msg = { workspace = true } -ics23 = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } unionlabs = { workspace = true } diff --git a/cosmwasm/ibc-union/lightclient/starknet/Cargo.toml b/cosmwasm/ibc-union/lightclient/starknet/Cargo.toml new file mode 100644 index 00000000000..0b8a893d6d8 --- /dev/null +++ b/cosmwasm/ibc-union/lightclient/starknet/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "starknet-light-client" +version = "0.0.0" + +authors = { workspace = true } +edition = { workspace = true } +license-file = "LICENSE" +publish = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[package.metadata.crane] +test-include = [] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +cosmwasm-std = { workspace = true } +embed-commit = { workspace = true } +ethereum-light-client = { workspace = true } +frissitheto = { workspace = true } +ibc-union-light-client = { workspace = true } +ibc-union-msg = { workspace = true } +serde = { workspace = true, features = ["derive"] } +starknet-light-client-types = { workspace = true, features = ["serde", "ethabi", "bincode"] } +starknet-storage-verifier = { workspace = true } +starknet-types = { workspace = true } +starknet-verifier = { workspace = true } +thiserror = { workspace = true } +unionlabs = { workspace = true } diff --git a/cosmwasm/ibc-union/lightclient/starknet/LICENSE b/cosmwasm/ibc-union/lightclient/starknet/LICENSE new file mode 100644 index 00000000000..b640dd2afdb --- /dev/null +++ b/cosmwasm/ibc-union/lightclient/starknet/LICENSE @@ -0,0 +1,59 @@ +License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +Parameters + +Licensor: Union.fi, Labs Inc. +Licensed Work: All files under this license file's directory/subdirectories. + The Licensed Work is (c) 2025 Union.fi, Labs Inc. +Change Date: Four years from the date the Licensed Work is published. +Change License: Apache-2.0 + + +For information about alternative licensing arrangements for the Licensed Work, +please contact info@union.build. + +Notice + +Business Source License 1.1 + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. diff --git a/cosmwasm/ibc-union/lightclient/starknet/src/client.rs b/cosmwasm/ibc-union/lightclient/starknet/src/client.rs new file mode 100644 index 00000000000..86c28247878 --- /dev/null +++ b/cosmwasm/ibc-union/lightclient/starknet/src/client.rs @@ -0,0 +1,162 @@ +use cosmwasm_std::{Addr, Empty, ensure}; +use ethereum_light_client::client::EthereumLightClient; +use ibc_union_light_client::{ + ClientCreationResult, IbcClient, IbcClientCtx, IbcClientError, StateUpdate, + spec::{Status, Timestamp}, +}; +use starknet_light_client_types::{ClientState, ConsensusState, Header, StorageProof}; +use starknet_storage_verifier::{Membership, PoseidonHash}; +use starknet_types::Felt; +use unionlabs::encoding::Bincode; + +use crate::errors::Error; + +pub struct StarknetLightClient; + +impl IbcClient for StarknetLightClient { + type Error = Error; + + type Header = Header; + + type Misbehaviour = (); + + type ClientState = ClientState; + + type ConsensusState = ConsensusState; + + type Encoding = Bincode; + + type CustomQuery = Empty; + + type StorageProof = StorageProof; + + fn verify_membership( + ctx: IbcClientCtx, + height: u64, + key: Vec, + storage_proof: Self::StorageProof, + value: Vec, + ) -> Result<(), IbcClientError> { + let consensus_state = ctx.read_self_consensus_state(height)?; + + let res = starknet_storage_verifier::verify_proof::( + consensus_state.ibc_storage_root, + commitment_key(&key), + Felt::from_be_bytes( + value + .try_into() + .map_err(|bz: Vec| Error::InvalidProofValue(bz.into()))?, + ), + &storage_proof.nodes, + ) + .map_err(Into::::into)?; + + ensure!(matches!(res, Membership::Membership), Error::InvalidProof); + + Ok(()) + } + + fn verify_non_membership( + ctx: IbcClientCtx, + height: u64, + key: Vec, + storage_proof: Self::StorageProof, + ) -> Result<(), IbcClientError> { + let consensus_state = ctx.read_self_consensus_state(height)?; + + let res = starknet_storage_verifier::verify_proof::( + consensus_state.ibc_storage_root, + commitment_key(&key), + Felt::ZERO, + &storage_proof.nodes, + ) + .map_err(Into::::into)?; + + ensure!( + matches!(res, Membership::NonMembership), + Error::InvalidProof + ); + + Ok(()) + } + + fn get_timestamp(consensus_state: &Self::ConsensusState) -> Timestamp { + consensus_state.timestamp + } + + fn get_latest_height(ClientState::V1(client_state): &Self::ClientState) -> u64 { + client_state.latest_height + } + + fn get_counterparty_chain_id(ClientState::V1(client_state): &Self::ClientState) -> String { + client_state.chain_id.to_string() + } + + fn status(ctx: IbcClientCtx, client_state: &Self::ClientState) -> Status { + let _ = client_state; + let _ = ctx; + // FIXME: expose the ctx to this call to allow threading this call to L1 + // client. generally, we want to thread if a client is an L2 so always + // provide the ctx? + Status::Active + } + + fn verify_creation( + _caller: Addr, + _client_state: &Self::ClientState, + _consensus_state: &Self::ConsensusState, + _relayer: Addr, + ) -> Result, IbcClientError> { + Ok(ClientCreationResult::new()) + } + + fn verify_header( + ctx: IbcClientCtx, + _caller: Addr, + header: Self::Header, + _relayer: Addr, + ) -> Result, IbcClientError> { + let ClientState::V1(mut client_state) = ctx.read_self_client_state()?; + + let l1_consensus_state = ctx + .read_consensus_state::( + client_state.l1_client_id, + header.l1_height, + ) + .map_err(Into::::into)?; + + starknet_verifier::verify_header(&client_state, &header, l1_consensus_state.state_root) + .map_err(Into::::into)?; + + let update_height = header.l2_block.block_number.try_into().expect("impossible"); + + let consensus_state = ConsensusState { + timestamp: Timestamp::from_secs(header.l2_block.block_timestamp), + // TODO: What do we want to store here? + global_root: header.l2_block.contracts_trie_root, + ibc_storage_root: header.l2_ibc_contract_proof.contract_leaf_data.storage_root, + }; + + let state_update = StateUpdate::new(update_height, consensus_state); + + if client_state.latest_height < update_height { + client_state.latest_height = update_height; + Ok(state_update.overwrite_client_state(ClientState::V1(client_state))) + } else { + Ok(state_update) + } + } + + fn misbehaviour( + _ctx: IbcClientCtx, + _caller: Addr, + _misbehaviour: Self::Misbehaviour, + _relayer: Addr, + ) -> Result> { + Err(Error::Unimplemented.into()) + } +} + +fn commitment_key(key: &[u8]) -> Felt { + todo!() +} diff --git a/cosmwasm/ibc-union/lightclient/starknet/src/contract.rs b/cosmwasm/ibc-union/lightclient/starknet/src/contract.rs new file mode 100644 index 00000000000..252ba5d3375 --- /dev/null +++ b/cosmwasm/ibc-union/lightclient/starknet/src/contract.rs @@ -0,0 +1 @@ +ibc_union_light_client::entrypoints!(crate::client::StarknetLightClient); diff --git a/cosmwasm/ibc-union/lightclient/starknet/src/errors.rs b/cosmwasm/ibc-union/lightclient/starknet/src/errors.rs new file mode 100644 index 00000000000..b6c483690c2 --- /dev/null +++ b/cosmwasm/ibc-union/lightclient/starknet/src/errors.rs @@ -0,0 +1,33 @@ +use ethereum_light_client::client::EthereumLightClient; +use ibc_union_light_client::IbcClientError; +use unionlabs::primitives::Bytes; + +use crate::client::StarknetLightClient; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("unimplemented")] + Unimplemented, + + #[error(transparent)] + Verify(#[from] starknet_verifier::Error), + + #[error(transparent)] + Proof(#[from] starknet_storage_verifier::Error), + + #[error(transparent)] + EthereumLightClient(#[from] IbcClientError), + + #[error("invalid proof")] + InvalidProof, + + #[error("invalid proof value: {0}")] + InvalidProofValue(Bytes), +} + +// required for IbcClient trait +impl From for IbcClientError { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) + } +} diff --git a/cosmwasm/ibc-union/lightclient/starknet/src/lib.rs b/cosmwasm/ibc-union/lightclient/starknet/src/lib.rs new file mode 100644 index 00000000000..00c1933129c --- /dev/null +++ b/cosmwasm/ibc-union/lightclient/starknet/src/lib.rs @@ -0,0 +1,3 @@ +pub mod client; +pub mod contract; +pub mod errors; diff --git a/lib/starknet-light-client-types/Cargo.toml b/lib/starknet-light-client-types/Cargo.toml index 280220ac9fa..2275487092c 100644 --- a/lib/starknet-light-client-types/Cargo.toml +++ b/lib/starknet-light-client-types/Cargo.toml @@ -12,25 +12,32 @@ repository = { workspace = true } workspace = true [dependencies] -alloy = { workspace = true, features = ["sol-types"], optional = true } +alloy-sol-types = { workspace = true, optional = true } bincode = { workspace = true, features = ["alloc", "derive"], optional = true } ethereum-light-client-types = { workspace = true } ibc-union-spec = { workspace = true } -pathfinder-crypto = "0.21.3" 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 } +starknet-crypto = { workspace = true } +starknet-types = { workspace = true } +unionlabs-encoding = { workspace = true } +unionlabs-primitives = { workspace = true } [features] bincode = [ "dep:bincode", - "unionlabs/bincode", + "unionlabs-encoding/bincode", + "starknet-types/bincode", + "unionlabs-primitives/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"] +ethabi = ["dep:alloy-sol-types", "unionlabs-encoding/ethabi"] +serde = [ + "dep:serde", + "starknet-types/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 index 51eeebaa9d3..40dff1bd418 100644 --- a/lib/starknet-light-client-types/src/client_state.rs +++ b/lib/starknet-light-client-types/src/client_state.rs @@ -1,5 +1,6 @@ -use pathfinder_crypto::Felt; -use unionlabs::primitives::{H160, H256}; +use ibc_union_spec::ClientId; +use starknet_types::Felt; +use unionlabs_primitives::H160; #[derive(Debug, Clone, PartialEq)] #[cfg_attr( @@ -17,6 +18,7 @@ pub enum ClientState { #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct ClientStateV1 { pub chain_id: Felt, + pub l1_client_id: ClientId, pub latest_height: u64, pub ibc_contract_address: Felt, /// https://docs.starknet.io/learn/cheatsheets/chain-info#important-addresses diff --git a/lib/starknet-light-client-types/src/consensus_state.rs b/lib/starknet-light-client-types/src/consensus_state.rs index 36f139edf7e..271b96f2b9e 100644 --- a/lib/starknet-light-client-types/src/consensus_state.rs +++ b/lib/starknet-light-client-types/src/consensus_state.rs @@ -1,23 +1,23 @@ use ibc_union_spec::Timestamp; -use unionlabs::primitives::H256; +use starknet_types::Felt; #[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 global_root: Felt, + pub ibc_storage_root: Felt, pub timestamp: Timestamp, } #[cfg(feature = "ethabi")] pub mod ethabi { - use unionlabs::impl_ethabi_via_try_from_into; + use unionlabs_encoding::impl_ethabi_via_try_from_into; use super::*; impl_ethabi_via_try_from_into!(ConsensusState => SolConsensusState); - alloy::sol! { + alloy_sol_types::sol! { struct SolConsensusState { bytes32 global_root; bytes32 ibc_storage_root; @@ -28,8 +28,8 @@ pub mod ethabi { 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(), + global_root: value.global_root.to_be_bytes().into(), + ibc_storage_root: value.ibc_storage_root.to_be_bytes().into(), timestamp: value.timestamp.as_nanos(), } } @@ -38,8 +38,8 @@ pub mod ethabi { 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), + global_root: Felt::from_be_bytes(value.global_root.0), + ibc_storage_root: Felt::from_be_bytes(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 index 1a7386a9f68..a5a89907902 100644 --- a/lib/starknet-light-client-types/src/header.rs +++ b/lib/starknet-light-client-types/src/header.rs @@ -1,11 +1,7 @@ pub use ethereum_light_client_types::AccountProof; -use pathfinder_crypto::{ - Felt, - hash::{pedersen_hash, poseidon_hash_many}, -}; -use unionlabs::primitives::{Bytes, H256, U256}; - -use crate::storage_proof::MerkleNode; +use starknet_crypto::{pedersen_hash, poseidon_hash_many}; +use starknet_types::{Felt, MerkleNode}; +use unionlabs_primitives::Bytes; #[derive(Debug, Clone, PartialEq)] #[cfg_attr( @@ -92,12 +88,13 @@ pub struct ContractLeafData { impl ContractLeafData { pub fn hash(&self) -> Felt { pedersen_hash( - pedersen_hash( - pedersen_hash(self.class_hash, self.storage_root), - self.nonce, + &pedersen_hash( + &pedersen_hash(&self.class_hash.into(), &self.storage_root.into()), + &self.nonce.into(), ), - Felt::ZERO, + &Felt::ZERO.into(), ) + .into() } } @@ -109,12 +106,12 @@ impl L2Block { poseidon_hash_many( &[ // hex(b"STARKNET_BLOCK_HASH1") - Felt::from_hex_str("0x535441524b4e45545f424c4f434b5f4841534831").unwrap(), + Felt::from_hex("0x535441524b4e45545f424c4f434b5f4841534831").unwrap(), self.block_number.into(), poseidon_hash_many( &[ // hex(b"STARKNET_STATE_V0") - Felt::from_hex_str("0x535441524b4e45545f53544154455f5630").unwrap(), + Felt::from_hex("0x535441524b4e45545f53544154455f5630").unwrap(), self.contracts_trie_root, self.classes_trie_root, ] @@ -124,22 +121,19 @@ impl L2Block { self.sequencer_address, 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_be_slice( - [ - (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 - L1DaMode::Calldata => 0_u64, - // 0b1000_0000 ++ 7 bytes 0 padding - L1DaMode::Blob => 1 << 63, - }, - ] - .map(u64::to_be_bytes) - .as_flattened(), - ) - .unwrap(), + Felt::from_be_bytes({ + let mut bz = [0; 32]; + + bz[0..8].copy_from_slice(&(self.transaction_count as u64).to_be_bytes()); + bz[8..16].copy_from_slice(&(self.events_count as u64).to_be_bytes()); + bz[16..24].copy_from_slice(&(self.state_diff_length as u64).to_be_bytes()); + bz[24] = match self.l1_da_mode { + L1DaMode::Calldata => 0b0000_0000, + L1DaMode::Blob => 0b1000_0000, + }; + + bz + }), self.state_diff_commitment, self.transactions_commitment, self.events_commitment, @@ -147,7 +141,7 @@ impl L2Block { poseidon_hash_many( &[ // hex(b"STARKNET_GAS_PRICES0") - Felt::from_hex_str("0x535441524b4e45545f4741535f50524943455330").unwrap(), + Felt::from_hex("0x535441524b4e45545f4741535f50524943455330").unwrap(), self.l1_gas_price.0.into(), self.l1_gas_price.1.into(), self.l1_data_gas_price.0.into(), @@ -158,7 +152,14 @@ impl L2Block { .map(Into::into), ) .into(), - Felt::from_be_slice(self.protocol_version.as_bytes()).unwrap(), + Felt::from_be_bytes({ + let mut bz = [0; 32]; + + bz[32 - self.protocol_version.len()..] + .copy_from_slice(self.protocol_version.as_bytes()); + + bz + }), Felt::ZERO, self.parent_block_hash, ] @@ -170,39 +171,45 @@ impl L2Block { #[test] fn l2_block_hash_3996475() { - 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") - .into(), - classes_trie_root: hex!("052dedb4984ca5bde1fa31f46bdedd2462779d7a6db3039be87eb0c532d79470") - .into(), - contracts_trie_root: hex!( - "02c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb" + parent_block_hash: Felt::from_hex( + "07488afa914e19281d6a859f1673d91f84b124576677bc90790954934bcf6a90", + ) + .unwrap(), + classes_trie_root: Felt::from_hex( + "052dedb4984ca5bde1fa31f46bdedd2462779d7a6db3039be87eb0c532d79470", ) - .into(), - sequencer_address: hex!("01176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8") - .into(), + .unwrap(), + contracts_trie_root: Felt::from_hex( + "02c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb", + ) + .unwrap(), + sequencer_address: Felt::from_hex( + "01176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", + ) + .unwrap(), 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" + state_diff_commitment: Felt::from_hex( + "000d69e24d96773a920991dcd7f86fea0526acb3dae9bb3955caf840c71b54f6", + ) + .unwrap(), + transactions_commitment: Felt::from_hex( + "01df3ce5acd86d8c2d7f1155997a70a004ee0a0c36c67c9baafe87ace22f30d9", ) - .into(), - transactions_commitment: hex!( - "01df3ce5acd86d8c2d7f1155997a70a004ee0a0c36c67c9baafe87ace22f30d9" + .unwrap(), + events_commitment: Felt::from_hex( + "030a53d5d62958b18f1094b66c4ad4c3bcee8dd2a36666fc5fc8b46ddaa5b37c", ) - .into(), - events_commitment: hex!("030a53d5d62958b18f1094b66c4ad4c3bcee8dd2a36666fc5fc8b46ddaa5b37c") - .into(), - receipts_commitment: hex!( - "0494e30696606f6208ac02b701f2350460c35b0be17cdf23e4017c79a6a69f2f" + .unwrap(), + receipts_commitment: Felt::from_hex( + "0494e30696606f6208ac02b701f2350460c35b0be17cdf23e4017c79a6a69f2f", ) - .into(), + .unwrap(), l1_gas_price: (0x6df5cf40, 0x27d11e1709d4), l1_data_gas_price: (0x1, 0x5cb2), l2_gas_price: (0x1edd2, 0xb2d05e00), @@ -214,7 +221,7 @@ fn l2_block_hash_3996475() { assert_eq!( block.hash(), - Felt::from_hex_str("0x366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675") + Felt::from_hex("0x366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675") .unwrap() ); } diff --git a/lib/starknet-light-client-types/src/storage_proof.rs b/lib/starknet-light-client-types/src/storage_proof.rs index 9f9f088e8e4..fd0b4ccd588 100644 --- a/lib/starknet-light-client-types/src/storage_proof.rs +++ b/lib/starknet-light-client-types/src/storage_proof.rs @@ -1,4 +1,4 @@ -use unionlabs::primitives::H256; +use starknet_types::{Felt, MerkleNode}; #[derive(Debug, Clone, PartialEq)] #[cfg_attr( @@ -9,17 +9,4 @@ use unionlabs::primitives::H256; #[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] pub struct StorageProof { pub nodes: Vec, - pub root: 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 enum MerkleNode { - BinaryNode { left: H256, right: H256 }, - EdgeNode { path: H256, length: u8, child: H256 }, } diff --git a/lib/starknet-light-client/Cargo.toml b/lib/starknet-light-client/Cargo.toml deleted file mode 100644 index 8a4efc2e238..00000000000 --- a/lib/starknet-light-client/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "starknet-light-client" -version = "0.0.0" - -authors = { workspace = true } -edition = { workspace = true } -license-file = { workspace = true } -publish = { workspace = true } -repository = { workspace = true } - -[lints] -workspace = true - -[dependencies] diff --git a/lib/starknet-light-client/src/lib.rs b/lib/starknet-light-client/src/lib.rs deleted file mode 100644 index b93cf3ffd9c..00000000000 --- a/lib/starknet-light-client/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/lib/starknet-storage-verifier/Cargo.toml b/lib/starknet-storage-verifier/Cargo.toml index c03e29f125a..b471e8d1277 100644 --- a/lib/starknet-storage-verifier/Cargo.toml +++ b/lib/starknet-storage-verifier/Cargo.toml @@ -12,16 +12,15 @@ repository = { workspace = true } workspace = true [dependencies] -bitvec = { workspace = true } -pathfinder-crypto = "0.21.3" -serde = { workspace = true, optional = true, features = ["derive"] } -thiserror.workspace = true +bitvec = { workspace = true, features = ["alloc"] } +starknet-crypto = { workspace = true } +starknet-types = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -serde_json = { workspace = true } -starknet-storage-verifier = { workspace = true, features = ["serde"] } +pathfinder-crypto = { workspace = true } +serde_json = { workspace = true } +starknet-types = { 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 cdaeadddfe4..ff27ebe4124 100644 --- a/lib/starknet-storage-verifier/src/lib.rs +++ b/lib/starknet-storage-verifier/src/lib.rs @@ -1,102 +1,68 @@ use std::collections::BTreeMap; -pub use pathfinder_crypto::Felt; -use pathfinder_crypto::hash::{pedersen_hash, poseidon_hash}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; +use bitvec::{order::Msb0, vec::BitVec}; +use starknet_crypto::{pedersen_hash, poseidon_hash}; +use starknet_types::{Felt, MerkleNode}; + +type CryptoFelt = starknet_crypto::Felt; + +fn to_crypto_felt(felt: Felt) -> CryptoFelt { + starknet_crypto::Felt::from_bytes_be(&felt.to_be_bytes()) +} pub trait FeltHash { - fn hash(a: Felt, b: Felt) -> Felt; + fn hash(a: CryptoFelt, b: CryptoFelt) -> CryptoFelt; } pub enum PedersenHash {} impl FeltHash for PedersenHash { - fn hash(a: Felt, b: Felt) -> Felt { - pedersen_hash(a, b) + fn hash(a: CryptoFelt, b: CryptoFelt) -> CryptoFelt { + 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() + fn hash(a: CryptoFelt, b: CryptoFelt) -> CryptoFelt { + poseidon_hash(a, b) + } +} + +pub fn hash_node(node: &CryptoMerkleNode) -> CryptoFelt { + match node { + CryptoMerkleNode::BinaryNode { left, right } => H::hash(*left, *right), + CryptoMerkleNode::EdgeNode { + path, + length, + child, + } => { + H::hash(*child, *path) + { + let mut length_ = [0; 32]; + // Safe as len() is guaranteed to be <= 251 + length_[31] = *length; + CryptoFelt::from_bytes_be(&length_) + } + } } } -/// A node in the Merkle-Patricia tree, can be a leaf, binary node, or an edge node. #[derive(Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))] -pub enum MerkleNode { - /// Binary/branch node. - /// - /// An internal node whose both children are non-zero. +pub enum CryptoMerkleNode { 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, + left: CryptoFelt, + right: CryptoFelt, }, - /// Edge/leaf node. - /// - /// 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) + path: CryptoFelt, length: u8, - /// The hash of the unique non-zero maximal-height descendant node - #[cfg_attr(feature = "serde", serde(with = "felt"))] - child: Felt, + child: CryptoFelt, }, } -impl MerkleNode { - pub fn hash(&self) -> Felt { - match self { - MerkleNode::BinaryNode { left, right } => H::hash(*left, *right), - MerkleNode::EdgeNode { - path, - length, - child, - } => H::hash(*child, *path) + Felt::from_u64((*length).into()), - } - } -} - -#[cfg(feature = "serde")] -pub mod felt { - use pathfinder_crypto::Felt; - use serde::{Deserializer, Serialize, Serializer, de::Deserialize}; - - pub fn serialize(data: &Felt, serializer: S) -> Result - where - S: Serializer, - { - if serializer.is_human_readable() { - serializer.collect_str(&data) - } else { - data.to_be_bytes().serialize(serializer) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - String::deserialize(deserializer) - .and_then(|s| Felt::from_hex_str(&s).map_err(serde::de::Error::custom)) - } else { - <[u8; 32]>::deserialize(deserializer) - .and_then(|bz| Felt::from_be_bytes(bz).map_err(serde::de::Error::custom)) - } - } +pub fn felt_bits(felt: CryptoFelt) -> BitVec { + BitVec::from_slice(&felt.to_bytes_be()) } #[derive(Debug, PartialEq)] @@ -113,23 +79,40 @@ pub enum Error { ValueMismatch { expected: Felt, found: Felt }, } -pub fn verify_proof( - mut root: Felt, +pub fn verify_proof<'a, H: FeltHash>( + root: Felt, key: Felt, value: Felt, - proof: impl IntoIterator, + proof: impl IntoIterator, ) -> Result { + let mut root = to_crypto_felt(root); + let mut proof = proof .into_iter() - .map(|n| (n.hash::(), n)) + .map(|n| match n { + MerkleNode::BinaryNode { left, right } => CryptoMerkleNode::BinaryNode { + left: to_crypto_felt(*left), + right: to_crypto_felt(*right), + }, + MerkleNode::EdgeNode { + path, + length, + child, + } => CryptoMerkleNode::EdgeNode { + path: to_crypto_felt(*path), + length: *length, + child: to_crypto_felt(*child), + }, + }) + .map(|n| (hash_node::(&n), n)) .collect::>(); // https://github.com/eqlabs/pathfinder/blob/a34566b9a9f6ea6d7eb3889130d62c8f3fe6a499/crates/crypto/src/algebra/field/felt.rs#L176 - let mut remaining_path = key.view_bits(); + let mut remaining_path = &felt_bits(to_crypto_felt(key))[5..]; while let Some(proof_node) = proof.remove(&root) { match proof_node { - MerkleNode::BinaryNode { left, right } => { + CryptoMerkleNode::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 @@ -141,19 +124,14 @@ pub fn verify_proof( // Advance by a single bit remaining_path = &remaining_path[1..]; } - MerkleNode::EdgeNode { + CryptoMerkleNode::EdgeNode { path, length, child, } => { - let path_view = &path.view_bits()[(251 - length) as usize..251]; + let path_view = &felt_bits(path)[5..][(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: @@ -174,17 +152,19 @@ pub fn verify_proof( } } - if root != value { + if !proof.is_empty() { + dbg!(proof); + + return Err(Error::UnusedNodes); + } + + if root.to_bytes_be() != value.to_be_bytes() { return Err(Error::ValueMismatch { expected: value, - found: root, + found: Felt::from_be_bytes(root.to_bytes_be()), }); } - if !proof.is_empty() { - return Err(Error::UnusedNodes); - } - Ok(Membership::Membership) } @@ -306,31 +286,32 @@ fn contract_membership() { ) .unwrap(); - let key = - Felt::from_hex_str("0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e") - .unwrap(); + let key = Felt::from_hex("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(); + let class_hash = CryptoFelt::from_hex_unchecked( + "0x69b893a8b6e1bf94740e33d9584a01295510f3b51f024d9833b2acaf1be4045", + ); + let nonce = CryptoFelt::from_hex_unchecked("0x0"); + let storage_root = CryptoFelt::from_hex_unchecked( + "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa", + ); // https://docs.starknet.io/learn/protocol/state#the-contract-trie - pedersen_hash( - pedersen_hash(pedersen_hash(class_hash, storage_root), nonce), - Felt::ZERO, + Felt::from_be_bytes( + pedersen_hash( + &pedersen_hash(&pedersen_hash(&class_hash, &storage_root), &nonce), + &CryptoFelt::ZERO, + ) + .to_bytes_be(), ) }; // contracts_proof.contract_leaves_data.storage_root - let root = - Felt::from_hex_str("0x2c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb") - .unwrap(); + let root = Felt::from_hex("0x2c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb") + .unwrap(); - let res = verify_proof::(root, key, value, proof).unwrap(); + let res = verify_proof::(root, key, value, &proof).unwrap(); assert_eq!(res, Membership::Membership); } @@ -373,19 +354,16 @@ fn contract_storage_membership() { ) .unwrap(); - let key = - Felt::from_hex_str("0x03d0f817b2e6b145a39886c95257e1bade33bc907b2125d2b4b93ced393d8e6b") - .unwrap(); - let value = - Felt::from_hex_str("0x49ff5b3a7d38e2b50198f408fa8281635b5bc81ee49ab87ac36c8324c214427") - .unwrap(); + let key = Felt::from_hex("0x03d0f817b2e6b145a39886c95257e1bade33bc907b2125d2b4b93ced393d8e6b") + .unwrap(); + let value = Felt::from_hex("0x49ff5b3a7d38e2b50198f408fa8281635b5bc81ee49ab87ac36c8324c214427") + .unwrap(); // contracts_proof.contract_leaves_data.storage_root - let root = - Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") - .unwrap(); + let root = Felt::from_hex("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); - let res = verify_proof::(root, key, value, proof).unwrap(); + let res = verify_proof::(root, key, value, &proof).unwrap(); assert_eq!(res, Membership::Membership); } @@ -416,114 +394,14 @@ fn contract_storage_non_membership() { ) .unwrap(); - let key = Felt::from_hex_str("0x0").unwrap(); - let value = Felt::from_hex_str("0x0").unwrap(); + let key = Felt::from_hex("0x0").unwrap(); + let value = Felt::from_hex("0x0").unwrap(); // contracts_proof.contract_leaves_data.storage_root - let root = - Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") - .unwrap(); + let root = Felt::from_hex("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); - let res = verify_proof::(root, key, value, proof).unwrap(); + let res = verify_proof::(root, key, value, &proof).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); -// } diff --git a/lib/starknet-types/Cargo.toml b/lib/starknet-types/Cargo.toml new file mode 100644 index 00000000000..5594b435d6e --- /dev/null +++ b/lib/starknet-types/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "starknet-types" +version = "0.0.0" + +authors = { workspace = true } +edition = { workspace = true } +license-file = { workspace = true } +publish = { workspace = true } +repository = { workspace = true } + +[lints] +workspace = true + +[dependencies] +bincode = { workspace = true, features = ["alloc", "derive"], optional = true } +ruint = { workspace = true } +serde = { workspace = true, optional = true, features = ["derive"] } +starknet-crypto = { workspace = true } + +[dev-dependencies] +hex-literal = { workspace = true } +pathfinder-crypto = { workspace = true } + +[features] +bincode = ["dep:bincode"] +serde = ["dep:serde"] diff --git a/lib/starknet-types/src/lib.rs b/lib/starknet-types/src/lib.rs new file mode 100644 index 00000000000..6d271258a1b --- /dev/null +++ b/lib/starknet-types/src/lib.rs @@ -0,0 +1,189 @@ +// TODO: Check for size (must be < modulus) + +use std::fmt; + +use ruint::{ParseError, aliases::U256}; + +#[derive(Clone, Copy, PartialEq)] +pub struct Felt(ruint::aliases::U256); + +impl fmt::Debug for Felt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Felt({self})") + } +} + +impl fmt::Display for Felt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:x}", self.0) + } +} + +impl From for Felt { + fn from(value: u64) -> Self { + Self(U256::from(value)) + } +} + +impl From for Felt { + fn from(value: u128) -> Self { + Self(U256::from(value)) + } +} + +impl From for Felt { + fn from(value: starknet_crypto::Felt) -> Self { + Self::from_be_bytes(value.to_bytes_be()) + } +} + +impl From for starknet_crypto::Felt { + fn from(value: Felt) -> Self { + Self::from_bytes_be(&value.to_be_bytes()) + } +} + +impl Felt { + pub const ZERO: Self = Self::from_be_bytes([0; 32]); + + pub const fn from_be_bytes(bytes: [u8; 32]) -> Self { + Self(U256::from_be_bytes(bytes)) + } + + pub const fn to_be_bytes(&self) -> [u8; 32] { + self.0.to_be_bytes() + } + + pub const fn from_le_bytes(bytes: [u8; 32]) -> Self { + Self(U256::from_le_bytes(bytes)) + } + + pub const fn to_le_bytes(&self) -> [u8; 32] { + self.0.to_le_bytes() + } + + pub fn from_hex(s: &str) -> Result { + U256::from_str_radix(s.strip_prefix("0x").unwrap_or(s), 16).map(Felt) + } +} + +/// A node in the Merkle-Patricia tree, can be a leaf, binary node, or an edge node. +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(untagged) +)] +#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))] +pub enum MerkleNode { + /// Binary/branch node. + /// + /// An internal node whose both children are non-zero. + BinaryNode { + /// The hash of the left child + left: Felt, + /// The hash of the right child + right: Felt, + }, + /// Edge/leaf node. + /// + /// 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) + path: Felt, + /// The length of the path (bounded by 251) + length: u8, + /// The hash of the unique non-zero maximal-height descendant node + child: Felt, + }, +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Felt { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + if serializer.is_human_readable() { + serializer.collect_str(self) + } else { + serializer.serialize_bytes(&self.0.to_be_bytes::<32>()) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Felt { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + String::deserialize(deserializer).and_then(|s| { + U256::from_str_radix(s.strip_prefix("0x").unwrap_or(&s), 16) + .map(Felt) + .map_err(serde::de::Error::custom) + }) + } else { + <[u8; 32]>::deserialize(deserializer).map(Felt::from_be_bytes) + } + } +} + +#[cfg(feature = "bincode")] +impl bincode::Decode for Felt { + fn decode>( + decoder: &mut D, + ) -> Result { + use bincode::{ + config::{Config, Endianness, IntEncoding}, + de::read::Reader, + }; + + decoder.claim_bytes_read(32)?; + + match decoder.config().int_encoding() { + IntEncoding::Variable => Err(bincode::error::DecodeError::Other( + "varint encoding is not supported for Felt", + )), + IntEncoding::Fixed => { + let mut bytes = [0u8; 32]; + decoder.reader().read(&mut bytes)?; + match decoder.config().endianness() { + Endianness::Little => Ok(Self::from_le_bytes(bytes)), + Endianness::Big => Ok(Self::from_be_bytes(bytes)), + _ => Err(bincode::error::DecodeError::Other("unknown endianness")), + } + } + _ => Err(bincode::error::DecodeError::Other("unknown int encoding")), + } + } +} +#[cfg(feature = "bincode")] +bincode::impl_borrow_decode!(Felt); + +#[cfg(feature = "bincode")] +impl bincode::Encode for Felt { + fn encode( + &self, + encoder: &mut E, + ) -> Result<(), bincode::error::EncodeError> { + use bincode::{ + config::{Config, Endianness, IntEncoding}, + enc::write::Writer, + }; + + match encoder.config().int_encoding() { + IntEncoding::Variable => Err(bincode::error::EncodeError::Other( + "varint encoding is not supported for Felt", + )), + IntEncoding::Fixed => match encoder.config().endianness() { + Endianness::Big => encoder.writer().write(&self.to_be_bytes()), + Endianness::Little => encoder.writer().write(&self.to_le_bytes()), + _ => Err(bincode::error::EncodeError::Other("unknown endianness")), + }, + _ => Err(bincode::error::EncodeError::Other("unknown int encoding")), + } + } +} diff --git a/lib/starknet-verifier/Cargo.toml b/lib/starknet-verifier/Cargo.toml index cee8a5e9fa8..3b8ee0ae974 100644 --- a/lib/starknet-verifier/Cargo.toml +++ b/lib/starknet-verifier/Cargo.toml @@ -12,11 +12,15 @@ repository = { workspace = true } workspace = true [dependencies] -evm-storage-verifier.workspace = true -hex-literal.workspace = true -keccak-const = "0.2.0" -rlp.workspace = true -starknet-light-client-types = { workspace = true } -starknet-storage-verifier = { workspace = true } -thiserror.workspace = true -unionlabs-primitives.workspace = true +evm-storage-verifier = { workspace = true } +hex-literal = { workspace = true } +keccak-const = "0.2.0" +rlp = { workspace = true } +starknet-light-client-types = { workspace = true } +starknet-storage-verifier = { workspace = true } +starknet-types = { workspace = true } +thiserror = { workspace = true } +unionlabs-primitives = { workspace = true } + +[dev-dependencies] +ibc-union-spec = { workspace = true } diff --git a/lib/starknet-verifier/src/lib.rs b/lib/starknet-verifier/src/lib.rs index 89a2b420430..f23fc6cd2d9 100644 --- a/lib/starknet-verifier/src/lib.rs +++ b/lib/starknet-verifier/src/lib.rs @@ -1,5 +1,5 @@ -use starknet_light_client_types::{ClientStateV1, Header, storage_proof::MerkleNode}; -use starknet_storage_verifier::{Felt, Membership, PedersenHash}; +use starknet_light_client_types::{ClientStateV1, Header}; +use starknet_storage_verifier::{Membership, PedersenHash}; use unionlabs_primitives::{H256, U256}; #[derive(Debug, PartialEq, thiserror::Error)] @@ -17,8 +17,8 @@ pub enum Error { /// 1. Verify the L2 block hash in the L1 /// 2. Verify the L2 IBC account in the L2 contracts root pub fn verify_header( - client_state: ClientStateV1, - header: Header, + client_state: &ClientStateV1, + header: &Header, l1_state_root: H256, ) -> Result<(), Error> { // 1. @@ -33,7 +33,7 @@ pub fn verify_header( evm_storage_verifier::verify_storage_proof( header.l1_contract_account_proof.storage_root, L2_BLOCK_HASH_SLOT, - &rlp::encode(&U256::from_be_bytes(*header.l2_block.hash().as_be_bytes())), + &rlp::encode(&U256::from_be_bytes(header.l2_block.hash().to_be_bytes())), &header.l1_block_hash_proof, ) .map_err(Error::L1BlockHashProof)?; @@ -43,27 +43,7 @@ pub fn verify_header( header.l2_block.contracts_trie_root, client_state.ibc_contract_address, header.l2_ibc_contract_proof.contract_leaf_data.hash(), - header - .l2_ibc_contract_proof - .nodes - .into_iter() - .map(|node| match node { - MerkleNode::BinaryNode { left, right } => { - starknet_storage_verifier::MerkleNode::BinaryNode { - left: Felt::from_be_bytes(*left.get()).unwrap(), - right: Felt::from_be_bytes(*right.get()).unwrap(), - } - } - MerkleNode::EdgeNode { - path, - length, - child, - } => starknet_storage_verifier::MerkleNode::EdgeNode { - path: Felt::from_be_bytes(*path.get()).unwrap(), - length, - child: Felt::from_be_bytes(*child.get()).unwrap(), - }, - }), + &header.l2_ibc_contract_proof.nodes, ) .map_err(Error::L2IbcAccountProof)?; @@ -99,10 +79,8 @@ pub const L2_BLOCK_HASH_SLOT: U256 = { #[cfg(test)] mod tests { use hex_literal::hex; - use starknet_light_client_types::{ - StorageProof, - header::{AccountProof, ContractProof, L1DaMode, L2Block}, - }; + use starknet_light_client_types::header::{AccountProof, ContractProof, L1DaMode, L2Block}; + use starknet_types::{Felt, MerkleNode}; use unionlabs_primitives::H160; use super::*; @@ -119,46 +97,38 @@ mod tests { #[test] fn verify_header_works() { - let felt = |s| -> H256 { Felt::from_hex_str(s).unwrap().to_be_bytes().into() }; + let felt = |s| Felt::from_hex(s).unwrap(); let l2_block = L2Block { block_number: 4174049, - parent_block_hash: Felt::from_hex_str( - "6de8c5b146b0127e0a7842ffa97931850339016cc6fa2083f8817a8fef3260e", - ) - .unwrap(), - classes_trie_root: Felt::from_hex_str( + parent_block_hash: felt( + "0x6de8c5b146b0127e0a7842ffa97931850339016cc6fa2083f8817a8fef3260e", + ), + classes_trie_root: felt( "0x5e4ce3094b99bc894ff49cb80319f494384158834b69fc8b396f28b65386d49", - ) - .unwrap(), - contracts_trie_root: Felt::from_hex_str( + ), + contracts_trie_root: felt( "0x8eb9daf1010e9400d1549fe06ae61e8bb5b52a85b784a26e4353294b69db0f", - ) - .unwrap(), - sequencer_address: Felt::from_hex_str( - "1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", - ) - .unwrap(), + ), + sequencer_address: felt( + "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8", + ), block_timestamp: 1765453275, transaction_count: 6, events_count: 4 + 4 + 11 + 5 + 7 + 5, state_diff_length: 39, - state_diff_commitment: Felt::from_hex_str( + state_diff_commitment: felt( "0x700341e929df7d4cd196e97bfe84b13bc65a981d93229dfe249b2ab80a9be37", - ) - .unwrap(), - transactions_commitment: Felt::from_hex_str( + ), + transactions_commitment: felt( "0x7646130360d27c6e5de4d09f5e8141301a20aa66fc4c8c17189be631466a264", - ) - .unwrap(), - events_commitment: Felt::from_hex_str( + ), + events_commitment: felt( "0x31571f5901e6081cb7b10c64b44df4407f56b4de12c98d98aba7854c4f99cd4", - ) - .unwrap(), - receipts_commitment: Felt::from_hex_str( + ), + receipts_commitment: felt( "0x7a1f73a67ca072a6d7e61f80252ab5c1e4ec3fd916398b26e11fc92ce55a1e2", - ) - .unwrap(), + ), l1_gas_price: (0x42f7e3a4, 0x1eafaf65743a), l1_data_gas_price: (0xef9bf, 0x6dcb3ada5), l2_gas_price: (0x410a0, 0x1dcd65000), @@ -305,12 +275,13 @@ mod tests { nodes, contract_leaf_data: starknet_light_client_types::header::ContractLeafData { nonce: Felt::ZERO, - class_hash: Felt::from_hex_str( + // REVIEW: Should we verify the class in the classes trie? + class_hash: felt( "0x69b893a8b6e1bf94740e33d9584a01295510f3b51f024d9833b2acaf1be4045" - ).unwrap(), - storage_root: Felt::from_hex_str( + ), + storage_root: felt( "0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa" - ).unwrap(), + ), }, }, }; @@ -322,15 +293,13 @@ mod tests { let client_state = ClientStateV1 { chain_id: Felt::ZERO, latest_height: 1, - ibc_contract_address: Felt::from_hex_str( + l1_client_id: ClientId!(1), + ibc_contract_address: felt( "0x0712ae872c44ec2baee50a19191029e437811fb22de12afb3014642cbe33f09e", - ) - .unwrap() - .to_be_bytes() - .into(), + ), l1_contract_address: H160::new(hex!("c662c410C0ECf747543f5bA90660f6ABeBD9C8c4")), }; - verify_header(client_state, header, l1_state_root).unwrap(); + verify_header(&client_state, &header, l1_state_root).unwrap(); } } diff --git a/voyager/plugins/event-source/starknet/src/call.rs b/voyager/plugins/event-source/starknet/src/call.rs index d1cf68963f6..71d56f51571 100644 --- a/voyager/plugins/event-source/starknet/src/call.rs +++ b/voyager/plugins/event-source/starknet/src/call.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeSet; - use enumorph::Enumorph; use macros::model; use unionlabs::primitives::H256;