diff --git a/Cargo.lock b/Cargo.lock index 1863416a254..27bf3ab6b41 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", @@ -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]] @@ -4867,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" @@ -5517,6 +5536,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 +5569,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 +5663,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", ] @@ -5724,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" @@ -7388,6 +7455,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" @@ -7850,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", @@ -8100,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" @@ -8131,19 +8213,42 @@ dependencies = [ "regex", ] +[[package]] +name = "lambdaworks-crypto" +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", + "lambdaworks-math 0.13.0", "rand 0.8.5", "rand_chacha 0.3.1", + "serde", "sha2 0.10.9", "sha3", ] +[[package]] +name = "lambdaworks-math" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "lambdaworks-math" version = "0.13.0" @@ -8154,6 +8259,8 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "rand 0.8.5", + "serde", + "serde_json", ] [[package]] @@ -8767,7 +8874,7 @@ dependencies = [ "smallvec", "tagptr", "thiserror 1.0.69", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -9322,7 +9429,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -9882,7 +9989,7 @@ dependencies = [ "glob", "opentelemetry", "percent-encoding", - "rand 0.9.1", + "rand 0.9.2", "thiserror 2.0.12", ] @@ -10255,6 +10362,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" @@ -10707,7 +10826,7 @@ checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash 0.7.0", "impl-codec 0.5.1", - "impl-serde", + "impl-serde 0.3.2", "uint", ] @@ -10720,6 +10839,7 @@ dependencies = [ "fixed-hash 0.8.0", "impl-codec 0.6.0", "impl-rlp", + "impl-serde 0.4.0", "uint", ] @@ -11034,7 +11154,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", @@ -11143,9 +11263,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", @@ -11797,7 +11917,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", @@ -12094,6 +12214,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 +12374,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 +12686,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 +13463,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 0.2.0", +] + +[[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 +13550,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", @@ -13325,7 +13558,7 @@ dependencies = [ "rfc6979 0.4.0", "sha2 0.10.9", "starknet-curve", - "starknet-types-core", + "starknet-types-core 0.2.0", "zeroize", ] @@ -13335,33 +13568,137 @@ 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-storage-verifier" +name = "starknet-light-client" +version = "0.0.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", + "pathfinder-crypto", + "serde", + "starknet-core", + "starknet-types-core 1.0.0", + "unionlabs", +] + +[[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-utils", "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" +dependencies = [ + "bitvec 1.0.1", + "pathfinder-crypto", + "serde", + "serde_json", + "starknet-storage-verifier", + "thiserror 2.0.12", ] [[package]] name = "starknet-types-core" -version = "0.2.4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa3d91e38f091dbc543d33589eb7716bed2a8eb1c20879e484561977832b60a" +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", + "serde", + "zeroize", +] + +[[package]] +name = "starknet-types-core" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90d23b1bc014ee4cce40056ab3114bcbcdc2dbc1e845bbfb1f8bd0bab63507d4" +checksum = "8a12690813e587969cb4a9e7d8ebdb069d4bb7ec8d03275c5f719310c8e1f07c" dependencies = [ "blake2", "digest 0.10.7", - "lambdaworks-crypto", - "lambdaworks-math", + "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", +] + +[[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]] @@ -15028,7 +15365,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid", + "uuid 1.16.0", ] [[package]] @@ -15277,7 +15614,7 @@ dependencies = [ "http 1.3.1", "httparse", "log", - "rand 0.9.1", + "rand 0.9.2", "rustls 0.23.26", "rustls-pki-types", "sha1", @@ -15893,6 +16230,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" @@ -15900,7 +16247,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]] @@ -16886,6 +17233,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..2f05863c03b 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", @@ -295,6 +296,9 @@ members = [ "cosmwasm/pausable", "cosmwasm/gatekeeper", "cosmwasm/proxy-account-factory", + "lib/starknet-light-client-types", + "lib/starknet-verifier", + "lib/starknet-light-client", ] [workspace.package] @@ -419,6 +423,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 } @@ -528,8 +534,8 @@ 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 strum = { version = "0.27", default-features = false } subtle-encoding = { version = "0.5.1", 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..280220ac9fa --- /dev/null +++ b/lib/starknet-light-client-types/Cargo.toml @@ -0,0 +1,36 @@ +[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 } +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 } + +[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..51eeebaa9d3 --- /dev/null +++ b/lib/starknet-light-client-types/src/client_state.rs @@ -0,0 +1,27 @@ +use pathfinder_crypto::Felt; +use unionlabs::primitives::{H160, H256}; + +#[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..1a7386a9f68 --- /dev/null +++ b/lib/starknet-light-client-types/src/header.rs @@ -0,0 +1,220 @@ +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; + +#[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 Header { + pub l1_height: u64, + pub l1_contract_account_proof: AccountProof, + pub l1_block_hash_proof: Vec, + pub l2_block: L2Block, + pub l2_ibc_contract_proof: ContractProof, +} + +#[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 L2Block { + pub block_number: u64, + pub parent_block_hash: Felt, + pub contracts_trie_root: Felt, + pub classes_trie_root: Felt, + pub sequencer_address: Felt, + // SECONDS + 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)] +#[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, +} + +#[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) -> 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_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" + ) + .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: L1DaMode::Blob, + protocol_version: "0.14.0".to_owned(), + }; + + dbg!(&block); + + assert_eq!( + block.hash(), + Felt::from_hex_str("0x366cae7718ded291ef9c5f4c2aba8c3c27baa0e563fd64ba72fe51c2abc4675") + .unwrap() + ); +} 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/storage_proof.rs b/lib/starknet-light-client-types/src/storage_proof.rs new file mode 100644 index 00000000000..9f9f088e8e4 --- /dev/null +++ b/lib/starknet-light-client-types/src/storage_proof.rs @@ -0,0 +1,25 @@ +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 { + 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 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 fb5f728d09b..c03e29f125a 100644 --- a/lib/starknet-storage-verifier/Cargo.toml +++ b/lib/starknet-storage-verifier/Cargo.toml @@ -12,10 +12,16 @@ repository = { workspace = true } workspace = true [dependencies] -serde = { workspace = true, features = ["derive"] } -serde-utils = { workspace = true } -starknet-crypto = { workspace = true } +bitvec = { workspace = true } +pathfinder-crypto = "0.21.3" +serde = { workspace = true, optional = true, features = ["derive"] } +thiserror.workspace = true [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 22ed6b2e3ea..cdaeadddfe4 100644 --- a/lib/starknet-storage-verifier/src/lib.rs +++ b/lib/starknet-storage-verifier/src/lib.rs @@ -1,46 +1,78 @@ +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 starknet_crypto::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), -} - -/// 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. + 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, + }, } -/// 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, +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}; - use starknet_crypto::Felt; pub fn serialize(data: &Felt, serializer: S) -> Result where @@ -49,7 +81,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 +91,439 @@ 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)) } } } + +#[derive(Debug, PartialEq)] +pub enum Membership { + Membership, + NonMembership, +} + +#[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( + mut root: Felt, + key: Felt, + value: Felt, + proof: impl IntoIterator, +) -> Result { + let mut proof = proof + .into_iter() + .map(|n| (n.hash::(), n)) + .collect::>(); + + // 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(&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 + root = match remaining_path[0] { + false => left, + true => right, + }; + + // Advance by a single bit + remaining_path = &remaining_path[1..]; + } + 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}"); + + 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); + return Ok(Membership::NonMembership); + } + + // Set the next hash to the child's hash + root = child; + + // Advance by the whole edge path + remaining_path = &remaining_path[length as usize..]; + } + } + } + + if root != value { + return Err(Error::ValueMismatch { + expected: value, + found: root, + }); + } + + 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 root = + Felt::from_hex_str("0x2c6e3ddcdcf9bcd4b9e01c4b94408b6cf8b82ca9a1b40d808612483278b5afb") + .unwrap(); + + let res = verify_proof::(root, key, value, proof).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 root = + Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .unwrap(); + + let res = verify_proof::(root, key, value, proof).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 root = + Felt::from_hex_str("0x2c8771df74e758b1fed285eef0cd07cb84b55abfabfb0d6a0f1b7b3aff761fa") + .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-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/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..31f04ae1713 --- /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..cdeb6353cf8 --- /dev/null +++ b/voyager/plugins/event-source/starknet/src/main.rs @@ -0,0 +1,1247 @@ +// #![warn(clippy::unwrap_used)] + +use std::{cmp::Ordering, collections::VecDeque, num::NonZeroU32}; + +use cainome_cairo_serde::CairoSerde; +use jsonrpsee::{Extensions, core::async_trait}; +use serde::{Deserialize, Serialize}; +use starknet::{ + core::types::{BlockId, EventFilter, Felt}, + macros::selector, + providers::{JsonRpcClient, Provider, Url, jsonrpc::HttpTransport}, +}; +use tracing::{info, instrument, warn}; +use unionlabs::{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::{Data, EventProvableHeight}, + }, + plugin::Plugin, + primitives::ChainId, + rpc::{PluginServer, RpcError, RpcResult, types::PluginInfo}, + vm::{Op, call, conc, pass::PassResult, seq}, +}; + +use crate::{ + call::{FetchBlock, FetchBlocks, MakeChainEvent, ModuleCall}, + ibc_events::CairoIbcEvent, +}; + +pub mod ibc_events; + +pub mod call; + +#[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!() + } +}