From a577c123480dfbcb8e8b0ed1719cc8c06c50c7a6 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Wed, 4 Dec 2024 23:07:15 +0100 Subject: [PATCH 01/49] current state minus cosmwasm --- Cargo.lock | 384 +++++++++++++++++- Cargo.toml | 46 ++- packages/ethereum-light-client/Cargo.toml | 33 ++ .../ethereum-light-client/src/client_state.rs | 39 ++ packages/ethereum-light-client/src/config.rs | 382 +++++++++++++++++ .../src/consensus_state.rs | 66 +++ packages/ethereum-light-client/src/error.rs | 132 ++++++ packages/ethereum-light-client/src/lib.rs | 14 + .../ethereum-light-client/src/membership.rs | 225 ++++++++++ .../src/test/bls_verifier.rs | 48 +++ .../src/test/client_update_ack_0.json | 177 ++++++++ .../src/test/client_update_recv_0.json | 177 ++++++++ .../src/test/commitment_proof_fixture.json | 54 +++ .../src/test/fixtures.rs | 33 ++ .../test/initial_client_state_fixture.json | 32 ++ .../test/initial_consensus_state_fixture.json | 8 + .../ethereum-light-client/src/test/mod.rs | 2 + packages/ethereum-light-client/src/trie.rs | 136 +++++++ .../ethereum-light-client/src/types/bls.rs | 33 ++ .../ethereum-light-client/src/types/domain.rs | 39 ++ .../ethereum-light-client/src/types/fork.rs | 10 + .../src/types/fork_data.rs | 25 ++ .../src/types/fork_parameters.rs | 32 ++ .../ethereum-light-client/src/types/height.rs | 8 + .../src/types/light_client.rs | 108 +++++ .../ethereum-light-client/src/types/mod.rs | 11 + .../src/types/signing_data.rs | 21 + .../src/types/storage_proof.rs | 13 + .../src/types/sync_committee.rs | 126 ++++++ .../src/types/wrappers.rs | 185 +++++++++ packages/ethereum-light-client/src/verify.rs | 371 +++++++++++++++++ packages/ethereum-trie-db/Cargo.toml | 24 ++ packages/ethereum-trie-db/src/error.rs | 20 + packages/ethereum-trie-db/src/lib.rs | 3 + packages/ethereum-trie-db/src/trie_db.rs | 74 ++++ packages/ethereum-trie-db/src/types.rs | 291 +++++++++++++ packages/ethereum-utils/Cargo.toml | 10 + packages/ethereum-utils/src/base64.rs | 259 ++++++++++++ packages/ethereum-utils/src/ensure.rs | 3 + packages/ethereum-utils/src/hex.rs | 61 +++ packages/ethereum-utils/src/lib.rs | 4 + packages/ethereum-utils/src/slot.rs | 19 + .../Cargo.toml | 0 .../README.md | 0 .../build.rs | 0 .../src/lib.rs | 0 .../src/programs.rs | 0 .../src/prover.rs | 0 .../Cargo.toml | 0 .../README.md | 0 .../src/eth.rs | 0 .../src/lib.rs | 0 .../src/light_block.rs | 0 .../src/merkle.rs | 0 .../src/rpc.rs | 0 55 files changed, 3710 insertions(+), 28 deletions(-) create mode 100644 packages/ethereum-light-client/Cargo.toml create mode 100644 packages/ethereum-light-client/src/client_state.rs create mode 100644 packages/ethereum-light-client/src/config.rs create mode 100644 packages/ethereum-light-client/src/consensus_state.rs create mode 100644 packages/ethereum-light-client/src/error.rs create mode 100644 packages/ethereum-light-client/src/lib.rs create mode 100644 packages/ethereum-light-client/src/membership.rs create mode 100644 packages/ethereum-light-client/src/test/bls_verifier.rs create mode 100644 packages/ethereum-light-client/src/test/client_update_ack_0.json create mode 100644 packages/ethereum-light-client/src/test/client_update_recv_0.json create mode 100644 packages/ethereum-light-client/src/test/commitment_proof_fixture.json create mode 100644 packages/ethereum-light-client/src/test/fixtures.rs create mode 100644 packages/ethereum-light-client/src/test/initial_client_state_fixture.json create mode 100644 packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json create mode 100644 packages/ethereum-light-client/src/test/mod.rs create mode 100644 packages/ethereum-light-client/src/trie.rs create mode 100644 packages/ethereum-light-client/src/types/bls.rs create mode 100644 packages/ethereum-light-client/src/types/domain.rs create mode 100644 packages/ethereum-light-client/src/types/fork.rs create mode 100644 packages/ethereum-light-client/src/types/fork_data.rs create mode 100644 packages/ethereum-light-client/src/types/fork_parameters.rs create mode 100644 packages/ethereum-light-client/src/types/height.rs create mode 100644 packages/ethereum-light-client/src/types/light_client.rs create mode 100644 packages/ethereum-light-client/src/types/mod.rs create mode 100644 packages/ethereum-light-client/src/types/signing_data.rs create mode 100644 packages/ethereum-light-client/src/types/storage_proof.rs create mode 100644 packages/ethereum-light-client/src/types/sync_committee.rs create mode 100644 packages/ethereum-light-client/src/types/wrappers.rs create mode 100644 packages/ethereum-light-client/src/verify.rs create mode 100644 packages/ethereum-trie-db/Cargo.toml create mode 100644 packages/ethereum-trie-db/src/error.rs create mode 100644 packages/ethereum-trie-db/src/lib.rs create mode 100644 packages/ethereum-trie-db/src/trie_db.rs create mode 100644 packages/ethereum-trie-db/src/types.rs create mode 100644 packages/ethereum-utils/Cargo.toml create mode 100644 packages/ethereum-utils/src/base64.rs create mode 100644 packages/ethereum-utils/src/ensure.rs create mode 100644 packages/ethereum-utils/src/hex.rs create mode 100644 packages/ethereum-utils/src/lib.rs create mode 100644 packages/ethereum-utils/src/slot.rs rename packages/{prover => sp1-ics07-tendermint-prover}/Cargo.toml (100%) rename packages/{prover => sp1-ics07-tendermint-prover}/README.md (100%) rename packages/{prover => sp1-ics07-tendermint-prover}/build.rs (100%) rename packages/{prover => sp1-ics07-tendermint-prover}/src/lib.rs (100%) rename packages/{prover => sp1-ics07-tendermint-prover}/src/programs.rs (100%) rename packages/{prover => sp1-ics07-tendermint-prover}/src/prover.rs (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/Cargo.toml (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/README.md (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/src/eth.rs (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/src/lib.rs (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/src/light_block.rs (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/src/merkle.rs (100%) rename packages/{utils => sp1-ics07-tendermint-utils}/src/rpc.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 20756acd..2b2af159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,7 +122,7 @@ dependencies = [ "alloy-primitives 0.8.14", "alloy-rlp", "alloy-serde", - "alloy-trie", + "alloy-trie 0.7.4", "auto_impl", "c-kzg", "derive_more 1.0.0", @@ -231,6 +231,8 @@ dependencies = [ "alloy-serde", "c-kzg", "derive_more 1.0.0", + "ethereum_ssz", + "ethereum_ssz_derive", "once_cell", "serde", "sha2 0.10.8", @@ -374,7 +376,7 @@ dependencies = [ "ruint", "rustc-hash 2.1.0", "serde", - "sha3", + "sha3 0.10.8", "tiny-keccak", ] @@ -525,6 +527,23 @@ dependencies = [ "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-beacon" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c8db5fb70d2fece7bc1cd5adf42e72fc8a23547adeff8f558d9063f1e7788c" +dependencies = [ + "alloy-eips", + "alloy-primitives 0.8.14", + "alloy-rpc-types-engine", + "alloy-serde", + "ethereum_ssz", + "ethereum_ssz_derive", + "serde", + "serde_with", + "thiserror 2.0.4", +] + [[package]] name = "alloy-rpc-types-engine" version = "0.7.2" @@ -537,6 +556,8 @@ dependencies = [ "alloy-rlp", "alloy-serde", "derive_more 1.0.0", + "ethereum_ssz", + "ethereum_ssz_derive", "serde", "strum", ] @@ -806,6 +827,22 @@ dependencies = [ "ws_stream_wasm", ] +[[package]] +name = "alloy-trie" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a46c9c4fdccda7982e7928904bd85fe235a0404ee3d7e197fff13d61eac8b4f" +dependencies = [ + "alloy-primitives 0.8.14", + "alloy-rlp", + "derive_more 1.0.0", + "hashbrown 0.14.5", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "alloy-trie" version = "0.7.4" @@ -821,6 +858,11 @@ dependencies = [ "tracing", ] +[[package]] +name = "amcl" +version = "0.3.0" +source = "git+https://github.com/Snowfork/milagro_bls?rev=bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095#bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -1317,6 +1359,18 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -1335,6 +1389,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bls12_381" version = "0.7.1" @@ -1382,6 +1445,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "bytemuck" version = "1.20.0" @@ -1604,7 +1673,7 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "thiserror 1.0.69", ] @@ -1969,6 +2038,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" @@ -2143,9 +2221,9 @@ dependencies = [ "k256", "log", "rand", - "rlp", + "rlp 0.5.2", "serde", - "sha3", + "sha3 0.10.8", "zeroize", ] @@ -2203,7 +2281,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "thiserror 1.0.69", "uuid 0.8.2", ] @@ -2220,9 +2298,9 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3", + "sha3 0.10.8", "thiserror 1.0.69", - "uint", + "uint 0.9.5", ] [[package]] @@ -2234,12 +2312,57 @@ dependencies = [ "crunchy", "fixed-hash", "impl-codec", - "impl-rlp", + "impl-rlp 0.3.0", "impl-serde", "scale-info", "tiny-keccak", ] +[[package]] +name = "ethereum-light-client" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.8.14", + "alloy-rlp", + "alloy-rpc-types-beacon", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-trie 0.5.3", + "base64 0.22.1", + "ethereum-trie-db", + "ethereum-utils", + "ethereum_ssz", + "ethereum_ssz_derive", + "milagro_bls", + "serde", + "serde_json", + "serde_with", + "sha2 0.10.8", + "thiserror 2.0.4", + "tree_hash", + "tree_hash_derive", + "typenum", +] + +[[package]] +name = "ethereum-trie-db" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.8.14", + "ethereum-utils", + "hash-db", + "hash256-std-hasher", + "memory-db", + "primitive-types 0.13.1", + "rlp 0.6.1", + "rlp-derive 0.2.0", + "serde", + "sha3 0.8.2", + "thiserror 2.0.4", + "trie-db", +] + [[package]] name = "ethereum-types" version = "0.14.1" @@ -2249,11 +2372,72 @@ dependencies = [ "ethbloom", "fixed-hash", "impl-codec", - "impl-rlp", + "impl-rlp 0.3.0", "impl-serde", - "primitive-types", + "primitive-types 0.12.2", "scale-info", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "ethereum-utils" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.8.14", + "base64 0.22.1", + "serde", +] + +[[package]] +name = "ethereum_hashing" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" +dependencies = [ + "cpufeatures", + "ring 0.17.8", + "sha2 0.10.8", +] + +[[package]] +name = "ethereum_serde_utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70cbccfccf81d67bff0ab36e591fa536c8a935b078a7b0e58c1d00d418332fc9" +dependencies = [ + "alloy-primitives 0.8.14", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfbba28f4f3f32d92c06a64f5bf6c4537b5d4e21f28c689bd2bbaecfea4e0d3e" +dependencies = [ + "alloy-primitives 0.8.14", + "derivative", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d37845ba7c16bf4be8be4b5786f03a2ba5f2fda0d7f9e7cb2282f69cff420d7" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -2358,7 +2542,7 @@ dependencies = [ "once_cell", "open-fastrlp", "rand", - "rlp", + "rlp 0.5.2", "serde", "serde_json", "strum", @@ -2716,6 +2900,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2877,6 +3070,21 @@ dependencies = [ "rayon", ] +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -3479,7 +3687,7 @@ dependencies = [ "ripemd", "serde", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", ] [[package]] @@ -3642,7 +3850,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" dependencies = [ - "rlp", + "rlp 0.5.2", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp 0.6.1", ] [[package]] @@ -3996,12 +4213,31 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memory-db" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] + [[package]] name = "memuse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" +[[package]] +name = "milagro_bls" +version = "1.5.0" +source = "git+https://github.com/Snowfork/milagro_bls?rev=bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095#bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095" +dependencies = [ + "amcl", + "rand", + "zeroize", +] + [[package]] name = "mime" version = "0.3.17" @@ -4271,7 +4507,10 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95f06be0417d97f81fe4e5c86d7d01b392655a9cac9c19a848aa033e18937b23" dependencies = [ + "alloy-rlp", "const-hex", + "proptest", + "serde", "smallvec", ] @@ -4290,6 +4529,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -4909,10 +5154,21 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", + "impl-rlp 0.3.0", "impl-serde", "scale-info", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-rlp 0.4.0", + "uint 0.10.0", ] [[package]] @@ -5438,7 +5694,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", - "rlp-derive", + "rlp-derive 0.1.0", + "rustc-hex", +] + +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", "rustc-hex", ] @@ -5453,6 +5719,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rlp-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "652db34deaaa57929e10ca18e5454a32cb0efc351ae80d320334bbf907b908b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "rrs-succinct" version = "0.1.0" @@ -5478,10 +5755,10 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "proptest", "rand", - "rlp", + "rlp 0.5.2", "ruint-macro", "serde", "valuable", @@ -6006,7 +6283,7 @@ dependencies = [ "cfg-if", "cpufeatures", "digest 0.9.0", - "opaque-debug", + "opaque-debug 0.3.1", ] [[package]] @@ -6019,6 +6296,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" +dependencies = [ + "block-buffer 0.7.3", + "byte-tools", + "digest 0.8.1", + "keccak", + "opaque-debug 0.2.3", +] + [[package]] name = "sha3" version = "0.10.8" @@ -6105,6 +6395,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "snowbridge-amcl" @@ -7487,6 +7780,41 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tree_hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373495c23db675a5192de8b610395e1bec324d596f9e6111192ce903dc11403a" +dependencies = [ + "alloy-primitives 0.8.14", + "ethereum_hashing", + "smallvec", +] + +[[package]] +name = "tree_hash_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0857056ca4eb5de8c417309be42bcff6017b47e86fbaddde609b4633f66061e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "trie-db" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c992b4f40c234a074d48a757efeabb1a6be88af84c0c23f7ca158950cb0ae7f" +dependencies = [ + "hash-db", + "log", + "rustc-hex", + "smallvec", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -7559,6 +7887,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -8283,6 +8623,6 @@ dependencies = [ "rand", "serde", "sha2 0.10.8", - "sha3", + "sha3 0.10.8", "subtle", ] diff --git a/Cargo.toml b/Cargo.toml index ff10f5cb..92b8b95c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,21 +18,26 @@ keywords = ["cosmos", "ibc", "sp1", "tendermint", "ethereum", "bridge", "solid [workspace.dependencies] ibc-eureka-solidity-types = { path = "packages/solidity", default-features = false } ibc-eureka-relayer-lib = { path = "packages/relayer-lib", default-features = false } -sp1-ics07-tendermint-prover = { path = "packages/prover", default-features = false } -sp1-ics07-tendermint-utils = { path = "packages/utils", default-features = false } +sp1-ics07-tendermint-prover = { path = "packages/sp1-ics07-tendermint-prover", default-features = false } +sp1-ics07-tendermint-utils = { path = "packages/sp1-ics07-tendermint-utils", default-features = false } sp1-ics07-tendermint-update-client = { path = "programs/sp1-programs/update-client", default-features = false } sp1-ics07-tendermint-membership = { path = "programs/sp1-programs/membership", default-features = false } +ethereum-trie-db = { path = "packages/ethereum-trie-db", default-features = false } +ethereum-utils = { path = "packages/ethereum-utils", default-features = false } +ethereum-light-client = { path = "packages/ethereum-light-client", default-features = false } -serde = { version = "1.0", default-features = false } -serde_json = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } serde_cbor = { version = "0.11", default-features = false } -serde_with = { version = "3.11", default-features = false } +serde_with = { version = "3.11", default-features = false, features = ["alloc"] } hex = { version = "0.4", default-features = false } +base64 = { version = "0.22", default-features = false } prost = { version = "0.13", default-features = false } bincode = { version = "1.3", default-features = false } subtle-encoding = { version = "0.5", default-features = false } sha2 = { version = "0.10", default-features = false } +sha3 = { version = "0.8", default-features = false } tokio = { version = "1.0", default-features = false } axum = { version = "0.7", default-features = false } @@ -48,6 +53,7 @@ futures = { version = "0.3", default-features = false } clap = { version = "4.5", default-features = false, features = ["std"] } # std feature is required for clap time = { version = "0.3", default-features = false } dotenv = { version = "0.15", default-features = false } +thiserror = "2.0" tendermint = { version = "0.40", default-features = false } tendermint-rpc = { version = "0.40", default-features = false } @@ -68,12 +74,40 @@ ibc-client-tendermint-types = { version = "0.56", default-features = false } alloy = { version = "0.7", default-features = false } alloy-contract = { version = "0.7", default-features = false } alloy-sol-types = { version = "0.8", default-features = false } -alloy-primitives = { version = "0.8", default-features = false } +alloy-primitives = { version = "0.8", default-features = false, features = ["serde"] } +alloy-trie = { version = "0.5", features = ["serde"] } # TODO: default-features = false, +alloy-serde = "0.7" +alloy-rlp = "0.3" +alloy-rpc-types-eth = { version = "0.7", default-features = false, features = ["serde"] } +alloy-rpc-types-beacon = { version = "0.7", default-features = false, features = ["ssz"]} +alloy-rpc-types-engine = { version = "0.7", default-features = false, features = ["ssz"]} sp1-sdk = { version = "3.3", default-features = false } sp1-zkvm = { version = "3.3", default-features = false } sp1-helper = { version = "3.3", default-features = false } +# The dependencies below are maintained by Sigma Prime (for use in Lighthouse (and the broader Ethereum ecosystem)) +ethereum_ssz = "0.8" +ethereum_ssz_derive = "0.8" +tree_hash = "0.8" +tree_hash_derive = "0.8" + +# The dependencies below are maintained by Parity Tech +trie-db = "0.29" +hash-db = "0.16" +memory-db = "0.32" +hash256-std-hasher = "0.15" +rlp = "0.6" +rlp-derive = "0.2" +primitive-types = { version = "0.13", default-features = false, features = ["rlp"] } + +# From union (might be removed if they are replaced by something more standard) +typenum = { version = "1.17.0", default-features = false } + +# dev-dependencies +cw-multi-test = "2.2.0" +milagro_bls = { git = "https://github.com/Snowfork/milagro_bls", rev = "bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095", default-features = false } # Only used for testing, not to be used in production! + [patch.crates-io] sha2-v0-9-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.9.9" } sha2-v0-10-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.10.8" } diff --git a/packages/ethereum-light-client/Cargo.toml b/packages/ethereum-light-client/Cargo.toml new file mode 100644 index 000000000..f4065ad6 --- /dev/null +++ b/packages/ethereum-light-client/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "ethereum-light-client" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +[dependencies] +ethereum-trie-db = { workspace = true } +ethereum-utils = { workspace = true } + +alloy-primitives = { workspace = true } +alloy-rpc-types-eth = { workspace = true } +alloy-rpc-types-beacon = { workspace = true } +alloy-rpc-types-engine = { workspace = true } +alloy-trie = { workspace = true } +alloy-serde = { workspace = true } +alloy-rlp = { workspace = true } +ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } +tree_hash = { workspace = true } +tree_hash_derive = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +serde_with = { workspace = true } + +# From union (might be removed if they are replaced by something more standard) +typenum = { workspace = true, features = ["const-generics", "no_std"] } + +[dev-dependencies] +milagro_bls = { workspace = true } +base64 = { workspace = true } diff --git a/packages/ethereum-light-client/src/client_state.rs b/packages/ethereum-light-client/src/client_state.rs new file mode 100644 index 000000000..284640c5 --- /dev/null +++ b/packages/ethereum-light-client/src/client_state.rs @@ -0,0 +1,39 @@ +use alloy_primitives::{Address, B256, U256}; +use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; + +use crate::types::{fork_parameters::ForkParameters, height::Height}; + +#[serde_as] +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct ClientState { + #[serde_as(as = "DisplayFromStr")] + pub chain_id: u64, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub genesis_validators_root: B256, + pub min_sync_committee_participants: u64, // TODO: Needs be added to e2e tests + pub genesis_time: u64, + pub fork_parameters: ForkParameters, + pub seconds_per_slot: u64, + pub slots_per_epoch: u64, + pub epochs_per_sync_committee_period: u64, + pub latest_slot: u64, + // TODO: Should this be frozen_slot? + pub frozen_height: Height, + #[serde(with = "ethereum_utils::base64::uint256")] + pub ibc_commitment_slot: U256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub ibc_contract_address: Address, +} + +impl From> for ClientState { + fn from(value: Vec) -> Self { + serde_json::from_slice(&value).unwrap() + } +} + +impl From for Vec { + fn from(value: ClientState) -> Self { + serde_json::to_vec(&value).unwrap() + } +} diff --git a/packages/ethereum-light-client/src/config.rs b/packages/ethereum-light-client/src/config.rs new file mode 100644 index 000000000..fe5477d8 --- /dev/null +++ b/packages/ethereum-light-client/src/config.rs @@ -0,0 +1,382 @@ +use core::{ + fmt::{self, Debug}, + str::FromStr, +}; + +use serde::{Deserialize, Serialize}; +use typenum::{NonZero, Unsigned}; + +use alloy_primitives::FixedBytes; + +/// Minimal config. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Minimal; + +/// Mainnet config. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Mainnet; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PresetBaseKind { + Minimal, + Mainnet, +} + +impl fmt::Display for PresetBaseKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + PresetBaseKind::Minimal => "minimal", + PresetBaseKind::Mainnet => "mainnet", + }) + } +} + +impl FromStr for PresetBaseKind { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "minimal" => Ok(Self::Minimal), + "mainnet" => Ok(Self::Mainnet), + _ => Err(s.to_string()), + } + } +} + +// https://github.com/rust-lang/rust/issues/35853#issuecomment-415993963 +macro_rules! with_dollar_sign { + ($($body:tt)*) => { + macro_rules! __with_dollar_sign { $($body)* } + __with_dollar_sign!($); + } +} + +macro_rules! consts_traits { + ($($CONST:ident $(,)?),+) => { + $( + #[allow(non_camel_case_types)] + pub trait $CONST: Send + Sync + Unpin + 'static { + // Extra traits are required because the builtin derives bound all generic + // types unconditionally + type $CONST: Unsigned + NonZero + Debug + Clone + PartialEq + Eq + Send + Sync + Unpin + Default; + } + )+ + + pub trait ChainSpec: 'static + Debug + Clone + PartialEq + Eq + Default + Send + Sync + Unpin + Default + $($CONST+)+ { + const PRESET: preset::Preset; + // const PRESET_BASE_KIND: PresetBaseKind; + + type PERIOD: 'static + Unsigned; + } + + with_dollar_sign! { + ($d:tt) => { + // TODO: Keep an eye on this issue https://github.com/rust-lang/rust/issues/98291, as it might resolve an issue with macro_export-ing this macro (currently it is only available in this crate) + macro_rules! mk_chain_spec { + ($d T:ident is $d preset:path) => { + $( + impl $d crate::config:: $CONST for $d T { + #[allow(non_camel_case_types)] + type $CONST = $d crate::typenum::U<{ $d preset.$CONST }>; + } + )* + + impl $d crate::config::ChainSpec for $d T { + const PRESET: $d crate::config::preset::Preset = $d preset; + // const PRESET_BASE_KIND: PresetBaseKind = PresetBaseKind::Mainnet; + + type PERIOD = $d crate::typenum::Prod< + ::EPOCHS_PER_SYNC_COMMITTEE_PERIOD, + ::SLOTS_PER_EPOCH, + >; + } + }; + } + } + } + }; +} + +consts_traits![ + // Misc + DEPOSIT_CONTRACT_TREE_DEPTH, + MAX_VALIDATORS_PER_COMMITTEE, + // Time parameters + SECONDS_PER_SLOT, + SLOTS_PER_EPOCH, + // Max operations per block + MAX_PROPOSER_SLASHINGS, + MAX_ATTESTER_SLASHINGS, + MAX_ATTESTATIONS, + MAX_DEPOSITS, + MAX_VOLUNTARY_EXITS, + MAX_BLS_TO_EXECUTION_CHANGES, + MAX_BLOB_COMMITMENTS_PER_BLOCK, + // Execution + MAX_BYTES_PER_TRANSACTION, + MAX_TRANSACTIONS_PER_PAYLOAD, + BYTES_PER_LOGS_BLOOM, + MAX_EXTRA_DATA_BYTES, + MAX_WITHDRAWALS_PER_PAYLOAD, + // Sync committee + SYNC_COMMITTEE_SIZE, + EPOCHS_PER_SYNC_COMMITTEE_PERIOD, + // Sync protocol + MIN_SYNC_COMMITTEE_PARTICIPANTS, + UPDATE_TIMEOUT, +]; + +self::mk_chain_spec!(Minimal is preset::MINIMAL); +self::mk_chain_spec!(Mainnet is preset::MAINNET); + +pub(crate) use mk_chain_spec; + +use crate::types::{fork::Fork, fork_parameters::ForkParameters, wrappers::Version}; + +/// Values that are constant across all configurations. +pub mod consts { + /// + #[must_use] + pub const fn get_subtree_index(idx: u64) -> u64 { + idx % 2_u64.pow(idx.ilog2()) + } + + /// Convenience function safely to call [`u64::ilog2`] and convert the result into a usize. + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + #[must_use] + pub const fn floorlog2(n: u64) -> usize { + // conversion is safe since usize is either 32 or 64 bits as per cfg above + n.ilog2() as usize + } + + // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#constants + // REVIEW: Is it possible to implement get_generalized_index in const rust? + + // https://github.com/ethereum/consensus-specs/blob/dev/ssz/merkle-proofs.md + /// `get_generalized_index(BeaconState, "finalized_checkpoint", "root")` + pub const FINALIZED_ROOT_INDEX: u64 = 105; + /// `get_generalized_index(BeaconState, "current_sync_committee")` + pub const CURRENT_SYNC_COMMITTEE_INDEX: u64 = 54; + /// `get_generalized_index(BeaconState, "next_sync_committee")` + pub const NEXT_SYNC_COMMITTEE_INDEX: u64 = 55; + /// `get_generalized_index(BeaconBlockBody, "execution_payload")` + pub const EXECUTION_PAYLOAD_INDEX: u64 = 25; +} + +pub mod preset { + #[allow(non_snake_case)] + #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] + pub struct Preset { + /// Misc + /// --------------------------------------------------------------- + pub DEPOSIT_CONTRACT_TREE_DEPTH: usize, + pub MAX_VALIDATORS_PER_COMMITTEE: usize, + + /// Time parameters + /// --------------------------------------------------------------- + pub SECONDS_PER_SLOT: usize, + pub SLOTS_PER_EPOCH: usize, + + /// Max operations per block + /// --------------------------------------------------------------- + pub MAX_PROPOSER_SLASHINGS: usize, + pub MAX_ATTESTER_SLASHINGS: usize, + pub MAX_ATTESTATIONS: usize, + pub MAX_DEPOSITS: usize, + pub MAX_VOLUNTARY_EXITS: usize, + pub MAX_BLS_TO_EXECUTION_CHANGES: usize, + pub MAX_BLOB_COMMITMENTS_PER_BLOCK: usize, + + /// Execution + /// --------------------------------------------------------------- + pub MAX_BYTES_PER_TRANSACTION: usize, + pub MAX_TRANSACTIONS_PER_PAYLOAD: usize, + pub BYTES_PER_LOGS_BLOOM: usize, + pub MAX_EXTRA_DATA_BYTES: usize, + pub MAX_WITHDRAWALS_PER_PAYLOAD: usize, + + /// Sync committee + /// --------------------------------------------------------------- + pub SYNC_COMMITTEE_SIZE: usize, + pub EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize, + + /// Sync protocol + /// --------------------------------------------------------------- + pub MIN_SYNC_COMMITTEE_PARTICIPANTS: usize, + pub UPDATE_TIMEOUT: usize, + } + + /// + pub const MAINNET: Preset = Preset { + DEPOSIT_CONTRACT_TREE_DEPTH: 32, + MAX_VALIDATORS_PER_COMMITTEE: 2048, + + SECONDS_PER_SLOT: 12, + SLOTS_PER_EPOCH: 32, + + MAX_PROPOSER_SLASHINGS: 16, + MAX_ATTESTER_SLASHINGS: 2, + MAX_ATTESTATIONS: 128, + MAX_DEPOSITS: 16, + MAX_VOLUNTARY_EXITS: 16, + MAX_BLS_TO_EXECUTION_CHANGES: 16, + MAX_BLOB_COMMITMENTS_PER_BLOCK: 4096, + SYNC_COMMITTEE_SIZE: 512, + EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256, + MIN_SYNC_COMMITTEE_PARTICIPANTS: 1, + UPDATE_TIMEOUT: 8192, + + MAX_BYTES_PER_TRANSACTION: 1_073_741_824, + MAX_TRANSACTIONS_PER_PAYLOAD: 1_048_576, + BYTES_PER_LOGS_BLOOM: 256, + MAX_EXTRA_DATA_BYTES: 32, + MAX_WITHDRAWALS_PER_PAYLOAD: 16, + }; + + /// + pub const MINIMAL: Preset = Preset { + DEPOSIT_CONTRACT_TREE_DEPTH: 32, + MAX_VALIDATORS_PER_COMMITTEE: 2048, + + SECONDS_PER_SLOT: 6, + SLOTS_PER_EPOCH: 8, + + MAX_PROPOSER_SLASHINGS: 16, + MAX_ATTESTER_SLASHINGS: 2, + MAX_ATTESTATIONS: 128, + MAX_DEPOSITS: 16, + MAX_VOLUNTARY_EXITS: 16, + MAX_BLS_TO_EXECUTION_CHANGES: 16, + MAX_BLOB_COMMITMENTS_PER_BLOCK: 16, + + SYNC_COMMITTEE_SIZE: 32, + EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8, + MIN_SYNC_COMMITTEE_PARTICIPANTS: 1, + UPDATE_TIMEOUT: 64, + + MAX_BYTES_PER_TRANSACTION: 1_073_741_824, + MAX_TRANSACTIONS_PER_PAYLOAD: 1_048_576, + BYTES_PER_LOGS_BLOOM: 256, + MAX_EXTRA_DATA_BYTES: 32, + MAX_WITHDRAWALS_PER_PAYLOAD: 4, + }; +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Config { + pub preset: preset::Preset, + pub fork_parameters: ForkParameters, + pub min_genesis_time: u64, +} + +pub const GOERLI: Config = Config { + preset: preset::MAINNET, + fork_parameters: ForkParameters { + genesis_fork_version: Version(FixedBytes([0, 0, 16, 32])), + genesis_slot: (0), + altair: Fork { + version: Version(FixedBytes([1, 0, 16, 32])), + epoch: (36660), + }, + bellatrix: Fork { + version: Version(FixedBytes([2, 0, 16, 32])), + epoch: 112_260, + }, + capella: Fork { + version: Version(FixedBytes([3, 0, 16, 32])), + epoch: 162_304, + }, + deneb: Fork { + version: Version(FixedBytes([4, 0, 16, 32])), + epoch: 231_680, + }, + }, + min_genesis_time: 1_614_588_812, +}; + +pub const MAINNET: Config = Config { + preset: preset::MAINNET, + fork_parameters: ForkParameters { + genesis_fork_version: Version(FixedBytes([0, 0, 0, 0])), + genesis_slot: 0, + + altair: Fork { + version: Version(FixedBytes([1, 0, 0, 0])), + epoch: 74_240, + }, + bellatrix: Fork { + version: Version(FixedBytes([2, 0, 0, 0])), + epoch: 144_896, + }, + capella: Fork { + version: Version(FixedBytes([3, 0, 0, 0])), + epoch: 194_048, + }, + // TODO: enabled march 13th 2024 + deneb: Fork { + version: Version(FixedBytes([4, 0, 0, 0])), + epoch: u64::MAX, + }, + }, + min_genesis_time: 1_606_824_000, +}; + +pub const MINIMAL: Config = Config { + preset: preset::MINIMAL, + fork_parameters: ForkParameters { + genesis_fork_version: Version(FixedBytes([0, 0, 0, 1])), + genesis_slot: 0, + + altair: Fork { + version: Version(FixedBytes([1, 0, 0, 1])), + epoch: 0, + }, + + bellatrix: Fork { + version: Version(FixedBytes([2, 0, 0, 1])), + epoch: 0, + }, + + capella: Fork { + version: Version(FixedBytes([3, 0, 0, 1])), + epoch: 0, + }, + + // NOTE: dummy data + deneb: Fork { + version: Version(FixedBytes([4, 0, 0, 1])), + epoch: 0, + }, + }, + min_genesis_time: 1_578_009_600, +}; + +pub const SEPOLIA: Config = Config { + preset: preset::MAINNET, + fork_parameters: ForkParameters { + genesis_fork_version: Version(FixedBytes([144, 0, 0, 105])), + genesis_slot: 0, + + altair: Fork { + version: Version(FixedBytes([144, 0, 0, 112])), + epoch: 50, + }, + + bellatrix: Fork { + version: Version(FixedBytes([144, 0, 0, 113])), + epoch: 100, + }, + + capella: Fork { + version: Version(FixedBytes([144, 0, 0, 114])), + epoch: 56_832, + }, + + deneb: Fork { + version: Version(FixedBytes([144, 0, 0, 115])), + epoch: 132_608, + }, + }, + min_genesis_time: 1_655_647_200, +}; diff --git a/packages/ethereum-light-client/src/consensus_state.rs b/packages/ethereum-light-client/src/consensus_state.rs new file mode 100644 index 000000000..14d507a6 --- /dev/null +++ b/packages/ethereum-light-client/src/consensus_state.rs @@ -0,0 +1,66 @@ +use alloy_primitives::{FixedBytes, B256}; +use serde::{Deserialize, Serialize}; + +use crate::types::sync_committee::{ActiveSyncCommittee, SyncCommittee}; + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] +pub struct ConsensusState { + pub slot: u64, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub state_root: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub storage_root: B256, + pub timestamp: u64, + /// aggregate public key of current sync committee + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub current_sync_committee: FixedBytes<48>, + /// aggregate public key of next sync committee + #[serde(with = "ethereum_utils::base64::option_with_default")] + pub next_sync_committee: Option>, +} + +impl From> for ConsensusState { + fn from(value: Vec) -> Self { + serde_json::from_slice(&value).unwrap() + } +} + +impl From for Vec { + fn from(value: ConsensusState) -> Self { + serde_json::to_vec(&value).unwrap() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct TrustedConsensusState { + pub state: ConsensusState, + /// Full sync committee data which corresponds to the aggregate key that we + /// store at the client. + /// + /// This sync committee can either be the current sync committee or the next sync + /// committee. That's because the verifier uses next or current sync committee's + /// public keys to verify the signature against. It is based on + pub sync_committee: ActiveSyncCommittee, +} + +impl TrustedConsensusState { + pub fn finalized_slot(&self) -> u64 { + self.state.slot + } + + pub fn current_sync_committee(&self) -> Option<&SyncCommittee> { + if let ActiveSyncCommittee::Current(committee) = &self.sync_committee { + Some(committee) + } else { + None + } + } + + pub fn next_sync_committee(&self) -> Option<&SyncCommittee> { + if let ActiveSyncCommittee::Next(committee) = &self.sync_committee { + Some(committee) + } else { + None + } + } +} diff --git a/packages/ethereum-light-client/src/error.rs b/packages/ethereum-light-client/src/error.rs new file mode 100644 index 000000000..fe0ac226 --- /dev/null +++ b/packages/ethereum-light-client/src/error.rs @@ -0,0 +1,132 @@ +use alloy_primitives::B256; +use alloy_rpc_types_beacon::BlsPublicKey; + +#[derive(thiserror::Error, Debug, Clone, PartialEq)] +pub enum EthereumIBCError { + #[error("IBC path is empty")] + EmptyPath, + + #[error("unable to decode storage proof")] + StorageProofDecode, + + #[error("invalid commitment key, expected ({0}) but found ({1})")] + InvalidCommitmentKey(String, String), + + #[error("expected value ({0}) and stored value ({1}) don't match")] + StoredValueMistmatch(String, String), + + #[error("verify storage proof error: {0}")] + VerifyStorageProof(String), + + #[error("insufficient number of sync committee participants ({0})")] + InsufficientSyncCommitteeParticipants(usize), + + #[error("update header contains deneb specific information")] + MustBeDeneb, + + #[error("invalid chain version")] + InvalidChainVersion, + + #[error(transparent)] + InvalidMerkleBranch(#[from] InvalidMerkleBranch), + + #[error("finalized slot cannot be the genesis slot")] + FinalizedSlotIsGenesis, + + #[error( + "update slot {update_signature_slot} is more recent than the \ + calculated current slot {current_slot}" + )] + UpdateMoreRecentThanCurrentSlot { + current_slot: u64, + update_signature_slot: u64, + }, + + #[error( + "(update_signature_slot > update_attested_slot >= update_finalized_slot) must hold, \ + found: ({update_signature_slot} > {update_attested_slot} >= {update_finalized_slot})" + )] + InvalidSlots { + update_signature_slot: u64, + update_attested_slot: u64, + update_finalized_slot: u64, + }, + + #[error( + "signature period ({signature_period}) must be equal to `store_period` \ + ({stored_period}) or `store_period + 1` when the next sync committee is stored" + )] + InvalidSignaturePeriodWhenNextSyncCommitteeExists { + signature_period: u64, + stored_period: u64, + }, + + #[error( + "signature period ({signature_period}) must be equal to `store_period` \ + ({stored_period}) when the next sync committee is not stored" + )] + InvalidSignaturePeriodWhenNextSyncCommitteeDoesNotExist { + signature_period: u64, + stored_period: u64, + }, + + #[error( + "irrelevant update since the order of the slots in the update data, and stored data is not correct. \ + either the update_attested_slot (found {update_attested_slot}) must be > the trusted_finalized_slot \ + (found {trusted_finalized_slot}) or if it is not, then the update_attested_period \ + (found {update_attested_period}) must be the same as the store_period (found {stored_period}) and \ + the update_sync_committee must be set (was set: {update_sync_committee_is_set}) and the trusted \ + next_sync_committee must be unset (was set: {trusted_next_sync_committee_is_set})" + )] + IrrelevantUpdate { + update_attested_slot: u64, + trusted_finalized_slot: u64, + update_attested_period: u64, + stored_period: u64, + update_sync_committee_is_set: bool, + trusted_next_sync_committee_is_set: bool, + }, + + #[error( + "next sync committee ({found}) does not match with the one in the current state ({expected})" + )] + NextSyncCommitteeMismatch { + expected: BlsPublicKey, + found: BlsPublicKey, + }, + + #[error( + "expected current sync committee to be provided since `update_period == current_period`" + )] + ExpectedCurrentSyncCommittee, + + #[error("expected next sync committee to be provided since `update_period > current_period`")] + ExpectedNextSyncCommittee, + + #[error("fast aggregate verify error: {0}")] + FastAggregateVerify(String), + + #[error("not enough signatures")] + NotEnoughSignatures, + + #[error("failed to verify finalized_header is finalized")] + ValidateFinalizedHeaderFailed(#[source] Box), + + #[error("failed to verify next sync committee against attested header")] + ValidateNextSyncCommitteeFailed(#[source] Box), +} + +#[derive(Debug, PartialEq, Clone, thiserror::Error)] +#[error("invalid merkle branch \ + (leaf: {leaf}, branch: [{branch}], \ + depth: {depth}, index: {index}, root: {root}, found: {found})", + branch = .branch.iter().map(ToString::to_string).collect::>().join(", ") +)] +pub struct InvalidMerkleBranch { + pub leaf: B256, + pub branch: Vec, + pub depth: usize, + pub index: u64, + pub root: B256, + pub found: B256, +} diff --git a/packages/ethereum-light-client/src/lib.rs b/packages/ethereum-light-client/src/lib.rs new file mode 100644 index 000000000..ea68bf2c --- /dev/null +++ b/packages/ethereum-light-client/src/lib.rs @@ -0,0 +1,14 @@ +pub mod client_state; +pub mod config; +pub mod consensus_state; +pub mod error; +pub mod membership; +pub mod trie; +pub mod verify; + +pub mod types; + +pub use typenum; // re-export (for some weird macro stuff in config.rs) + +#[cfg(test)] +mod test; diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs new file mode 100644 index 000000000..26df2031 --- /dev/null +++ b/packages/ethereum-light-client/src/membership.rs @@ -0,0 +1,225 @@ +use alloy_primitives::{keccak256, Bytes, Keccak256, U256}; +use alloy_rlp::encode_fixed_size; +use alloy_trie::{proof::verify_proof, Nibbles}; +use ethereum_utils::hex::to_hex; + +use crate::{ + client_state::ClientState, consensus_state::ConsensusState, error::EthereumIBCError, + types::storage_proof::StorageProof, +}; + +pub fn verify_membership( + trusted_consensus_state: ConsensusState, + client_state: ClientState, + proof: Vec, + path: Vec>, + raw_value: Option>, +) -> Result<(), EthereumIBCError> { + let path = path.first().ok_or(EthereumIBCError::EmptyPath)?; + + let storage_proof: StorageProof = serde_json::from_slice(proof.as_slice()) + .map_err(|_| EthereumIBCError::StorageProofDecode)?; + + check_commitment_key( + path.to_vec(), + client_state.ibc_commitment_slot, + storage_proof.key.into(), + )?; + + let mut value = raw_value.clone(); + if let Some(raw_value) = raw_value { + // membership proof (otherwise non-membership proof) + let proof_value = storage_proof.value.to_be_bytes_vec(); + if proof_value != raw_value { + return Err(EthereumIBCError::StoredValueMistmatch( + to_hex(raw_value), + to_hex(proof_value), + )); + } + + value = Some(encode_fixed_size(&U256::from_be_slice(&proof_value)).to_vec()); + } + + let proof: Vec<&Bytes> = storage_proof.proof.iter().map(|b| &b.0).collect(); + + verify_proof::>( + trusted_consensus_state.storage_root, + Nibbles::unpack(keccak256(storage_proof.key)), + value, + proof, + ) + .map_err(|err| EthereumIBCError::VerifyStorageProof(err.to_string())) +} + +fn check_commitment_key( + path: Vec, + ibc_commitment_slot: U256, + key: U256, +) -> Result<(), EthereumIBCError> { + let expected_commitment_key = ibc_commitment_key_v2(path, ibc_commitment_slot); + + // Data MUST be stored to the commitment path that is defined in ICS23. + if expected_commitment_key != key { + Err(EthereumIBCError::InvalidCommitmentKey( + format!("0x{expected_commitment_key:x}"), + format!("0x{key:x}"), + )) + } else { + Ok(()) + } +} + +// TODO: Unit test +#[must_use = "calculating the commitment key has no effect"] +fn ibc_commitment_key_v2(path: Vec, slot: U256) -> U256 { + let path_hash = keccak256(path); + + let mut hasher = Keccak256::new(); + hasher.update(path_hash); + hasher.update(slot.to_be_bytes_vec()); + + hasher.finalize().into() +} + +#[cfg(test)] +mod test { + use crate::{ + client_state::ClientState, + consensus_state::ConsensusState, + test::fixtures::{load_fixture, CommitmentProofFixture}, + types::{storage_proof::StorageProof, wrappers::MyBytes}, + }; + use alloy_primitives::{ + hex::{self, FromHex}, + Bytes, B256, U256, + }; + use ethereum_utils::hex::FromBeHex; + + use super::verify_membership; + + #[test] + fn test_with_fixture() { + let commitment_proof_fixture: CommitmentProofFixture = + load_fixture("commitment_proof_fixture"); + + let trusted_consensus_state = commitment_proof_fixture.consensus_state; + let client_state = commitment_proof_fixture.client_state; + let storage_proof = commitment_proof_fixture.storage_proof; + let path = commitment_proof_fixture.path; + let value = storage_proof.value.to_be_bytes_vec(); + + verify_membership( + trusted_consensus_state, + client_state, + serde_json::to_vec(&storage_proof).unwrap(), + vec![path], + Some(value), + ) + .unwrap(); + } + + #[test] + fn test_verify_membership() { + let client_state: ClientState = ClientState { + ibc_commitment_slot: U256::from_be_hex( + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ..Default::default() + }; + + let consensus_state: ConsensusState = ConsensusState { + storage_root: B256::from_hex( + "0xe488caae2c0464e311e4a2df82bc74885fa81778d04131db6af3a451110a5eb5", + ) + .unwrap(), + ..Default::default() + }; + + let key = + B256::from_hex("0x75d7411cb01daad167713b5a9b7219670f0e500653cbbcd45cfe1bfe04222459") + .unwrap(); + let value = + U256::from_be_hex("0xb2ae8ab0be3bda2f81dc166497902a1832fea11b886bc7a0980dec7a219582db"); + + let proof = vec![ + MyBytes(Bytes::from_hex("0xf8718080a0911797c4b8cdbd1d8fa643b31ff0a469fae0f9b2ecbb0fa45a5ebe497f5e7130a065ea7eb6ae4e9747a131961beda4e9fd3040521e58845f4a286fb472eb0415168080a057b16d9a3bbb2d106b4d1b12dca3504f61899c7c660b036848511426ed342dd680808080808080808080").unwrap()), + MyBytes(Bytes::from_hex("0xf843a03d3c3bcf030006afea2a677a6ff5bf3f7f111e87461c8848cf062a5756d1a888a1a0b2ae8ab0be3bda2f81dc166497902a1832fea11b886bc7a0980dec7a219582db").unwrap()), + ]; + + let path = vec![hex::decode("0x30372d74656e6465726d696e742d30010000000000000001").unwrap()]; + + let storage_proof = StorageProof { + key, + value, + proof: proof.clone(), + }; + let storage_proof_bz = serde_json::to_vec(&storage_proof).unwrap(); + + verify_membership( + consensus_state.clone(), + client_state.clone(), + storage_proof_bz, + path.clone(), + Some(value.to_be_bytes_vec()), + ) + .unwrap(); + + // should fail as a non-membership proof + let value = U256::from(0); + let storage_proof = StorageProof { key, value, proof }; + let storage_proof_bz = serde_json::to_vec(&storage_proof).unwrap(); + + verify_membership(consensus_state, client_state, storage_proof_bz, path, None).unwrap_err(); + } + + #[test] + fn test_verify_non_membership() { + let client_state: ClientState = ClientState { + ibc_commitment_slot: U256::from_be_hex( + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ..Default::default() + }; + + let consensus_state: ConsensusState = ConsensusState { + storage_root: B256::from_hex( + "0x8fce1302ff9ebea6343badec86e9814151872067d2dd47de08ec83e9bc7d22b3", + ) + .unwrap(), + ..Default::default() + }; + + let key = + B256::from_hex("0x7a0c5ed5d5cb00ab03f4363e63deb3b05017026890db9f2110e931630567bf93") + .unwrap(); + + let proof = vec![ + MyBytes(Bytes::from_hex("0xf838a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594eb9407e2a087056b69d43d21df69b82e31533c8a").unwrap()), + ]; + + let path = vec![hex::decode("0x30372d74656e6465726d696e742d30020000000000000001").unwrap()]; + + let value = U256::from(0); + let proof = StorageProof { key, value, proof }; + let proof_bz = serde_json::to_vec(&proof).unwrap(); + + verify_membership( + consensus_state.clone(), + client_state.clone(), + proof_bz.clone(), + path.clone(), + None, + ) + .unwrap(); + + // should fail as a membership proof + verify_membership( + consensus_state, + client_state, + proof_bz, + path, + Some(value.to_be_bytes_vec()), + ) + .unwrap_err(); + } +} diff --git a/packages/ethereum-light-client/src/test/bls_verifier.rs b/packages/ethereum-light-client/src/test/bls_verifier.rs new file mode 100644 index 000000000..df5a890f --- /dev/null +++ b/packages/ethereum-light-client/src/test/bls_verifier.rs @@ -0,0 +1,48 @@ +use thiserror::Error; + +use crate::types::bls::BlsVerify; + +pub struct TestBlsVerifier; + +#[derive(Error, Debug)] +pub enum BlsError { + #[error("bls error: {0}")] + Bls(String), +} + +impl BlsVerify for TestBlsVerifier { + type Error = BlsError; + + fn fast_aggregate_verify( + &self, + public_keys: Vec<&crate::types::bls::BlsPublicKey>, + msg: alloy_primitives::B256, + signature: crate::types::bls::BlsSignature, + ) -> Result<(), Self::Error> { + let public_keys = public_keys + .iter() + .cloned() + .map(|pk| milagro_bls::PublicKey::from_bytes(pk.as_ref())) + .collect::, _>>() + .map_err(|_| { + BlsError::Bls("failed to convert to milagro_bls public keys".to_string()) + })?; + + let public_keys: Vec<&milagro_bls::PublicKey> = public_keys.iter().collect(); + + let signature = milagro_bls::Signature::from_bytes(signature.as_slice()) + .map_err(|_| BlsError::Bls("failed to convert to milagro_bls signature".to_string()))?; + + let aggregate_signature = milagro_bls::AggregateSignature::aggregate(&[&signature]); + let aggregate_pubkey = milagro_bls::AggregatePublicKey::aggregate(&public_keys) + .map_err(|_| BlsError::Bls("failed to aggregate public keys".to_string()))?; + + let res = aggregate_signature + .fast_aggregate_verify_pre_aggregated(msg.as_slice(), &aggregate_pubkey); + if res { + Ok(()) + } else { + Err(BlsError::Bls("failed to verify signature".to_string())) + } + } +} diff --git a/packages/ethereum-light-client/src/test/client_update_ack_0.json b/packages/ethereum-light-client/src/test/client_update_ack_0.json new file mode 100644 index 000000000..f466effb --- /dev/null +++ b/packages/ethereum-light-client/src/test/client_update_ack_0.json @@ -0,0 +1,177 @@ +{ + "trusted_sync_committee": { + "trusted_height": { + "revision_number": 0, + "revision_height": 32 + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "gmHVzMuPqcx/A6CKMEJLWPxv8nSa+Ub/M+WbLnMH42KuWnEZbMCtnPlGNk2zbZvx" + } + }, + "consensus_update": { + "attested_header": { + "beacon": { + "slot": 80, + "proposer_index": 60, + "parent_root": "KTGY1cnrh9MaGTvZKAfIMJwh7U4TxOPUTlV2DnU3vhQ=", + "state_root": "hdxV7foaxAw1IvznUUahNeUVlCmOoC0+4O/Q42nslyE=", + "body_root": "VhcHNRVKhsxdIAIHZES+dd5W5AdveV2ZopPcRh/Slyk=" + }, + "execution": { + "parent_hash": "IeIjL66DUoHdjlKUsO1R97lkeHcyL8vTRsqtOxIv+4A=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "x3p3pKop4ci1bkj9fib9JWueIeiMyuf2B0kqSZN6QMg=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "mgm78yp3W4GGVb7WSWsH+i7QN3M3By9tBxq2EMoc4Ko=", + "block_number": 80, + "gas_limit": 30000000, + "timestamp": 1733323811, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ2c=", + "block_hash": "jQh77GjuWFjPVwVrRFepR3DGk+FXik5VhOyriUPcz9A=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "hCfwRRLO3eVJncd/ce+8ix9iVWNcACJFGhGcf5muGxI=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "pIhTiDUfYbkJOfy5+o2knxIrN70+e3d2PB68rvSPGxQ=" + ] + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/" + }, + "next_sync_committee_branch": [ + "Bj1HUrNYq0t1WuBccfcVBIBBjgEkh4mcXRbQv1vt4jU=", + "J1Y6YWuDG2U27JP5xagxkcrUrQx3Dz9geIJsYNQgtG0=", + "GyG2kk+cTmjb/FJnj/gIQFo/NUUKsZ94sPjvROXw3Rs=", + "kcKMULnNQJ/GF4dTE+zzN7CU0MqYEWeJqLPyoPhe3P8=", + "Md5nqXy3UtLGZ+PW05Y6W7B0ZhYJ6297Utx8sd3A0yc=" + ], + "finalized_header": { + "beacon": { + "slot": 64, + "proposer_index": 61, + "parent_root": "yZZJxxeZlqmh4HrxTIVntPa0881mtzGu1PVPKy9W8wI=", + "state_root": "6RzrP4yEsu1JqSNXUGwVHzw2Vh6go9WuxNgBTdIrAHw=", + "body_root": "ninYsM2Hsvn9SgbCqyOnUcg8XGlve4OSoymcgA2jA1w=" + }, + "execution": { + "parent_hash": "MWAKv7hnL23beMp6JpZ2wfudkgBApmKrz3xWv60hoFk=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "SKBgrfKP9po8BelsHw0yqN0zT6tubmg01pcAvarpD1M=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "Ju1XcKfTnBew7PCgkIt3dWIXwqqHo0cLrK36D3TlYwg=", + "block_number": 64, + "gas_limit": 30000000, + "timestamp": 1733323715, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX/4=", + "block_hash": "c/Y2GFO0JjgWkhl3sxsHHN3ZyzPYk6jxOfKr7GaaBj0=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "TqyGk/jDi7gxWwPeTovL+BOX5An8a5ZxcwBDVFyPB5w=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "smbCiytqdQ32wF9afqelV7GAtv9rhSYzCh1mHLbjK5I=" + ] + }, + "finality_branch": [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", + "yNg3I5+qkUkmXt9skVrpXnJwhpDfnE5NvxaoIF9zHHk=", + "GyG2kk+cTmjb/FJnj/gIQFo/NUUKsZ94sPjvROXw3Rs=", + "kcKMULnNQJ/GF4dTE+zzN7CU0MqYEWeJqLPyoPhe3P8=", + "Md5nqXy3UtLGZ+PW05Y6W7B0ZhYJ6297Utx8sd3A0yc=" + ], + "sync_aggregate": { + "sync_committee_bits": "/////w==", + "sync_committee_signature": "mAAmBm2FrMOFMYwvmenu2dysVLp3426vbTjAdYbzLC6dQCcCbNn/FKsnfwy3sz/uAozfIoija6soUPFPdBAsqqF4gDQgjPXxjG19/PrMsYps3mCGU/YKE+OV2oHzseg4" + }, + "signature_slot": 81 + }, + "account_update": { + "account_proof": { + "storage_root": "gACK4yVCkw01w2NwcyXK45pGJZuHZtosHlxxh8xI2KU=", + "proof": [ + "+QIRoJy9mzlgCIj9261sHUdqQEz+GrMLf9jYl6L51ns1iSxRoOAzheF0YL+kuBiDEtbUcCrpIP2dNRc9czMmFfHEMY6xoIA8IU97ymCRwpz7bXAdaalbk3TF1x0NqmYp7K5hfbHloA9KsHUPNkbtA/EdqG4BtM5oflmeZxRLOB+GAMrj7MBYoEd3tzNyEl9ueOR8juyljpWUMQkD4qfrkf+LyqMfmHEMoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foL0O7icYOrkBBbdEQ1lHSk/55rSUpCQZRqXG7zkeoAERoIPGl55GPAKBj/6truuKvJ8vUedn+5FRp/yJmJ60C1esoMvNwdImpUDFDLHmFeevmfFx1DZbRXNJQOItR+xKojoUoF/w4VuX4r5bV0vGxWmrUGeeDfjOf9jT83lqAHlS89vQoGEV1qu7Mr7wexFPARVLC8LtTqrezKP1I/uTawgG2K/EoP01e158rErRzyOwW3in60/8jV5UwNB8Utr8KeH/h9RIoM+a4y5rGh2oy+b7TKocjHkkQlLOZIGHvlQdceZjuU0QoPeSXskhoxP247G/pKTy9TUFLIwJGCPSBadBGbpNmW1WoJYvD3zLQ8eLoj+ZvmPX++/JrK+7O+hycOuNhQ5QkGz/oDf/APviEFvODm7Z6oCh1nuKR2sf89F3rJWXpTJB5HqngA==", + "+QHxoAQnJWYscBxLniD3psqO7Sb7t1xXpmGhiB/qxpjCB3IBoINqrps3YgRrlvRQjfdUdfCFdZllPWNLCvV0gA4j84pcoHkqNszwtQkFzcYnxz0Kl6oSw5H/jbwmv5x5GP9JdETUoKELQddIabNCwJvyVuJZm7qJPCatFwnBpm8IJnslnvA0oFDQk1+BSpJhQtA8z3B8bNm1HNoVWfpR/hJiYvt5pVNloMioOdj1JP0Ne5EyXNL5s9A25iDYZ2zwBVDW1DJit4R8gKAMMgAETzD49tBU2jIcKc4zU06oXYWmVVALx2PpuHizraAWluLWgFRL7/b5z459AOYI95/81xbcWKXLgmCfjgfLnqChXiN4nos9rAQ6BvtbTVp84T5n/Hp3liEejfYNjs1tvqCp+Y+PiVm8nYxbCVfRSV0+HECNGBfHYQQ889tDI07luqB98vsLt44lf2P8LLz8BxBZV425ba9yAGYSDTMwufQXk6B+gAO6odWiW7CFF5/5HtxPeyO84HvQb90AsH+l9oRb/qC8Nq1uDISWu/0Z1hh1ohnSSWcHTRKOMerKvMu4h6OL7aC+RERcY2f1Dg10fo1lydcuFCIm4KskqIYgrOwO3FVqp6Clxi0aZx7Al5KXtFCLuvQ5OF6dggSjXhItvFThMtkaf4A=", + "+FGgAnmxTv4hI+4DcjVsya6WhHnv2f7vUVqjdm8bY3wXwpGAgICAgKBjnI1fD3VH9FtFHzFcVpoPgkwr3U4onPZQPfg9qK9HWICAgICAgICAgIA=", + "+GifPQ9d80Yr6WVBOXY1pLN87huE4ois3IfBMyxIE4Rd4rhG+EQBgKCAAIrjJUKTDTXDY3BzJcrjmkYlm4dm2iweXHGHzEjYpaCyrvYec6w6k7Xz7qVixad4i1445NH774ghBs6vw68t1w==" + ] + } + } +} \ No newline at end of file diff --git a/packages/ethereum-light-client/src/test/client_update_recv_0.json b/packages/ethereum-light-client/src/test/client_update_recv_0.json new file mode 100644 index 000000000..273124a9 --- /dev/null +++ b/packages/ethereum-light-client/src/test/client_update_recv_0.json @@ -0,0 +1,177 @@ +{ + "trusted_sync_committee": { + "trusted_height": { + "revision_number": 0, + "revision_height": 80 + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/" + } + }, + "consensus_update": { + "attested_header": { + "beacon": { + "slot": 144, + "proposer_index": 51, + "parent_root": "+yh1WNRauh0IoR14jOS9YYA8BSwU5ql+E4ajjh2hWbE=", + "state_root": "t2Npp4IsrZd8OLNL8v+cTMdRTuRUKovGLbE/RXfqzMo=", + "body_root": "oACCFHQz+I7uHiReeh8Fn1MUSkgNNCiIRySzM7HgrK0=" + }, + "execution": { + "parent_hash": "kiQmPrWtDFwFuLYmrWJURMVib4HGSHrCv5hQlJZs8hA=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "QlGpbOzuzjivfYy30jZyL0/oe0QRoXcEiZQdd2Cee+g=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "DLwM+MyfeWkYifp55rURFoK9Oca1Cgxnwv3YeRoLZ9c=", + "block_number": 144, + "gas_limit": 30000000, + "timestamp": 1733324195, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk=", + "block_hash": "wx65XbLIRlawwdvTr5w3SCwnrfBlPQVJ8kWdXGcEib4=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "lZvuHxJFqiCi1zbH60V0sSFRNxjSUVtquKFE54jAyvw=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "7w2pFYfCXz/YEmbX194wmgP3lDiHyPS94MrRKl8l3CY=" + ] + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "i2YT7mQ5VjqF+copuvosuCOeOrl00GkwUvJXJxdLnyCWBMTyt0bdErb/XRyVjOAG" + }, + "next_sync_committee_branch": [ + "U2HrF590me2/CeUU0xcALx02XXLhSlbJMentrMyj/yk=", + "ebAO+3qEsn1nB1DQQJxFDBxcgtzM8eXeJd9w8BWdSQI=", + "WymxeDBGfowY8TYHxSI/XbPWQVKEvZGNB4y9fBPuwU4=", + "ntgTQDU97bD+hJvWqBXT4OiKFJrXvYf4Z7Pv/xj/q2Y=", + "8TTv8NTCcS2oecKHq4Lh7PJO+8B6Y3rkkxj3l6zxgCI=" + ], + "finalized_header": { + "beacon": { + "slot": 128, + "proposer_index": 38, + "parent_root": "g2y+wD+OzF49DHq2lHr56OA5kSuJEPysWtB3A5cmvQQ=", + "state_root": "SKYVwF3KUJZG8fgp0vEVLaAXwcft6fc6Q0W8UqLjwmI=", + "body_root": "yCa9k/2R/0axrwGRG36iPoho/y28qDhP5kXfjlheKnA=" + }, + "execution": { + "parent_hash": "5fj6049De37qa95MmhlWrpmf8obaTHrjKDrDpiiLQb8=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "dKHHD8cFmGJizQ6FiYElwSjDM6OodwqhY6NEDM1XwHw=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "bVUgnc8OWfHdxGlvfE9um34ppr4unJJ/FiRRsC9rClY=", + "block_number": 128, + "gas_limit": 30000000, + "timestamp": 1733324099, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA=", + "block_hash": "UoNVvB0D96Tko9HHLHK1c87/GpiD8naUFgQYL2CP/EA=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "iZKIv5s+K/SdfciQZatJ9ypFijMHl1UK+k287Z/Jt2o=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "HNuubpfS189mRdAQyoihXjcAvc3bVDQkwoSvOcHy0TY=" + ] + }, + "finality_branch": [ + "EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", + "0Ydom60/vjDwrY5S6xIkX/GvZ91lDqNBBQIvdPke0uQ=", + "WymxeDBGfowY8TYHxSI/XbPWQVKEvZGNB4y9fBPuwU4=", + "ntgTQDU97bD+hJvWqBXT4OiKFJrXvYf4Z7Pv/xj/q2Y=", + "8TTv8NTCcS2oecKHq4Lh7PJO+8B6Y3rkkxj3l6zxgCI=" + ], + "sync_aggregate": { + "sync_committee_bits": "/////w==", + "sync_committee_signature": "hT+YlXESJFkVSsOe2A99ypztYm/lMc31J+K0kRmdjprhDTQh3DOPbx9C8F6MgFnrAJHIlswc6h0jABBiUl71jVXZDkvpGcrJO4HjjtcEw8m/T/zDnrInDfik+arDvcjA" + }, + "signature_slot": 145 + }, + "account_update": { + "account_proof": { + "storage_root": "CB2+UVpo5UlCwDuzbKY++v3wS24w3J/u5vNmCLcmkEQ=", + "proof": [ + "+QIRoJy9mzlgCIj9261sHUdqQEz+GrMLf9jYl6L51ns1iSxRoNvtQnGvsya7jNya5NqK/0PEH6HAXEgplAFd0uTDuVVcoIA8IU97ymCRwpz7bXAdaalbk3TF1x0NqmYp7K5hfbHloGY/odeIzddwH+qzE0aMOkWsfWkKxGAF6JO6ELHDq+DBoEd3tzNyEl9ueOR8juyljpWUMQkD4qfrkf+LyqMfmHEMoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foL0O7icYOrkBBbdEQ1lHSk/55rSUpCQZRqXG7zkeoAERoIPGl55GPAKBj/6truuKvJ8vUedn+5FRp/yJmJ60C1esoMvNwdImpUDFDLHmFeevmfFx1DZbRXNJQOItR+xKojoUoF/w4VuX4r5bV0vGxWmrUGeeDfjOf9jT83lqAHlS89vQoGEV1qu7Mr7wexFPARVLC8LtTqrezKP1I/uTawgG2K/EoPXSv0BRB8Lem6Y92oN68nWXI0ZMCuwXjVhJtTZC3kVHoPWONEoWRJJh5QIguZTTlLSV+zdl5lKtT8PV/KFakTKtoPeSXskhoxP247G/pKTy9TUFLIwJGCPSBadBGbpNmW1WoJYvD3zLQ8eLoj+ZvmPX++/JrK+7O+hycOuNhQ5QkGz/oDf/APviEFvODm7Z6oCh1nuKR2sf89F3rJWXpTJB5HqngA==", + "+QHxoAQnJWYscBxLniD3psqO7Sb7t1xXpmGhiB/qxpjCB3IBoINqrps3YgRrlvRQjfdUdfCFdZllPWNLCvV0gA4j84pcoHkqNszwtQkFzcYnxz0Kl6oSw5H/jbwmv5x5GP9JdETUoKELQddIabNCwJvyVuJZm7qJPCatFwnBpm8IJnslnvA0oFDQk1+BSpJhQtA8z3B8bNm1HNoVWfpR/hJiYvt5pVNloMioOdj1JP0Ne5EyXNL5s9A25iDYZ2zwBVDW1DJit4R8gKAMMgAETzD49tBU2jIcKc4zU06oXYWmVVALx2PpuHizraAWluLWgFRL7/b5z459AOYI95/81xbcWKXLgmCfjgfLnqChXiN4nos9rAQ6BvtbTVp84T5n/Hp3liEejfYNjs1tvqCp+Y+PiVm8nYxbCVfRSV0+HECNGBfHYQQ889tDI07luqB98vsLt44lf2P8LLz8BxBZV425ba9yAGYSDTMwufQXk6CwGcffGXvqDnt7/OOCxF6IeRds1AghLm5//inSL2URDaC8Nq1uDISWu/0Z1hh1ohnSSWcHTRKOMerKvMu4h6OL7aC+RERcY2f1Dg10fo1lydcuFCIm4KskqIYgrOwO3FVqp6Clxi0aZx7Al5KXtFCLuvQ5OF6dggSjXhItvFThMtkaf4A=", + "+FGgAnmxTv4hI+4DcjVsya6WhHnv2f7vUVqjdm8bY3wXwpGAgICAgKC5G8KD86sBd/7k2Kco30ZAfu5wZpDR3vOFXxxf+AILZ4CAgICAgICAgIA=", + "+GifPQ9d80Yr6WVBOXY1pLN87huE4ois3IfBMyxIE4Rd4rhG+EQBgKAIHb5RWmjlSULAO7Nspj76/fBLbjDcn+7m82YItyaQRKCyrvYec6w6k7Xz7qVixad4i1445NH774ghBs6vw68t1w==" + ] + } + } +} \ No newline at end of file diff --git a/packages/ethereum-light-client/src/test/commitment_proof_fixture.json b/packages/ethereum-light-client/src/test/commitment_proof_fixture.json new file mode 100644 index 000000000..bfcda534 --- /dev/null +++ b/packages/ethereum-light-client/src/test/commitment_proof_fixture.json @@ -0,0 +1,54 @@ +{ + "path": "MDctdGVuZGVybWludC0wAQAAAAAAAAAB", + "storage_proof": { + "key": "dddBHLAdqtFncTtam3IZZw8OUAZTy7zUXP4b/gQiJFk=", + "value": "2I8ZMhvV5J49VMMoiXCqKJDaElVciHse1occSXuIqDU=", + "proof": [ + "+LGAgKA1vP+FdEX62HLO0mDDznSBAnq4E0EvC3+NzysP+DCE1aAYkNFrQJMgx1j+VH6PJvOUGbkjS5sk/DvOLqhtJmkR1YCAoFexbZo7uy0Qa00bEtyjUE9hiZx8ZgsDaEhRFCbtNC3WgICAgICg2ewz2fYL/2sT+bFjbDoKx8vxyGbecuNvIGkP63MwSQugKk2h+7aafsHLGXnx0xO7YYjriGJC1JcwMGXs13gsanKAgIA=", + "+EOgPTw7zwMABq/qKmd6b/W/P38RHodGHIhIzwYqV1bRqIihoNiPGTIb1eSePVTDKIlwqiiQ2hJVXIh7HtaHHEl7iKg1" + ] + }, + "proof_height": { + "revision_number": 0, + "revision_height": 144 + }, + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733323331, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 144, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "kLJZNUYUcyqmuyyK25qXaVWAujs=", + "min_sync_committee_participants": 32 + }, + "consensus_state": { + "slot": 144, + "state_root": "QlGpbOzuzjivfYy30jZyL0/oe0QRoXcEiZQdd2Cee+g=", + "storage_root": "CB2+UVpo5UlCwDuzbKY++v3wS24w3J/u5vNmCLcmkEQ=", + "timestamp": 1733324195000000000, + "current_sync_committee": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/", + "next_sync_committee": "i2YT7mQ5VjqF+copuvosuCOeOrl00GkwUvJXJxdLnyCWBMTyt0bdErb/XRyVjOAG" + } +} diff --git a/packages/ethereum-light-client/src/test/fixtures.rs b/packages/ethereum-light-client/src/test/fixtures.rs new file mode 100644 index 000000000..1d0e7d7e --- /dev/null +++ b/packages/ethereum-light-client/src/test/fixtures.rs @@ -0,0 +1,33 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +use crate::{ + client_state::ClientState, + consensus_state::ConsensusState, + types::{height::Height, storage_proof::StorageProof}, +}; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct CommitmentProofFixture { + #[serde(with = "ethereum_utils::base64")] + pub path: Vec, + pub storage_proof: StorageProof, + pub proof_height: Height, + pub client_state: ClientState, + pub consensus_state: ConsensusState, +} + +pub fn load_fixture(name: &str) -> T +where + T: serde::de::DeserializeOwned, +{ + // Construct the path relative to the Cargo manifest directory + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("src/test"); + path.push(format!("{}.json", name)); + + // Open the file and deserialize its contents + let file = std::fs::File::open(path).unwrap(); + serde_json::from_reader(file).unwrap() +} diff --git a/packages/ethereum-light-client/src/test/initial_client_state_fixture.json b/packages/ethereum-light-client/src/test/initial_client_state_fixture.json new file mode 100644 index 000000000..ca46f7d8 --- /dev/null +++ b/packages/ethereum-light-client/src/test/initial_client_state_fixture.json @@ -0,0 +1,32 @@ +{ + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733323331, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 32, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "kLJZNUYUcyqmuyyK25qXaVWAujs=", + "min_sync_committee_participants": 32 + +} diff --git a/packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json b/packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json new file mode 100644 index 000000000..8ffb0ecc --- /dev/null +++ b/packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json @@ -0,0 +1,8 @@ +{ + "slot": 32, + "state_root": "HxRWmeUw4+VcAqOjqLvlhppfdaImWEflLR3avxucMn4=", + "storage_root": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "timestamp": 1733323523000000000, + "current_sync_committee": "gmHVzMuPqcx/A6CKMEJLWPxv8nSa+Ub/M+WbLnMH42KuWnEZbMCtnPlGNk2zbZvx", + "next_sync_committee": "gmHVzMuPqcx/A6CKMEJLWPxv8nSa+Ub/M+WbLnMH42KuWnEZbMCtnPlGNk2zbZvx" +} \ No newline at end of file diff --git a/packages/ethereum-light-client/src/test/mod.rs b/packages/ethereum-light-client/src/test/mod.rs new file mode 100644 index 000000000..dc7f8fc4 --- /dev/null +++ b/packages/ethereum-light-client/src/test/mod.rs @@ -0,0 +1,2 @@ +pub mod bls_verifier; +pub mod fixtures; diff --git a/packages/ethereum-light-client/src/trie.rs b/packages/ethereum-light-client/src/trie.rs new file mode 100644 index 000000000..9de4e7eb --- /dev/null +++ b/packages/ethereum-light-client/src/trie.rs @@ -0,0 +1,136 @@ +use alloy_primitives::B256; +use sha2::{Digest, Sha256}; + +use crate::error::{EthereumIBCError, InvalidMerkleBranch}; +// https://github.com/ethereum/consensus-specs/blob/efb554f4c4848f8bfc260fcf3ff4b806971716f6/specs/phase0/beacon-chain.md#is_valid_merkle_branch +pub fn validate_merkle_branch( + leaf: B256, + branch: Vec, + depth: usize, + index: u64, + root: B256, +) -> Result<(), EthereumIBCError> { + let mut value = leaf; + for (i, branch_node) in branch.iter().take(depth).enumerate() { + if (index / 2u64.checked_pow(i as u32).unwrap()) % 2 != 0 { + let mut hasher = Sha256::new(); + hasher.update(branch_node); + hasher.update(value); + + value = B256::from_slice(&hasher.finalize()[..]); + } else { + let mut hasher = Sha256::new(); + hasher.update(value); + hasher.update(branch_node); + + value = B256::from_slice(&hasher.finalize()[..]); + } + } + + if value == root { + Ok(()) + } else { + Err(EthereumIBCError::InvalidMerkleBranch(InvalidMerkleBranch { + leaf, + branch, + depth, + index, + root, + found: value, + })) + } +} + +#[cfg(test)] +mod test { + + use alloy_primitives::{hex::FromHex, Address, Bloom, Bytes, B256, U256}; + + use crate::{ + client_state::ClientState, + config::{ + consts::{floorlog2, get_subtree_index, EXECUTION_PAYLOAD_INDEX}, + MINIMAL, + }, + trie::validate_merkle_branch, + types::{ + light_client::{BeaconBlockHeader, ExecutionPayloadHeader, LightClientHeader}, + wrappers::{MyBloom, MyBranch, MyBytes}, + }, + verify::get_lc_execution_root, + }; + + #[test] + fn test_validate_merkle_branch_with_execution_payload() { + let header = LightClientHeader { + beacon: BeaconBlockHeader { + slot: 10000, + proposer_index: 0, + parent_root: B256::default(), + state_root: B256::default(), + body_root: B256::from_hex( + "0x045a26b541713c820616774b2082317cdd74dcff424c255c803e558843e55371", + ) + .unwrap(), + }, + execution: ExecutionPayloadHeader { + parent_hash: B256::from_hex( + "f55156c2b27326547193bcd2501c8300a0f3617a7d71f096fc992955f042ea50", + ) + .unwrap(), + fee_recipient: Address::from_hex("0x8943545177806ED17B9F23F0a21ee5948eCaa776").unwrap(), + state_root: B256::from_hex( + "47baba45d0ee0f0abaa42d7fbdba87908052d81fe33806576215bcf136167510", + ) + .unwrap(), + receipts_root: B256::from_hex( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ).unwrap(), + logs_bloom: MyBloom(Bloom::from_hex("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()), + prev_randao: B256::from_hex("707a729f27185bfd88c746532e0909f7f4604dc5b25b6d9ffb5cfec6ca7987d9").unwrap(), + block_number: 80, + gas_limit: 30000000, + gas_used: 0, + timestamp: 1732901097, + extra_data: MyBytes(Bytes::from_hex("0xd883010e06846765746888676f312e32322e34856c696e7578").unwrap()), + base_fee_per_gas: U256::from(27136), + block_hash: B256::from_hex("c001e15851608006eb33999e829bb265706929091f4c9a08f6853f6fbe96a730").unwrap(), + transactions_root: B256::from_hex("0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1").unwrap(), + withdrawals_root: B256::from_hex("0x28ba1834a3a7b657460ce79fa3a1d909ab8828fd557659d4d0554a9bdbc0ec30").unwrap(), + blob_gas_used: 0, + excess_blob_gas: 0, + }, + execution_branch: MyBranch([ + B256::from_hex("0xd320d2b395e1065b0b2e3dbb7843c6d77cb7830ef340ffc968caa0f92e26f080") + .unwrap(), + B256::from_hex("0x6c6dd63656639d153a2e86a9cab291e7a26e957ad635fec872d2836e92340c23") + .unwrap(), + B256::from_hex("0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71") + .unwrap(), + B256::from_hex("0xee70868f724f428f301007b0967c82d9c31fb5fd549d7f25342605169b90a3d6") + .unwrap(), + ]), + }; + + // inputs + let leaf = get_lc_execution_root( + &ClientState { + slots_per_epoch: 32, + fork_parameters: MINIMAL.fork_parameters, + ..Default::default() + }, + &header, + ); + let depth = floorlog2(EXECUTION_PAYLOAD_INDEX); + let index = get_subtree_index(EXECUTION_PAYLOAD_INDEX); + let root = header.beacon.body_root; + + println!("Leaf: {:?}", leaf); + println!("Branch: {:?}", header.execution_branch); + println!("Depth: {:?}", depth); + println!("Index: {:?}", index); + println!("Root: {:?}", root); + + validate_merkle_branch(leaf, header.execution_branch.0.into(), depth, index, root).unwrap(); + } +} diff --git a/packages/ethereum-light-client/src/types/bls.rs b/packages/ethereum-light-client/src/types/bls.rs new file mode 100644 index 000000000..b9e10160 --- /dev/null +++ b/packages/ethereum-light-client/src/types/bls.rs @@ -0,0 +1,33 @@ +use alloy_primitives::{FixedBytes, B256}; + +/// The Domain Separation Tag for hash_to_point in Ethereum beacon chain BLS12-381 signatures. +/// +/// This is also the name of the ciphersuite that defines beacon chain BLS signatures. +/// +/// See: +/// +/// +pub const BLS_DST_SIG: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + +/// The number of bytes in a BLS12-381 public key. +pub const BLS_PUBLIC_KEY_BYTES_LEN: usize = 48; + +/// The number of bytes in a BLS12-381 secret key. +pub const BLS_SECRET_KEY_BYTES_LEN: usize = 32; + +/// The number of bytes in a BLS12-381 signature. +pub const BLS_SIGNATURE_BYTES_LEN: usize = 96; + +pub type BlsPublicKey = FixedBytes; +pub type BlsSignature = FixedBytes; + +pub trait BlsVerify { + type Error: std::fmt::Display; + + fn fast_aggregate_verify( + &self, + public_keys: Vec<&BlsPublicKey>, + msg: B256, + signature: BlsSignature, + ) -> Result<(), Self::Error>; +} diff --git a/packages/ethereum-light-client/src/types/domain.rs b/packages/ethereum-light-client/src/types/domain.rs new file mode 100644 index 000000000..95bd0782 --- /dev/null +++ b/packages/ethereum-light-client/src/types/domain.rs @@ -0,0 +1,39 @@ +use alloy_primitives::{hex, FixedBytes, B256}; + +use super::{fork_data::compute_fork_data_root, wrappers::Version}; + +pub struct DomainType(pub [u8; 4]); +impl DomainType { + pub const BEACON_PROPOSER: Self = Self(hex!("00000000")); + pub const BEACON_ATTESTER: Self = Self(hex!("01000000")); + pub const RANDAO: Self = Self(hex!("02000000")); + pub const DEPOSIT: Self = Self(hex!("03000000")); + pub const VOLUNTARY_EXIT: Self = Self(hex!("04000000")); + pub const SELECTION_PROOF: Self = Self(hex!("05000000")); + pub const AGGREGATE_AND_PROOF: Self = Self(hex!("06000000")); + pub const SYNC_COMMITTEE: Self = Self(hex!("07000000")); + pub const SYNC_COMMITTEE_SELECTION_PROOF: Self = Self(hex!("08000000")); + pub const CONTRIBUTION_AND_PROOF: Self = Self(hex!("09000000")); + pub const BLS_TO_EXECUTION_CHANGE: Self = Self(hex!("0A000000")); + pub const APPLICATION_MASK: Self = Self(hex!("00000001")); +} + +/// Return the domain for the `domain_type` and `fork_version`. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_domain) +pub fn compute_domain( + domain_type: DomainType, + fork_version: Option, + genesis_validators_root: Option, + genesis_fork_version: Version, +) -> B256 { + let fork_version = fork_version.unwrap_or(genesis_fork_version); + let genesis_validators_root = genesis_validators_root.unwrap_or_default(); + let fork_data_root = compute_fork_data_root(fork_version, genesis_validators_root); + + let mut domain = [0; 32]; + domain[..4].copy_from_slice(&domain_type.0); + domain[4..].copy_from_slice(&fork_data_root[..28]); + + FixedBytes(domain) +} diff --git a/packages/ethereum-light-client/src/types/fork.rs b/packages/ethereum-light-client/src/types/fork.rs new file mode 100644 index 000000000..fb758b93 --- /dev/null +++ b/packages/ethereum-light-client/src/types/fork.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +use super::wrappers::Version; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct Fork { + pub version: Version, + #[serde(default)] // TODO: REMOVE AND FIX IN E2E + pub epoch: u64, +} diff --git a/packages/ethereum-light-client/src/types/fork_data.rs b/packages/ethereum-light-client/src/types/fork_data.rs new file mode 100644 index 000000000..a96ba9ba --- /dev/null +++ b/packages/ethereum-light-client/src/types/fork_data.rs @@ -0,0 +1,25 @@ +use alloy_primitives::B256; +use serde::{Deserialize, Serialize}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +use crate::types::wrappers::Version; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +pub struct ForkData { + pub current_version: Version, + pub genesis_validators_root: B256, +} + +/// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`. +/// This is used primarily in signature domains to avoid collisions across forks/chains. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root) +pub fn compute_fork_data_root(current_version: Version, genesis_validators_root: B256) -> B256 { + let fork_data = ForkData { + current_version, + genesis_validators_root, + }; + + fork_data.tree_hash_root() +} diff --git a/packages/ethereum-light-client/src/types/fork_parameters.rs b/packages/ethereum-light-client/src/types/fork_parameters.rs new file mode 100644 index 000000000..164b7ab1 --- /dev/null +++ b/packages/ethereum-light-client/src/types/fork_parameters.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; + +use super::{fork::Fork, wrappers::Version}; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct ForkParameters { + pub genesis_fork_version: Version, + #[serde(default)] // TODO: REMOVE AND FIX IN E2E + pub genesis_slot: u64, + pub altair: Fork, + pub bellatrix: Fork, + pub capella: Fork, + pub deneb: Fork, +} + +/// Returns the fork version based on the `epoch` and `fork_parameters`. +/// NOTE: This implementation is based on capella. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/fork.md#modified-compute_fork_version) +pub fn compute_fork_version(fork_parameters: &ForkParameters, epoch: u64) -> Version { + if epoch >= fork_parameters.deneb.epoch { + fork_parameters.deneb.version.clone() + } else if epoch >= fork_parameters.capella.epoch { + fork_parameters.capella.version.clone() + } else if epoch >= fork_parameters.bellatrix.epoch { + fork_parameters.bellatrix.version.clone() + } else if epoch >= fork_parameters.altair.epoch { + fork_parameters.altair.version.clone() + } else { + fork_parameters.genesis_fork_version.clone() + } +} diff --git a/packages/ethereum-light-client/src/types/height.rs b/packages/ethereum-light-client/src/types/height.rs new file mode 100644 index 000000000..630bfb18 --- /dev/null +++ b/packages/ethereum-light-client/src/types/height.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct Height { + #[serde(default)] + pub revision_number: u64, + pub revision_height: u64, +} diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs new file mode 100644 index 000000000..1076ff26 --- /dev/null +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -0,0 +1,108 @@ +use alloy_primitives::{Address, B256, U256}; +use serde::{Deserialize, Serialize}; +use tree_hash_derive::TreeHash; + +use crate::config::consts::{ + floorlog2, EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX, +}; + +use super::{ + sync_committee::{SyncAggregate, SyncCommittee, TrustedSyncCommittee}, + wrappers::{MyBloom, MyBranch, MyBytes}, +}; + +const EXECUTION_BRANCH_SIZE: usize = floorlog2(EXECUTION_PAYLOAD_INDEX); +const NEXT_SYNC_COMMITTEE_BRANCH_SIZE: usize = floorlog2(NEXT_SYNC_COMMITTEE_INDEX); +const FINALITY_BRANCH_SIZE: usize = floorlog2(FINALIZED_ROOT_INDEX); + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct Header { + pub trusted_sync_committee: TrustedSyncCommittee, + pub consensus_update: LightClientUpdate, + pub account_update: AccountUpdate, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct LightClientUpdate { + /// Header attested to by the sync committee + pub attested_header: LightClientHeader, + /// Next sync committee corresponding to `attested_header.state_root` + // NOTE: These fields aren't actually optional, they are just because of the current structure of the ethereum Header. + // TODO: Remove the Option and improve ethereum::header::Header to be an enum, instead of using optional fields and bools. + #[serde(default)] + pub next_sync_committee: Option, + pub next_sync_committee_branch: Option>, + /// Finalized header corresponding to `attested_header.state_root` + pub finalized_header: LightClientHeader, + pub finality_branch: MyBranch, + /// Sync committee aggregate signature + pub sync_aggregate: SyncAggregate, + /// Slot at which the aggregate signature was created (untrusted) + pub signature_slot: u64, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct AccountUpdate { + pub account_proof: AccountProof, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct AccountProof { + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub storage_root: B256, + pub proof: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +pub struct LightClientHeader { + pub beacon: BeaconBlockHeader, + pub execution: ExecutionPayloadHeader, + pub execution_branch: MyBranch, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +pub struct BeaconBlockHeader { + pub slot: u64, + pub proposer_index: u64, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub parent_root: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub state_root: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub body_root: B256, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +pub struct ExecutionPayloadHeader { + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub parent_hash: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub fee_recipient: Address, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub state_root: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub receipts_root: B256, + pub logs_bloom: MyBloom, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub prev_randao: B256, + pub block_number: u64, + pub gas_limit: u64, + #[serde(default)] + pub gas_used: u64, + pub timestamp: u64, + pub extra_data: MyBytes, + #[serde(with = "ethereum_utils::base64::uint256")] + pub base_fee_per_gas: U256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub block_hash: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub transactions_root: B256, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub withdrawals_root: B256, + // new in Deneb + #[serde(default)] + pub blob_gas_used: u64, + // new in Deneb + #[serde(default)] + pub excess_blob_gas: u64, +} diff --git a/packages/ethereum-light-client/src/types/mod.rs b/packages/ethereum-light-client/src/types/mod.rs new file mode 100644 index 000000000..2954b14d --- /dev/null +++ b/packages/ethereum-light-client/src/types/mod.rs @@ -0,0 +1,11 @@ +pub mod bls; +pub mod domain; +pub mod fork; +pub mod fork_data; +pub mod fork_parameters; +pub mod height; +pub mod light_client; +pub mod signing_data; +pub mod storage_proof; +pub mod sync_committee; +pub mod wrappers; diff --git a/packages/ethereum-light-client/src/types/signing_data.rs b/packages/ethereum-light-client/src/types/signing_data.rs new file mode 100644 index 000000000..e2cc398e --- /dev/null +++ b/packages/ethereum-light-client/src/types/signing_data.rs @@ -0,0 +1,21 @@ +use alloy_primitives::B256; +use serde::{Deserialize, Serialize}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +pub struct SigningData { + pub object_root: B256, + pub domain: B256, +} + +/// Return the signing root for the corresponding signing data +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_signing_root) +pub fn compute_signing_root(ssz_object: &T, domain: B256) -> B256 { + SigningData { + object_root: ssz_object.tree_hash_root(), + domain, + } + .tree_hash_root() +} diff --git a/packages/ethereum-light-client/src/types/storage_proof.rs b/packages/ethereum-light-client/src/types/storage_proof.rs new file mode 100644 index 000000000..269aac24 --- /dev/null +++ b/packages/ethereum-light-client/src/types/storage_proof.rs @@ -0,0 +1,13 @@ +use alloy_primitives::{B256, U256}; +use serde::{Deserialize, Serialize}; + +use super::wrappers::MyBytes; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct StorageProof { + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub key: B256, + #[serde(with = "ethereum_utils::base64::uint256")] + pub value: U256, + pub proof: Vec, +} diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs new file mode 100644 index 000000000..519cf3bf --- /dev/null +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -0,0 +1,126 @@ +use alloy_primitives::Bytes; +use ethereum_utils::slot::compute_epoch_at_slot; +use serde::{Deserialize, Serialize}; +use tree_hash_derive::TreeHash; + +use super::{ + bls::{BlsPublicKey, BlsSignature}, + height::Height, + wrappers::VecBlsPublicKey, +}; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +pub struct SyncCommittee { + pub pubkeys: VecBlsPublicKey, + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub aggregate_pubkey: BlsPublicKey, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub enum ActiveSyncCommittee { + Current(SyncCommittee), + Next(SyncCommittee), +} + +impl Default for ActiveSyncCommittee { + fn default() -> Self { + ActiveSyncCommittee::Current(SyncCommittee { + pubkeys: VecBlsPublicKey::default(), + aggregate_pubkey: BlsPublicKey::default(), + }) + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct TrustedSyncCommittee { + pub trusted_height: Height, + pub current_sync_committee: Option, + pub next_sync_committee: Option, +} + +impl TrustedSyncCommittee { + pub fn get_active_sync_committee(&self) -> ActiveSyncCommittee { + if let Some(sync_committee) = &self.current_sync_committee { + ActiveSyncCommittee::Current(sync_committee.clone()) + } else if let Some(sync_committee) = &self.next_sync_committee { + ActiveSyncCommittee::Next(sync_committee.clone()) + } else { + ActiveSyncCommittee::default() + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct SyncAggregate { + /// The bits representing the sync committee's participation. + #[serde(with = "ethereum_utils::base64")] + pub sync_committee_bits: Bytes, // TODO: Consider changing this to a BitVector + /// The aggregated signature of the sync committee. + #[serde(with = "ethereum_utils::base64::fixed_size")] + pub sync_committee_signature: BlsSignature, +} + +impl SyncAggregate { + // TODO: Unit test + /// Returns the number of bits that are set to `true`. + #[must_use] + pub fn num_sync_committe_participants(&self) -> usize { + self.sync_committee_bits + .iter() + .map(|byte| byte.count_ones() as usize) + .sum() + } + + // TODO: Unit test + // Returns if at least 2/3 of the sync committee signed + // + // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#process_light_client_update + pub fn validate_signature_supermajority(&self) -> bool { + self.num_sync_committe_participants() * 3 >= self.sync_committee_bits.len() * 2 + } +} + +/// Returns the sync committee period at a given `epoch`. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/validator.md#sync-committee) +pub fn compute_sync_committee_period(epochs_per_sync_committee_period: u64, epoch: u64) -> u64 { + epoch / epochs_per_sync_committee_period +} + +/// Returns the sync committee period at a given `slot`. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#compute_sync_committee_period_at_slot) +pub fn compute_sync_committee_period_at_slot( + slots_per_epoch: u64, + epochs_per_sync_committee_period: u64, + slot: u64, +) -> u64 { + compute_sync_committee_period( + epochs_per_sync_committee_period, + compute_epoch_at_slot(slots_per_epoch, slot), + ) +} + +#[cfg(test)] +mod test { + use crate::{test::fixtures::load_fixture, types::light_client::Header}; + use alloy_primitives::{hex::FromHex, B256}; + use tree_hash::TreeHash; + + #[test] + fn test_sync_committee_tree_hash_root() { + let header: Header = load_fixture("client_update_ack_0"); + assert_ne!(header, Header::default()); + let sync_committee = header + .consensus_update + .next_sync_committee + .unwrap() + .tree_hash_root(); + + let expected = + B256::from_hex("0x5361eb179f7499edbf09e514d317002f1d365d72e14a56c931e9edaccca3ff29") + .unwrap(); + + assert_eq!(expected, sync_committee); + } +} diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs new file mode 100644 index 000000000..9792a86b --- /dev/null +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -0,0 +1,185 @@ +use alloy_primitives::{aliases::B32, Bloom, Bytes, FixedBytes, B256}; +use serde::{Deserialize, Serialize}; +use tree_hash::{MerkleHasher, TreeHash, BYTES_PER_CHUNK}; + +use crate::config::consts::{floorlog2, EXECUTION_PAYLOAD_INDEX}; + +use super::bls::BlsPublicKey; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct Version(#[serde(with = "ethereum_utils::base64::fixed_size")] pub B32); + +impl TreeHash for Version { + fn tree_hash_type() -> tree_hash::TreeHashType { + FixedBytes::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + FixedBytes::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct MyBytes(#[serde(with = "ethereum_utils::base64")] pub Bytes); + +impl TreeHash for MyBytes { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + let leaves = (self.0.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + + let mut hasher = MerkleHasher::with_leaves(leaves); + + for item in &self.0 { + hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() + } + + tree_hash::mix_in_length(&hasher.finish().unwrap(), self.0.len()) + } +} + +impl AsRef<[u8]> for MyBytes { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct MyBloom(#[serde(with = "ethereum_utils::base64::fixed_size")] pub Bloom); + +impl TreeHash for MyBloom { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + let leaves = (self.0.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + + let mut hasher = MerkleHasher::with_leaves(leaves); + + for item in &self.0 { + hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() + } + + hasher.finish().unwrap() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct MyBranch( + #[serde(with = "ethereum_utils::base64::fixed_size::vec::fixed_size")] pub [B256; N], +); + +impl TreeHash for MyBranch { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + let leaves = (self.0.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + let mut hasher = MerkleHasher::with_leaves(leaves); + + for item in &self.0 { + hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() + } + + hasher.finish().unwrap() + } +} + +impl Default for MyBranch { + fn default() -> Self { + Self([B256::default(); N]) + } +} + +impl From> for Vec { + fn from(val: MyBranch) -> Self { + val.0.to_vec() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct VecBlsPublicKey( + #[serde(with = "ethereum_utils::base64::fixed_size::vec")] pub Vec, +); + +impl TreeHash for VecBlsPublicKey { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + let leaves = self.0.len(); + let mut hasher = MerkleHasher::with_leaves(leaves); + + for item in &self.0 { + hasher.write(item.tree_hash_root().as_ref()).unwrap() + } + + hasher.finish().unwrap() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +pub struct MyBlsPublicKey(#[serde(with = "ethereum_utils::base64::fixed_size")] pub BlsPublicKey); + +impl TreeHash for MyBlsPublicKey { + fn tree_hash_type() -> tree_hash::TreeHashType { + FixedBytes::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + FixedBytes::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs new file mode 100644 index 000000000..6b6abca9 --- /dev/null +++ b/packages/ethereum-light-client/src/verify.rs @@ -0,0 +1,371 @@ +use alloy_primitives::B256; + +use ethereum_trie_db::trie_db::verify_account_storage_root; +use ethereum_utils::{ + ensure::ensure, + slot::{compute_epoch_at_slot, compute_slot_at_timestamp, GENESIS_SLOT}, +}; +use tree_hash::TreeHash; + +use crate::{ + client_state::ClientState, + config::consts::{ + floorlog2, get_subtree_index, EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, + NEXT_SYNC_COMMITTEE_INDEX, + }, + consensus_state::{ConsensusState, TrustedConsensusState}, + error::EthereumIBCError, + trie::validate_merkle_branch, + types::{ + bls::BlsVerify, + domain::{compute_domain, DomainType}, + fork_parameters::compute_fork_version, + light_client::{Header, LightClientHeader, LightClientUpdate}, + signing_data::compute_signing_root, + sync_committee::compute_sync_committee_period_at_slot, + }, +}; + +pub fn verify_header( + consensus_state: &ConsensusState, + client_state: &ClientState, + current_timestamp: u64, + header: &Header, + bls_verifier: V, +) -> Result<(), EthereumIBCError> { + let trusted_consensus_state = TrustedConsensusState { + state: consensus_state.clone(), + sync_committee: header.trusted_sync_committee.get_active_sync_committee(), + }; + + // Ethereum consensus-spec says that we should use the slot at the current timestamp. + let current_slot = compute_slot_at_timestamp( + client_state.genesis_time, + client_state.seconds_per_slot, + current_timestamp, + ) + .unwrap(); + + validate_light_client_update::( + client_state, + &trusted_consensus_state, + &header.consensus_update, + current_slot, + bls_verifier, + )?; + + // check whether at least 2/3 of the sync committee signed + ensure( + header + .consensus_update + .sync_aggregate + .validate_signature_supermajority(), + EthereumIBCError::NotEnoughSignatures, + )?; + + let proof_data = header.account_update.account_proof.clone(); + + verify_account_storage_root( + header.consensus_update.attested_header.execution.state_root, + client_state.ibc_contract_address, + &proof_data.proof, + proof_data.storage_root, + ) + .map_err(|err| EthereumIBCError::VerifyStorageProof(err.to_string())) +} + +// TODO: Update comments +/// Verifies if the light client `update` is valid. +/// +/// * `update`: The light client update we want to verify. +/// * `current_slot`: The slot number computed based on the current timestamp. +/// * `genesis_validators_root`: The latest `genesis_validators_root` that is saved by the light client. +/// * `bls_verifier`: BLS verification implementation. +/// +/// ## Important Notes +/// * This verification does not assume that the updated header is greater (in terms of height) than the +/// light client state. When the updated header is in the next signature period, the light client uses +/// the next sync committee to verify the signature, then it saves the next sync committee as the current +/// sync committee. However, it's not mandatory for light clients to expect the next sync committee to be given +/// during these updates. So if it's not given, the light client still can validate updates until the next signature +/// period arrives. In a situation like this, the update can be any header within the same signature period. And +/// this function only allows a non-existent next sync committee to be set in that case. It doesn't allow a sync committee +/// to be changed or removed. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#validate_light_client_update) +pub fn validate_light_client_update( + client_state: &ClientState, + trusted_consensus_state: &TrustedConsensusState, + update: &LightClientUpdate, + current_slot: u64, + bls_verifier: V, +) -> Result<(), EthereumIBCError> { + // Verify sync committee has sufficient participants + ensure( + update.sync_aggregate.num_sync_committe_participants() + >= client_state + .min_sync_committee_participants + .try_into() + .unwrap(), + EthereumIBCError::InsufficientSyncCommitteeParticipants( + update.sync_aggregate.num_sync_committe_participants(), + ), + )?; + + is_valid_light_client_header(client_state, &update.attested_header)?; + + // Verify update does not skip a sync committee period + let update_attested_slot = update.attested_header.beacon.slot; + let update_finalized_slot = update.finalized_header.beacon.slot; + + ensure( + update_finalized_slot != GENESIS_SLOT, + EthereumIBCError::FinalizedSlotIsGenesis, + )?; + + ensure( + current_slot >= update.signature_slot, + EthereumIBCError::UpdateMoreRecentThanCurrentSlot { + current_slot, + update_signature_slot: update.signature_slot, + }, + )?; + + ensure( + update.signature_slot > update_attested_slot + && update_attested_slot >= update_finalized_slot, + EthereumIBCError::InvalidSlots { + update_signature_slot: update.signature_slot, + update_attested_slot, + update_finalized_slot, + }, + )?; + + // Let's say N is the signature period of the header we store, we can only do updates with + // the following settings: + // 1. stored_period = N, signature_period = N: + // - the light client must have the `current_sync_committee` and use it to verify the new header. + // 2. stored_period = N, signature_period = N + 1: + // - the light client must have the `next_sync_committee` and use it to verify the new header. + let stored_period = compute_sync_committee_period_at_slot( + client_state.slots_per_epoch, + client_state.epochs_per_sync_committee_period, + trusted_consensus_state.finalized_slot(), + ); + let signature_period = compute_sync_committee_period_at_slot( + client_state.slots_per_epoch, + client_state.epochs_per_sync_committee_period, + update.signature_slot, + ); + + if trusted_consensus_state.next_sync_committee().is_some() { + ensure( + signature_period == stored_period || signature_period == stored_period + 1, + EthereumIBCError::InvalidSignaturePeriodWhenNextSyncCommitteeExists { + signature_period, + stored_period, + }, + )?; + } else { + ensure( + signature_period == stored_period, + EthereumIBCError::InvalidSignaturePeriodWhenNextSyncCommitteeDoesNotExist { + signature_period, + stored_period, + }, + )?; + } + + // Verify update is relevant + let update_attested_period = compute_sync_committee_period_at_slot( + client_state.slots_per_epoch, + client_state.epochs_per_sync_committee_period, + update_attested_slot, + ); + + // There are two options to do a light client update: + // 1. We are updating the header with a newer one. + // 2. We haven't set the next sync committee yet and we can use any attested header within the same + // signature period to set the next sync committee. This means that the stored header could be larger. + // The light client implementation needs to take care of it. + ensure( + update_attested_slot > trusted_consensus_state.finalized_slot() + || (update_attested_period == stored_period + && update.next_sync_committee.is_some() + && trusted_consensus_state.next_sync_committee().is_none()), + EthereumIBCError::IrrelevantUpdate { + update_attested_slot, + trusted_finalized_slot: trusted_consensus_state.finalized_slot(), + update_attested_period, + stored_period, + update_sync_committee_is_set: update.next_sync_committee.is_some(), + trusted_next_sync_committee_is_set: trusted_consensus_state + .next_sync_committee() + .is_some(), + }, + )?; + + // Verify that the `finality_branch`, if present, confirms `finalized_header` + // to match the finalized checkpoint root saved in the state of `attested_header`. + // NOTE(aeryz): We always expect to get `finalized_header` and it's embedded into the type definition. + is_valid_light_client_header(client_state, &update.finalized_header)?; + let finalized_root = update.finalized_header.beacon.tree_hash_root(); + + // This confirms that the `finalized_header` is really finalized. + validate_merkle_branch( + finalized_root, + update.finality_branch.clone().into(), + floorlog2(FINALIZED_ROOT_INDEX), + get_subtree_index(FINALIZED_ROOT_INDEX), + update.attested_header.beacon.state_root, + ) + .map_err(|e| EthereumIBCError::ValidateFinalizedHeaderFailed(Box::new(e)))?; + + // Verify that if the update contains the next sync committee, and the signature periods do match, + // next sync committees match too. + if let (Some(next_sync_committee), Some(stored_next_sync_committee)) = ( + &update.next_sync_committee, + trusted_consensus_state.next_sync_committee(), + ) { + if update_attested_period == stored_period { + ensure( + next_sync_committee == stored_next_sync_committee, + EthereumIBCError::NextSyncCommitteeMismatch { + expected: stored_next_sync_committee.aggregate_pubkey, + found: next_sync_committee.aggregate_pubkey, + }, + )?; + } + // This validates the given next sync committee against the attested header's state root. + validate_merkle_branch( + next_sync_committee.tree_hash_root(), + update + .next_sync_committee_branch + .clone() + .unwrap_or_default() + .into(), + floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), + update.attested_header.beacon.state_root, + ) + .map_err(|e| EthereumIBCError::ValidateNextSyncCommitteeFailed(Box::new(e)))?; + } + + // Verify sync committee aggregate signature + let sync_committee = if signature_period == stored_period { + trusted_consensus_state + .current_sync_committee() + .ok_or(EthereumIBCError::ExpectedCurrentSyncCommittee)? + } else { + trusted_consensus_state + .next_sync_committee() + .ok_or(EthereumIBCError::ExpectedNextSyncCommittee)? + }; + + // It's not mandatory for all of the members of the sync committee to participate. So we are extracting the + // public keys of the ones who participated. + let participant_pubkeys = update + .sync_aggregate + .sync_committee_bits + .iter() + .flat_map(|byte| (0..8).rev().map(move |i| (byte & (1 << i)) != 0)) + .zip(sync_committee.pubkeys.0.iter()) + .filter_map(|(included, pubkey)| included.then_some(pubkey)) + .collect::>(); + + let fork_version_slot = std::cmp::max(update.signature_slot, 1) - 1; + let fork_version = compute_fork_version( + &client_state.fork_parameters, + compute_epoch_at_slot(client_state.slots_per_epoch, fork_version_slot), + ); + + let domain = compute_domain( + DomainType::SYNC_COMMITTEE, + Some(fork_version), + Some(client_state.genesis_validators_root), + client_state.fork_parameters.genesis_fork_version.clone(), + ); + let signing_root = compute_signing_root(&update.attested_header.beacon, domain); + + bls_verifier + .fast_aggregate_verify( + participant_pubkeys, + signing_root, + update.sync_aggregate.sync_committee_signature, + ) + .map_err(|err| EthereumIBCError::FastAggregateVerify(err.to_string()))?; + + Ok(()) +} + +/// Validates a light client header. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/light-client/sync-protocol.md#modified-is_valid_light_client_header) +pub fn is_valid_light_client_header( + client_state: &ClientState, + header: &LightClientHeader, +) -> Result<(), EthereumIBCError> { + let epoch = compute_epoch_at_slot(client_state.slots_per_epoch, header.beacon.slot); + + if epoch < client_state.fork_parameters.deneb.epoch { + ensure( + header.execution.blob_gas_used == 0 && header.execution.excess_blob_gas == 0, + EthereumIBCError::MustBeDeneb, + )?; + } + + ensure( + epoch >= client_state.fork_parameters.capella.epoch, + EthereumIBCError::InvalidChainVersion, + )?; + + validate_merkle_branch( + get_lc_execution_root(client_state, header), + header.execution_branch.0.into(), + floorlog2(EXECUTION_PAYLOAD_INDEX), + get_subtree_index(EXECUTION_PAYLOAD_INDEX), + header.beacon.body_root, + ) +} + +pub fn get_lc_execution_root(client_state: &ClientState, header: &LightClientHeader) -> B256 { + let epoch = compute_epoch_at_slot(client_state.slots_per_epoch, header.beacon.slot); + + ensure( + epoch >= client_state.fork_parameters.deneb.epoch, + "only deneb or higher epochs are supported", + ) + .unwrap(); + + header.execution.tree_hash_root() +} + +#[cfg(test)] +mod test { + use crate::test::{bls_verifier::TestBlsVerifier, fixtures::load_fixture}; + + use super::*; + + #[test] + fn test_verify_header() { + let bls_verifier = TestBlsVerifier; + + let client_state: ClientState = load_fixture("initial_client_state_fixture"); + assert_ne!(client_state, ClientState::default()); + + let consensus_state: ConsensusState = load_fixture("initial_consensus_state_fixture"); + assert_ne!(consensus_state, ConsensusState::default()); + + let header: Header = load_fixture("client_update_ack_0"); + assert_ne!(header, Header::default()); + + verify_header( + &consensus_state, + &client_state, + header.consensus_update.attested_header.execution.timestamp + 1000, + &header, + bls_verifier, + ) + .unwrap(); + } +} diff --git a/packages/ethereum-trie-db/Cargo.toml b/packages/ethereum-trie-db/Cargo.toml new file mode 100644 index 000000000..46f812a0 --- /dev/null +++ b/packages/ethereum-trie-db/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ethereum-trie-db" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +[dependencies] +ethereum-utils = { workspace = true } + +trie-db = { workspace = true } +hash-db = { workspace = true } +memory-db = { workspace = true } +hash256-std-hasher = { workspace = true } +rlp = { workspace = true } +rlp-derive = { workspace = true } +primitive-types = { workspace = true } +sha3 = { workspace = true } + +alloy-primitives = { workspace = true } # Needed for alloy-based interfaces/functions +thiserror = { workspace = true } +serde = { workspace = true, features = ["derive"] } + + + diff --git a/packages/ethereum-trie-db/src/error.rs b/packages/ethereum-trie-db/src/error.rs new file mode 100644 index 000000000..ec80657a --- /dev/null +++ b/packages/ethereum-trie-db/src/error.rs @@ -0,0 +1,20 @@ +use ethereum_utils::hex; + +#[derive(Debug, PartialEq, thiserror::Error, Clone)] +pub enum TrieDBError { + #[error("get trie node failed: {0}")] + GetTrieNodeFailed(String), + + #[error("rlp decoding failed: {0:?}")] + RlpDecode(#[from] rlp::DecoderError), + + #[error( + "proof is invalid due to value mismatch, expected: {expected}, actual: {actual}", + expected = hex::to_hex(expected), + actual = hex::to_hex(actual) + )] + ValueMismatch { expected: Vec, actual: Vec }, + + #[error("proof is invalid due to missing value: {v}", v = hex::to_hex(value))] + ValueMissing { value: Vec }, +} diff --git a/packages/ethereum-trie-db/src/lib.rs b/packages/ethereum-trie-db/src/lib.rs new file mode 100644 index 000000000..051c2615 --- /dev/null +++ b/packages/ethereum-trie-db/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod trie_db; +pub mod types; diff --git a/packages/ethereum-trie-db/src/trie_db.rs b/packages/ethereum-trie-db/src/trie_db.rs new file mode 100644 index 000000000..1b21eb67 --- /dev/null +++ b/packages/ethereum-trie-db/src/trie_db.rs @@ -0,0 +1,74 @@ +use alloy_primitives::{Address, B256}; +use ethereum_utils::ensure::ensure; +use hash_db::HashDB; +use memory_db::{HashKey, MemoryDB}; +use primitive_types::{H160, H256, U256}; +use rlp_derive::RlpDecodable; +use trie_db::{Trie, TrieDBBuilder}; + +use crate::{ + error::TrieDBError, + types::{keccak_256, EthLayout, KeccakHasher}, +}; + +#[derive(Debug, Clone, RlpDecodable)] +pub struct Account { + pub nonce: u64, + pub balance: U256, + pub storage_root: H256, + pub code_hash: H256, +} + +/// Verifies if the `storage_root` of a contract can be verified against the state `root`. +/// +/// * `root`: Light client update's (attested/finalized) execution block's state root. +/// * `address`: Address of the contract. +/// * `proof`: Proof of storage. +/// * `storage_root`: Storage root of the contract. +/// +/// NOTE: You must not trust the `root` unless you've verified it. +pub fn verify_account_storage_root( + root: B256, + address: Address, + proof: impl IntoIterator>, + storage_root: B256, +) -> Result<(), TrieDBError> { + let storage_root: H256 = H256(storage_root.into()); + let address: H160 = H160(address.into()); + + match get_node(root, address.as_ref(), proof)? { + Some(account) => { + let account = + rlp::decode::(account.as_ref()).map_err(TrieDBError::RlpDecode)?; + ensure( + account.storage_root == storage_root, + TrieDBError::ValueMismatch { + expected: storage_root.as_ref().into(), + actual: account.storage_root.as_ref().into(), + }, + )?; + Ok(()) + } + None => Err(TrieDBError::ValueMissing { + value: address.as_ref().into(), + })?, + } +} + +fn get_node( + root: B256, + key: impl AsRef<[u8]>, + proof: impl IntoIterator>, +) -> Result>, TrieDBError> { + let mut db = MemoryDB::, Vec>::default(); + proof.into_iter().for_each(|n| { + db.insert(hash_db::EMPTY_PREFIX, n.as_ref()); + }); + + let root: H256 = H256(root.into()); + + let trie = TrieDBBuilder::::new(&db, &root).build(); + + trie.get(&keccak_256(key.as_ref())) + .map_err(|e| TrieDBError::GetTrieNodeFailed(e.to_string())) +} diff --git a/packages/ethereum-trie-db/src/types.rs b/packages/ethereum-trie-db/src/types.rs new file mode 100644 index 000000000..0993977d --- /dev/null +++ b/packages/ethereum-trie-db/src/types.rs @@ -0,0 +1,291 @@ +// Copyright 2015-2020 Parity Technologies (UK) Ltd. +// This file is part of Open Ethereum. + +// Open Ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Open Ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Open Ethereum. If not, see . + +//! `NodeCodec` implementation for Rlp + +use std::{borrow::Borrow, marker::PhantomData, ops::Range}; + +use hash_db::Hasher; +use primitive_types::H256; +use rlp::{DecoderError, Prototype, Rlp, RlpStream}; +use sha3::{Digest, Keccak256}; +use trie_db::{ + node::{NibbleSlicePlan, NodeHandlePlan, NodePlan, Value, ValuePlan}, + ChildReference, NodeCodec, TrieLayout, +}; + +#[derive(Default, Clone)] +pub struct EthLayout; + +impl TrieLayout for EthLayout { + const USE_EXTENSION: bool = true; + const ALLOW_EMPTY: bool = false; + const MAX_INLINE_VALUE: Option = None; + type Hash = KeccakHasher; + type Codec = RlpNodeCodec; +} + +use hash256_std_hasher::Hash256StdHasher; + +/// Concrete implementation of Hasher using Keccak 256-bit hashes +#[derive(Debug)] +pub struct KeccakHasher; + +impl Hasher for KeccakHasher { + type Out = H256; + type StdHasher = Hash256StdHasher; + const LENGTH: usize = 32; + + fn hash(x: &[u8]) -> Self::Out { + keccak_256(x).into() + } +} + +/// Performs a Keccak-256 hash on the given input. +pub fn keccak_256(input: &[u8]) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.input(input); + hasher.result().into() +} + +/// Concrete implementation of a `NodeCodec` with Rlp encoding, generic over the `Hasher` +#[derive(Default, Clone)] +pub struct RlpNodeCodec { + mark: PhantomData, +} + +const HASHED_NULL_NODE_BYTES: [u8; 32] = [ + 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, + 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, +]; +const HASHED_NULL_NODE: H256 = H256(HASHED_NULL_NODE_BYTES); + +/// Encode a partial value with an iterator as input. +fn encode_partial_from_iterator_iter<'a>( + mut partial: impl Iterator + 'a, + odd: bool, + is_leaf: bool, +) -> impl Iterator + 'a { + let first = if odd { partial.next().unwrap_or(0) } else { 0 }; + encode_partial_inner_iter(first, partial, odd, is_leaf) +} + +/// Encode a partial value with an iterator as input. +fn encode_partial_inner_iter<'a>( + first_byte: u8, + partial_remaining: impl Iterator + 'a, + odd: bool, + is_leaf: bool, +) -> impl Iterator + 'a { + let encoded_type = if is_leaf { 0x20 } else { 0 }; + let first = if odd { + 0x10 + encoded_type + first_byte + } else { + encoded_type + }; + std::iter::once(first).chain(partial_remaining) +} + +fn decode_value_range(rlp: Rlp, mut offset: usize) -> Result, DecoderError> { + let payload = rlp.payload_info()?; + offset += payload.header_len; + Ok(offset..(offset + payload.value_len)) +} + +fn decode_child_handle_plan( + child_rlp: Rlp, + mut offset: usize, +) -> Result { + Ok(if child_rlp.is_data() && child_rlp.size() == H::LENGTH { + let payload = child_rlp.payload_info()?; + offset += payload.header_len; + NodeHandlePlan::Hash(offset..(offset + payload.value_len)) + } else { + NodeHandlePlan::Inline(offset..(offset + child_rlp.as_raw().len())) + }) +} + +// NOTE: what we'd really like here is: +// `impl NodeCodec for RlpNodeCodec where H::Out: Decodable` +// but due to the current limitations of Rust const evaluation we can't +// do `const HASHED_NULL_NODE: H::Out = H::Out( … … )`. Perhaps one day soon? +impl NodeCodec for RlpNodeCodec { + type Error = DecoderError; + type HashOut = ::Out; + + fn hashed_null_node() -> ::Out { + HASHED_NULL_NODE + } + + fn decode_plan(data: &[u8]) -> Result { + let r = Rlp::new(data); + match r.prototype()? { + // either leaf or extension - decode first item with NibbleSlice::??? + // and use is_leaf return to figure out which. + // if leaf, second item is a value (is_data()) + // if extension, second item is a node (either SHA3 to be looked up and + // fed back into this function or inline RLP which can be fed back into this function). + Prototype::List(2) => { + let (partial_rlp, mut partial_offset) = r.at_with_offset(0)?; + let partial_payload = partial_rlp.payload_info()?; + partial_offset += partial_payload.header_len; + + let (partial, is_leaf) = if partial_rlp.is_empty() { + ( + NibbleSlicePlan::new(partial_offset..partial_offset, 0), + false, + ) + } else { + let partial_header = partial_rlp.data()?[0]; + // check leaf bit from header. + let is_leaf = partial_header & 32 == 32; + // Check the header bit to see if we're dealing with an odd partial (only a nibble of header info) + // or an even partial (skip a full byte). + let (start, byte_offset) = if partial_header & 16 == 16 { + (0, 1) + } else { + (1, 0) + }; + let range = + (partial_offset + start)..(partial_offset + partial_payload.value_len); + (NibbleSlicePlan::new(range, byte_offset), is_leaf) + }; + + let (value_rlp, value_offset) = r.at_with_offset(1)?; + Ok(if is_leaf { + let value = decode_value_range(value_rlp, value_offset)?; + NodePlan::Leaf { + partial, + value: ValuePlan::Inline(value), + } + } else { + let child = decode_child_handle_plan::(value_rlp, value_offset)?; + NodePlan::Extension { partial, child } + }) + } + // branch - first 16 are nodes, 17th is a value (or empty). + Prototype::List(17) => { + let mut children = [ + None, None, None, None, None, None, None, None, None, None, None, None, None, + None, None, None, + ]; + for (i, child) in children.iter_mut().enumerate() { + let (child_rlp, child_offset) = r.at_with_offset(i)?; + if !child_rlp.is_empty() { + *child = Some(decode_child_handle_plan::( + child_rlp, + child_offset, + )?); + } + } + let (value_rlp, value_offset) = r.at_with_offset(16)?; + let value = if value_rlp.is_empty() { + None + } else { + Some(decode_value_range(value_rlp, value_offset)?) + }; + Ok(NodePlan::Branch { + value: value.map(ValuePlan::Inline), + children, + }) + } + // an empty branch index. + Prototype::Data(0) => Ok(NodePlan::Empty), + // something went wrong. + _ => Err(DecoderError::Custom("Rlp is not valid.")), + } + } + + fn is_empty_node(data: &[u8]) -> bool { + Rlp::new(data).is_empty() + } + + fn empty_node() -> &'static [u8] { + &[0x80] + } + + fn leaf_node(partial: impl Iterator, _: usize, value: Value) -> Vec { + let mut stream = RlpStream::new_list(2); + stream.append_iter(partial.collect::>()); + stream.append(&match value { + Value::Inline(v) => v, + Value::Node(v) => v, + }); + stream.out().into() + } + + fn extension_node( + partial: impl Iterator, + number_nibble: usize, + child_ref: ChildReference<::Out>, + ) -> Vec { + let mut stream = RlpStream::new_list(2); + stream.append_iter(encode_partial_from_iterator_iter( + partial, + number_nibble % 2 > 0, + false, + )); + match child_ref { + ChildReference::Hash(hash) => stream.append(&hash.as_ref()), + ChildReference::Inline(inline_data, length) => { + let bytes = &AsRef::<[u8]>::as_ref(&inline_data)[..length]; + stream.append_raw(bytes, 1) + } + }; + stream.out().into() + } + + fn branch_node( + children: impl Iterator< + Item = impl Borrow::Out>>>, + >, + value: Option, + ) -> Vec { + let mut stream = RlpStream::new_list(17); + for child_ref in children { + match child_ref.borrow() { + Some(c) => match c { + ChildReference::Hash(h) => stream.append(&h.as_ref()), + ChildReference::Inline(inline_data, length) => { + let bytes = &AsRef::<[u8]>::as_ref(inline_data)[..*length]; + stream.append_raw(bytes, 1) + } + }, + None => stream.append_empty_data(), + }; + } + if let Some(value) = value { + stream.append(&match value { + Value::Inline(v) => v, + Value::Node(v) => v, + }); + } else { + stream.append_empty_data(); + } + stream.out().into() + } + + fn branch_node_nibbled( + _partial: impl Iterator, + _number_nibble: usize, + _children: impl Iterator< + Item = impl Borrow::Out>>>, + >, + _maybe_value: Option, + ) -> Vec { + unreachable!("This codec is only used with a trie Layout that uses extension node.") + } +} diff --git a/packages/ethereum-utils/Cargo.toml b/packages/ethereum-utils/Cargo.toml new file mode 100644 index 000000000..37072baf --- /dev/null +++ b/packages/ethereum-utils/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ethereum-utils" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +[dependencies] +alloy-primitives = { workspace = true } +base64 = { workspace = true } +serde = { workspace = true } diff --git a/packages/ethereum-utils/src/base64.rs b/packages/ethereum-utils/src/base64.rs new file mode 100644 index 000000000..9aaac278 --- /dev/null +++ b/packages/ethereum-utils/src/base64.rs @@ -0,0 +1,259 @@ +use base64::prelude::*; +use serde::{de, Deserialize, Deserializer}; + +pub fn serialize>(data: T, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&BASE64_STANDARD.encode(data)) +} + +pub fn deserialize<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: TryFrom>, +{ + let s = String::deserialize(deserializer)?; + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + T::try_from(decoded).map_err(|_| de::Error::custom("Invalid base64 data")) +} + +pub mod fixed_size { + use base64::prelude::*; + use serde::{de, Deserialize, Deserializer}; + + pub fn serialize>(data: T, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&BASE64_STANDARD.encode(data)) + } + + pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: TryFrom<[u8; N]>, + { + let s = String::deserialize(deserializer)?; + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + + let fixed_sized: [u8; N] = decoded + .as_slice() + .try_into() + .map_err(|_| de::Error::custom("Invalid base64 data"))?; + T::try_from(fixed_sized).map_err(|_| de::Error::custom("Invalid base64 data")) + } + + pub mod vec { + use base64::prelude::*; + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize>( + #[allow(clippy::ptr_arg)] // required by serde + bytes: &Vec, + serializer: S, + ) -> Result { + serializer.collect_seq(bytes.iter().map(|b| BASE64_STANDARD.encode(b))) + } + + pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: TryFrom<[u8; N]>, + { + let vec = Vec::::deserialize(deserializer)?; + vec.into_iter() + .map(|s| { + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + let fixed_sized: [u8; N] = decoded + .as_slice() + .try_into() + .map_err(|_| de::Error::custom("Invalid base64 data"))?; + T::try_from(fixed_sized).map_err(|_| de::Error::custom("Invalid base64 data")) + }) + .collect() + } + + pub mod fixed_size { + use base64::prelude::*; + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize, const NN: usize>( + #[allow(clippy::ptr_arg)] // required by serde + bytes: &[T; NN], + serializer: S, + ) -> Result { + serializer.collect_seq(bytes.iter().map(|b| BASE64_STANDARD.encode(b))) + } + + pub fn deserialize<'de, D, T, const N: usize, const NN: usize>( + deserializer: D, + ) -> Result<[T; NN], D::Error> + where + D: Deserializer<'de>, + T: TryFrom<[u8; N]>, + { + let vec = Vec::::deserialize(deserializer)?; + let items: Vec = vec + .into_iter() + .map(|s| { + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + let fixed_sized: [u8; N] = decoded + .as_slice() + .try_into() + .map_err(|_| de::Error::custom("Invalid base64 data"))?; + T::try_from(fixed_sized) + .map_err(|_| de::Error::custom("Invalid base64 data")) + }) + .collect::, _>>()?; + + items + .try_into() + .map_err(|_| de::Error::custom("Invalid base64 data")) + } + } + + pub mod fixed_size_with_option { + use base64::prelude::*; + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize, const NN: usize>( + #[allow(clippy::ptr_arg)] // required by serde + bytes: &Option<[T; NN]>, + serializer: S, + ) -> Result { + if let Some(bytes) = bytes { + serializer.collect_seq(bytes.iter().map(|b| BASE64_STANDARD.encode(b))) + } else { + serializer.collect_seq(None::) + } + } + + pub fn deserialize<'de, D, T, const N: usize, const NN: usize>( + deserializer: D, + ) -> Result, D::Error> + where + D: Deserializer<'de>, + T: TryFrom<[u8; N]>, + { + let vec = Vec::::deserialize(deserializer)?; + if vec.is_empty() { + return Ok(None); + } + let items: Vec = vec + .into_iter() + .map(|s| { + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + let fixed_sized: [u8; N] = decoded + .as_slice() + .try_into() + .map_err(|_| de::Error::custom("Invalid base64 data"))?; + T::try_from(fixed_sized) + .map_err(|_| de::Error::custom("Invalid base64 data")) + }) + .collect::, _>>()?; + + let data = items + .try_into() + .map_err(|_| de::Error::custom("Invalid base64 data"))?; + + Ok(Some(data)) + } + } + } +} + +pub mod uint256 { + use alloy_primitives::U256; + use base64::{prelude::BASE64_STANDARD, Engine}; + use serde::{de, Deserialize, Deserializer}; + + pub fn serialize(data: &U256, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&BASE64_STANDARD.encode(data.to_be_bytes_vec())) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + + Ok(U256::from_be_slice(decoded.as_slice())) + } +} + +pub mod bytes { + use alloy_primitives::Bytes; + use base64::{prelude::BASE64_STANDARD, Engine}; + use serde::{de, Deserialize, Deserializer}; + + pub fn serialize(data: &Bytes, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&BASE64_STANDARD.encode(data)) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + Ok(Bytes::from(decoded)) + } +} + +pub mod option_with_default { + use base64::{prelude::BASE64_STANDARD, Engine}; + use serde::{de, Deserialize, Deserializer}; + + pub fn serialize>(data: &Option, serializer: S) -> Result + where + S: serde::Serializer, + { + if let Some(data) = data { + serializer.serialize_str(&BASE64_STANDARD.encode(data)) + } else { + serializer.serialize_str("") + } + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + for<'a> T: TryFrom<&'a [u8]>, + { + let s = String::deserialize(deserializer)?; + if s.is_empty() { + return Ok(None); + } + + let decoded = BASE64_STANDARD + .decode(s.as_bytes()) + .map_err(de::Error::custom)?; + + let data: T = T::try_from(decoded.as_slice()) + .map_err(|_| de::Error::custom("Invalid base64 data"))?; + + Ok(Some(data)) + } +} diff --git a/packages/ethereum-utils/src/ensure.rs b/packages/ethereum-utils/src/ensure.rs new file mode 100644 index 000000000..72cd8bd5 --- /dev/null +++ b/packages/ethereum-utils/src/ensure.rs @@ -0,0 +1,3 @@ +pub fn ensure(expr: bool, err: E) -> Result<(), E> { + expr.then_some(()).ok_or(err) +} diff --git a/packages/ethereum-utils/src/hex.rs b/packages/ethereum-utils/src/hex.rs new file mode 100644 index 000000000..a71f0cce --- /dev/null +++ b/packages/ethereum-utils/src/hex.rs @@ -0,0 +1,61 @@ +use alloy_primitives::{hex, U256}; + +// TODO: Unit test +pub fn to_hex>(data: T) -> String { + let data = data.as_ref(); + + let encoded = if data.is_empty() { + "0".to_string() + } else { + hex::encode(data) + }; + + format!("0x{encoded}") +} + +pub trait FromBeHex { + fn from_be_hex(hex_str: &str) -> Self; +} + +// TODO: Unit test sad paths (i.e. should this check for valid be?) +impl FromBeHex for U256 { + fn from_be_hex(hex_str: &str) -> Self { + let data = hex::decode(hex_str).unwrap(); + U256::from_be_slice(data.as_slice()) + } +} + +#[cfg(test)] +mod test { + use alloy_primitives::{hex::FromHex, B256, U256}; + + use super::FromBeHex; + + #[test] + // This is primarily to document how to convert the alloy primitives to and from hex + fn test_alloy_primitive_hex() { + // From hex string to B256 + let expected_hex = "0x75d7411cb01daad167713b5a9b7219670f0e500653cbbcd45cfe1bfe04222459"; + let b256 = B256::from_hex(expected_hex).unwrap(); + let b256_hex = format!("0x{b256:x}"); + assert_eq!(expected_hex, b256_hex); + + // From hex string to U256 + let u256 = U256::from_be_hex(expected_hex); + let u256_hex = format!("0x{u256:x}"); + assert_eq!(expected_hex, u256_hex); + + // From B256 to U256 to hex string + let u256: U256 = b256.into(); + let u256_hex = format!("0x{u256:x}"); + assert_eq!(expected_hex, u256_hex); + } + + #[test] + fn test_to_be_hex() { + let be_hex_str = "0x0000000000000000000000000000000000000000000000000000000000000001"; + let u256 = U256::from_be_hex(be_hex_str); + let num: u64 = u256.to_base_le(10).reduce(|acc, n| acc + n).unwrap(); + assert_eq!(1, num); + } +} diff --git a/packages/ethereum-utils/src/lib.rs b/packages/ethereum-utils/src/lib.rs new file mode 100644 index 000000000..a3f92e29 --- /dev/null +++ b/packages/ethereum-utils/src/lib.rs @@ -0,0 +1,4 @@ +pub mod base64; +pub mod ensure; +pub mod hex; +pub mod slot; diff --git a/packages/ethereum-utils/src/slot.rs b/packages/ethereum-utils/src/slot.rs new file mode 100644 index 000000000..baf0ce23 --- /dev/null +++ b/packages/ethereum-utils/src/slot.rs @@ -0,0 +1,19 @@ +pub const GENESIS_SLOT: u64 = 0; + +pub fn compute_slot_at_timestamp( + genesis_time: u64, + seconds_per_slot: u64, + timestamp_seconds: u64, +) -> Option { + timestamp_seconds + .checked_sub(genesis_time)? + .checked_div(seconds_per_slot)? + .checked_add(GENESIS_SLOT) +} + +/// Returns the epoch at a given `slot`. +/// +/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_epoch_at_slot) +pub fn compute_epoch_at_slot(slots_per_epoch: u64, slot: u64) -> u64 { + slot / slots_per_epoch +} diff --git a/packages/prover/Cargo.toml b/packages/sp1-ics07-tendermint-prover/Cargo.toml similarity index 100% rename from packages/prover/Cargo.toml rename to packages/sp1-ics07-tendermint-prover/Cargo.toml diff --git a/packages/prover/README.md b/packages/sp1-ics07-tendermint-prover/README.md similarity index 100% rename from packages/prover/README.md rename to packages/sp1-ics07-tendermint-prover/README.md diff --git a/packages/prover/build.rs b/packages/sp1-ics07-tendermint-prover/build.rs similarity index 100% rename from packages/prover/build.rs rename to packages/sp1-ics07-tendermint-prover/build.rs diff --git a/packages/prover/src/lib.rs b/packages/sp1-ics07-tendermint-prover/src/lib.rs similarity index 100% rename from packages/prover/src/lib.rs rename to packages/sp1-ics07-tendermint-prover/src/lib.rs diff --git a/packages/prover/src/programs.rs b/packages/sp1-ics07-tendermint-prover/src/programs.rs similarity index 100% rename from packages/prover/src/programs.rs rename to packages/sp1-ics07-tendermint-prover/src/programs.rs diff --git a/packages/prover/src/prover.rs b/packages/sp1-ics07-tendermint-prover/src/prover.rs similarity index 100% rename from packages/prover/src/prover.rs rename to packages/sp1-ics07-tendermint-prover/src/prover.rs diff --git a/packages/utils/Cargo.toml b/packages/sp1-ics07-tendermint-utils/Cargo.toml similarity index 100% rename from packages/utils/Cargo.toml rename to packages/sp1-ics07-tendermint-utils/Cargo.toml diff --git a/packages/utils/README.md b/packages/sp1-ics07-tendermint-utils/README.md similarity index 100% rename from packages/utils/README.md rename to packages/sp1-ics07-tendermint-utils/README.md diff --git a/packages/utils/src/eth.rs b/packages/sp1-ics07-tendermint-utils/src/eth.rs similarity index 100% rename from packages/utils/src/eth.rs rename to packages/sp1-ics07-tendermint-utils/src/eth.rs diff --git a/packages/utils/src/lib.rs b/packages/sp1-ics07-tendermint-utils/src/lib.rs similarity index 100% rename from packages/utils/src/lib.rs rename to packages/sp1-ics07-tendermint-utils/src/lib.rs diff --git a/packages/utils/src/light_block.rs b/packages/sp1-ics07-tendermint-utils/src/light_block.rs similarity index 100% rename from packages/utils/src/light_block.rs rename to packages/sp1-ics07-tendermint-utils/src/light_block.rs diff --git a/packages/utils/src/merkle.rs b/packages/sp1-ics07-tendermint-utils/src/merkle.rs similarity index 100% rename from packages/utils/src/merkle.rs rename to packages/sp1-ics07-tendermint-utils/src/merkle.rs diff --git a/packages/utils/src/rpc.rs b/packages/sp1-ics07-tendermint-utils/src/rpc.rs similarity index 100% rename from packages/utils/src/rpc.rs rename to packages/sp1-ics07-tendermint-utils/src/rpc.rs From bb1a04f959b4188e060395de938f390ae4ad088e Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Thu, 5 Dec 2024 16:36:30 +0100 Subject: [PATCH 02/49] testing and cleanup --- Cargo.lock | 343 ++++++++++++++- Cargo.toml | 56 +-- .../e2esuite/light_clients.go | 18 +- e2e/interchaintestv8/e2esuite/utils.go | 10 + e2e/interchaintestv8/ibc_eureka_test.go | 73 +++- e2e/interchaintestv8/relayer_test.go | 4 +- e2e/interchaintestv8/sp1_ics07_test.go | 2 +- e2e/interchaintestv8/testvalues/values.go | 8 +- e2e/interchaintestv8/types/rust_fixtures.go | 55 +++ .../{fixtures.go => solidity_fixtures.go} | 50 +-- justfile | 26 +- packages/ethereum-light-client/src/config.rs | 42 +- packages/ethereum-light-client/src/error.rs | 2 +- .../ethereum-light-client/src/membership.rs | 13 +- .../src/test/commitment_proof_fixture.json | 54 --- .../src/test/fixtures.rs | 2 +- ...dBack_Groth16_1_initial_client_state.json} | 5 +- ...ack_Groth16_2_initial_consensus_state.json | 8 + ...eumAndBack_Groth16_3_update_header_0.json} | 185 ++++---- ...eumAndBack_Groth16_4_commitment_proof.json | 54 +++ ...eumAndBack_Groth16_5_update_header_0.json} | 181 ++++---- ...eumAndBack_Groth16_6_commitment_proof.json | 54 +++ .../test/fixtures/sync_committee_fixture.json | 37 ++ .../test/initial_consensus_state_fixture.json | 8 - packages/ethereum-light-client/src/trie.rs | 26 +- .../ethereum-light-client/src/types/domain.rs | 77 +++- .../ethereum-light-client/src/types/fork.rs | 4 +- .../src/types/fork_data.rs | 9 +- .../src/types/fork_parameters.rs | 6 +- .../src/types/light_client.rs | 14 +- .../src/types/storage_proof.rs | 4 +- .../src/types/sync_committee.rs | 22 +- .../src/types/wrappers.rs | 51 +-- packages/ethereum-light-client/src/verify.rs | 12 +- packages/ethereum-utils/src/base64.rs | 58 ++- programs/08-wasm-eth/Cargo.toml | 27 ++ programs/08-wasm-eth/src/contract.rs | 411 ++++++++++++++++++ programs/08-wasm-eth/src/custom_query.rs | 79 ++++ programs/08-wasm-eth/src/error.rs | 11 + programs/08-wasm-eth/src/lib.rs | 5 + programs/08-wasm-eth/src/msg.rs | 152 +++++++ programs/08-wasm-eth/src/state.rs | 19 + 42 files changed, 1794 insertions(+), 483 deletions(-) create mode 100644 e2e/interchaintestv8/types/rust_fixtures.go rename e2e/interchaintestv8/types/{fixtures.go => solidity_fixtures.go} (82%) delete mode 100644 packages/ethereum-light-client/src/test/commitment_proof_fixture.json rename packages/ethereum-light-client/src/test/{initial_client_state_fixture.json => fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json} (88%) create mode 100644 packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json rename packages/ethereum-light-client/src/test/{client_update_ack_0.json => fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json} (51%) create mode 100644 packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json rename packages/ethereum-light-client/src/test/{client_update_recv_0.json => fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json} (50%) create mode 100644 packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json create mode 100644 packages/ethereum-light-client/src/test/fixtures/sync_committee_fixture.json delete mode 100644 packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json create mode 100644 programs/08-wasm-eth/Cargo.toml create mode 100644 programs/08-wasm-eth/src/contract.rs create mode 100644 programs/08-wasm-eth/src/custom_query.rs create mode 100644 programs/08-wasm-eth/src/error.rs create mode 100644 programs/08-wasm-eth/src/lib.rs create mode 100644 programs/08-wasm-eth/src/msg.rs create mode 100644 programs/08-wasm-eth/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index 2b2af159..cb21136e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -942,6 +942,36 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "rayon", + "zeroize", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -976,6 +1006,7 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "paste", + "rayon", "rustc_version 0.4.1", "zeroize", ] @@ -1025,6 +1056,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -1041,11 +1085,23 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ + "ark-serialize-derive", "ark-std 0.4.0", "digest 0.10.7", "num-bigint 0.4.6", ] +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-std" version = "0.3.0" @@ -1064,6 +1120,7 @@ checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", "rand", + "rayon", ] [[package]] @@ -1252,6 +1309,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bimap" version = "0.6.3" @@ -1423,6 +1486,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bnum" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" + [[package]] name = "bs58" version = "0.5.1" @@ -1664,7 +1733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", - "bech32", + "bech32 0.9.1", "bs58", "digest 0.10.7", "generic-array 0.14.7", @@ -1755,6 +1824,93 @@ dependencies = [ "tendermint-proto", ] +[[package]] +name = "cosmwasm-core" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa" + +[[package]] +name = "cosmwasm-crypto" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "cosmwasm-core", + "digest 0.10.7", + "ecdsa", + "ed25519-zebra", + "k256", + "num-traits", + "p256", + "rand_core", + "rayon", + "sha2 0.10.8", + "thiserror 1.0.69", +] + +[[package]] +name = "cosmwasm-derive" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "cosmwasm-schema" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b4d949b6041519c58993a73f4bbfba8083ba14f7001eae704865a09065845" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ef1b5835a65fcca3ab8b9a02b4f4dacc78e233a5c2f20b270efb9db0666d12" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "cosmwasm-std" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b" +dependencies = [ + "base64 0.22.1", + "bech32 0.11.0", + "bnum", + "cosmwasm-core", + "cosmwasm-crypto", + "cosmwasm-derive", + "derive_more 1.0.0", + "hex", + "rand_core", + "schemars", + "serde", + "serde-json-wasm", + "sha2 0.10.8", + "static_assertions", + "thiserror 1.0.69", +] + [[package]] name = "cpufeatures" version = "0.2.16" @@ -1826,6 +1982,33 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -1841,6 +2024,68 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-08-wasm-etheruem-light-client" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.8.14", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus", + "ethereum-light-client", + "ethereum-utils", + "ibc-proto", + "prost", + "serde", + "tendermint-proto", + "thiserror 2.0.4", +] + +[[package]] +name = "cw-multi-test" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1149fe104344cc4f4ca0fc784b7411042fd1626813fe85fd412b05252a0ae9d8" +dependencies = [ + "anyhow", + "bech32 0.11.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "itertools 0.13.0", + "prost", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror 2.0.4", +] + +[[package]] +name = "cw-storage-plus" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07dfee7f12f802431a856984a32bce1cb7da1e6c006b5409e3981035ce562dec" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", + "thiserror 1.0.69", +] + [[package]] name = "darling" version = "0.20.10" @@ -2124,6 +2369,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ecdsa" version = "0.16.9" @@ -2163,6 +2414,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.14.5", + "hex", + "rand_core", + "sha2 0.10.8", + "zeroize", +] + [[package]] name = "either" version = "1.13.0" @@ -2701,6 +2967,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fixed-hash" version = "0.8.0" @@ -3096,6 +3368,9 @@ name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -4622,6 +4897,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "p3-air" version = "0.1.4-succinct" @@ -5146,6 +5433,15 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -5705,6 +6001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" dependencies = [ "bytes", + "rlp-derive 0.2.0", "rustc-hex", ] @@ -5996,6 +6293,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.90", +] + [[package]] name = "schnellru" version = "0.2.3" @@ -6126,6 +6447,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-json-wasm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5" +dependencies = [ + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.15" @@ -6156,6 +6486,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "serde_json" version = "1.0.133" diff --git a/Cargo.toml b/Cargo.toml index 92b8b95c..0507949e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "programs/relayer", "programs/operator", "programs/sp1-programs/*", + "programs/08-wasm-eth" ] resolver = "2" @@ -24,14 +25,14 @@ sp1-ics07-tendermint-update-client = { path = "programs/sp1-programs/update-clie sp1-ics07-tendermint-membership = { path = "programs/sp1-programs/membership", default-features = false } ethereum-trie-db = { path = "packages/ethereum-trie-db", default-features = false } ethereum-utils = { path = "packages/ethereum-utils", default-features = false } -ethereum-light-client = { path = "packages/ethereum-light-client", default-features = false } +ethereum-light-client = { path = "packages/ethereum-light-client", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } serde_cbor = { version = "0.11", default-features = false } serde_with = { version = "3.11", default-features = false, features = ["alloc"] } hex = { version = "0.4", default-features = false } -base64 = { version = "0.22", default-features = false } +base64 = { version = "0.22", default-features = false } prost = { version = "0.13", default-features = false } bincode = { version = "1.3", default-features = false } subtle-encoding = { version = "0.5", default-features = false } @@ -43,7 +44,7 @@ tokio = { version = "1.0", default-features = false } axum = { version = "0.7", default-features = false } tonic = { version = "0.12", default-features = false } tonic-build = { version = "0.12", default-features = false } -reqwest = { version = "0.12", default-features = false } +reqwest = { version = "0.12", default-features = false } tracing = { version = "0.1", default-features = false } tracing-subscriber = { version = "0.3", default-features = false } @@ -53,7 +54,7 @@ futures = { version = "0.3", default-features = false } clap = { version = "4.5", default-features = false, features = ["std"] } # std feature is required for clap time = { version = "0.3", default-features = false } dotenv = { version = "0.15", default-features = false } -thiserror = "2.0" +thiserror = { version = "2.0", default-features = false } tendermint = { version = "0.40", default-features = false } tendermint-rpc = { version = "0.40", default-features = false } @@ -61,6 +62,7 @@ tendermint-light-client-verifier = { version = "0.40", default-features = false ibc-proto = { version = "0.51", default-features = false } cosmos-sdk-proto = { version = "0.26", default-features = false } +tendermint-proto = { version = "0.40", default-features = false } ibc-primitives = { version = "0.56", default-features = false } ibc-client-tendermint = { version = "0.56", default-features = false } @@ -71,14 +73,14 @@ ibc-core-commitment-types = { version = "0.56", default-features = false } ibc-core-handler-types = { version = "0.56", default-features = false } ibc-client-tendermint-types = { version = "0.56", default-features = false } -alloy = { version = "0.7", default-features = false } -alloy-contract = { version = "0.7", default-features = false } -alloy-sol-types = { version = "0.8", default-features = false } -alloy-primitives = { version = "0.8", default-features = false, features = ["serde"] } -alloy-trie = { version = "0.5", features = ["serde"] } # TODO: default-features = false, -alloy-serde = "0.7" -alloy-rlp = "0.3" -alloy-rpc-types-eth = { version = "0.7", default-features = false, features = ["serde"] } +alloy = { version = "0.7", default-features = false } +alloy-contract = { version = "0.7", default-features = false } +alloy-sol-types = { version = "0.8", default-features = false } +alloy-primitives = { version = "0.8", default-features = false, features = ["serde", "rlp"] } +alloy-trie = { version = "0.5", features = ["serde"] } # TODO: default-features = false, +alloy-serde = { version = "0.7", default-feutures = false } +alloy-rlp = { version = "0.3", default-feutures = false } +alloy-rpc-types-eth = { version = "0.7", default-features = false, features = ["serde"] } alloy-rpc-types-beacon = { version = "0.7", default-features = false, features = ["ssz"]} alloy-rpc-types-engine = { version = "0.7", default-features = false, features = ["ssz"]} @@ -86,27 +88,31 @@ sp1-sdk = { version = "3.3", default-features = false } sp1-zkvm = { version = "3.3", default-features = false } sp1-helper = { version = "3.3", default-features = false } +cosmwasm-schema = { version = "2.1", default-features = false } +cosmwasm-std = { version = "2.1", default-features = false } +cw-storage-plus = { version = "2.0", default-features = false } + # The dependencies below are maintained by Sigma Prime (for use in Lighthouse (and the broader Ethereum ecosystem)) -ethereum_ssz = "0.8" -ethereum_ssz_derive = "0.8" -tree_hash = "0.8" -tree_hash_derive = "0.8" +ethereum_ssz = { version = "0.8", default-features = false } +ethereum_ssz_derive = { version = "0.8", default-features = false } +tree_hash = { version = "0.8", default-features = false } +tree_hash_derive = { version = "0.8", default-features = false } # The dependencies below are maintained by Parity Tech -trie-db = "0.29" -hash-db = "0.16" -memory-db = "0.32" -hash256-std-hasher = "0.15" -rlp = "0.6" -rlp-derive = "0.2" -primitive-types = { version = "0.13", default-features = false, features = ["rlp"] } +trie-db = { version = "0.29", default-features = false, features = ["std"] } +hash-db = { version = "0.16", default-features = false } +memory-db = { version = "0.32", default-features = false } +hash256-std-hasher = { version = "0.15", default-features = false } +rlp = { version = "0.6", default-features = false, features = ["derive", "std"] } +rlp-derive = { version = "0.2", default-features = false } +primitive-types = { version = "0.13", default-features = false, features = ["rlp"] } # From union (might be removed if they are replaced by something more standard) typenum = { version = "1.17.0", default-features = false } # dev-dependencies -cw-multi-test = "2.2.0" -milagro_bls = { git = "https://github.com/Snowfork/milagro_bls", rev = "bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095", default-features = false } # Only used for testing, not to be used in production! +cw-multi-test = { version = "2.2.0"} +milagro_bls = { git = "https://github.com/Snowfork/milagro_bls", rev = "bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095", default-features = false } # Only used for testing, not to be used in production! [patch.crates-io] sha2-v0-9-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.9.9" } diff --git a/e2e/interchaintestv8/e2esuite/light_clients.go b/e2e/interchaintestv8/e2esuite/light_clients.go index 5836bca5..26c87f1c 100644 --- a/e2e/interchaintestv8/e2esuite/light_clients.go +++ b/e2e/interchaintestv8/e2esuite/light_clients.go @@ -19,21 +19,22 @@ import ( "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/ethereum" "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/testvalues" + "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types" ethereumligthclient "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/ethereumlightclient" ) -func (s *TestSuite) CreateEthereumLightClient(ctx context.Context, simdRelayerUser ibc.Wallet, ibcContractAddress string) { +func (s *TestSuite) CreateEthereumLightClient(ctx context.Context, simdRelayerUser ibc.Wallet, ibcContractAddress string, rustFixtureGenerator *types.RustFixtureGenerator) { switch s.ethTestnetType { case testvalues.EthTestnetTypePoW: s.createDummyLightClient(ctx, simdRelayerUser) case testvalues.EthTestnetTypePoS: - s.createUnionLightClient(ctx, simdRelayerUser, ibcContractAddress) + s.createUnionLightClient(ctx, simdRelayerUser, ibcContractAddress, rustFixtureGenerator) default: panic(fmt.Sprintf("Unrecognized Ethereum testnet type: %v", s.ethTestnetType)) } } -func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress string, minimumUpdateTo int64, simdRelayerUser ibc.Wallet) { +func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress string, minimumUpdateTo int64, simdRelayerUser ibc.Wallet, rustFixtureGenerator *types.RustFixtureGenerator) { if s.ethTestnetType != testvalues.EthTestnetTypePoS { return } @@ -182,7 +183,7 @@ func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress stri wasmClientState, _ := s.GetUnionClientState(ctx, simd, s.EthereumLightClientID) _, unionConsensusState = s.GetUnionConsensusState(ctx, simd, s.EthereumLightClientID, wasmClientState.LatestHeight) - for _, header := range headers { + for i, header := range headers { logHeader("Updating eth light client", header) headerBz := simd.Config().EncodingConfig.Codec.MustMarshal(&header) wasmHeader := ibcwasmtypes.ClientMessage{ @@ -200,6 +201,10 @@ func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress stri s.LastEtheruemLightClientUpdate = header.ConsensusUpdate.AttestedHeader.Beacon.Slot fmt.Println("Updated eth light client to block number", s.LastEtheruemLightClientUpdate) + + err = rustFixtureGenerator.GenerateRustFixture(fmt.Sprintf("update_header_%d", i), header) + s.Require().NoError(err) + time.Sleep(10 * time.Second) if s.LastEtheruemLightClientUpdate > uint64(updateTo) { @@ -211,7 +216,7 @@ func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress stri s.Require().Greater(s.LastEtheruemLightClientUpdate, uint64(minimumUpdateTo)) } -func (s *TestSuite) createUnionLightClient(ctx context.Context, simdRelayerUser ibc.Wallet, ibcContractAddress string) { +func (s *TestSuite) createUnionLightClient(ctx context.Context, simdRelayerUser ibc.Wallet, ibcContractAddress string, rustFixtureGenerator *types.RustFixtureGenerator) { eth, simd := s.ChainA, s.ChainB file, err := os.Open("e2e/interchaintestv8/wasm/ethereum_light_client_minimal.wasm.gz") @@ -311,6 +316,9 @@ func (s *TestSuite) createUnionLightClient(ctx context.Context, simdRelayerUser s.EthereumLightClientID, err = ibctesting.ParseClientIDFromEvents(res.Events) s.Require().NoError(err) s.Require().Equal("08-wasm-0", s.EthereumLightClientID) + + rustFixtureGenerator.GenerateRustFixture("initial_client_state", ethClientState) + rustFixtureGenerator.GenerateRustFixture("initial_consensus_state", ethConsensusState) } func (s *TestSuite) createDummyLightClient(ctx context.Context, simdRelayerUser ibc.Wallet) { diff --git a/e2e/interchaintestv8/e2esuite/utils.go b/e2e/interchaintestv8/e2esuite/utils.go index f306c6b6..2e17809a 100644 --- a/e2e/interchaintestv8/e2esuite/utils.go +++ b/e2e/interchaintestv8/e2esuite/utils.go @@ -11,6 +11,7 @@ import ( "io" "math/big" "strconv" + "strings" "time" "unicode" @@ -439,3 +440,12 @@ func (s *TestSuite) CreateTMClientHeader( TrustedValidators: oldHeader.TrustedValidators, } } + +func (s *TestSuite) GetTopLevelTestName() string { + parts := strings.Split(s.T().Name(), "/") + if len(parts) >= 2 { + return parts[1] + } + + return s.T().Name() +} diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index a9aeef99..f220cadd 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -47,7 +47,7 @@ import ( "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/testvalues" "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types" "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/erc20" - ethereumligthclient "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/ethereumlightclient" + ethereumlightclient "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/ethereumlightclient" "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/sp1ics07tendermint" ) @@ -56,8 +56,9 @@ import ( type IbcEurekaTestSuite struct { e2esuite.TestSuite - // Whether to generate fixtures for the solidity tests - generateFixtures bool + // Whether to generate fixtures for tests or not + generateSolidityFixtures bool + rustFixtureGenerator *types.RustFixtureGenerator // The private key of a test account key *ecdsa.PrivateKey @@ -116,8 +117,11 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context, proofType operator. os.Setenv(testvalues.EnvKeyTendermintRPC, simd.GetHostRPCAddress()) os.Setenv(testvalues.EnvKeySp1Prover, prover) os.Setenv(testvalues.EnvKeyOperatorPrivateKey, hex.EncodeToString(crypto.FromECDSA(operatorKey))) - if os.Getenv(testvalues.EnvKeyGenerateFixtures) == testvalues.EnvValueGenerateFixtures_True { - s.generateFixtures = true + if os.Getenv(testvalues.EnvKeyGenerateSolidityFixtures) == testvalues.EnvValueGenerateFixtures_True { + s.generateSolidityFixtures = true + } + if os.Getenv(testvalues.EnvKeyGenerateRustFixtures) == testvalues.EnvValueGenerateFixtures_True { + s.rustFixtureGenerator = types.NewRustFixtureGenerator(s.GetTopLevelTestName(), true) } })) @@ -178,7 +182,7 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context, proofType operator. _, simdRelayerUser := s.GetRelayerUsers(ctx) s.Require().True(s.Run("Add ethereum light client on Cosmos chain", func() { - s.CreateEthereumLightClient(ctx, simdRelayerUser, s.contractAddresses.IbcStore) + s.CreateEthereumLightClient(ctx, simdRelayerUser, s.contractAddresses.IbcStore, s.rustFixtureGenerator) })) s.Require().True(s.Run("Add client and counterparty on EVM", func() { @@ -419,12 +423,12 @@ func (s *IbcEurekaTestSuite) ICS20TransferERC20TokenfromEthereumToCosmosAndBackT var recvAck []byte var denomOnCosmos transfertypes.Denom s.Require().True(s.Run("Receive packets on Cosmos chain", func() { - s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, sendBlockNumber, simdRelayerUser) + s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, sendBlockNumber, simdRelayerUser, s.rustFixtureGenerator) recvPacketMsgs := make([]sdk.Msg, numOfTransfers) for i := 0; i < numOfTransfers; i++ { path := ibchostv2.PacketCommitmentKey(sendPacket.SourceChannel, uint64(i+1)) - storageProofBz := s.getCommitmentProof(path) + storageProofBz := s.getCommitmentProof(ctx, path) packet := channeltypesv2.Packet{ Sequence: uint64(i + 1), @@ -508,8 +512,8 @@ func (s *IbcEurekaTestSuite) ICS20TransferERC20TokenfromEthereumToCosmosAndBackT s.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) s.T().Logf("Multicall ack %d packets gas used: %d", numOfTransfers, receipt.GasUsed) - if s.generateFixtures { - s.Require().NoError(types.GenerateAndSaveFixture( + if s.generateSolidityFixtures { + s.Require().NoError(types.GenerateAndSaveSolidityFixture( fmt.Sprintf("acknowledgeMultiPacket_%d-%s.json", numOfTransfers, proofType.String()), s.contractAddresses.Erc20, "multicall", ackMulticall, sendPacket, )) @@ -660,8 +664,8 @@ func (s *IbcEurekaTestSuite) ICS20TransferERC20TokenfromEthereumToCosmosAndBackT recvBlockNumber = receipt.BlockNumber.Int64() - if s.generateFixtures { - s.Require().NoError(types.GenerateAndSaveFixture( + if s.generateSolidityFixtures { + s.Require().NoError(types.GenerateAndSaveSolidityFixture( fmt.Sprintf("receiveMultiPacket_%d-%s.json", numOfTransfers, proofType.String()), s.contractAddresses.Erc20, "multicall", multicallRecvMsg, packet, )) @@ -693,12 +697,12 @@ func (s *IbcEurekaTestSuite) ICS20TransferERC20TokenfromEthereumToCosmosAndBackT })) s.Require().True(s.Run("Acknowledge packets on Cosmos chain", func() { - s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, recvBlockNumber, simdRelayerUser) + s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, recvBlockNumber, simdRelayerUser, s.rustFixtureGenerator) ackMsgs := make([]sdk.Msg, numOfTransfers) for i := 0; i < numOfTransfers; i++ { path := ibchostv2.PacketAcknowledgementKey(returnPacket.DestinationChannel, uint64(i+1)) - storageProofBz := s.getCommitmentProof(path) + storageProofBz := s.getCommitmentProof(ctx, path) ackMsgs[i] = &channeltypesv2.MsgAcknowledgement{ Packet: returnPacket, @@ -860,8 +864,8 @@ func (s *IbcEurekaTestSuite) ICS20TransferNativeCosmosCoinsToEthereumAndBackTest s.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) recvBlockNumber = receipt.BlockNumber.Int64() - if s.generateFixtures { - s.Require().NoError(types.GenerateAndSaveFixture(fmt.Sprintf("receiveNativePacket-%s.json", pt.String()), s.contractAddresses.Erc20, "recvPacket", msg, packet)) + if s.generateSolidityFixtures { + s.Require().NoError(types.GenerateAndSaveSolidityFixture(fmt.Sprintf("receiveNativePacket-%s.json", pt.String()), s.contractAddresses.Erc20, "recvPacket", msg, packet)) } ethReceiveAckEvent, err = e2esuite.GetEvmEvent(receipt, s.ics26Contract.ParseWriteAcknowledgement) @@ -910,10 +914,10 @@ func (s *IbcEurekaTestSuite) ICS20TransferNativeCosmosCoinsToEthereumAndBackTest })) s.Require().True(s.Run("Acknowledge packet on Cosmos chain", func() { - s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, recvBlockNumber, simdRelayerUser) + s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, recvBlockNumber, simdRelayerUser, s.rustFixtureGenerator) path := ibchostv2.PacketAcknowledgementKey(sendPacket.DestinationChannel, sendPacket.Sequence) - storageProofBz := s.getCommitmentProof(path) + storageProofBz := s.getCommitmentProof(ctx, path) _, err := s.BroadcastMessages(ctx, simd, simdRelayerUser, 200_000, &channeltypesv2.MsgAcknowledgement{ Packet: sendPacket, @@ -997,10 +1001,10 @@ func (s *IbcEurekaTestSuite) ICS20TransferNativeCosmosCoinsToEthereumAndBackTest var cosmosReceiveAck []byte s.Require().True(s.Run("Receive packet on Cosmos chain", func() { - s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, sendBlockNumber, simdRelayerUser) + s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, sendBlockNumber, simdRelayerUser, s.rustFixtureGenerator) path := ibchostv2.PacketCommitmentKey(returnPacket.SourceChannel, uint64(returnPacket.Sequence)) - storageProofBz := s.getCommitmentProof(path) + storageProofBz := s.getCommitmentProof(ctx, path) _, err := s.BroadcastMessages(ctx, simd, simdRelayerUser, 200_000, &channeltypesv2.MsgRecvPacket{ Packet: channeltypesv2.Packet{ @@ -1170,8 +1174,8 @@ func (s *IbcEurekaTestSuite) ICS20TransferTimeoutFromEthereumToCosmosChainTest(c receipt := s.GetTxReciept(ctx, eth, tx.Hash()) s.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) - if s.generateFixtures { - s.Require().NoError(types.GenerateAndSaveFixture(fmt.Sprintf("timeoutPacket-%s.json", pt.String()), s.contractAddresses.Erc20, "timeoutPacket", msg, packet)) + if s.generateSolidityFixtures { + s.Require().NoError(types.GenerateAndSaveSolidityFixture(fmt.Sprintf("timeoutPacket-%s.json", pt.String()), s.contractAddresses.Erc20, "timeoutPacket", msg, packet)) } s.Require().True(s.Run("Verify balances on Ethereum", func() { @@ -1226,7 +1230,7 @@ func (s *IbcEurekaTestSuite) createICS20MsgSendPacket( } } -func (s *IbcEurekaTestSuite) getCommitmentProof(path []byte) []byte { +func (s *IbcEurekaTestSuite) getCommitmentProof(ctx context.Context, path []byte) []byte { eth, simd := s.ChainA, s.ChainB storageKey := ethereum.GetCommitmentsStorageKey(path) @@ -1240,11 +1244,32 @@ func (s *IbcEurekaTestSuite) getCommitmentProof(path []byte) []byte { for _, proofStr := range proofResp.StorageProof[0].Proof { proofBz = append(proofBz, ethcommon.FromHex(proofStr)) } - storageProof := ethereumligthclient.StorageProof{ + storageProof := ethereumlightclient.StorageProof{ Key: ethereum.HexToBeBytes(proofResp.StorageProof[0].Key), Value: ethereum.HexToBeBytes(proofResp.StorageProof[0].Value), Proof: proofBz, } + + if s.rustFixtureGenerator.ShouldGenerateFixture() { + _, unionClientState := s.GetUnionClientState(ctx, simd, s.EthereumLightClientID) + _, unionConsensusState := s.GetUnionConsensusState(ctx, simd, s.EthereumLightClientID, clienttypes.Height{ + RevisionNumber: 0, + RevisionHeight: s.LastEtheruemLightClientUpdate, + }) + + err = s.rustFixtureGenerator.GenerateRustFixture("commitment_proof", &types.CommitmentProofFixture{ + Path: path, + StorageProof: storageProof, + ProofHeight: clienttypes.Height{ + RevisionNumber: 0, + RevisionHeight: s.LastEtheruemLightClientUpdate, + }, + ClientState: unionClientState, + ConsensusState: unionConsensusState, + }) + s.Require().NoError(err) + } + return simd.Config().EncodingConfig.Codec.MustMarshal(&storageProof) } diff --git a/e2e/interchaintestv8/relayer_test.go b/e2e/interchaintestv8/relayer_test.go index c12342f2..4c8ef5c0 100644 --- a/e2e/interchaintestv8/relayer_test.go +++ b/e2e/interchaintestv8/relayer_test.go @@ -372,12 +372,12 @@ func (s *RelayerTestSuite) ICS20TransferERC20TokenBatchedAckTest( var txHash []byte s.Require().True(s.Run("Receive packets on Cosmos chain", func() { - s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, sendBlockNumber, simdRelayerUser) + s.UpdateEthClient(ctx, s.contractAddresses.IbcStore, sendBlockNumber, simdRelayerUser, s.rustFixtureGenerator) recvPacketMsgs := make([]sdk.Msg, numOfTransfers) for i := 0; i < numOfTransfers; i++ { path := ibchostv2.PacketCommitmentKey(sendPacket.SourceChannel, uint64(i+1)) - storageProofBz := s.getCommitmentProof(path) + storageProofBz := s.getCommitmentProof(ctx, path) packet := channeltypesv2.Packet{ Sequence: uint64(i + 1), diff --git a/e2e/interchaintestv8/sp1_ics07_test.go b/e2e/interchaintestv8/sp1_ics07_test.go index 54762382..8bf85a3d 100644 --- a/e2e/interchaintestv8/sp1_ics07_test.go +++ b/e2e/interchaintestv8/sp1_ics07_test.go @@ -74,7 +74,7 @@ func (s *SP1ICS07TendermintTestSuite) SetupSuite(ctx context.Context, pt operato os.Setenv(testvalues.EnvKeyTendermintRPC, simd.GetHostRPCAddress()) os.Setenv(testvalues.EnvKeySp1Prover, "network") os.Setenv(testvalues.EnvKeyOperatorPrivateKey, hex.EncodeToString(crypto.FromECDSA(s.key))) - s.generateFixtures = os.Getenv(testvalues.EnvKeyGenerateFixtures) == testvalues.EnvValueGenerateFixtures_True + s.generateFixtures = os.Getenv(testvalues.EnvKeyGenerateSolidityFixtures) == testvalues.EnvValueGenerateFixtures_True // make sure that the SP1_PRIVATE_KEY is set. s.Require().NotEmpty(os.Getenv(testvalues.EnvKeySp1PrivateKey)) diff --git a/e2e/interchaintestv8/testvalues/values.go b/e2e/interchaintestv8/testvalues/values.go index ab31a71d..4f677c8a 100644 --- a/e2e/interchaintestv8/testvalues/values.go +++ b/e2e/interchaintestv8/testvalues/values.go @@ -27,8 +27,10 @@ const ( EnvKeySp1Prover = "SP1_PROVER" // EnvKeySp1PrivateKey Private key for the prover network. EnvKeySp1PrivateKey = "SP1_PRIVATE_KEY" - // EnvKeyGenerateFixtures Generate fixtures for the solidity tests if set to true. - EnvKeyGenerateFixtures = "GENERATE_FIXTURES" + // EnvKeyGenerateSolidityFixtures Generate fixtures for the solidity tests if set to true. + EnvKeyGenerateSolidityFixtures = "GENERATE_SOLIDITY_FIXTURES" + // EnvKeyGenerateSolidityFixtures Generate fixtures for the solidity tests if set to true. + EnvKeyGenerateRustFixtures = "GENERATE_RUST_FIXTURES" // The log level for the Rust logger. EnvKeyRustLog = "RUST_LOG" // Address of the SP1ICS07Tendermint contract for operator. @@ -59,6 +61,8 @@ const ( SolidityFixturesDir = "test/solidity-ibc/fixtures/" // SP1ICS07FixturesDir is the directory where the SP1ICS07 fixtures are stored. SP1ICS07FixturesDir = "test/sp1-ics07/fixtures" + // RustFixturesDir is the directory where the Rust fixtures are stored. + RustFixturesDir = "packages/ethereum-light-client/src/test/fixtures" // RelayerConfigFilePath is the path to generate the relayer config file. RelayerConfigFilePath = "programs/relayer/config.json" // E2EDeployScriptPath is the path to the E2E deploy script. diff --git a/e2e/interchaintestv8/types/rust_fixtures.go b/e2e/interchaintestv8/types/rust_fixtures.go new file mode 100644 index 000000000..60e3eb3e --- /dev/null +++ b/e2e/interchaintestv8/types/rust_fixtures.go @@ -0,0 +1,55 @@ +package types + +import ( + "encoding/json" + "fmt" + "os" + + clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" + "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/testvalues" + ethereumlightclient "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/ethereumlightclient" +) + +type CommitmentProofFixture struct { + Path []byte `json:"path"` + StorageProof ethereumlightclient.StorageProof `json:"storage_proof"` + ProofHeight clienttypes.Height `json:"proof_height"` + ClientState ethereumlightclient.ClientState `json:"client_state"` + ConsensusState ethereumlightclient.ConsensusState `json:"consensus_state"` +} + +type RustFixtureGenerator struct { + shouldGenerateFixture bool + prefix string + + // fixtureCount is used to create a clear order of fixtures + fixtureCount uint +} + +// NewRustFixtureGenerator creates a new RustFixtureGenerator +// If shouldGenerateFixture is false, the generator will not generate any fixtures +func NewRustFixtureGenerator(prefix string, shouldGenerateFixture bool) *RustFixtureGenerator { + return &RustFixtureGenerator{ + prefix: prefix, + shouldGenerateFixture: shouldGenerateFixture, + } +} + +// GenerateRustFixture generates a fixture by json marshalling jsonMarshalble and saves it to a file +func (g *RustFixtureGenerator) GenerateRustFixture(name string, jsonMarshalble interface{}) error { + fixturesBz, err := json.MarshalIndent(jsonMarshalble, "", " ") + if err != nil { + return err + } + + g.fixtureCount++ + + fixtureName := fmt.Sprintf("%s_%d_%s", g.prefix, g.fixtureCount, name) + filePath := fmt.Sprintf("%s/%s.json", testvalues.RustFixturesDir, fixtureName) + + return os.WriteFile(filePath, fixturesBz, 0644) +} + +func (g *RustFixtureGenerator) ShouldGenerateFixture() bool { + return g.shouldGenerateFixture +} diff --git a/e2e/interchaintestv8/types/fixtures.go b/e2e/interchaintestv8/types/solidity_fixtures.go similarity index 82% rename from e2e/interchaintestv8/types/fixtures.go rename to e2e/interchaintestv8/types/solidity_fixtures.go index 609c133c..032073ca 100644 --- a/e2e/interchaintestv8/types/fixtures.go +++ b/e2e/interchaintestv8/types/solidity_fixtures.go @@ -14,8 +14,8 @@ import ( "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/testvalues" ) -// GenericFixture is the fixture to be unmarshalled into a test case in Solidity tests -type GenericFixture struct { +// GenericSolidityFixture is the fixture to be unmarshalled into a test case in Solidity tests +type GenericSolidityFixture struct { // Hex encoded bytes for sp1 genesis fixture Sp1GenesisFixture string `json:"sp1_genesis_fixture"` // Hex encoded bytes to be fed into the router contract @@ -28,29 +28,46 @@ type GenericFixture struct { Timestamp int64 `json:"timestamp"` } -func generateFixture(erc20Address, methodName string, msg any, packet ics26router.IICS26RouterMsgsPacket) (GenericFixture, error) { +// GenerateAndSaveSolidityFixture generates a fixture and saves it to a file +func GenerateAndSaveSolidityFixture(fileName, erc20Address, methodName string, msg any, packet ics26router.IICS26RouterMsgsPacket) error { + fixture, err := generateFixture(erc20Address, methodName, msg, packet) + if err != nil { + return err + } + + fixtureBz, err := json.Marshal(fixture) + if err != nil { + return err + } + + filePath := testvalues.SolidityFixturesDir + "/" + fileName + // nolint:gosec + return os.WriteFile(filePath, fixtureBz, 0o644) +} + +func generateFixture(erc20Address, methodName string, msg any, packet ics26router.IICS26RouterMsgsPacket) (GenericSolidityFixture, error) { genesisBz, err := getGenesisFixture() if err != nil { - return GenericFixture{}, err + return GenericSolidityFixture{}, err } ics26Abi, err := abi.JSON(strings.NewReader(ics26router.ContractMetaData.ABI)) if err != nil { - return GenericFixture{}, err + return GenericSolidityFixture{}, err } packetBz, err := abiEncodePacket(packet) if err != nil { - return GenericFixture{}, err + return GenericSolidityFixture{}, err } msgBz, err := ics26Abi.Pack(methodName, msg) if err != nil { - return GenericFixture{}, err + return GenericSolidityFixture{}, err } // Generate the fixture - fixture := GenericFixture{ + fixture := GenericSolidityFixture{ Sp1GenesisFixture: hex.EncodeToString(genesisBz), Msg: hex.EncodeToString(msgBz), Erc20Address: erc20Address, @@ -103,20 +120,3 @@ func abiEncodePacket(packet ics26router.IICS26RouterMsgsPacket) ([]byte, error) return args.Pack(packet) } - -// GenerateAndSaveFixture generates a fixture and saves it to a file -func GenerateAndSaveFixture(fileName, erc20Address, methodName string, msg any, packet ics26router.IICS26RouterMsgsPacket) error { - fixture, err := generateFixture(erc20Address, methodName, msg, packet) - if err != nil { - return err - } - - fixtureBz, err := json.Marshal(fixture) - if err != nil { - return err - } - - filePath := testvalues.SolidityFixturesDir + "/" + fileName - // nolint:gosec - return os.WriteFile(filePath, fixtureBz, 0o644) -} diff --git a/justfile b/justfile index 5008a98e..6608f4f3 100644 --- a/justfile +++ b/justfile @@ -115,23 +115,23 @@ deploy-sp1-ics07: genesis-sp1-ics07 generate-fixtures-solidity: clean @echo "Generating fixtures... This may take a while." @echo "Generating recvPacket and acknowledgePacket groth16 fixtures..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferERC20TokenfromEthereumToCosmosAndBack_Groth16$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferERC20TokenfromEthereumToCosmosAndBack_Groth16$' -timeout 40m @echo "Generating recvPacket and acknowledgePacket plonk fixtures..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferERC20TokenfromEthereumToCosmosAndBack_Plonk$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferERC20TokenfromEthereumToCosmosAndBack_Plonk$' -timeout 40m @echo "Generating recvPacket and acknowledgePacket groth16 fixtures for 25 packets..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/Test_25_ICS20TransferERC20TokenfromEthereumToCosmosAndBack_Groth16$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/Test_25_ICS20TransferERC20TokenfromEthereumToCosmosAndBack_Groth16$' -timeout 40m @echo "Generating recvPacket and acknowledgePacket groth16 fixtures for 50 packets..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/Test_50_ICS20TransferERC20TokenfromEthereumToCosmosAndBack_Groth16$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/Test_50_ICS20TransferERC20TokenfromEthereumToCosmosAndBack_Groth16$' -timeout 40m @echo "Generating recvPacket and acknowledgePacket plonk fixtures for 50 packets..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/Test_50_ICS20TransferERC20TokenfromEthereumToCosmosAndBack_Plonk$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/Test_50_ICS20TransferERC20TokenfromEthereumToCosmosAndBack_Plonk$' -timeout 40m @echo "Generating native SdkCoin recvPacket groth16 fixtures..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16$' -timeout 40m @echo "Generating native SdkCoin recvPacket plonk fixtures..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Plonk$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Plonk$' -timeout 40m @echo "Generating timeoutPacket groth16 fixtures..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferTimeoutFromEthereumToCosmosChain_Groth16$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferTimeoutFromEthereumToCosmosChain_Groth16$' -timeout 40m @echo "Generating timeoutPacket plonk fixtures..." - cd e2e/interchaintestv8 && GENERATE_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferTimeoutFromEthereumToCosmosChain_Plonk$' -timeout 40m + cd e2e/interchaintestv8 && GENERATE_SOLIDITY_FIXTURES=true SP1_PROVER=network go test -v -run '^TestWithIbcEurekaTestSuite/TestICS20TransferTimeoutFromEthereumToCosmosChain_Plonk$' -timeout 40m # Generate the fixture files for the Celestia Mocha testnet using the prover parameter. # The prover parameter should be one of: ["mock", "network", "local"] @@ -151,10 +151,10 @@ generate-fixtures-sp1-ics07: build-operator "sleep 60 && RUST_LOG=info SP1_PROVER=network ./target/release/operator fixtures update-client-and-membership --key-paths clients/07-tendermint-0/clientState,clients/07-tendermint-001/clientState --trusted-block $TRUSTED_HEIGHT --target-block $TARGET_HEIGHT -p groth16 -o 'test/sp1-ics07/fixtures/uc_and_memberships_fixture-groth16.json'" \ "sleep 80 && RUST_LOG=info SP1_PROVER=network ./target/release/operator fixtures membership --key-paths clients/07-tendermint-0/clientState,clients/07-tendermint-001/clientState --trusted-block $TRUSTED_HEIGHT -o 'test/sp1-ics07/fixtures/memberships_fixture-plonk.json'" \ "sleep 100 && RUST_LOG=info SP1_PROVER=network ./target/release/operator fixtures membership --key-paths clients/07-tendermint-0/clientState,clients/07-tendermint-001/clientState --trusted-block $TRUSTED_HEIGHT -p groth16 -o 'test/sp1-ics07/fixtures/memberships_fixture-groth16.json'" - cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/TestDoubleSignMisbehaviour_Plonk$' -timeout 40m - cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/TestBreakingTimeMonotonicityMisbehaviour_Groth16' -timeout 40m - cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/Test100Membership_Groth16' -timeout 40m - cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/Test25Membership_Plonk' -timeout 40m + cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_SOLIDITY_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/TestDoubleSignMisbehaviour_Plonk$' -timeout 40m + cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_SOLIDITY_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/TestBreakingTimeMonotonicityMisbehaviour_Groth16' -timeout 40m + cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_SOLIDITY_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/Test100Membership_Groth16' -timeout 40m + cd e2e/interchaintestv8 && RUST_LOG=info SP1_PROVER=network GENERATE_SOLIDITY_FIXTURES=true go test -v -run '^TestWithSP1ICS07TendermintTestSuite/Test25Membership_Plonk' -timeout 40m @echo "Fixtures generated at 'test/sp1-ics07/fixtures'" protoImageName := "ghcr.io/cosmos/proto-builder:0.14.0" diff --git a/packages/ethereum-light-client/src/config.rs b/packages/ethereum-light-client/src/config.rs index fe5477d8..532855a5 100644 --- a/packages/ethereum-light-client/src/config.rs +++ b/packages/ethereum-light-client/src/config.rs @@ -132,7 +132,7 @@ self::mk_chain_spec!(Mainnet is preset::MAINNET); pub(crate) use mk_chain_spec; -use crate::types::{fork::Fork, fork_parameters::ForkParameters, wrappers::Version}; +use crate::types::{fork::Fork, fork_parameters::ForkParameters, wrappers::WrappedVersion}; /// Values that are constant across all configurations. pub mod consts { @@ -273,22 +273,22 @@ pub struct Config { pub const GOERLI: Config = Config { preset: preset::MAINNET, fork_parameters: ForkParameters { - genesis_fork_version: Version(FixedBytes([0, 0, 16, 32])), + genesis_fork_version: WrappedVersion(FixedBytes([0, 0, 16, 32])), genesis_slot: (0), altair: Fork { - version: Version(FixedBytes([1, 0, 16, 32])), + version: WrappedVersion(FixedBytes([1, 0, 16, 32])), epoch: (36660), }, bellatrix: Fork { - version: Version(FixedBytes([2, 0, 16, 32])), + version: WrappedVersion(FixedBytes([2, 0, 16, 32])), epoch: 112_260, }, capella: Fork { - version: Version(FixedBytes([3, 0, 16, 32])), + version: WrappedVersion(FixedBytes([3, 0, 16, 32])), epoch: 162_304, }, deneb: Fork { - version: Version(FixedBytes([4, 0, 16, 32])), + version: WrappedVersion(FixedBytes([4, 0, 16, 32])), epoch: 231_680, }, }, @@ -298,24 +298,24 @@ pub const GOERLI: Config = Config { pub const MAINNET: Config = Config { preset: preset::MAINNET, fork_parameters: ForkParameters { - genesis_fork_version: Version(FixedBytes([0, 0, 0, 0])), + genesis_fork_version: WrappedVersion(FixedBytes([0, 0, 0, 0])), genesis_slot: 0, altair: Fork { - version: Version(FixedBytes([1, 0, 0, 0])), + version: WrappedVersion(FixedBytes([1, 0, 0, 0])), epoch: 74_240, }, bellatrix: Fork { - version: Version(FixedBytes([2, 0, 0, 0])), + version: WrappedVersion(FixedBytes([2, 0, 0, 0])), epoch: 144_896, }, capella: Fork { - version: Version(FixedBytes([3, 0, 0, 0])), + version: WrappedVersion(FixedBytes([3, 0, 0, 0])), epoch: 194_048, }, // TODO: enabled march 13th 2024 deneb: Fork { - version: Version(FixedBytes([4, 0, 0, 0])), + version: WrappedVersion(FixedBytes([4, 0, 0, 0])), epoch: u64::MAX, }, }, @@ -325,27 +325,27 @@ pub const MAINNET: Config = Config { pub const MINIMAL: Config = Config { preset: preset::MINIMAL, fork_parameters: ForkParameters { - genesis_fork_version: Version(FixedBytes([0, 0, 0, 1])), + genesis_fork_version: WrappedVersion(FixedBytes([0, 0, 0, 1])), genesis_slot: 0, altair: Fork { - version: Version(FixedBytes([1, 0, 0, 1])), + version: WrappedVersion(FixedBytes([1, 0, 0, 1])), epoch: 0, }, bellatrix: Fork { - version: Version(FixedBytes([2, 0, 0, 1])), + version: WrappedVersion(FixedBytes([2, 0, 0, 1])), epoch: 0, }, capella: Fork { - version: Version(FixedBytes([3, 0, 0, 1])), + version: WrappedVersion(FixedBytes([3, 0, 0, 1])), epoch: 0, }, // NOTE: dummy data deneb: Fork { - version: Version(FixedBytes([4, 0, 0, 1])), + version: WrappedVersion(FixedBytes([4, 0, 0, 1])), epoch: 0, }, }, @@ -355,26 +355,26 @@ pub const MINIMAL: Config = Config { pub const SEPOLIA: Config = Config { preset: preset::MAINNET, fork_parameters: ForkParameters { - genesis_fork_version: Version(FixedBytes([144, 0, 0, 105])), + genesis_fork_version: WrappedVersion(FixedBytes([144, 0, 0, 105])), genesis_slot: 0, altair: Fork { - version: Version(FixedBytes([144, 0, 0, 112])), + version: WrappedVersion(FixedBytes([144, 0, 0, 112])), epoch: 50, }, bellatrix: Fork { - version: Version(FixedBytes([144, 0, 0, 113])), + version: WrappedVersion(FixedBytes([144, 0, 0, 113])), epoch: 100, }, capella: Fork { - version: Version(FixedBytes([144, 0, 0, 114])), + version: WrappedVersion(FixedBytes([144, 0, 0, 114])), epoch: 56_832, }, deneb: Fork { - version: Version(FixedBytes([144, 0, 0, 115])), + version: WrappedVersion(FixedBytes([144, 0, 0, 115])), epoch: 132_608, }, }, diff --git a/packages/ethereum-light-client/src/error.rs b/packages/ethereum-light-client/src/error.rs index fe0ac226..f168f434 100644 --- a/packages/ethereum-light-client/src/error.rs +++ b/packages/ethereum-light-client/src/error.rs @@ -28,7 +28,7 @@ pub enum EthereumIBCError { InvalidChainVersion, #[error(transparent)] - InvalidMerkleBranch(#[from] InvalidMerkleBranch), + InvalidMerkleBranch(#[from] Box), // boxed to decrease enum size #[error("finalized slot cannot be the genesis slot")] FinalizedSlotIsGenesis, diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs index 26df2031..119c4d0b 100644 --- a/packages/ethereum-light-client/src/membership.rs +++ b/packages/ethereum-light-client/src/membership.rs @@ -87,7 +87,7 @@ mod test { client_state::ClientState, consensus_state::ConsensusState, test::fixtures::{load_fixture, CommitmentProofFixture}, - types::{storage_proof::StorageProof, wrappers::MyBytes}, + types::{storage_proof::StorageProof, wrappers::WrappedBytes}, }; use alloy_primitives::{ hex::{self, FromHex}, @@ -99,8 +99,9 @@ mod test { #[test] fn test_with_fixture() { - let commitment_proof_fixture: CommitmentProofFixture = - load_fixture("commitment_proof_fixture"); + let commitment_proof_fixture: CommitmentProofFixture = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof", + ); let trusted_consensus_state = commitment_proof_fixture.consensus_state; let client_state = commitment_proof_fixture.client_state; @@ -142,8 +143,8 @@ mod test { U256::from_be_hex("0xb2ae8ab0be3bda2f81dc166497902a1832fea11b886bc7a0980dec7a219582db"); let proof = vec![ - MyBytes(Bytes::from_hex("0xf8718080a0911797c4b8cdbd1d8fa643b31ff0a469fae0f9b2ecbb0fa45a5ebe497f5e7130a065ea7eb6ae4e9747a131961beda4e9fd3040521e58845f4a286fb472eb0415168080a057b16d9a3bbb2d106b4d1b12dca3504f61899c7c660b036848511426ed342dd680808080808080808080").unwrap()), - MyBytes(Bytes::from_hex("0xf843a03d3c3bcf030006afea2a677a6ff5bf3f7f111e87461c8848cf062a5756d1a888a1a0b2ae8ab0be3bda2f81dc166497902a1832fea11b886bc7a0980dec7a219582db").unwrap()), + WrappedBytes(Bytes::from_hex("0xf8718080a0911797c4b8cdbd1d8fa643b31ff0a469fae0f9b2ecbb0fa45a5ebe497f5e7130a065ea7eb6ae4e9747a131961beda4e9fd3040521e58845f4a286fb472eb0415168080a057b16d9a3bbb2d106b4d1b12dca3504f61899c7c660b036848511426ed342dd680808080808080808080").unwrap()), + WrappedBytes(Bytes::from_hex("0xf843a03d3c3bcf030006afea2a677a6ff5bf3f7f111e87461c8848cf062a5756d1a888a1a0b2ae8ab0be3bda2f81dc166497902a1832fea11b886bc7a0980dec7a219582db").unwrap()), ]; let path = vec![hex::decode("0x30372d74656e6465726d696e742d30010000000000000001").unwrap()]; @@ -194,7 +195,7 @@ mod test { .unwrap(); let proof = vec![ - MyBytes(Bytes::from_hex("0xf838a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594eb9407e2a087056b69d43d21df69b82e31533c8a").unwrap()), + WrappedBytes(Bytes::from_hex("0xf838a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594eb9407e2a087056b69d43d21df69b82e31533c8a").unwrap()), ]; let path = vec![hex::decode("0x30372d74656e6465726d696e742d30020000000000000001").unwrap()]; diff --git a/packages/ethereum-light-client/src/test/commitment_proof_fixture.json b/packages/ethereum-light-client/src/test/commitment_proof_fixture.json deleted file mode 100644 index bfcda534..000000000 --- a/packages/ethereum-light-client/src/test/commitment_proof_fixture.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "path": "MDctdGVuZGVybWludC0wAQAAAAAAAAAB", - "storage_proof": { - "key": "dddBHLAdqtFncTtam3IZZw8OUAZTy7zUXP4b/gQiJFk=", - "value": "2I8ZMhvV5J49VMMoiXCqKJDaElVciHse1occSXuIqDU=", - "proof": [ - "+LGAgKA1vP+FdEX62HLO0mDDznSBAnq4E0EvC3+NzysP+DCE1aAYkNFrQJMgx1j+VH6PJvOUGbkjS5sk/DvOLqhtJmkR1YCAoFexbZo7uy0Qa00bEtyjUE9hiZx8ZgsDaEhRFCbtNC3WgICAgICg2ewz2fYL/2sT+bFjbDoKx8vxyGbecuNvIGkP63MwSQugKk2h+7aafsHLGXnx0xO7YYjriGJC1JcwMGXs13gsanKAgIA=", - "+EOgPTw7zwMABq/qKmd6b/W/P38RHodGHIhIzwYqV1bRqIihoNiPGTIb1eSePVTDKIlwqiiQ2hJVXIh7HtaHHEl7iKg1" - ] - }, - "proof_height": { - "revision_number": 0, - "revision_height": 144 - }, - "client_state": { - "chain_id": "3151908", - "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", - "genesis_time": 1733323331, - "fork_parameters": { - "genesis_fork_version": "EAAAOA==", - "altair": { - "version": "IAAAOA==" - }, - "bellatrix": { - "version": "MAAAOA==" - }, - "capella": { - "version": "QAAAOA==" - }, - "deneb": { - "version": "UAAAOA==" - } - }, - "seconds_per_slot": 6, - "slots_per_epoch": 8, - "epochs_per_sync_committee_period": 8, - "latest_slot": 144, - "frozen_height": { - "revision_number": 0, - "revision_height": 0 - }, - "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", - "ibc_contract_address": "kLJZNUYUcyqmuyyK25qXaVWAujs=", - "min_sync_committee_participants": 32 - }, - "consensus_state": { - "slot": 144, - "state_root": "QlGpbOzuzjivfYy30jZyL0/oe0QRoXcEiZQdd2Cee+g=", - "storage_root": "CB2+UVpo5UlCwDuzbKY++v3wS24w3J/u5vNmCLcmkEQ=", - "timestamp": 1733324195000000000, - "current_sync_committee": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/", - "next_sync_committee": "i2YT7mQ5VjqF+copuvosuCOeOrl00GkwUvJXJxdLnyCWBMTyt0bdErb/XRyVjOAG" - } -} diff --git a/packages/ethereum-light-client/src/test/fixtures.rs b/packages/ethereum-light-client/src/test/fixtures.rs index 1d0e7d7e..c833f9b4 100644 --- a/packages/ethereum-light-client/src/test/fixtures.rs +++ b/packages/ethereum-light-client/src/test/fixtures.rs @@ -24,7 +24,7 @@ where { // Construct the path relative to the Cargo manifest directory let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("src/test"); + path.push("src/test/fixtures"); path.push(format!("{}.json", name)); // Open the file and deserialize its contents diff --git a/packages/ethereum-light-client/src/test/initial_client_state_fixture.json b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json similarity index 88% rename from packages/ethereum-light-client/src/test/initial_client_state_fixture.json rename to packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json index ca46f7d8..bb215567 100644 --- a/packages/ethereum-light-client/src/test/initial_client_state_fixture.json +++ b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json @@ -1,7 +1,7 @@ { "chain_id": "3151908", "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", - "genesis_time": 1733323331, + "genesis_time": 1733407803, "fork_parameters": { "genesis_fork_version": "EAAAOA==", "altair": { @@ -26,7 +26,6 @@ "revision_height": 0 }, "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", - "ibc_contract_address": "kLJZNUYUcyqmuyyK25qXaVWAujs=", + "ibc_contract_address": "Pus1Pj9GbHF70jfiXjd1yeYfhpY=", "min_sync_committee_participants": 32 - } diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json new file mode 100644 index 000000000..1da1c746 --- /dev/null +++ b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json @@ -0,0 +1,8 @@ +{ + "slot": 32, + "state_root": "yIADlPrcUUOqTri8ZYeoYh9e0nQndM7TrJVp4gQPD0k=", + "storage_root": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "timestamp": 1733407995000000000, + "current_sync_committee": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD", + "next_sync_committee": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD" +} \ No newline at end of file diff --git a/packages/ethereum-light-client/src/test/client_update_ack_0.json b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json similarity index 51% rename from packages/ethereum-light-client/src/test/client_update_ack_0.json rename to packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json index f466effb..d8df8d8a 100644 --- a/packages/ethereum-light-client/src/test/client_update_ack_0.json +++ b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json @@ -6,171 +6,170 @@ }, "next_sync_committee": { "pubkeys": [ - "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" ], - "aggregate_pubkey": "gmHVzMuPqcx/A6CKMEJLWPxv8nSa+Ub/M+WbLnMH42KuWnEZbMCtnPlGNk2zbZvx" + "aggregate_pubkey": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD" } }, "consensus_update": { "attested_header": { "beacon": { "slot": 80, - "proposer_index": 60, - "parent_root": "KTGY1cnrh9MaGTvZKAfIMJwh7U4TxOPUTlV2DnU3vhQ=", - "state_root": "hdxV7foaxAw1IvznUUahNeUVlCmOoC0+4O/Q42nslyE=", - "body_root": "VhcHNRVKhsxdIAIHZES+dd5W5AdveV2ZopPcRh/Slyk=" + "proposer_index": 25, + "parent_root": "NlekTueD21MkMXOIh+1MmF/J9CrkmZv66spYesCVsJQ=", + "state_root": "xuF1/mikzEFmF1Pc5yrD1n40GsbPGmp9M5/gqp/S0ho=", + "body_root": "7rtnE/VXD/CMMnt3ftCZBbqak9OS6Utmeeo2So+Q3gU=" }, "execution": { - "parent_hash": "IeIjL66DUoHdjlKUsO1R97lkeHcyL8vTRsqtOxIv+4A=", + "parent_hash": "wJC+gZETcSKHSj7Yu0reQWCcNkSvAInT4CiaZggu5lo=", "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "x3p3pKop4ci1bkj9fib9JWueIeiMyuf2B0kqSZN6QMg=", + "state_root": "LiJXxsCQiY7Py+Moq31eYwxPNPhl9yJ0bGlmAzvtwHw=", "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "mgm78yp3W4GGVb7WSWsH+i7QN3M3By9tBxq2EMoc4Ko=", + "prev_randao": "1jC5ZU0vKvjYcxEMFJDXIazRcwp/bgE6SJNHYo1JDd8=", "block_number": 80, "gas_limit": 30000000, - "timestamp": 1733323811, + "timestamp": 1733408283, "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ2c=", - "block_hash": "jQh77GjuWFjPVwVrRFepR3DGk+FXik5VhOyriUPcz9A=", + "block_hash": "79sNb+MH27Eg+aJBAtgrZwtAsfGPnQQwoR+vD/UePmU=", "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" }, "execution_branch": [ - "hCfwRRLO3eVJncd/ce+8ix9iVWNcACJFGhGcf5muGxI=", + "LmEeN0VFfprSbSzqEJ1d4jRMk4jiilxpyr6uRgUU+SU=", "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "pIhTiDUfYbkJOfy5+o2knxIrN70+e3d2PB68rvSPGxQ=" + "UgvNn2R4SQFIlQ1VBsGxb/ZHIU1AlpEUeOCR0iqYCJY=" ] }, "next_sync_committee": { "pubkeys": [ - "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" ], - "aggregate_pubkey": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/" + "aggregate_pubkey": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe" }, "next_sync_committee_branch": [ - "Bj1HUrNYq0t1WuBccfcVBIBBjgEkh4mcXRbQv1vt4jU=", - "J1Y6YWuDG2U27JP5xagxkcrUrQx3Dz9geIJsYNQgtG0=", - "GyG2kk+cTmjb/FJnj/gIQFo/NUUKsZ94sPjvROXw3Rs=", - "kcKMULnNQJ/GF4dTE+zzN7CU0MqYEWeJqLPyoPhe3P8=", - "Md5nqXy3UtLGZ+PW05Y6W7B0ZhYJ6297Utx8sd3A0yc=" + "YTWQEU/vCtSt0LPg5NtUfs6Uwfz7fIqmBZNY4pyZuBM=", + "sN4B29I4KUX3o9pmjH4SIkQ14VKztRixQo73PrS4PZQ=", + "OsO00eQO+jeWCS0gfXgUdEJPI1+ZAIg1kfXAAUcPsDQ=", + "sCek+pC9ySDXdCjT1t8QySt68QWaXOD3iqm12W2Jlbc=", + "7cuAvzpPr9OW7N06mldF5gn16N/5cuD28t2YXLNPZnk=" ], "finalized_header": { "beacon": { "slot": 64, - "proposer_index": 61, - "parent_root": "yZZJxxeZlqmh4HrxTIVntPa0881mtzGu1PVPKy9W8wI=", - "state_root": "6RzrP4yEsu1JqSNXUGwVHzw2Vh6go9WuxNgBTdIrAHw=", - "body_root": "ninYsM2Hsvn9SgbCqyOnUcg8XGlve4OSoymcgA2jA1w=" + "proposer_index": 39, + "parent_root": "i/FPE5oXcsSYF0xLZtWiPx7cnoBZo0Kc/HscjMVizO4=", + "state_root": "wobedKhB7hSl5kUeBzhI/KkTv7wynUESa/1KSyQNWtU=", + "body_root": "IoxUVJZ73fdRF9bhGvEcWjtkMafFPSFei5k68TX3VJo=" }, "execution": { - "parent_hash": "MWAKv7hnL23beMp6JpZ2wfudkgBApmKrz3xWv60hoFk=", + "parent_hash": "7W5Stq/gIqVRTjGxjXfczqEyS2U73FokKoA6Dh+NK7s=", "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "SKBgrfKP9po8BelsHw0yqN0zT6tubmg01pcAvarpD1M=", + "state_root": "RIquC1ijfk4hNvprzGZ8gJC/+/A794md4WqkN5kHtQ4=", "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "Ju1XcKfTnBew7PCgkIt3dWIXwqqHo0cLrK36D3TlYwg=", + "prev_randao": "mLIiuZeTtjH0ZCzXCrHYqerfIwnfjK+o2ZiUPTpdFkw=", "block_number": 64, "gas_limit": 30000000, - "timestamp": 1733323715, + "timestamp": 1733408187, "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX/4=", - "block_hash": "c/Y2GFO0JjgWkhl3sxsHHN3ZyzPYk6jxOfKr7GaaBj0=", + "block_hash": "yqb7iCDzjqlbyiS/ccGag6WMEB/AT8YnCiPCKfwt9uw=", "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" }, "execution_branch": [ - "TqyGk/jDi7gxWwPeTovL+BOX5An8a5ZxcwBDVFyPB5w=", + "xDEyxC+53LW9gybVGPp0Tfmjl62SjojALtkVQz9s9Is=", "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "smbCiytqdQ32wF9afqelV7GAtv9rhSYzCh1mHLbjK5I=" + "kCPZTUkwMbHNDxsNIgG4YFrEznahbokZe/vNmJAy3Bs=" ] }, "finality_branch": [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", - "yNg3I5+qkUkmXt9skVrpXnJwhpDfnE5NvxaoIF9zHHk=", - "GyG2kk+cTmjb/FJnj/gIQFo/NUUKsZ94sPjvROXw3Rs=", - "kcKMULnNQJ/GF4dTE+zzN7CU0MqYEWeJqLPyoPhe3P8=", - "Md5nqXy3UtLGZ+PW05Y6W7B0ZhYJ6297Utx8sd3A0yc=" + "9GI7ErmLRdu448f3+EsD+dXYrkYuINl8F/ysuorSMm0=", + "OsO00eQO+jeWCS0gfXgUdEJPI1+ZAIg1kfXAAUcPsDQ=", + "sCek+pC9ySDXdCjT1t8QySt68QWaXOD3iqm12W2Jlbc=", + "7cuAvzpPr9OW7N06mldF5gn16N/5cuD28t2YXLNPZnk=" ], "sync_aggregate": { "sync_committee_bits": "/////w==", - "sync_committee_signature": "mAAmBm2FrMOFMYwvmenu2dysVLp3426vbTjAdYbzLC6dQCcCbNn/FKsnfwy3sz/uAozfIoija6soUPFPdBAsqqF4gDQgjPXxjG19/PrMsYps3mCGU/YKE+OV2oHzseg4" + "sync_committee_signature": "hEM3Bv7mWv3oslH8317lojgRJuaZvtps80uylnLueVvlzP5of2sQnG9msQZcD3TYGfpxvX0hnAy9m/NrNd8fc4QXwnrkLgFAWqh+47Pn7EjbK9HqWVLnvB45KlwX79w0" }, "signature_slot": 81 }, "account_update": { "account_proof": { - "storage_root": "gACK4yVCkw01w2NwcyXK45pGJZuHZtosHlxxh8xI2KU=", + "storage_root": "UMSy+0fN7u9iWv60HXAmZ+xmF+Vr7Bhkrc5R/s5k43M=", "proof": [ - "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" ] } } diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json new file mode 100644 index 000000000..0fdff101 --- /dev/null +++ b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json @@ -0,0 +1,54 @@ +{ + "path": "MDctdGVuZGVybWludC0wAwAAAAAAAAAB", + "storage_proof": { + "key": "z4MIXTcym36YbZwkQWDschTHgBp83kz7bYZ/zELHO3A=", + "value": "hGDiH3O1PXeeSzKRzTUzjpL66ZmHNfGgtxUMB0wHMaY=", + "proof": [ + "+HGAgKA6BiC+LtHLi1QUcOvXp/4Ey0uNco6kzwhuY+dIrcntAICAgICAgICAgKDZ7DPZ9gv/axP5sWNsOgrHy/HIZt5y428gaQ/rczBJC6AqTaH7tpp+wcsZefHTE7thiOuIYkLUlzAwZezXeCxqcoCAgA==", + "+EOgOCBn1SkRXM4FoKixmPRs60ule4noP5PH5oOlpmmmXRahoIRg4h9ztT13nksykc01M46S+umZhzXxoLcVDAdMBzGm" + ] + }, + "proof_height": { + "revision_number": 0, + "revision_height": 80 + }, + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733407803, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 80, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "Pus1Pj9GbHF70jfiXjd1yeYfhpY=", + "min_sync_committee_participants": 32 + }, + "consensus_state": { + "slot": 80, + "state_root": "LiJXxsCQiY7Py+Moq31eYwxPNPhl9yJ0bGlmAzvtwHw=", + "storage_root": "UMSy+0fN7u9iWv60HXAmZ+xmF+Vr7Bhkrc5R/s5k43M=", + "timestamp": 1733408283000000000, + "current_sync_committee": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD", + "next_sync_committee": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe" + } +} diff --git a/packages/ethereum-light-client/src/test/client_update_recv_0.json b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json similarity index 50% rename from packages/ethereum-light-client/src/test/client_update_recv_0.json rename to packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json index 273124a9..b08efede 100644 --- a/packages/ethereum-light-client/src/test/client_update_recv_0.json +++ b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json @@ -6,171 +6,170 @@ }, "next_sync_committee": { "pubkeys": [ - "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" ], - "aggregate_pubkey": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/" + "aggregate_pubkey": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe" } }, "consensus_update": { "attested_header": { "beacon": { "slot": 144, - "proposer_index": 51, - "parent_root": "+yh1WNRauh0IoR14jOS9YYA8BSwU5ql+E4ajjh2hWbE=", - "state_root": "t2Npp4IsrZd8OLNL8v+cTMdRTuRUKovGLbE/RXfqzMo=", - "body_root": "oACCFHQz+I7uHiReeh8Fn1MUSkgNNCiIRySzM7HgrK0=" + "proposer_index": 19, + "parent_root": "noItn1+uyy+W/92z4oVJFK8ac/T80CZo/K5TktxISIk=", + "state_root": "GCjDO3cPt2zkO4y4Y2nxWL2FBaOyCKGlDCtix4hWckY=", + "body_root": "JwAXlihnGZ7+bNu0MN3YhFUnt+qXOuQVc0co9xVghLc=" }, "execution": { - "parent_hash": "kiQmPrWtDFwFuLYmrWJURMVib4HGSHrCv5hQlJZs8hA=", + "parent_hash": "pcqjSudk77UOc80zBjNwvLwTThxio3CbTpwhz0U/dio=", "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "QlGpbOzuzjivfYy30jZyL0/oe0QRoXcEiZQdd2Cee+g=", + "state_root": "JO+3htK/8MDf+cf6sh5XaZJML5L66wuPkQG39BRIT6w=", "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "DLwM+MyfeWkYifp55rURFoK9Oca1Cgxnwv3YeRoLZ9c=", + "prev_randao": "D9NU6oRyAq5uSSJGYR90uSydzEa//IsgDviMj/LY1T4=", "block_number": 144, "gas_limit": 30000000, - "timestamp": 1733324195, + "timestamp": 1733408667, "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk=", - "block_hash": "wx65XbLIRlawwdvTr5w3SCwnrfBlPQVJ8kWdXGcEib4=", + "block_hash": "2mKLFw/r+90+6w1Vfro7rM3l9W9KVpirwQKnBHEKJj0=", "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" }, "execution_branch": [ - "lZvuHxJFqiCi1zbH60V0sSFRNxjSUVtquKFE54jAyvw=", + "RwWU2OtzImFiVHzy6rQYORkIUUYQxmZT99p5sHGdZ5s=", "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "7w2pFYfCXz/YEmbX194wmgP3lDiHyPS94MrRKl8l3CY=" + "X7xxqqZgbddcXEN5PrWwYBu3NFge3O8gqUdEdOxVEJk=" ] }, "next_sync_committee": { "pubkeys": [ - "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" ], - "aggregate_pubkey": "i2YT7mQ5VjqF+copuvosuCOeOrl00GkwUvJXJxdLnyCWBMTyt0bdErb/XRyVjOAG" + "aggregate_pubkey": "llqLwJDEnKUubFmaF2HQO7oeFKuIoYxG9bAdYQ6C8Akk9M0T8jci8TNk15UoEyIl" }, "next_sync_committee_branch": [ - "U2HrF590me2/CeUU0xcALx02XXLhSlbJMentrMyj/yk=", - "ebAO+3qEsn1nB1DQQJxFDBxcgtzM8eXeJd9w8BWdSQI=", - "WymxeDBGfowY8TYHxSI/XbPWQVKEvZGNB4y9fBPuwU4=", - "ntgTQDU97bD+hJvWqBXT4OiKFJrXvYf4Z7Pv/xj/q2Y=", - "8TTv8NTCcS2oecKHq4Lh7PJO+8B6Y3rkkxj3l6zxgCI=" + "eB34gOFsk1/HX2I1CV9Ih3bIb269+feRs8mJ1K7AxhU=", + "o6OxugUBZG1wksq0DufSy+IR3kOcqgRpwq4nfVXjlok=", + "sNQYjZCbNKTXGSRAsighUnkD4O1GMfQHv71WepfOjnI=", + "9rDXZu1xo5PfVwNIwg/2nXHxWn61l9dIXHq6t8H2IZI=", + "qdXDOUrODU9WLHckIdaWx8tgc86RfKzD8YuBxRnpxCM=" ], "finalized_header": { "beacon": { "slot": 128, - "proposer_index": 38, - "parent_root": "g2y+wD+OzF49DHq2lHr56OA5kSuJEPysWtB3A5cmvQQ=", - "state_root": "SKYVwF3KUJZG8fgp0vEVLaAXwcft6fc6Q0W8UqLjwmI=", - "body_root": "yCa9k/2R/0axrwGRG36iPoho/y28qDhP5kXfjlheKnA=" + "proposer_index": 61, + "parent_root": "ICVxBTiY1i6ytSfAkr8BZgsslIZEAfBCWhrhFhrKJZM=", + "state_root": "trqHeiATdBKzeFROSpnnW+Ggf+LaWus2qMG3Gjua/uU=", + "body_root": "qZRSMS6Ek7wo5ASFM+AM1kNIc3V7jNWS7WIZhViOTLA=" }, "execution": { - "parent_hash": "5fj6049De37qa95MmhlWrpmf8obaTHrjKDrDpiiLQb8=", + "parent_hash": "Z05CPblHpIQuL1NmX2MmoRv7IvTIQ+lIYxJ75L2fwVk=", "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "dKHHD8cFmGJizQ6FiYElwSjDM6OodwqhY6NEDM1XwHw=", + "state_root": "djI7S1KUcpaVjyTbeHvZzH1tSvM2Rv7Sma/pgkeh16A=", "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "bVUgnc8OWfHdxGlvfE9um34ppr4unJJ/FiRRsC9rClY=", + "prev_randao": "m9MEiShQfTIEaaj6bhbamf/cyQqTem2y1UhITTGCieY=", "block_number": 128, "gas_limit": 30000000, - "timestamp": 1733324099, + "timestamp": 1733408571, "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA=", - "block_hash": "UoNVvB0D96Tko9HHLHK1c87/GpiD8naUFgQYL2CP/EA=", + "block_hash": "PvaxoPdifciaZTdRx9dyRndGzZJypSlZOfSwHumolDg=", "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" }, "execution_branch": [ - "iZKIv5s+K/SdfciQZatJ9ypFijMHl1UK+k287Z/Jt2o=", + "LQJDppP4UKvZ/LUNKw7vNci2hdPYN7za2MdgnvGh+ns=", "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "HNuubpfS189mRdAQyoihXjcAvc3bVDQkwoSvOcHy0TY=" + "9mIbi+txOKdH2pZbRQfJGheSuT93Lbl4leyLBBRtSek=" ] }, "finality_branch": [ "EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", - "0Ydom60/vjDwrY5S6xIkX/GvZ91lDqNBBQIvdPke0uQ=", - "WymxeDBGfowY8TYHxSI/XbPWQVKEvZGNB4y9fBPuwU4=", - "ntgTQDU97bD+hJvWqBXT4OiKFJrXvYf4Z7Pv/xj/q2Y=", - "8TTv8NTCcS2oecKHq4Lh7PJO+8B6Y3rkkxj3l6zxgCI=" + "lEBRQFpyoqod+eoAePD3fIIy3eZ0DVv3dIFrnLwnCWw=", + "sNQYjZCbNKTXGSRAsighUnkD4O1GMfQHv71WepfOjnI=", + "9rDXZu1xo5PfVwNIwg/2nXHxWn61l9dIXHq6t8H2IZI=", + "qdXDOUrODU9WLHckIdaWx8tgc86RfKzD8YuBxRnpxCM=" ], "sync_aggregate": { "sync_committee_bits": "/////w==", - "sync_committee_signature": "hT+YlXESJFkVSsOe2A99ypztYm/lMc31J+K0kRmdjprhDTQh3DOPbx9C8F6MgFnrAJHIlswc6h0jABBiUl71jVXZDkvpGcrJO4HjjtcEw8m/T/zDnrInDfik+arDvcjA" + "sync_committee_signature": "jcaUvHR7oJhe+pjOWNZ1B2ZYQNgzjf+By+FZl+RuEB+D6G8Fw7r10+K/DobC/OTKDmZp8qnJlwR65cnN2qZqh0iQWgMMuXWxqq9q18Jt/NgWh9B2slJbD0+mUORFv7vY" }, "signature_slot": 145 }, "account_update": { "account_proof": { - "storage_root": "CB2+UVpo5UlCwDuzbKY++v3wS24w3J/u5vNmCLcmkEQ=", + "storage_root": "LNIdwMCrMXD0A6mAkgxkZ5kST7VB89Te2PorkMrErVg=", "proof": [ - "+QIRoJy9mzlgCIj9261sHUdqQEz+GrMLf9jYl6L51ns1iSxRoNvtQnGvsya7jNya5NqK/0PEH6HAXEgplAFd0uTDuVVcoIA8IU97ymCRwpz7bXAdaalbk3TF1x0NqmYp7K5hfbHloGY/odeIzddwH+qzE0aMOkWsfWkKxGAF6JO6ELHDq+DBoEd3tzNyEl9ueOR8juyljpWUMQkD4qfrkf+LyqMfmHEMoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foL0O7icYOrkBBbdEQ1lHSk/55rSUpCQZRqXG7zkeoAERoIPGl55GPAKBj/6truuKvJ8vUedn+5FRp/yJmJ60C1esoMvNwdImpUDFDLHmFeevmfFx1DZbRXNJQOItR+xKojoUoF/w4VuX4r5bV0vGxWmrUGeeDfjOf9jT83lqAHlS89vQoGEV1qu7Mr7wexFPARVLC8LtTqrezKP1I/uTawgG2K/EoPXSv0BRB8Lem6Y92oN68nWXI0ZMCuwXjVhJtTZC3kVHoPWONEoWRJJh5QIguZTTlLSV+zdl5lKtT8PV/KFakTKtoPeSXskhoxP247G/pKTy9TUFLIwJGCPSBadBGbpNmW1WoJYvD3zLQ8eLoj+ZvmPX++/JrK+7O+hycOuNhQ5QkGz/oDf/APviEFvODm7Z6oCh1nuKR2sf89F3rJWXpTJB5HqngA==", - "+QHxoAQnJWYscBxLniD3psqO7Sb7t1xXpmGhiB/qxpjCB3IBoINqrps3YgRrlvRQjfdUdfCFdZllPWNLCvV0gA4j84pcoHkqNszwtQkFzcYnxz0Kl6oSw5H/jbwmv5x5GP9JdETUoKELQddIabNCwJvyVuJZm7qJPCatFwnBpm8IJnslnvA0oFDQk1+BSpJhQtA8z3B8bNm1HNoVWfpR/hJiYvt5pVNloMioOdj1JP0Ne5EyXNL5s9A25iDYZ2zwBVDW1DJit4R8gKAMMgAETzD49tBU2jIcKc4zU06oXYWmVVALx2PpuHizraAWluLWgFRL7/b5z459AOYI95/81xbcWKXLgmCfjgfLnqChXiN4nos9rAQ6BvtbTVp84T5n/Hp3liEejfYNjs1tvqCp+Y+PiVm8nYxbCVfRSV0+HECNGBfHYQQ889tDI07luqB98vsLt44lf2P8LLz8BxBZV425ba9yAGYSDTMwufQXk6CwGcffGXvqDnt7/OOCxF6IeRds1AghLm5//inSL2URDaC8Nq1uDISWu/0Z1hh1ohnSSWcHTRKOMerKvMu4h6OL7aC+RERcY2f1Dg10fo1lydcuFCIm4KskqIYgrOwO3FVqp6Clxi0aZx7Al5KXtFCLuvQ5OF6dggSjXhItvFThMtkaf4A=", - "+FGgAnmxTv4hI+4DcjVsya6WhHnv2f7vUVqjdm8bY3wXwpGAgICAgKC5G8KD86sBd/7k2Kco30ZAfu5wZpDR3vOFXxxf+AILZ4CAgICAgICAgIA=", - "+GifPQ9d80Yr6WVBOXY1pLN87huE4ois3IfBMyxIE4Rd4rhG+EQBgKAIHb5RWmjlSULAO7Nspj76/fBLbjDcn+7m82YItyaQRKCyrvYec6w6k7Xz7qVixad4i1445NH774ghBs6vw68t1w==" + "+QIRoEqvnXgQSE6uML1+jas4PVF1tiIo22EFAKi/9NHndXGooOVBk+ODbAmW/oiCKL8aKzwF+qyHbl2o5Qb/ozbCM6ojoKsZigoIFy1+rpw72dqLstEaNIs0OHAvt/LB8bVw60dAoDvNV7PEQbn+RElhWDWK0yBVFWSUAPie+UUbicsNLvdHoN00IIObq67nYefqo4rV9Zaxqbhxbn6bkmGUmpZKWn1hoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foLzvA+KpdGPjRsdJlB8ejvxTiOShmR0HnpitUrrUAhK5oIPGl55GPAKBj/6truuKvJ8vUedn+5FRp/yJmJ60C1esoMvNwdImpUDFDLHmFeevmfFx1DZbRXNJQOItR+xKojoUoJmNmYDMJMjLe1v5yT9zr59e+vNHDKFWpagulbksSpEPoPy/d7p1MmcO5NywlRj54vQBdBjeJHDNd/HzriIPCVjYoBeWYXQn5n7RDN+KcrAmiacAunHrkxhqGxIMmtCw5W6uoK0LuGtHGGwEIj6FqcM90ch91uXBf3U/T9ClZ3LYp4OZoGm3Si0sJG+LQGhv/VfDzx+p7jJASdBUX8seZ84WVsEGoHzaprc7+/5FwSGnCUk3uJukyupPXR7ue6s5TcnN9GlUoOHGC5EFoWlZIcDp/qj04TjxsiFJhAF6o7uBH1Ul9ge/gA==", + "+QGRoAW/OheSLPSH4l2/4k9e4Gqcu6AIFeeUxVgmfV348TJggKC1Dd8AKIzIwTNSUbBLymVjI1RWNN3SIw8XhNPyF2m7U6ChpXt8UzlXsptRWcjDUno50TVNuwuqpI+nBaNUdlL5pKCZjxpgZi8W4bfVYmdSvqX/emcnaVEpIdkA69F1jFBLJ6COn69uca0GKG4WS94nlNllxcmAAkzIo6i5lCYJr1Z+v6Cz7/jDJ2RT50/6Uz9iqlGmT/ptcN6EMvkYguUn6yvwRICgW4c0sG9Yfjxp/QRldX24lk/lnljNycWZQzRf6OGXyRagDWgrEDC2jbKjJ5P2IvLvPfFKiv+ZUvlBO9vNnN1bLHmgmeYaf7+xnVndxQtXJssAH72qha3wIK27GL+ui87ytt2AgKCiJCLqmRojruVuNbkH5tFGPMmaWMr+5hDuVtL2SUram6AqT1h3lSJKnVY1mgcfNXPZOAyq3Um8obY6G6fiDZ5mZKAYkkbHr4+u0sfT+H3ItaUdeH9vitZZrG+RcilLPf/tTIA=", + "+GmgIBIndIQYPU8oMKp/nGRX2TS5REap2KbZyPAYf55t07S4RvhEAYCgLNIdwMCrMXD0A6mAkgxkZ5kST7VB89Te2PorkMrErVigsq72HnOsOpO18+6lYsWneIteOOTR+++IIQbOr8OvLdc=" ] } } diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json new file mode 100644 index 000000000..612e6944 --- /dev/null +++ b/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json @@ -0,0 +1,54 @@ +{ + "path": "MDctdGVuZGVybWludC0wAQAAAAAAAAAB", + "storage_proof": { + "key": "dddBHLAdqtFncTtam3IZZw8OUAZTy7zUXP4b/gQiJFk=", + "value": "gGNSwJ98jIunXa1eK/lC2dnaArcna4V5tYLEW99U3aY=", + "proof": [ + "+LGAgKA6BiC+LtHLi1QUcOvXp/4Ey0uNco6kzwhuY+dIrcntAKAfa/LQO28G+qbqc0zEwwvN1KA/O6AU1SfXJG3qwHyYZICAoFexbZo7uy0Qa00bEtyjUE9hiZx8ZgsDaEhRFCbtNC3WgICAgICg2ewz2fYL/2sT+bFjbDoKx8vxyGbecuNvIGkP63MwSQugKk2h+7aafsHLGXnx0xO7YYjriGJC1JcwMGXs13gsanKAgIA=", + "+EOgPTw7zwMABq/qKmd6b/W/P38RHodGHIhIzwYqV1bRqIihoIBjUsCffIyLp12tXiv5QtnZ2gK3J2uFebWCxFvfVN2m" + ] + }, + "proof_height": { + "revision_number": 0, + "revision_height": 144 + }, + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733407803, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 144, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "Pus1Pj9GbHF70jfiXjd1yeYfhpY=", + "min_sync_committee_participants": 32 + }, + "consensus_state": { + "slot": 144, + "state_root": "JO+3htK/8MDf+cf6sh5XaZJML5L66wuPkQG39BRIT6w=", + "storage_root": "LNIdwMCrMXD0A6mAkgxkZ5kST7VB89Te2PorkMrErVg=", + "timestamp": 1733408667000000000, + "current_sync_committee": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe", + "next_sync_committee": "llqLwJDEnKUubFmaF2HQO7oeFKuIoYxG9bAdYQ6C8Akk9M0T8jci8TNk15UoEyIl" + } +} diff --git a/packages/ethereum-light-client/src/test/fixtures/sync_committee_fixture.json b/packages/ethereum-light-client/src/test/fixtures/sync_committee_fixture.json new file mode 100644 index 000000000..20d05db0 --- /dev/null +++ b/packages/ethereum-light-client/src/test/fixtures/sync_committee_fixture.json @@ -0,0 +1,37 @@ +{ + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "p7kUGHfzl+nSo2zYZAc4e7zsbVV7MMzZ5ircohfkWNdJW1geBI+hCEIYyt+PRbn/" +} diff --git a/packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json b/packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json deleted file mode 100644 index 8ffb0ecc..000000000 --- a/packages/ethereum-light-client/src/test/initial_consensus_state_fixture.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "slot": 32, - "state_root": "HxRWmeUw4+VcAqOjqLvlhppfdaImWEflLR3avxucMn4=", - "storage_root": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "timestamp": 1733323523000000000, - "current_sync_committee": "gmHVzMuPqcx/A6CKMEJLWPxv8nSa+Ub/M+WbLnMH42KuWnEZbMCtnPlGNk2zbZvx", - "next_sync_committee": "gmHVzMuPqcx/A6CKMEJLWPxv8nSa+Ub/M+WbLnMH42KuWnEZbMCtnPlGNk2zbZvx" -} \ No newline at end of file diff --git a/packages/ethereum-light-client/src/trie.rs b/packages/ethereum-light-client/src/trie.rs index 9de4e7eb..c39c2876 100644 --- a/packages/ethereum-light-client/src/trie.rs +++ b/packages/ethereum-light-client/src/trie.rs @@ -30,14 +30,16 @@ pub fn validate_merkle_branch( if value == root { Ok(()) } else { - Err(EthereumIBCError::InvalidMerkleBranch(InvalidMerkleBranch { - leaf, - branch, - depth, - index, - root, - found: value, - })) + Err(EthereumIBCError::InvalidMerkleBranch(Box::new( + InvalidMerkleBranch { + leaf, + branch, + depth, + index, + root, + found: value, + }, + ))) } } @@ -55,7 +57,7 @@ mod test { trie::validate_merkle_branch, types::{ light_client::{BeaconBlockHeader, ExecutionPayloadHeader, LightClientHeader}, - wrappers::{MyBloom, MyBranch, MyBytes}, + wrappers::{WrappedBloom, WrappedBranch, WrappedBytes}, }, verify::get_lc_execution_root, }; @@ -86,13 +88,13 @@ mod test { receipts_root: B256::from_hex( "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", ).unwrap(), - logs_bloom: MyBloom(Bloom::from_hex("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()), + logs_bloom: WrappedBloom(Bloom::from_hex("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()), prev_randao: B256::from_hex("707a729f27185bfd88c746532e0909f7f4604dc5b25b6d9ffb5cfec6ca7987d9").unwrap(), block_number: 80, gas_limit: 30000000, gas_used: 0, timestamp: 1732901097, - extra_data: MyBytes(Bytes::from_hex("0xd883010e06846765746888676f312e32322e34856c696e7578").unwrap()), + extra_data: WrappedBytes(Bytes::from_hex("0xd883010e06846765746888676f312e32322e34856c696e7578").unwrap()), base_fee_per_gas: U256::from(27136), block_hash: B256::from_hex("c001e15851608006eb33999e829bb265706929091f4c9a08f6853f6fbe96a730").unwrap(), transactions_root: B256::from_hex("0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1").unwrap(), @@ -100,7 +102,7 @@ mod test { blob_gas_used: 0, excess_blob_gas: 0, }, - execution_branch: MyBranch([ + execution_branch: WrappedBranch([ B256::from_hex("0xd320d2b395e1065b0b2e3dbb7843c6d77cb7830ef340ffc968caa0f92e26f080") .unwrap(), B256::from_hex("0x6c6dd63656639d153a2e86a9cab291e7a26e957ad635fec872d2836e92340c23") diff --git a/packages/ethereum-light-client/src/types/domain.rs b/packages/ethereum-light-client/src/types/domain.rs index 95bd0782..21750de3 100644 --- a/packages/ethereum-light-client/src/types/domain.rs +++ b/packages/ethereum-light-client/src/types/domain.rs @@ -1,7 +1,9 @@ use alloy_primitives::{hex, FixedBytes, B256}; +use serde::{Deserialize, Serialize}; -use super::{fork_data::compute_fork_data_root, wrappers::Version}; +use super::{fork_data::compute_fork_data_root, wrappers::WrappedVersion}; +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct DomainType(pub [u8; 4]); impl DomainType { pub const BEACON_PROPOSER: Self = Self(hex!("00000000")); @@ -23,9 +25,9 @@ impl DomainType { /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_domain) pub fn compute_domain( domain_type: DomainType, - fork_version: Option, + fork_version: Option, genesis_validators_root: Option, - genesis_fork_version: Version, + genesis_fork_version: WrappedVersion, ) -> B256 { let fork_version = fork_version.unwrap_or(genesis_fork_version); let genesis_validators_root = genesis_validators_root.unwrap_or_default(); @@ -37,3 +39,72 @@ pub fn compute_domain( FixedBytes(domain) } + +#[cfg(test)] +mod test { + use alloy_primitives::aliases::B32; + use ethereum_utils::base64::FromBase64; + + use crate::config::MINIMAL; + + use super::*; + + #[test] + fn test_compute_domain() { + let domain_type = DomainType::SYNC_COMMITTEE; + let fork_version = MINIMAL.fork_parameters.deneb.version; + let genesis_validators_root = + B256::from_base64("1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=").unwrap(); + let genesis_fork_version = MINIMAL.fork_parameters.genesis_fork_version; + + let domain = compute_domain( + domain_type, + Some(fork_version), + Some(genesis_validators_root), + genesis_fork_version, + ); + + // expected domain taken from running the same code in the union repo + let expected = B256::from_base64("BwAAAOqlZkuFxencFtZKxu4VzJLsR3mQBhswAkaW22c=").unwrap(); + + assert_eq!(domain, expected); + } + + #[test] + fn test_compute_domain_with_union_data() { + // this test is essentially a copy of the union unit test for compute_domain + let domain_type = DomainType([1, 2, 3, 4]); + let current_version = WrappedVersion(B32::from([5, 6, 7, 8])); + let genesis_validators_root = B256::new([1; 32]); + let fork_data_root = + compute_fork_data_root(current_version.clone(), genesis_validators_root); + let genesis_version = WrappedVersion(B32::from([0, 0, 0, 0])); + + let mut domain = B256::default(); + domain.0[..4].copy_from_slice(&domain_type.0); + domain.0[4..].copy_from_slice(&fork_data_root[..28]); + + // Uses the values instead of the default ones when `current_version` and + // `genesis_validators_root` is provided. + assert_eq!( + domain, + compute_domain( + domain_type.clone(), + Some(current_version), + Some(genesis_validators_root), + genesis_version.clone(), + ) + ); + + let fork_data_root = compute_fork_data_root(genesis_version.clone(), Default::default()); + let mut domain = B256::default(); + domain.0[..4].copy_from_slice(&domain_type.0); + domain.0[4..].copy_from_slice(&fork_data_root[..28]); + + // Uses default values when version and validators root is None + assert_eq!( + domain, + compute_domain(domain_type, None, None, genesis_version) + ); + } +} diff --git a/packages/ethereum-light-client/src/types/fork.rs b/packages/ethereum-light-client/src/types/fork.rs index fb758b93..29a8e0e7 100644 --- a/packages/ethereum-light-client/src/types/fork.rs +++ b/packages/ethereum-light-client/src/types/fork.rs @@ -1,10 +1,10 @@ use serde::{Deserialize, Serialize}; -use super::wrappers::Version; +use super::wrappers::WrappedVersion; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct Fork { - pub version: Version, + pub version: WrappedVersion, #[serde(default)] // TODO: REMOVE AND FIX IN E2E pub epoch: u64, } diff --git a/packages/ethereum-light-client/src/types/fork_data.rs b/packages/ethereum-light-client/src/types/fork_data.rs index a96ba9ba..be7e79f5 100644 --- a/packages/ethereum-light-client/src/types/fork_data.rs +++ b/packages/ethereum-light-client/src/types/fork_data.rs @@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; -use crate::types::wrappers::Version; +use crate::types::wrappers::WrappedVersion; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] pub struct ForkData { - pub current_version: Version, + pub current_version: WrappedVersion, pub genesis_validators_root: B256, } @@ -15,7 +15,10 @@ pub struct ForkData { /// This is used primarily in signature domains to avoid collisions across forks/chains. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root) -pub fn compute_fork_data_root(current_version: Version, genesis_validators_root: B256) -> B256 { +pub fn compute_fork_data_root( + current_version: WrappedVersion, + genesis_validators_root: B256, +) -> B256 { let fork_data = ForkData { current_version, genesis_validators_root, diff --git a/packages/ethereum-light-client/src/types/fork_parameters.rs b/packages/ethereum-light-client/src/types/fork_parameters.rs index 164b7ab1..ef3bd131 100644 --- a/packages/ethereum-light-client/src/types/fork_parameters.rs +++ b/packages/ethereum-light-client/src/types/fork_parameters.rs @@ -1,10 +1,10 @@ use serde::{Deserialize, Serialize}; -use super::{fork::Fork, wrappers::Version}; +use super::{fork::Fork, wrappers::WrappedVersion}; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct ForkParameters { - pub genesis_fork_version: Version, + pub genesis_fork_version: WrappedVersion, #[serde(default)] // TODO: REMOVE AND FIX IN E2E pub genesis_slot: u64, pub altair: Fork, @@ -17,7 +17,7 @@ pub struct ForkParameters { /// NOTE: This implementation is based on capella. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/fork.md#modified-compute_fork_version) -pub fn compute_fork_version(fork_parameters: &ForkParameters, epoch: u64) -> Version { +pub fn compute_fork_version(fork_parameters: &ForkParameters, epoch: u64) -> WrappedVersion { if epoch >= fork_parameters.deneb.epoch { fork_parameters.deneb.version.clone() } else if epoch >= fork_parameters.capella.epoch { diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs index 1076ff26..b43312d1 100644 --- a/packages/ethereum-light-client/src/types/light_client.rs +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -8,7 +8,7 @@ use crate::config::consts::{ use super::{ sync_committee::{SyncAggregate, SyncCommittee, TrustedSyncCommittee}, - wrappers::{MyBloom, MyBranch, MyBytes}, + wrappers::{WrappedBloom, WrappedBranch, WrappedBytes}, }; const EXECUTION_BRANCH_SIZE: usize = floorlog2(EXECUTION_PAYLOAD_INDEX); @@ -31,10 +31,10 @@ pub struct LightClientUpdate { // TODO: Remove the Option and improve ethereum::header::Header to be an enum, instead of using optional fields and bools. #[serde(default)] pub next_sync_committee: Option, - pub next_sync_committee_branch: Option>, + pub next_sync_committee_branch: Option>, /// Finalized header corresponding to `attested_header.state_root` pub finalized_header: LightClientHeader, - pub finality_branch: MyBranch, + pub finality_branch: WrappedBranch, /// Sync committee aggregate signature pub sync_aggregate: SyncAggregate, /// Slot at which the aggregate signature was created (untrusted) @@ -50,14 +50,14 @@ pub struct AccountUpdate { pub struct AccountProof { #[serde(with = "ethereum_utils::base64::fixed_size")] pub storage_root: B256, - pub proof: Vec, + pub proof: Vec, } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] pub struct LightClientHeader { pub beacon: BeaconBlockHeader, pub execution: ExecutionPayloadHeader, - pub execution_branch: MyBranch, + pub execution_branch: WrappedBranch, } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] @@ -82,7 +82,7 @@ pub struct ExecutionPayloadHeader { pub state_root: B256, #[serde(with = "ethereum_utils::base64::fixed_size")] pub receipts_root: B256, - pub logs_bloom: MyBloom, + pub logs_bloom: WrappedBloom, #[serde(with = "ethereum_utils::base64::fixed_size")] pub prev_randao: B256, pub block_number: u64, @@ -90,7 +90,7 @@ pub struct ExecutionPayloadHeader { #[serde(default)] pub gas_used: u64, pub timestamp: u64, - pub extra_data: MyBytes, + pub extra_data: WrappedBytes, #[serde(with = "ethereum_utils::base64::uint256")] pub base_fee_per_gas: U256, #[serde(with = "ethereum_utils::base64::fixed_size")] diff --git a/packages/ethereum-light-client/src/types/storage_proof.rs b/packages/ethereum-light-client/src/types/storage_proof.rs index 269aac24..af3855f3 100644 --- a/packages/ethereum-light-client/src/types/storage_proof.rs +++ b/packages/ethereum-light-client/src/types/storage_proof.rs @@ -1,7 +1,7 @@ use alloy_primitives::{B256, U256}; use serde::{Deserialize, Serialize}; -use super::wrappers::MyBytes; +use super::wrappers::WrappedBytes; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct StorageProof { @@ -9,5 +9,5 @@ pub struct StorageProof { pub key: B256, #[serde(with = "ethereum_utils::base64::uint256")] pub value: U256, - pub proof: Vec, + pub proof: Vec, } diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 519cf3bf..d6c7bd84 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -6,12 +6,12 @@ use tree_hash_derive::TreeHash; use super::{ bls::{BlsPublicKey, BlsSignature}, height::Height, - wrappers::VecBlsPublicKey, + wrappers::WrappedVecBlsPublicKey, }; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] pub struct SyncCommittee { - pub pubkeys: VecBlsPublicKey, + pub pubkeys: WrappedVecBlsPublicKey, #[serde(with = "ethereum_utils::base64::fixed_size")] pub aggregate_pubkey: BlsPublicKey, } @@ -25,7 +25,7 @@ pub enum ActiveSyncCommittee { impl Default for ActiveSyncCommittee { fn default() -> Self { ActiveSyncCommittee::Current(SyncCommittee { - pubkeys: VecBlsPublicKey::default(), + pubkeys: WrappedVecBlsPublicKey::default(), aggregate_pubkey: BlsPublicKey::default(), }) } @@ -103,24 +103,20 @@ pub fn compute_sync_committee_period_at_slot( #[cfg(test)] mod test { - use crate::{test::fixtures::load_fixture, types::light_client::Header}; + use crate::{test::fixtures::load_fixture, types::sync_committee::SyncCommittee}; use alloy_primitives::{hex::FromHex, B256}; use tree_hash::TreeHash; #[test] fn test_sync_committee_tree_hash_root() { - let header: Header = load_fixture("client_update_ack_0"); - assert_ne!(header, Header::default()); - let sync_committee = header - .consensus_update - .next_sync_committee - .unwrap() - .tree_hash_root(); + let sync_committee: SyncCommittee = load_fixture("sync_committee_fixture"); + assert_ne!(sync_committee, SyncCommittee::default()); - let expected = + let actual_tree_hash_root = sync_committee.tree_hash_root(); + let expected_tree_hash_root = B256::from_hex("0x5361eb179f7499edbf09e514d317002f1d365d72e14a56c931e9edaccca3ff29") .unwrap(); - assert_eq!(expected, sync_committee); + assert_eq!(expected_tree_hash_root, actual_tree_hash_root); } } diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs index 9792a86b..46dbd367 100644 --- a/packages/ethereum-light-client/src/types/wrappers.rs +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -2,14 +2,12 @@ use alloy_primitives::{aliases::B32, Bloom, Bytes, FixedBytes, B256}; use serde::{Deserialize, Serialize}; use tree_hash::{MerkleHasher, TreeHash, BYTES_PER_CHUNK}; -use crate::config::consts::{floorlog2, EXECUTION_PAYLOAD_INDEX}; - use super::bls::BlsPublicKey; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] -pub struct Version(#[serde(with = "ethereum_utils::base64::fixed_size")] pub B32); +pub struct WrappedVersion(#[serde(with = "ethereum_utils::base64::fixed_size")] pub B32); -impl TreeHash for Version { +impl TreeHash for WrappedVersion { fn tree_hash_type() -> tree_hash::TreeHashType { FixedBytes::tree_hash_type() } @@ -28,9 +26,9 @@ impl TreeHash for Version { } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] -pub struct MyBytes(#[serde(with = "ethereum_utils::base64")] pub Bytes); +pub struct WrappedBytes(#[serde(with = "ethereum_utils::base64")] pub Bytes); -impl TreeHash for MyBytes { +impl TreeHash for WrappedBytes { fn tree_hash_type() -> tree_hash::TreeHashType { tree_hash::TreeHashType::List } @@ -56,16 +54,16 @@ impl TreeHash for MyBytes { } } -impl AsRef<[u8]> for MyBytes { +impl AsRef<[u8]> for WrappedBytes { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] -pub struct MyBloom(#[serde(with = "ethereum_utils::base64::fixed_size")] pub Bloom); +pub struct WrappedBloom(#[serde(with = "ethereum_utils::base64::fixed_size")] pub Bloom); -impl TreeHash for MyBloom { +impl TreeHash for WrappedBloom { fn tree_hash_type() -> tree_hash::TreeHashType { tree_hash::TreeHashType::List } @@ -92,11 +90,11 @@ impl TreeHash for MyBloom { } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] -pub struct MyBranch( +pub struct WrappedBranch( #[serde(with = "ethereum_utils::base64::fixed_size::vec::fixed_size")] pub [B256; N], ); -impl TreeHash for MyBranch { +impl TreeHash for WrappedBranch { fn tree_hash_type() -> tree_hash::TreeHashType { tree_hash::TreeHashType::List } @@ -121,24 +119,24 @@ impl TreeHash for MyBranch { } } -impl Default for MyBranch { +impl Default for WrappedBranch { fn default() -> Self { Self([B256::default(); N]) } } -impl From> for Vec { - fn from(val: MyBranch) -> Self { +impl From> for Vec { + fn from(val: WrappedBranch) -> Self { val.0.to_vec() } } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] -pub struct VecBlsPublicKey( +pub struct WrappedVecBlsPublicKey( #[serde(with = "ethereum_utils::base64::fixed_size::vec")] pub Vec, ); -impl TreeHash for VecBlsPublicKey { +impl TreeHash for WrappedVecBlsPublicKey { fn tree_hash_type() -> tree_hash::TreeHashType { tree_hash::TreeHashType::Vector } @@ -162,24 +160,3 @@ impl TreeHash for VecBlsPublicKey { hasher.finish().unwrap() } } - -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] -pub struct MyBlsPublicKey(#[serde(with = "ethereum_utils::base64::fixed_size")] pub BlsPublicKey); - -impl TreeHash for MyBlsPublicKey { - fn tree_hash_type() -> tree_hash::TreeHashType { - FixedBytes::tree_hash_type() - } - - fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { - self.0.tree_hash_packed_encoding() - } - - fn tree_hash_packing_factor() -> usize { - FixedBytes::tree_hash_packing_factor() - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - self.0.tree_hash_root() - } -} diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index 6b6abca9..4d9f979a 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -350,13 +350,19 @@ mod test { fn test_verify_header() { let bls_verifier = TestBlsVerifier; - let client_state: ClientState = load_fixture("initial_client_state_fixture"); + let client_state: ClientState = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state", + ); assert_ne!(client_state, ClientState::default()); - let consensus_state: ConsensusState = load_fixture("initial_consensus_state_fixture"); + let consensus_state: ConsensusState = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", + ); assert_ne!(consensus_state, ConsensusState::default()); - let header: Header = load_fixture("client_update_ack_0"); + let header: Header = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", + ); assert_ne!(header, Header::default()); verify_header( diff --git a/packages/ethereum-utils/src/base64.rs b/packages/ethereum-utils/src/base64.rs index 9aaac278..ee9f4942 100644 --- a/packages/ethereum-utils/src/base64.rs +++ b/packages/ethereum-utils/src/base64.rs @@ -1,6 +1,22 @@ -use base64::prelude::*; +use alloy_primitives::B256; +use base64::{prelude::*, DecodeError}; use serde::{de, Deserialize, Deserializer}; +pub trait FromBase64: Sized { + fn from_base64(s: &str) -> Result; +} + +pub fn from_base64(s: &str) -> Result, DecodeError> { + BASE64_STANDARD.decode(s.as_bytes()) +} + +impl FromBase64 for B256 { + fn from_base64(s: &str) -> Result { + let data = from_base64(s)?; + Ok(B256::from_slice(data.as_slice())) + } +} + pub fn serialize>(data: T, serializer: S) -> Result where S: serde::Serializer, @@ -14,9 +30,7 @@ where T: TryFrom>, { let s = String::deserialize(deserializer)?; - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; T::try_from(decoded).map_err(|_| de::Error::custom("Invalid base64 data")) } @@ -24,6 +38,8 @@ pub mod fixed_size { use base64::prelude::*; use serde::{de, Deserialize, Deserializer}; + use super::from_base64; + pub fn serialize>(data: T, serializer: S) -> Result where S: serde::Serializer, @@ -37,9 +53,7 @@ pub mod fixed_size { T: TryFrom<[u8; N]>, { let s = String::deserialize(deserializer)?; - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; let fixed_sized: [u8; N] = decoded .as_slice() @@ -52,6 +66,8 @@ pub mod fixed_size { use base64::prelude::*; use serde::{de, Deserialize, Deserializer, Serializer}; + use crate::base64::from_base64; + pub fn serialize>( #[allow(clippy::ptr_arg)] // required by serde bytes: &Vec, @@ -68,9 +84,7 @@ pub mod fixed_size { let vec = Vec::::deserialize(deserializer)?; vec.into_iter() .map(|s| { - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; let fixed_sized: [u8; N] = decoded .as_slice() .try_into() @@ -84,6 +98,8 @@ pub mod fixed_size { use base64::prelude::*; use serde::{de, Deserialize, Deserializer, Serializer}; + use crate::base64::from_base64; + pub fn serialize, const NN: usize>( #[allow(clippy::ptr_arg)] // required by serde bytes: &[T; NN], @@ -103,9 +119,7 @@ pub mod fixed_size { let items: Vec = vec .into_iter() .map(|s| { - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; let fixed_sized: [u8; N] = decoded .as_slice() .try_into() @@ -125,6 +139,8 @@ pub mod fixed_size { use base64::prelude::*; use serde::{de, Deserialize, Deserializer, Serializer}; + use crate::base64::from_base64; + pub fn serialize, const NN: usize>( #[allow(clippy::ptr_arg)] // required by serde bytes: &Option<[T; NN]>, @@ -151,9 +167,7 @@ pub mod fixed_size { let items: Vec = vec .into_iter() .map(|s| { - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; let fixed_sized: [u8; N] = decoded .as_slice() .try_into() @@ -178,6 +192,8 @@ pub mod uint256 { use base64::{prelude::BASE64_STANDARD, Engine}; use serde::{de, Deserialize, Deserializer}; + use super::from_base64; + pub fn serialize(data: &U256, serializer: S) -> Result where S: serde::Serializer, @@ -190,9 +206,7 @@ pub mod uint256 { D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; Ok(U256::from_be_slice(decoded.as_slice())) } @@ -226,6 +240,8 @@ pub mod option_with_default { use base64::{prelude::BASE64_STANDARD, Engine}; use serde::{de, Deserialize, Deserializer}; + use super::from_base64; + pub fn serialize>(data: &Option, serializer: S) -> Result where S: serde::Serializer, @@ -247,9 +263,7 @@ pub mod option_with_default { return Ok(None); } - let decoded = BASE64_STANDARD - .decode(s.as_bytes()) - .map_err(de::Error::custom)?; + let decoded = from_base64(&s).map_err(de::Error::custom)?; let data: T = T::try_from(decoded.as_slice()) .map_err(|_| de::Error::custom("Invalid base64 data"))?; diff --git a/programs/08-wasm-eth/Cargo.toml b/programs/08-wasm-eth/Cargo.toml new file mode 100644 index 000000000..63cd968e --- /dev/null +++ b/programs/08-wasm-eth/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cw-08-wasm-etheruem-light-client" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +[dependencies] +ibc-proto = { workspace = true } +ethereum-light-client = { workspace = true } +ethereum-utils = { workspace = true } + +alloy-primitives = { workspace = true } + +cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cw-storage-plus = { workspace = true } +tendermint-proto = { workspace = true } +prost = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } +alloy-primitives = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/programs/08-wasm-eth/src/contract.rs b/programs/08-wasm-eth/src/contract.rs new file mode 100644 index 000000000..5d857794 --- /dev/null +++ b/programs/08-wasm-eth/src/contract.rs @@ -0,0 +1,411 @@ +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use ethereum_light_client::{client_state::ClientState, consensus_state::ConsensusState}; +use ibc_proto::ibc::{ + core::client::v1::Height as IbcProtoHeight, + lightclients::wasm::v1::{ + ClientState as WasmClientState, ConsensusState as WasmConsensusState, + }, +}; +use prost::Message; +use tendermint_proto::google::protobuf::Any; + +use crate::error::ContractError; +use crate::msg::{ + CheckForMisbehaviourResult, ExecuteMsg, ExportMetadataResult, Height, InstantiateMsg, QueryMsg, + StatusResult, SudoMsg, TimestampAtHeightResult, UpdateStateResult, +}; +use crate::state::{consensus_db_key, HOST_CLIENT_STATE_KEY}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let client_state_bz: Vec = msg.client_state.into(); + let client_state = ClientState::from(client_state_bz); + let wasm_client_state = WasmClientState { + checksum: msg.checksum.into(), + data: client_state.clone().into(), + latest_height: Some(IbcProtoHeight { + revision_number: 0, + revision_height: client_state.latest_slot, + }), + }; + let wasm_client_state_any = Any::from_msg(&wasm_client_state).unwrap(); + deps.storage.set( + HOST_CLIENT_STATE_KEY.as_bytes(), + wasm_client_state_any.encode_to_vec().as_slice(), + ); + + let consensus_state_bz: Vec = msg.consensus_state.into(); + let consensus_state = ConsensusState::from(consensus_state_bz); + let wasm_consensus_state = WasmConsensusState { + data: consensus_state.clone().into(), + }; + let wasm_consensus_state_any = Any::from_msg(&wasm_consensus_state).unwrap(); + let height = Height { + revision_number: 0, + revision_height: consensus_state.slot, + }; + deps.storage.set( + consensus_db_key(&height).as_bytes(), + wasm_consensus_state_any.encode_to_vec().as_slice(), + ); + + Ok(Response::default()) +} + +#[entry_point] +pub fn sudo(_deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { + let result = match msg { + SudoMsg::VerifyMembership(_) => verify_membership()?, + SudoMsg::VerifyNonMembership(_) => verify_non_membership()?, + SudoMsg::UpdateState(_) => update_state()?, + SudoMsg::UpdateStateOnMisbehaviour(_) => unimplemented!(), + SudoMsg::VerifyUpgradeAndUpdateState(_) => unimplemented!(), + SudoMsg::MigrateClientStore(_) => unimplemented!(), + }; + + Ok(Response::default().set_data(result)) +} + +pub fn verify_membership() -> Result { + Ok(to_json_binary(&Ok::<(), ()>(()))?) +} + +pub fn verify_non_membership() -> Result { + Ok(to_json_binary(&Ok::<(), ()>(()))?) +} + +pub fn update_state() -> Result { + Ok(to_json_binary(&UpdateStateResult { heights: vec![] })?) +} + +#[entry_point] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + unimplemented!() +} + +#[entry_point] +pub fn query(_deps: Deps, env: Env, msg: QueryMsg) -> Result { + match msg { + QueryMsg::VerifyClientMessage(_) => verify_client_message(), + QueryMsg::CheckForMisbehaviour(_) => check_for_misbehaviour(), + QueryMsg::TimestampAtHeight(_) => timestamp_at_height(env), + QueryMsg::Status(_) => status(), + QueryMsg::ExportMetadata(_) => export_metadata(), + } +} + +pub fn verify_client_message() -> Result { + Ok(to_json_binary(&Ok::<(), ()>(()))?) +} + +pub fn check_for_misbehaviour() -> Result { + Ok(to_json_binary(&CheckForMisbehaviourResult { + found_misbehaviour: false, + })?) +} + +pub fn timestamp_at_height(env: Env) -> Result { + let now = env.block.time.seconds(); + Ok(to_json_binary(&TimestampAtHeightResult { timestamp: now })?) +} + +pub fn status() -> Result { + Ok(to_json_binary(&StatusResult { + status: "Active".to_string(), + })?) +} + +pub fn export_metadata() -> Result { + Ok(to_json_binary(&ExportMetadataResult { + genesis_metadata: vec![], + })?) +} + +#[cfg(test)] +mod tests { + mod instantiate_tests { + use alloy_primitives::{aliases::B32, FixedBytes, B256, U256}; + use cosmwasm_std::{ + coins, + testing::{message_info, mock_dependencies, mock_env}, + Storage, + }; + use ethereum_light_client::{ + client_state::ClientState, + consensus_state::ConsensusState, + types::{fork::Fork, fork_parameters::ForkParameters, wrappers::WrappedVersion}, + }; + use ibc_proto::ibc::lightclients::wasm::v1::{ + ClientState as WasmClientState, ConsensusState as WasmConsensusState, + }; + use prost::{Message, Name}; + use tendermint_proto::google::protobuf::Any; + + use crate::{ + contract::instantiate, + msg::{Height, InstantiateMsg}, + state::{consensus_db_key, HOST_CLIENT_STATE_KEY}, + }; + + #[test] + fn test_instantiate() { + let mut deps = mock_dependencies(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &coins(1, "uatom")); + + let client_state = ClientState { + chain_id: 0, + genesis_validators_root: B256::from([0; 32]), + min_sync_committee_participants: 0, + genesis_time: 0, + fork_parameters: ForkParameters { + genesis_fork_version: WrappedVersion(B32::from([0; 4])), + genesis_slot: 0, + altair: Fork { + version: WrappedVersion(B32::from([0; 4])), + epoch: 0, + }, + bellatrix: Fork { + version: WrappedVersion(B32::from([0; 4])), + epoch: 0, + }, + capella: Fork { + version: WrappedVersion(B32::from([0; 4])), + epoch: 0, + }, + deneb: Fork { + version: WrappedVersion(B32::from([0; 4])), + epoch: 0, + }, + }, + seconds_per_slot: 0, + slots_per_epoch: 0, + epochs_per_sync_committee_period: 0, + latest_slot: 42, + ibc_commitment_slot: U256::from(0), + ibc_contract_address: Default::default(), + frozen_height: ethereum_light_client::types::height::Height::default(), + }; + let client_state_bz: Vec = client_state.clone().into(); + + let consensus_state = ConsensusState { + slot: 0, + state_root: B256::from([0; 32]), + storage_root: B256::from([0; 32]), + timestamp: 0, + current_sync_committee: FixedBytes::<48>::from([0; 48]), + next_sync_committee: None, + }; + let consensus_state_bz: Vec = consensus_state.clone().into(); + + let msg = InstantiateMsg { + client_state: client_state_bz.into(), + consensus_state: consensus_state_bz.into(), + checksum: "also does not matter yet".as_bytes().into(), + }; + + let res = instantiate(deps.as_mut(), mock_env(), info, msg.clone()).unwrap(); + assert_eq!(0, res.messages.len()); + + let actual_wasm_client_state_any_bz = + deps.storage.get(HOST_CLIENT_STATE_KEY.as_bytes()).unwrap(); + let actual_wasm_client_state_any = + Any::decode(actual_wasm_client_state_any_bz.as_slice()).unwrap(); + assert_eq!( + WasmClientState::type_url(), + actual_wasm_client_state_any.type_url + ); + let actual_client_state = + WasmClientState::decode(actual_wasm_client_state_any.value.as_slice()).unwrap(); + assert_eq!(msg.checksum, actual_client_state.checksum); + assert_eq!(msg.client_state, actual_client_state.data); + assert_eq!( + 0, + actual_client_state.latest_height.unwrap().revision_number + ); + assert_eq!( + client_state.latest_slot, + actual_client_state.latest_height.unwrap().revision_height + ); + + let actual_wasm_consensus_state_any_bz = deps + .storage + .get( + consensus_db_key(&Height { + revision_number: 0, + revision_height: consensus_state.slot, + }) + .as_bytes(), + ) + .unwrap(); + let actual_wasm_consensus_state_any = + Any::decode(actual_wasm_consensus_state_any_bz.as_slice()).unwrap(); + assert_eq!( + WasmConsensusState::type_url(), + actual_wasm_consensus_state_any.type_url + ); + let actual_consensus_state = + WasmConsensusState::decode(actual_wasm_consensus_state_any.value.as_slice()) + .unwrap(); + assert_eq!(msg.consensus_state, actual_consensus_state.data); + } + } + + mod sudo_tests { + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env}, + Binary, + }; + + use crate::{ + contract::sudo, + msg::{ + Height, MerklePath, SudoMsg, UpdateStateMsg, VerifyMembershipMsg, + VerifyNonMembershipMsg, + }, + }; + + #[test] + fn test_verify_membership() { + let mut deps = mock_dependencies(); + let msg = SudoMsg::VerifyMembership(VerifyMembershipMsg { + height: Height { + revision_number: 0, + revision_height: 1, + }, + delay_time_period: 0, + delay_block_period: 0, + proof: Binary::default(), + merkle_path: MerklePath { key_path: vec![] }, + value: Binary::default(), + }); + let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); + assert_eq!(0, res.messages.len()); + } + + #[test] + fn test_verify_non_membership() { + let mut deps = mock_dependencies(); + let msg = SudoMsg::VerifyNonMembership(VerifyNonMembershipMsg { + height: Height { + revision_number: 0, + revision_height: 1, + }, + delay_time_period: 0, + delay_block_period: 0, + proof: Binary::default(), + merkle_path: MerklePath { key_path: vec![] }, + }); + let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); + assert_eq!(0, res.messages.len()); + } + + #[test] + fn test_update_state() { + let mut deps = mock_dependencies(); + let msg = SudoMsg::UpdateState(UpdateStateMsg { + client_message: Binary::default(), + }); + let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); + assert_eq!(0, res.messages.len()); + } + } + + mod query_tests { + use cosmwasm_std::{ + from_json, + testing::{mock_dependencies, mock_env}, + Binary, + }; + + use crate::{ + contract::query, + msg::{ + CheckForMisbehaviourMsg, CheckForMisbehaviourResult, ExportMetadataMsg, + ExportMetadataResult, Height, QueryMsg, StatusMsg, StatusResult, + TimestampAtHeightMsg, TimestampAtHeightResult, VerifyClientMessageMsg, + }, + }; + + #[test] + fn test_verify_client_message() { + let deps = mock_dependencies(); + query( + deps.as_ref(), + mock_env(), + QueryMsg::VerifyClientMessage(VerifyClientMessageMsg { + client_message: Binary::default(), + }), + ) + .unwrap(); + } + + #[test] + fn test_check_for_misbehaviour() { + let deps = mock_dependencies(); + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::CheckForMisbehaviour(CheckForMisbehaviourMsg { + client_message: Binary::default(), + }), + ) + .unwrap(); + let misbehaviour_result: CheckForMisbehaviourResult = from_json(&res).unwrap(); + assert!(!misbehaviour_result.found_misbehaviour); + } + + #[test] + fn test_timestamp_at_height() { + let deps = mock_dependencies(); + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::TimestampAtHeight(TimestampAtHeightMsg { + height: Height { + revision_number: 0, + revision_height: 1, + }, + }), + ) + .unwrap(); + let timestamp_at_height_result: TimestampAtHeightResult = from_json(&res).unwrap(); + assert_eq!( + mock_env().block.time.seconds(), + timestamp_at_height_result.timestamp + ); + } + + #[test] + fn test_status() { + let deps = mock_dependencies(); + let res = query(deps.as_ref(), mock_env(), QueryMsg::Status(StatusMsg {})).unwrap(); + let status_response: StatusResult = from_json(&res).unwrap(); + assert_eq!("Active", status_response.status); + } + + #[test] + fn test_export_metadata() { + let deps = mock_dependencies(); + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::ExportMetadata(ExportMetadataMsg {}), + ) + .unwrap(); + let export_metadata_result: ExportMetadataResult = from_json(&res).unwrap(); + assert_eq!(0, export_metadata_result.genesis_metadata.len()); + } + } +} diff --git a/programs/08-wasm-eth/src/custom_query.rs b/programs/08-wasm-eth/src/custom_query.rs new file mode 100644 index 000000000..5005573a --- /dev/null +++ b/programs/08-wasm-eth/src/custom_query.rs @@ -0,0 +1,79 @@ +use alloy_primitives::B256; +use cosmwasm_std::{Binary, CustomQuery, Deps, QueryRequest}; +use ethereum_light_client::types::bls::{BlsPublicKey, BlsSignature, BlsVerify}; +use ethereum_utils::{ensure::ensure, hex::to_hex}; +use thiserror::Error; + +#[derive(serde::Serialize, serde::Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum EthereumCustomQuery { + AggregateVerify { + public_keys: Vec, + message: Binary, + signature: Binary, + }, + Aggregate { + public_keys: Vec, + }, +} + +impl CustomQuery for EthereumCustomQuery {} + +pub struct BlsVerifier<'a> { + pub deps: Deps<'a, EthereumCustomQuery>, +} + +#[derive(Debug, PartialEq, thiserror::Error, Clone)] +#[error("signature cannot be verified (public_keys: {public_keys:?}, msg: {msg}, signature: {signature})", msg = to_hex(.msg))] +pub struct InvalidSignatureErr { + pub public_keys: Vec, + pub msg: B256, + pub signature: BlsSignature, +} + +#[derive(Error, Debug)] +pub enum BlsVerifierError { + #[error("fast aggregate verify error: {0}")] + FastAggregateVerify(String), + + #[error("invalid signature: {0}")] + InvalidSignature(InvalidSignatureErr), +} + +impl<'a> BlsVerify for BlsVerifier<'a> { + type Error = BlsVerifierError; + + fn fast_aggregate_verify( + &self, + public_keys: Vec<&BlsPublicKey>, + msg: B256, + signature: BlsSignature, + ) -> Result<(), Self::Error> { + let binary_public_keys: Vec = public_keys + .clone() + .into_iter() + .map(|p| Binary::from(p.to_vec())) + .collect(); + + let request: QueryRequest = + QueryRequest::Custom(EthereumCustomQuery::AggregateVerify { + public_keys: binary_public_keys, + message: Binary::from(msg.to_vec()), + signature: Binary::from(signature.to_vec()), + }); + let is_valid = self + .deps + .querier + .query(&request) + .map_err(|e| BlsVerifierError::FastAggregateVerify(e.to_string()))?; + + ensure( + is_valid, + BlsVerifierError::InvalidSignature(InvalidSignatureErr { + public_keys: public_keys.into_iter().copied().collect(), + msg, + signature, + }), + ) + } +} diff --git a/programs/08-wasm-eth/src/error.rs b/programs/08-wasm-eth/src/error.rs new file mode 100644 index 000000000..840de74f --- /dev/null +++ b/programs/08-wasm-eth/src/error.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized, +} diff --git a/programs/08-wasm-eth/src/lib.rs b/programs/08-wasm-eth/src/lib.rs new file mode 100644 index 000000000..025f28ab --- /dev/null +++ b/programs/08-wasm-eth/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod custom_query; +mod error; +pub mod msg; +pub mod state; diff --git a/programs/08-wasm-eth/src/msg.rs b/programs/08-wasm-eth/src/msg.rs new file mode 100644 index 000000000..4d4f129c --- /dev/null +++ b/programs/08-wasm-eth/src/msg.rs @@ -0,0 +1,152 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Binary; + +#[cw_serde] +pub struct InstantiateMsg { + pub client_state: Binary, + pub consensus_state: Binary, + pub checksum: Binary, +} + +#[cw_serde] +pub enum ExecuteMsg {} + +#[cw_serde] +pub enum SudoMsg { + VerifyMembership(VerifyMembershipMsg), + + VerifyNonMembership(VerifyNonMembershipMsg), + UpdateState(UpdateStateMsg), + + UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsg), + + VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsg), + + MigrateClientStore(MigrateClientStoreMsg), +} + +#[cw_serde] +pub struct VerifyMembershipMsg { + pub height: Height, + pub delay_time_period: u64, + pub delay_block_period: u64, + pub proof: Binary, + pub merkle_path: MerklePath, + pub value: Binary, +} + +#[cw_serde] +pub struct VerifyNonMembershipMsg { + pub height: Height, + pub delay_time_period: u64, + pub delay_block_period: u64, + pub proof: Binary, + pub merkle_path: MerklePath, +} + +#[cw_serde] +pub struct UpdateStateMsg { + pub client_message: Binary, +} + +#[cw_serde] +pub struct UpdateStateOnMisbehaviourMsg { + pub client_message: Binary, +} + +#[cw_serde] +pub struct VerifyUpgradeAndUpdateStateMsg { + pub upgrade_client_state: Binary, + pub upgrade_consensus_state: Binary, + pub proof_upgrade_client: Binary, + pub proof_upgrade_consensus_state: Binary, +} + +#[cw_serde] +pub struct MigrateClientStoreMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns[()]] + VerifyClientMessage(VerifyClientMessageMsg), + + #[returns[CheckForMisbehaviourResult]] + CheckForMisbehaviour(CheckForMisbehaviourMsg), + + #[returns[TimestampAtHeightResult]] + TimestampAtHeight(TimestampAtHeightMsg), + + #[returns(StatusResult)] + Status(StatusMsg), + + #[returns[ExportMetadataResult]] + ExportMetadata(ExportMetadataMsg), +} + +#[cw_serde] +pub struct VerifyClientMessageMsg { + pub client_message: Binary, +} + +#[cw_serde] +pub struct CheckForMisbehaviourMsg { + pub client_message: Binary, +} + +#[cw_serde] +pub struct TimestampAtHeightMsg { + pub height: Height, +} + +#[cw_serde] +pub struct StatusMsg {} + +#[cw_serde] +pub struct ExportMetadataMsg {} + +#[cw_serde] +pub struct Height { + /// the revision that the client is currently on + #[serde(default)] + pub revision_number: u64, + /// **height** is a height of remote chain + #[serde(default)] + pub revision_height: u64, +} + +#[cw_serde] +pub struct UpdateStateResult { + pub heights: Vec, +} + +#[cw_serde] +pub struct MerklePath { + pub key_path: Vec, +} + +#[cw_serde] +pub struct StatusResult { + pub status: String, +} + +#[cw_serde] +pub struct CheckForMisbehaviourResult { + pub found_misbehaviour: bool, +} + +#[cw_serde] +pub struct TimestampAtHeightResult { + pub timestamp: u64, +} + +#[cw_serde] +pub struct GenesisMetadata { + pub key: Vec, + pub value: Vec, +} + +#[cw_serde] +pub struct ExportMetadataResult { + pub genesis_metadata: Vec, +} diff --git a/programs/08-wasm-eth/src/state.rs b/programs/08-wasm-eth/src/state.rs new file mode 100644 index 000000000..29c599bb --- /dev/null +++ b/programs/08-wasm-eth/src/state.rs @@ -0,0 +1,19 @@ +use cosmwasm_schema::cw_serde; + +use crate::msg::Height; + +// Client state that is stored by the host +pub const HOST_CLIENT_STATE_KEY: &str = "clientState"; +pub const HOST_CONSENSUS_STATES_KEY: &str = "consensusStates"; + +#[cw_serde] +pub struct ClientState { + pub latest_height: u64, +} + +pub fn consensus_db_key(height: &Height) -> String { + format!( + "{}/{}-{}", + HOST_CONSENSUS_STATES_KEY, height.revision_number, height.revision_height + ) +} From 39c04140ac91095eab1095356b37374223aa1954 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Thu, 5 Dec 2024 19:58:40 +0100 Subject: [PATCH 03/49] fork tree_hash for now --- .github/workflows/rust.yml | 17 + .gitignore | 1 + Cargo.lock | 93 ++- Cargo.toml | 18 +- packages/ethereum-light-client/Cargo.toml | 2 +- packages/ethereum-trie-db/Cargo.toml | 2 +- packages/ethereum-utils/Cargo.toml | 2 +- packages/tree_hash/Cargo.toml | 25 + packages/tree_hash/src/impls.rs | 240 ++++++++ packages/tree_hash/src/lib.rs | 211 +++++++ packages/tree_hash/src/merkle_hasher.rs | 576 +++++++++++++++++++ packages/tree_hash/src/merkleize_padded.rs | 331 +++++++++++ packages/tree_hash/src/merkleize_standard.rs | 82 +++ packages/tree_hash/src/sha256.rs | 36 ++ packages/tree_hash/tests/tests.rs | 180 ++++++ programs/08-wasm-eth/Cargo.toml | 8 +- 16 files changed, 1787 insertions(+), 37 deletions(-) create mode 100644 packages/tree_hash/Cargo.toml create mode 100644 packages/tree_hash/src/impls.rs create mode 100644 packages/tree_hash/src/lib.rs create mode 100644 packages/tree_hash/src/merkle_hasher.rs create mode 100644 packages/tree_hash/src/merkleize_padded.rs create mode 100644 packages/tree_hash/src/merkleize_standard.rs create mode 100644 packages/tree_hash/src/sha256.rs create mode 100644 packages/tree_hash/tests/tests.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 50b648fd..c09d8db1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -101,3 +101,20 @@ jobs: with: command: build args: --bin operator --release --locked + + build-08-wasm-eth: + name: build-08-wasm-eth + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: "Set up environment" + uses: ./.github/setup + - name: Build + uses: actions-rs/cargo@v1 + with: + command: run-script + args: build-08-wasm-eth + + diff --git a/.gitignore b/.gitignore index fadd30ff..00c03b54 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ broadcast/*/31337/ # Rust target elf +artifacts # Config files and test artifacts programs/relayer/config.json diff --git a/Cargo.lock b/Cargo.lock index cb21136e..2496e73a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,8 +231,8 @@ dependencies = [ "alloy-serde", "c-kzg", "derive_more 1.0.0", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.8.0", + "ethereum_ssz_derive 0.8.0", "once_cell", "serde", "sha2 0.10.8", @@ -359,9 +359,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" dependencies = [ "alloy-rlp", + "arbitrary", "bytes", "cfg-if", "const-hex", + "derive_arbitrary", "derive_more 1.0.0", "foldhash", "hashbrown 0.15.2", @@ -372,6 +374,7 @@ dependencies = [ "keccak-asm", "paste", "proptest", + "proptest-derive", "rand", "ruint", "rustc-hash 2.1.0", @@ -537,8 +540,8 @@ dependencies = [ "alloy-primitives 0.8.14", "alloy-rpc-types-engine", "alloy-serde", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.8.0", + "ethereum_ssz_derive 0.8.0", "serde", "serde_with", "thiserror 2.0.4", @@ -556,8 +559,8 @@ dependencies = [ "alloy-rlp", "alloy-serde", "derive_more 1.0.0", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.8.0", + "ethereum_ssz_derive 0.8.0", "serde", "strum", ] @@ -942,6 +945,12 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + [[package]] name = "ark-bls12-381" version = "0.4.0" @@ -2249,6 +2258,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -2598,8 +2618,8 @@ dependencies = [ "base64 0.22.1", "ethereum-trie-db", "ethereum-utils", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.8.0", + "ethereum_ssz_derive 0.8.0", "milagro_bls", "serde", "serde_json", @@ -2654,17 +2674,6 @@ dependencies = [ "serde", ] -[[package]] -name = "ethereum_hashing" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" -dependencies = [ - "cpufeatures", - "ring 0.17.8", - "sha2 0.10.8", -] - [[package]] name = "ethereum_serde_utils" version = "0.7.0" @@ -2678,6 +2687,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ethereum_ssz" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e999563461faea0ab9bc0024e5e66adcee35881f3d5062f52f31a4070fe1522" +dependencies = [ + "alloy-primitives 0.8.14", + "itertools 0.13.0", + "smallvec", +] + [[package]] name = "ethereum_ssz" version = "0.8.0" @@ -2694,6 +2714,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "ethereum_ssz_derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3deae99c8e74829a00ba7a92d49055732b3c1f093f2ccfa3cbc621679b6fa91" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "ethereum_ssz_derive" version = "0.8.0" @@ -4179,6 +4211,7 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ + "arbitrary", "equivalent", "hashbrown 0.15.2", "serde", @@ -4782,9 +4815,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95f06be0417d97f81fe4e5c86d7d01b392655a9cac9c19a848aa033e18937b23" dependencies = [ - "alloy-rlp", "const-hex", - "proptest", "serde", "smallvec", ] @@ -5561,6 +5592,17 @@ dependencies = [ "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "prost" version = "0.13.3" @@ -6045,6 +6087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", + "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", @@ -8124,12 +8167,14 @@ dependencies = [ [[package]] name = "tree_hash" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373495c23db675a5192de8b610395e1bec324d596f9e6111192ce903dc11403a" dependencies = [ "alloy-primitives 0.8.14", - "ethereum_hashing", + "ethereum_ssz 0.7.1", + "ethereum_ssz_derive 0.7.1", + "rand", + "sha2 0.10.8", "smallvec", + "tree_hash_derive", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0507949e..57fb11dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,12 @@ license = "MIT" repository = "https://github.com/cosmos/solidity-ibc-eureka" keywords = ["cosmos", "ibc", "sp1", "tendermint", "ethereum", "bridge", "solidity", "eureka"] +[workspace.metadata.scripts] +build-08-wasm-eth = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.16.1 ./programs/08-wasm-eth""" + [workspace.dependencies] ibc-eureka-solidity-types = { path = "packages/solidity", default-features = false } ibc-eureka-relayer-lib = { path = "packages/relayer-lib", default-features = false } @@ -77,12 +83,12 @@ alloy = { version = "0.7", default-features = false } alloy-contract = { version = "0.7", default-features = false } alloy-sol-types = { version = "0.8", default-features = false } alloy-primitives = { version = "0.8", default-features = false, features = ["serde", "rlp"] } -alloy-trie = { version = "0.5", features = ["serde"] } # TODO: default-features = false, -alloy-serde = { version = "0.7", default-feutures = false } -alloy-rlp = { version = "0.3", default-feutures = false } +alloy-trie = { version = "0.5", default-features = false, features = ["serde"] } +alloy-serde = { version = "0.7", default-features = false } +alloy-rlp = { version = "0.3", default-features = false } alloy-rpc-types-eth = { version = "0.7", default-features = false, features = ["serde"] } -alloy-rpc-types-beacon = { version = "0.7", default-features = false, features = ["ssz"]} -alloy-rpc-types-engine = { version = "0.7", default-features = false, features = ["ssz"]} +alloy-rpc-types-beacon = { version = "0.7", default-features = false, features = ["ssz"] } +alloy-rpc-types-engine = { version = "0.7", default-features = false, features = ["ssz"] } sp1-sdk = { version = "3.3", default-features = false } sp1-zkvm = { version = "3.3", default-features = false } @@ -95,7 +101,7 @@ cw-storage-plus = { version = "2.0", default-features = false } # The dependencies below are maintained by Sigma Prime (for use in Lighthouse (and the broader Ethereum ecosystem)) ethereum_ssz = { version = "0.8", default-features = false } ethereum_ssz_derive = { version = "0.8", default-features = false } -tree_hash = { version = "0.8", default-features = false } +tree_hash = { path = "packages/tree_hash", version = "0.8", default-features = false } tree_hash_derive = { version = "0.8", default-features = false } # The dependencies below are maintained by Parity Tech diff --git a/packages/ethereum-light-client/Cargo.toml b/packages/ethereum-light-client/Cargo.toml index f4065ad6..4ea002f8 100644 --- a/packages/ethereum-light-client/Cargo.toml +++ b/packages/ethereum-light-client/Cargo.toml @@ -8,7 +8,7 @@ repository = { workspace = true } ethereum-trie-db = { workspace = true } ethereum-utils = { workspace = true } -alloy-primitives = { workspace = true } +alloy-primitives = { workspace = true, default-features = false } alloy-rpc-types-eth = { workspace = true } alloy-rpc-types-beacon = { workspace = true } alloy-rpc-types-engine = { workspace = true } diff --git a/packages/ethereum-trie-db/Cargo.toml b/packages/ethereum-trie-db/Cargo.toml index 46f812a0..8f5055e7 100644 --- a/packages/ethereum-trie-db/Cargo.toml +++ b/packages/ethereum-trie-db/Cargo.toml @@ -16,7 +16,7 @@ rlp-derive = { workspace = true } primitive-types = { workspace = true } sha3 = { workspace = true } -alloy-primitives = { workspace = true } # Needed for alloy-based interfaces/functions +alloy-primitives = { workspace = true, default-features = false } # Needed for alloy-based interfaces/functions thiserror = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/packages/ethereum-utils/Cargo.toml b/packages/ethereum-utils/Cargo.toml index 37072baf..95618c2a 100644 --- a/packages/ethereum-utils/Cargo.toml +++ b/packages/ethereum-utils/Cargo.toml @@ -5,6 +5,6 @@ edition = { workspace = true } repository = { workspace = true } [dependencies] -alloy-primitives = { workspace = true } +alloy-primitives = { workspace = true, default-features = false } base64 = { workspace = true } serde = { workspace = true } diff --git a/packages/tree_hash/Cargo.toml b/packages/tree_hash/Cargo.toml new file mode 100644 index 000000000..f39b498c --- /dev/null +++ b/packages/tree_hash/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "tree_hash" +version = "0.8.0" +edition = "2021" +description = "Efficient Merkle-hashing as used in Ethereum consensus" +license = "Apache-2.0" +readme = "../README.md" +repository = "https://github.com/sigp/tree_hash" +documentation = "https://docs.rs/tree_hash" +keywords = ["ethereum"] +categories = ["cryptography::cryptocurrencies"] + +[dependencies] +alloy-primitives = "0.8.0" +smallvec = "1.6.1" +sha2 = "0.10" + +[dev-dependencies] +rand = "0.8.5" +tree_hash_derive = "0.8.0" +ethereum_ssz = "0.7" +ethereum_ssz_derive = "0.7" + +[features] +arbitrary = ["alloy-primitives/arbitrary"] diff --git a/packages/tree_hash/src/impls.rs b/packages/tree_hash/src/impls.rs new file mode 100644 index 000000000..53d28f4b --- /dev/null +++ b/packages/tree_hash/src/impls.rs @@ -0,0 +1,240 @@ +use super::*; +use alloy_primitives::{Address, B256, U128, U256}; +use std::sync::Arc; + +fn int_to_hash256(int: u64) -> Hash256 { + let mut bytes = [0; HASHSIZE]; + bytes[0..8].copy_from_slice(&int.to_le_bytes()); + Hash256::from_slice(&bytes) +} + +macro_rules! impl_for_bitsize { + ($type: ident, $bit_size: expr) => { + impl TreeHash for $type { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Basic + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + PackedEncoding::from_slice(&self.to_le_bytes()) + } + + fn tree_hash_packing_factor() -> usize { + HASHSIZE / ($bit_size / 8) + } + + #[allow(clippy::cast_lossless)] // Lint does not apply to all uses of this macro. + fn tree_hash_root(&self) -> Hash256 { + int_to_hash256(*self as u64) + } + } + }; +} + +impl_for_bitsize!(u8, 8); +impl_for_bitsize!(u16, 16); +impl_for_bitsize!(u32, 32); +impl_for_bitsize!(u64, 64); +impl_for_bitsize!(usize, 64); + +impl TreeHash for bool { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Basic + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + (*self as u8).tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + u8::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> Hash256 { + int_to_hash256(*self as u64) + } +} + +/// Only valid for byte types less than 32 bytes. +macro_rules! impl_for_lt_32byte_u8_array { + ($len: expr) => { + impl TreeHash for [u8; $len] { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("bytesN should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("bytesN should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + let mut result = [0; 32]; + result[0..$len].copy_from_slice(&self[..]); + Hash256::from_slice(&result) + } + } + }; +} + +impl_for_lt_32byte_u8_array!(4); +impl_for_lt_32byte_u8_array!(32); + +impl TreeHash for [u8; 48] { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + let values_per_chunk = BYTES_PER_CHUNK; + let minimum_chunk_count = (48 + values_per_chunk - 1) / values_per_chunk; + merkle_root(self, minimum_chunk_count) + } +} + +impl TreeHash for U128 { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Basic + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + PackedEncoding::from_slice(&self.to_le_bytes::<{ Self::BYTES }>()) + } + + fn tree_hash_packing_factor() -> usize { + 2 + } + + fn tree_hash_root(&self) -> Hash256 { + Hash256::right_padding_from(&self.to_le_bytes::<{ Self::BYTES }>()) + } +} + +impl TreeHash for U256 { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Basic + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + PackedEncoding::from(self.to_le_bytes::<{ Self::BYTES }>()) + } + + fn tree_hash_packing_factor() -> usize { + 1 + } + + fn tree_hash_root(&self) -> Hash256 { + Hash256::from(self.to_le_bytes::<{ Self::BYTES }>()) + } +} + +impl TreeHash for Address { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + let mut result = [0; 32]; + result[0..20].copy_from_slice(self.as_slice()); + PackedEncoding::from_slice(&result) + } + + fn tree_hash_packing_factor() -> usize { + 1 + } + + fn tree_hash_root(&self) -> Hash256 { + let mut result = [0; 32]; + result[0..20].copy_from_slice(self.as_slice()); + Hash256::from_slice(&result) + } +} + +impl TreeHash for B256 { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + PackedEncoding::from_slice(self.as_slice()) + } + + fn tree_hash_packing_factor() -> usize { + 1 + } + + fn tree_hash_root(&self) -> Hash256 { + *self + } +} + +impl TreeHash for Arc { + fn tree_hash_type() -> TreeHashType { + T::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + self.as_ref().tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + T::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> Hash256 { + self.as_ref().tree_hash_root() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn bool() { + let mut true_bytes: Vec = vec![1]; + true_bytes.append(&mut vec![0; 31]); + + let false_bytes: Vec = vec![0; 32]; + + assert_eq!(true.tree_hash_root().as_slice(), true_bytes.as_slice()); + assert_eq!(false.tree_hash_root().as_slice(), false_bytes.as_slice()); + } + + #[test] + fn arc() { + let one = U128::from(1); + let one_arc = Arc::new(one); + assert_eq!(one_arc.tree_hash_root(), one.tree_hash_root()); + } + + #[test] + fn int_to_bytes() { + assert_eq!(int_to_hash256(0).as_slice(), &[0; 32]); + assert_eq!( + int_to_hash256(1).as_slice(), + &[ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0 + ] + ); + assert_eq!( + int_to_hash256(u64::max_value()).as_slice(), + &[ + 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + } +} diff --git a/packages/tree_hash/src/lib.rs b/packages/tree_hash/src/lib.rs new file mode 100644 index 000000000..7edfb1ee --- /dev/null +++ b/packages/tree_hash/src/lib.rs @@ -0,0 +1,211 @@ +pub mod impls; +mod merkle_hasher; +mod merkleize_padded; +mod merkleize_standard; +pub mod sha256; + +pub use merkle_hasher::{Error, MerkleHasher}; +pub use merkleize_padded::merkleize_padded; +pub use merkleize_standard::merkleize_standard; + +use sha256::{hash32_concat, hash_fixed, ZERO_HASHES, ZERO_HASHES_MAX_INDEX}; +use smallvec::SmallVec; + +pub const BYTES_PER_CHUNK: usize = 32; +pub const HASHSIZE: usize = 32; +pub const MERKLE_HASH_CHUNK: usize = 2 * BYTES_PER_CHUNK; +pub const MAX_UNION_SELECTOR: u8 = 127; +pub const SMALLVEC_SIZE: usize = 32; + +pub type Hash256 = alloy_primitives::B256; +pub type PackedEncoding = SmallVec<[u8; SMALLVEC_SIZE]>; + +/// Convenience method for `MerkleHasher` which also provides some fast-paths for small trees. +/// +/// `minimum_leaf_count` will only be used if it is greater than or equal to the minimum number of leaves that can be created from `bytes`. +pub fn merkle_root(bytes: &[u8], minimum_leaf_count: usize) -> Hash256 { + let leaves = std::cmp::max( + (bytes.len() + (HASHSIZE - 1)) / HASHSIZE, + minimum_leaf_count, + ); + + if leaves == 0 { + // If there are no bytes then the hash is always zero. + Hash256::ZERO + } else if leaves == 1 { + // If there is only one leaf, the hash is always those leaf bytes padded out to 32-bytes. + let mut hash = [0; HASHSIZE]; + hash[0..bytes.len()].copy_from_slice(bytes); + Hash256::from_slice(&hash) + } else if leaves == 2 { + // If there are only two leaves (this is common with BLS pubkeys), we can avoid some + // overhead with `MerkleHasher` and just do a simple 3-node tree here. + let mut leaves = [0; HASHSIZE * 2]; + leaves[0..bytes.len()].copy_from_slice(bytes); + + Hash256::from_slice(&hash_fixed(&leaves)) + } else { + // If there are 3 or more leaves, use `MerkleHasher`. + let mut hasher = MerkleHasher::with_leaves(leaves); + hasher + .write(bytes) + .expect("the number of leaves is adequate for the number of bytes"); + hasher + .finish() + .expect("the number of leaves is adequate for the number of bytes") + } +} + +/// Returns the node created by hashing `root` and `length`. +/// +/// Used in `TreeHash` for inserting the length of a list above it's root. +pub fn mix_in_length(root: &Hash256, length: usize) -> Hash256 { + let usize_len = std::mem::size_of::(); + + let mut length_bytes = [0; BYTES_PER_CHUNK]; + length_bytes[0..usize_len].copy_from_slice(&length.to_le_bytes()); + + Hash256::from_slice(&hash32_concat(root.as_slice(), &length_bytes)[..]) +} + +/// Returns `Some(root)` created by hashing `root` and `selector`, if `selector <= +/// MAX_UNION_SELECTOR`. Otherwise, returns `None`. +/// +/// Used in `TreeHash` for the "union" type. +/// +/// ## Specification +/// +/// ```ignore,text +/// mix_in_selector: Given a Merkle root root and a type selector selector ("uint256" little-endian +/// serialization) return hash(root + selector). +/// ``` +/// +/// https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.3/ssz/simple-serialize.md#union +pub fn mix_in_selector(root: &Hash256, selector: u8) -> Option { + if selector > MAX_UNION_SELECTOR { + return None; + } + + let mut chunk = [0; BYTES_PER_CHUNK]; + chunk[0] = selector; + + let root = hash32_concat(root.as_slice(), &chunk); + Some(Hash256::from_slice(&root)) +} + +/// Returns a cached padding node for a given height. +fn get_zero_hash(height: usize) -> &'static [u8] { + if height <= ZERO_HASHES_MAX_INDEX { + &ZERO_HASHES[height] + } else { + panic!("Tree exceeds MAX_TREE_DEPTH of {}", ZERO_HASHES_MAX_INDEX) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum TreeHashType { + Basic, + Vector, + List, + Container, +} + +pub trait TreeHash { + fn tree_hash_type() -> TreeHashType; + + fn tree_hash_packed_encoding(&self) -> PackedEncoding; + + fn tree_hash_packing_factor() -> usize; + + fn tree_hash_root(&self) -> Hash256; +} + +/// Punch through references. +impl<'a, T> TreeHash for &'a T +where + T: TreeHash, +{ + fn tree_hash_type() -> TreeHashType { + T::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + T::tree_hash_packed_encoding(*self) + } + + fn tree_hash_packing_factor() -> usize { + T::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> Hash256 { + T::tree_hash_root(*self) + } +} + +#[macro_export] +macro_rules! tree_hash_ssz_encoding_as_vector { + ($type: ident) => { + impl tree_hash::TreeHash for $type { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_root(&self) -> Vec { + tree_hash::merkle_root(&ssz::ssz_encode(self)) + } + } + }; +} + +#[macro_export] +macro_rules! tree_hash_ssz_encoding_as_list { + ($type: ident) => { + impl tree_hash::TreeHash for $type { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> Vec { + ssz::ssz_encode(self).tree_hash_root() + } + } + }; +} + +#[cfg(test)] +mod test { + use sha256::hash; + + use super::*; + + #[test] + fn mix_length() { + let hash = { + let mut preimage = vec![42; BYTES_PER_CHUNK]; + preimage.append(&mut vec![42]); + preimage.append(&mut vec![0; BYTES_PER_CHUNK - 1]); + hash(&preimage) + }; + + assert_eq!( + mix_in_length(&Hash256::from_slice(&[42; BYTES_PER_CHUNK]), 42).as_slice(), + &hash[..] + ); + } +} diff --git a/packages/tree_hash/src/merkle_hasher.rs b/packages/tree_hash/src/merkle_hasher.rs new file mode 100644 index 000000000..147a37f1 --- /dev/null +++ b/packages/tree_hash/src/merkle_hasher.rs @@ -0,0 +1,576 @@ +use crate::{get_zero_hash, sha256::HASH_LEN, Hash256, HASHSIZE}; +use sha2::{Digest, Sha256}; +use smallvec::{smallvec, SmallVec}; +use std::mem; + +type SmallVec8 = SmallVec<[T; 8]>; + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + /// The maximum number of leaves defined by the initialization `depth` has been exceed. + MaximumLeavesExceeded { max_leaves: usize }, +} + +/// Helper struct to store either a hash digest or a slice. +/// +/// Should be used as a left or right value for some node. +enum Preimage<'a> { + Digest([u8; HASH_LEN]), + Slice(&'a [u8]), +} + +impl<'a> Preimage<'a> { + /// Returns a 32-byte slice. + fn as_bytes(&self) -> &[u8] { + match self { + Preimage::Digest(digest) => digest.as_ref(), + Preimage::Slice(slice) => slice, + } + } +} + +/// A node that has had a left child supplied, but not a right child. +struct HalfNode { + /// The hasher context. + context: Sha256, + /// The tree id of the node. The root node has in id of `1` and ids increase moving down the + /// tree from left to right. + id: usize, +} + +impl HalfNode { + /// Create a new half-node from the given `left` value. + fn new(id: usize, left: Preimage) -> Self { + let mut context = Sha256::new(); + context.update(left.as_bytes()); + + Self { context, id } + } + + /// Complete the half-node by providing a `right` value. Returns a digest of the left and right + /// nodes. + fn finish(mut self, right: Preimage) -> [u8; HASH_LEN] { + self.context.update(right.as_bytes()); + self.context.finalize().into() + } +} + +/// Provides a Merkle-root hasher that allows for streaming bytes (i.e., providing any-length byte +/// slices without need to separate into leaves). Efficiently handles cases where not all leaves +/// have been provided by assuming all non-provided leaves are `[0; 32]` and pre-computing the +/// zero-value hashes at all depths of the tree. +/// +/// This algorithm aims to allocate as little memory as possible and it does this by "folding" up +/// the tree as each leaf is provided. Consider this step-by-step functional diagram of hashing a +/// tree with depth three: +/// +/// ## Functional Diagram +/// +/// Nodes that are `-` have not been defined and do not occupy memory. Nodes that are `L` are +/// leaves that are provided but are not stored. Nodes that have integers (`1`, `2`) are stored in +/// our struct. Finally, nodes that are `X` were stored, but are now removed. +/// +/// ### Start +/// +/// ```ignore +/// - +/// / \ +/// - - +/// / \ / \ +/// - - - - +/// ``` +/// +/// ### Provide first leaf +/// +/// ```ignore +/// - +/// / \ +/// 2 - +/// / \ / \ +/// L - - - +/// ``` +/// +/// ### Provide second leaf +/// +/// ```ignore +/// 1 +/// / \ +/// X - +/// / \ / \ +/// L L - - +/// ``` +/// +/// ### Provide third leaf +/// +/// ```ignore +/// 1 +/// / \ +/// X 3 +/// / \ / \ +/// L L L - +/// ``` +/// +/// ### Provide fourth and final leaf +/// +/// ```ignore +/// 1 +/// / \ +/// X X +/// / \ / \ +/// L L L L +/// ``` +/// +pub struct MerkleHasher { + /// Stores the nodes that are half-complete and awaiting a right node. + /// + /// A smallvec of size 8 means we can hash a tree with 256 leaves without allocating on the + /// heap. Each half-node is 232 bytes, so this smallvec may store 1856 bytes on the stack. + half_nodes: SmallVec8, + /// The depth of the tree that will be produced. + /// + /// Depth is counted top-down (i.e., the root node is at depth 0). A tree with 1 leaf has a + /// depth of 1, a tree with 4 leaves has a depth of 3. + depth: usize, + /// The next leaf that we are expecting to process. + next_leaf: usize, + /// A buffer of bytes that are waiting to be written to a leaf. + buffer: SmallVec<[u8; 32]>, + /// Set to Some(root) when the root of the tree is known. + root: Option, +} + +/// Returns the parent of node with id `i`. +fn get_parent(i: usize) -> usize { + i / 2 +} + +/// Gets the depth of a node with an id of `i`. +/// +/// It is a logic error to provide `i == 0`. +/// +/// E.g., if `i` is 1, depth is 0. If `i` is is 1, depth is 1. +fn get_depth(i: usize) -> usize { + let total_bits = mem::size_of::() * 8; + total_bits - i.leading_zeros() as usize - 1 +} + +impl MerkleHasher { + /// Instantiate a hasher for a tree with a given number of leaves. + /// + /// `num_leaves` will be rounded to the next power of two. E.g., if `num_leaves == 6`, then the + /// tree will _actually_ be able to accomodate 8 leaves and the resulting hasher is exactly the + /// same as one that was instantiated with `Self::with_leaves(8)`. + /// + /// ## Notes + /// + /// If `num_leaves == 0`, a tree of depth 1 will be created. If no leaves are provided it will + /// return a root of `[0; 32]`. + pub fn with_leaves(num_leaves: usize) -> Self { + let depth = get_depth(num_leaves.next_power_of_two()) + 1; + Self::with_depth(depth) + } + + /// Instantiates a new, empty hasher for a tree with `depth` layers which will have capacity + /// for `1 << (depth - 1)` leaf nodes. + /// + /// It is not possible to grow the depth of the tree after instantiation. + /// + /// ## Panics + /// + /// Panics if `depth == 0`. + fn with_depth(depth: usize) -> Self { + assert!(depth > 0, "merkle tree cannot have a depth of zero"); + + Self { + half_nodes: SmallVec::with_capacity(depth - 1), + depth, + next_leaf: 1 << (depth - 1), + buffer: SmallVec::with_capacity(32), + root: None, + } + } + + /// Write some bytes to the hasher. + /// + /// ## Errors + /// + /// Returns an error if the given bytes would create a leaf that would exceed the maximum + /// permissible number of leaves defined by the initialization `depth`. E.g., a tree of `depth + /// == 2` can only accept 2 leaves. A tree of `depth == 14` can only accept 8,192 leaves. + pub fn write(&mut self, bytes: &[u8]) -> Result<(), Error> { + let mut ptr = 0; + while ptr <= bytes.len() { + let slice = &bytes[ptr..std::cmp::min(bytes.len(), ptr + HASHSIZE)]; + + if self.buffer.is_empty() && slice.len() == HASHSIZE { + self.process_leaf(slice)?; + ptr += HASHSIZE + } else if self.buffer.len() + slice.len() < HASHSIZE { + self.buffer.extend_from_slice(slice); + ptr += HASHSIZE + } else { + let buf_len = self.buffer.len(); + let required = HASHSIZE - buf_len; + + let mut leaf = [0; HASHSIZE]; + leaf[..buf_len].copy_from_slice(&self.buffer); + leaf[buf_len..].copy_from_slice(&slice[0..required]); + + self.process_leaf(&leaf)?; + self.buffer = smallvec![]; + + ptr += required + } + } + + Ok(()) + } + + /// Process the next leaf in the tree. + /// + /// ## Errors + /// + /// Returns an error if the given leaf would exceed the maximum permissible number of leaves + /// defined by the initialization `depth`. E.g., a tree of `depth == 2` can only accept 2 + /// leaves. A tree of `depth == 14` can only accept 8,192 leaves. + fn process_leaf(&mut self, leaf: &[u8]) -> Result<(), Error> { + assert_eq!(leaf.len(), HASHSIZE, "a leaf must be 32 bytes"); + + let max_leaves = 1 << (self.depth + 1); + + if self.next_leaf > max_leaves { + return Err(Error::MaximumLeavesExceeded { max_leaves }); + } else if self.next_leaf == 1 { + // A tree of depth one has a root that is equal to the first given leaf. + self.root = Some(Hash256::from_slice(leaf)) + } else if self.next_leaf % 2 == 0 { + self.process_left_node(self.next_leaf, Preimage::Slice(leaf)) + } else { + self.process_right_node(self.next_leaf, Preimage::Slice(leaf)) + } + + self.next_leaf += 1; + + Ok(()) + } + + /// Returns the root of the Merkle tree. + /// + /// If not all leaves have been provided, the tree will be efficiently completed under the + /// assumption that all not-yet-provided leaves are equal to `[0; 32]`. + /// + /// ## Errors + /// + /// Returns an error if the bytes remaining in the buffer would create a leaf that would exceed + /// the maximum permissible number of leaves defined by the initialization `depth`. + pub fn finish(mut self) -> Result { + if !self.buffer.is_empty() { + let mut leaf = [0; HASHSIZE]; + leaf[..self.buffer.len()].copy_from_slice(&self.buffer); + self.process_leaf(&leaf)? + } + + // If the tree is incomplete, we must complete it by providing zero-hashes. + loop { + if let Some(root) = self.root { + break Ok(root); + } else if let Some(node) = self.half_nodes.last() { + let right_child = node.id * 2 + 1; + self.process_right_node(right_child, self.zero_hash(right_child)); + } else if self.next_leaf == 1 { + // The next_leaf can only be 1 if the tree has a depth of one. If have been no + // leaves supplied, assume a root of zero. + break Ok(Hash256::ZERO); + } else { + // The only scenario where there are (a) no half nodes and (b) a tree of depth + // two or more is where no leaves have been supplied at all. + // + // Once we supply this first zero-hash leaf then all future operations will be + // triggered via the `process_right_node` branch. + self.process_left_node(self.next_leaf, self.zero_hash(self.next_leaf)) + } + } + } + + /// Process a node that will become the left-hand node of some parent. The supplied `id` is + /// that of the node (not the parent). The `preimage` is the value of the node (i.e., if this + /// is a leaf node it will be the value of that leaf). + /// + /// In this scenario, the only option is to push a new half-node. + fn process_left_node(&mut self, id: usize, preimage: Preimage) { + self.half_nodes + .push(HalfNode::new(get_parent(id), preimage)) + } + + /// Process a node that will become the right-hand node of some parent. The supplied `id` is + /// that of the node (not the parent). The `preimage` is the value of the node (i.e., if this + /// is a leaf node it will be the value of that leaf). + /// + /// This operation will always complete one node, then it will attempt to crawl up the tree and + /// collapse all other completed nodes. For example, consider a tree of depth 3 (see diagram + /// below). When providing the node with id `7`, the node with id `3` will be completed which + /// will also provide the right-node for the `1` node. This function will complete both of + /// those nodes and ultimately find the root of the tree. + /// + /// ```ignore + /// 1 <-- completed + /// / \ + /// 2 3 <-- completed + /// / \ / \ + /// 4 5 6 7 <-- supplied right node + /// ``` + fn process_right_node(&mut self, id: usize, mut preimage: Preimage) { + let mut parent = get_parent(id); + + loop { + match self.half_nodes.last() { + Some(node) if node.id == parent => { + preimage = Preimage::Digest( + self.half_nodes + .pop() + .expect("if .last() is Some then .pop() must succeed") + .finish(preimage), + ); + if parent == 1 { + self.root = Some(Hash256::from_slice(preimage.as_bytes())); + break; + } else { + parent = get_parent(parent); + } + } + _ => { + self.half_nodes.push(HalfNode::new(parent, preimage)); + break; + } + } + } + } + + /// Returns a "zero hash" from a pre-computed set for the given node. + /// + /// Note: this node is not always zero, instead it is the result of hashing up a tree where the + /// leaves are all zeros. E.g., in a tree of depth 2, the `zero_hash` of a node at depth 1 + /// will be `[0; 32]`. However, the `zero_hash` for a node at depth 0 will be + /// `hash(concat([0; 32], [0; 32])))`. + fn zero_hash(&self, id: usize) -> Preimage<'static> { + Preimage::Slice(get_zero_hash(self.depth - (get_depth(id) + 1))) + } +} + +#[cfg(test)] +mod test { + use alloy_primitives::U256; + + use super::*; + use crate::merkleize_padded; + + // TODO: The new size is 120, not sure if that matters or not 🙈 + /// This test is just to ensure that the stack size of the `Context` remains the same. We choose + /// our smallvec size based upon this, so it's good to know if it suddenly changes in size. + //#[test] + //fn context_size() { + // assert_eq!( + // mem::size_of::(), + // 224, + // "Halfnode size should be as expected" + // ); + //} + + fn compare_with_reference(leaves: &[Hash256], depth: usize) { + let reference_bytes = leaves + .iter() + .flat_map(|hash| hash.as_slice()) + .copied() + .collect::>(); + + let reference_root = merkleize_padded(&reference_bytes, 1 << (depth - 1)); + + let merklizer_root_32_bytes = { + let mut m = MerkleHasher::with_depth(depth); + for leaf in leaves.iter() { + m.write(leaf.as_slice()).expect("should process leaf"); + } + m.finish().expect("should finish") + }; + + assert_eq!( + reference_root, merklizer_root_32_bytes, + "32 bytes should match reference root" + ); + + let merklizer_root_individual_3_bytes = { + let mut m = MerkleHasher::with_depth(depth); + for bytes in reference_bytes.chunks(3) { + m.write(bytes).expect("should process byte"); + } + m.finish().expect("should finish") + }; + + assert_eq!( + reference_root, merklizer_root_individual_3_bytes, + "3 bytes should match reference root" + ); + + let merklizer_root_individual_single_bytes = { + let mut m = MerkleHasher::with_depth(depth); + for byte in reference_bytes.iter() { + m.write(&[*byte]).expect("should process byte"); + } + m.finish().expect("should finish") + }; + + assert_eq!( + reference_root, merklizer_root_individual_single_bytes, + "single bytes should match reference root" + ); + } + + /// A simple wrapper to compare MerkleHasher to the reference function by just giving a number + /// of leaves and a depth. + fn compare_reference_with_len(leaves: u64, depth: usize) { + let leaves = (0..leaves) + .map(|leaf| Hash256::from(U256::from(leaf))) + .collect::>(); + compare_with_reference(&leaves, depth) + } + + /// Compares the `MerkleHasher::with_depth` and `MerkleHasher::with_leaves` generate consistent + /// results. + fn compare_new_with_leaf_count(num_leaves: u64, depth: usize) { + let leaves = (0..num_leaves) + .map(|leaf| Hash256::from(U256::from(leaf))) + .collect::>(); + + let from_depth = { + let mut m = MerkleHasher::with_depth(depth); + for leaf in leaves.iter() { + m.write(leaf.as_slice()).expect("should process leaf"); + } + m.finish() + }; + + let from_num_leaves = { + let mut m = MerkleHasher::with_leaves(num_leaves as usize); + for leaf in leaves.iter() { + m.process_leaf(leaf.as_slice()) + .expect("should process leaf"); + } + m.finish() + }; + + assert_eq!( + from_depth, from_num_leaves, + "hash generated by depth should match that from num leaves" + ); + } + + #[test] + fn with_leaves() { + compare_new_with_leaf_count(1, 1); + compare_new_with_leaf_count(2, 2); + compare_new_with_leaf_count(3, 3); + compare_new_with_leaf_count(4, 3); + compare_new_with_leaf_count(5, 4); + compare_new_with_leaf_count(6, 4); + compare_new_with_leaf_count(7, 4); + compare_new_with_leaf_count(8, 4); + compare_new_with_leaf_count(9, 5); + compare_new_with_leaf_count(10, 5); + compare_new_with_leaf_count(11, 5); + compare_new_with_leaf_count(12, 5); + compare_new_with_leaf_count(13, 5); + compare_new_with_leaf_count(14, 5); + compare_new_with_leaf_count(15, 5); + } + + #[test] + fn depth() { + assert_eq!(get_depth(1), 0); + assert_eq!(get_depth(2), 1); + assert_eq!(get_depth(3), 1); + assert_eq!(get_depth(4), 2); + assert_eq!(get_depth(5), 2); + assert_eq!(get_depth(6), 2); + assert_eq!(get_depth(7), 2); + assert_eq!(get_depth(8), 3); + } + + #[test] + fn with_0_leaves() { + let hasher = MerkleHasher::with_leaves(0); + assert_eq!(hasher.finish().unwrap(), Hash256::ZERO); + } + + #[test] + #[should_panic] + fn too_many_leaves() { + compare_reference_with_len(2, 1); + } + + #[test] + fn full_trees() { + compare_reference_with_len(1, 1); + compare_reference_with_len(2, 2); + compare_reference_with_len(4, 3); + compare_reference_with_len(8, 4); + compare_reference_with_len(16, 5); + compare_reference_with_len(32, 6); + compare_reference_with_len(64, 7); + compare_reference_with_len(128, 8); + compare_reference_with_len(256, 9); + compare_reference_with_len(256, 9); + compare_reference_with_len(8192, 14); + } + + #[test] + fn incomplete_trees() { + compare_reference_with_len(0, 1); + + compare_reference_with_len(0, 2); + compare_reference_with_len(1, 2); + + for i in 0..=4 { + compare_reference_with_len(i, 3); + } + + for i in 0..=7 { + compare_reference_with_len(i, 4); + } + + for i in 0..=15 { + compare_reference_with_len(i, 5); + } + + for i in 0..=32 { + compare_reference_with_len(i, 6); + } + + for i in 0..=64 { + compare_reference_with_len(i, 7); + } + + compare_reference_with_len(0, 14); + compare_reference_with_len(13, 14); + compare_reference_with_len(8191, 14); + } + + #[test] + fn remaining_buffer() { + let a = { + let mut m = MerkleHasher::with_leaves(2); + m.write(&[1]).expect("should write"); + m.finish().expect("should finish") + }; + + let b = { + let mut m = MerkleHasher::with_leaves(2); + let mut leaf = vec![1]; + leaf.extend_from_slice(&[0; 31]); + m.write(&leaf).expect("should write"); + m.write(&[0; 32]).expect("should write"); + m.finish().expect("should finish") + }; + + assert_eq!(a, b, "should complete buffer"); + } +} diff --git a/packages/tree_hash/src/merkleize_padded.rs b/packages/tree_hash/src/merkleize_padded.rs new file mode 100644 index 000000000..e12fb27b --- /dev/null +++ b/packages/tree_hash/src/merkleize_padded.rs @@ -0,0 +1,331 @@ +use crate::sha256::{hash32_concat, hash_fixed}; + +use super::{get_zero_hash, Hash256, BYTES_PER_CHUNK}; + +/// Merkleize `bytes` and return the root, optionally padding the tree out to `min_leaves` number of +/// leaves. +/// +/// **Note**: This function is generally worse than using the `crate::merkle_root` which uses +/// `MerkleHasher`. We only keep this function around for reference testing. +/// +/// First all nodes are extracted from `bytes` and then a padding node is added until the number of +/// leaf chunks is greater than or equal to `min_leaves`. Callers may set `min_leaves` to `0` if no +/// adding additional chunks should be added to the given `bytes`. +/// +/// If `bytes.len() <= BYTES_PER_CHUNK`, no hashing is done and `bytes` is returned, potentially +/// padded out to `BYTES_PER_CHUNK` length with `0`. +/// +/// ## CPU Performance +/// +/// A cache of `MAX_TREE_DEPTH` hashes are stored to avoid re-computing the hashes of padding nodes +/// (or their parents). Therefore, adding padding nodes only incurs one more hash per additional +/// height of the tree. +/// +/// ## Memory Performance +/// +/// This algorithm has two interesting memory usage properties: +/// +/// 1. The maximum memory footprint is roughly `O(V / 2)` memory, where `V` is the number of leaf +/// chunks with values (i.e., leaves that are not padding). The means adding padding nodes to +/// the tree does not increase the memory footprint. +/// 2. At each height of the tree half of the memory is freed until only a single chunk is stored. +/// 3. The input `bytes` are not copied into another list before processing. +/// +/// _Note: there are some minor memory overheads, including a handful of usizes and a list of +/// `MAX_TREE_DEPTH` hashes as `lazy_static` constants._ +pub fn merkleize_padded(bytes: &[u8], min_leaves: usize) -> Hash256 { + // If the bytes are just one chunk or less, pad to one chunk and return without hashing. + if bytes.len() <= BYTES_PER_CHUNK && min_leaves <= 1 { + let mut o = bytes.to_vec(); + o.resize(BYTES_PER_CHUNK, 0); + return Hash256::from_slice(&o); + } + + assert!( + bytes.len() > BYTES_PER_CHUNK || min_leaves > 1, + "Merkle hashing only needs to happen if there is more than one chunk" + ); + + // The number of leaves that can be made directly from `bytes`. + let leaves_with_values = (bytes.len() + (BYTES_PER_CHUNK - 1)) / BYTES_PER_CHUNK; + + // The number of parents that have at least one non-padding leaf. + // + // Since there is more than one node in this tree (see prior assertion), there should always be + // one or more initial parent nodes. + let initial_parents_with_values = std::cmp::max(1, next_even_number(leaves_with_values) / 2); + + // The number of leaves in the full tree (including padding nodes). + let num_leaves = std::cmp::max(leaves_with_values, min_leaves).next_power_of_two(); + + // The number of levels in the tree. + // + // A tree with a single node has `height == 1`. + let height = num_leaves.trailing_zeros() as usize + 1; + + assert!(height >= 2, "The tree should have two or more heights"); + + // A buffer/scratch-space used for storing each round of hashes at each height. + // + // This buffer is kept as small as possible; it will shrink so it never stores a padding node. + let mut chunks = ChunkStore::with_capacity(initial_parents_with_values); + + // Create a parent in the `chunks` buffer for every two chunks in `bytes`. + // + // I.e., do the first round of hashing, hashing from the `bytes` slice and filling the `chunks` + // struct. + for i in 0..initial_parents_with_values { + let start = i * BYTES_PER_CHUNK * 2; + + // Hash two chunks, creating a parent chunk. + let hash = match bytes.get(start..start + BYTES_PER_CHUNK * 2) { + // All bytes are available, hash as usual. + Some(slice) => hash_fixed(slice), + // Unable to get all the bytes, get a small slice and pad it out. + None => { + let mut preimage = bytes + .get(start..) + .expect("`i` can only be larger than zero if there are bytes to read") + .to_vec(); + preimage.resize(BYTES_PER_CHUNK * 2, 0); + hash_fixed(&preimage) + } + }; + + assert_eq!( + hash.len(), + BYTES_PER_CHUNK, + "Hashes should be exactly one chunk" + ); + + // Store the parent node. + chunks + .set(i, &hash) + .expect("Buffer should always have capacity for parent nodes") + } + + // Iterate through all heights above the leaf nodes and either (a) hash two children or, (b) + // hash a left child and a right padding node. + // + // Skip the 0'th height because the leaves have already been processed. Skip the highest-height + // in the tree as it is the root does not require hashing. + // + // The padding nodes for each height are cached via `lazy static` to simulate non-adjacent + // padding nodes (i.e., avoid doing unnecessary hashing). + for height in 1..height - 1 { + let child_nodes = chunks.len(); + let parent_nodes = next_even_number(child_nodes) / 2; + + // For each pair of nodes stored in `chunks`: + // + // - If two nodes are available, hash them to form a parent. + // - If one node is available, hash it and a cached padding node to form a parent. + for i in 0..parent_nodes { + let (left, right) = match (chunks.get(i * 2), chunks.get(i * 2 + 1)) { + (Ok(left), Ok(right)) => (left, right), + (Ok(left), Err(_)) => (left, get_zero_hash(height)), + // Deriving `parent_nodes` from `chunks.len()` has ensured that we never encounter the + // scenario where we expect two nodes but there are none. + (Err(_), Err(_)) => unreachable!("Parent must have one child"), + // `chunks` is a contiguous array so it is impossible for an index to be missing + // when a higher index is present. + (Err(_), Ok(_)) => unreachable!("Parent must have a left child"), + }; + + assert!( + left.len() == right.len() && right.len() == BYTES_PER_CHUNK, + "Both children should be `BYTES_PER_CHUNK` bytes." + ); + + let hash = hash32_concat(left, right); + + // Store a parent node. + chunks + .set(i, &hash) + .expect("Buf is adequate size for parent"); + } + + // Shrink the buffer so it neatly fits the number of new nodes created in this round. + // + // The number of `parent_nodes` is either decreasing or stable. It never increases. + chunks.truncate(parent_nodes); + } + + // There should be a single chunk left in the buffer and it is the Merkle root. + let root = chunks.into_vec(); + + assert_eq!(root.len(), BYTES_PER_CHUNK, "Only one chunk should remain"); + + Hash256::from_slice(&root) +} + +/// A helper struct for storing words of `BYTES_PER_CHUNK` size in a flat byte array. +#[derive(Debug)] +struct ChunkStore(Vec); + +impl ChunkStore { + /// Creates a new instance with `chunks` padding nodes. + fn with_capacity(chunks: usize) -> Self { + Self(vec![0; chunks * BYTES_PER_CHUNK]) + } + + /// Set the `i`th chunk to `value`. + /// + /// Returns `Err` if `value.len() != BYTES_PER_CHUNK` or `i` is out-of-bounds. + fn set(&mut self, i: usize, value: &[u8]) -> Result<(), ()> { + if i < self.len() && value.len() == BYTES_PER_CHUNK { + let slice = &mut self.0[i * BYTES_PER_CHUNK..i * BYTES_PER_CHUNK + BYTES_PER_CHUNK]; + slice.copy_from_slice(value); + Ok(()) + } else { + Err(()) + } + } + + /// Gets the `i`th chunk. + /// + /// Returns `Err` if `i` is out-of-bounds. + fn get(&self, i: usize) -> Result<&[u8], ()> { + if i < self.len() { + Ok(&self.0[i * BYTES_PER_CHUNK..i * BYTES_PER_CHUNK + BYTES_PER_CHUNK]) + } else { + Err(()) + } + } + + /// Returns the number of chunks presently stored in `self`. + fn len(&self) -> usize { + self.0.len() / BYTES_PER_CHUNK + } + + /// Truncates 'self' to `num_chunks` chunks. + /// + /// Functionally identical to `Vec::truncate`. + fn truncate(&mut self, num_chunks: usize) { + self.0.truncate(num_chunks * BYTES_PER_CHUNK) + } + + /// Consumes `self`, returning the underlying byte array. + fn into_vec(self) -> Vec { + self.0 + } +} + +/// Returns the next even number following `n`. If `n` is even, `n` is returned. +fn next_even_number(n: usize) -> usize { + n + n % 2 +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ZERO_HASHES_MAX_INDEX; + + pub fn reference_root(bytes: &[u8]) -> Hash256 { + crate::merkleize_standard(bytes) + } + + macro_rules! common_tests { + ($get_bytes: ident) => { + #[test] + fn zero_value_0_nodes() { + test_against_reference(&$get_bytes(0 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_1_nodes() { + test_against_reference(&$get_bytes(1 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_2_nodes() { + test_against_reference(&$get_bytes(2 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_3_nodes() { + test_against_reference(&$get_bytes(3 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_4_nodes() { + test_against_reference(&$get_bytes(4 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_8_nodes() { + test_against_reference(&$get_bytes(8 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_9_nodes() { + test_against_reference(&$get_bytes(9 * BYTES_PER_CHUNK), 0); + } + + #[test] + fn zero_value_8_nodes_varying_min_length() { + for i in 0..64 { + test_against_reference(&$get_bytes(8 * BYTES_PER_CHUNK), i); + } + } + + #[test] + fn zero_value_range_of_nodes() { + for i in 0..32 * BYTES_PER_CHUNK { + test_against_reference(&$get_bytes(i), 0); + } + } + + #[test] + fn max_tree_depth_min_nodes() { + let input = vec![0; 10 * BYTES_PER_CHUNK]; + let min_nodes = 2usize.pow(ZERO_HASHES_MAX_INDEX as u32); + assert_eq!( + merkleize_padded(&input, min_nodes).as_slice(), + get_zero_hash(ZERO_HASHES_MAX_INDEX) + ); + } + }; + } + + mod zero_value { + use super::*; + + fn zero_bytes(bytes: usize) -> Vec { + vec![0; bytes] + } + + common_tests!(zero_bytes); + } + + mod random_value { + use super::*; + use rand::RngCore; + + fn random_bytes(bytes: usize) -> Vec { + let mut bytes = Vec::with_capacity(bytes); + rand::thread_rng().fill_bytes(&mut bytes); + bytes + } + + common_tests!(random_bytes); + } + + fn test_against_reference(input: &[u8], min_nodes: usize) { + let mut reference_input = input.to_vec(); + reference_input.resize( + std::cmp::max( + reference_input.len(), + min_nodes.next_power_of_two() * BYTES_PER_CHUNK, + ), + 0, + ); + + assert_eq!( + reference_root(&reference_input), + merkleize_padded(input, min_nodes), + "input.len(): {:?}", + input.len() + ); + } +} diff --git a/packages/tree_hash/src/merkleize_standard.rs b/packages/tree_hash/src/merkleize_standard.rs new file mode 100644 index 000000000..f57c5948 --- /dev/null +++ b/packages/tree_hash/src/merkleize_standard.rs @@ -0,0 +1,82 @@ +use sha256::hash; + +use super::*; + +/// Merkleizes bytes and returns the root, using a simple algorithm that does not optimize to avoid +/// processing or storing padding bytes. +/// +/// **Note**: This function is generally worse than using the `crate::merkle_root` which uses +/// `MerkleHasher`. We only keep this function around for reference testing. +/// +/// The input `bytes` will be padded to ensure that the number of leaves is a power-of-two. +/// +/// ## CPU Performance +/// +/// Will hash all nodes in the tree, even if they are padding and pre-determined. +/// +/// ## Memory Performance +/// +/// - Duplicates the input `bytes`. +/// - Stores all internal nodes, even if they are padding. +/// - Does not free up unused memory during operation. +pub fn merkleize_standard(bytes: &[u8]) -> Hash256 { + // If the bytes are just one chunk (or less than one chunk) just return them. + if bytes.len() <= HASHSIZE { + let mut o = bytes.to_vec(); + o.resize(HASHSIZE, 0); + return Hash256::from_slice(&o[0..HASHSIZE]); + } + + let leaves = num_sanitized_leaves(bytes.len()); + let nodes = num_nodes(leaves); + let internal_nodes = nodes - leaves; + + let num_bytes = std::cmp::max(internal_nodes, 1) * HASHSIZE + bytes.len(); + + let mut o: Vec = vec![0; internal_nodes * HASHSIZE]; + + o.append(&mut bytes.to_vec()); + + assert_eq!(o.len(), num_bytes); + + let empty_chunk_hash = hash(&[0; MERKLE_HASH_CHUNK]); + + let mut i = nodes * HASHSIZE; + let mut j = internal_nodes * HASHSIZE; + + while i >= MERKLE_HASH_CHUNK { + i -= MERKLE_HASH_CHUNK; + + j -= HASHSIZE; + let hash = match o.get(i..i + MERKLE_HASH_CHUNK) { + // All bytes are available, hash as usual. + Some(slice) => hash(slice), + // Unable to get all the bytes. + None => { + match o.get(i..) { + // Able to get some of the bytes, pad them out. + Some(slice) => { + let mut bytes = slice.to_vec(); + bytes.resize(MERKLE_HASH_CHUNK, 0); + hash(&bytes) + } + // Unable to get any bytes, use the empty-chunk hash. + None => empty_chunk_hash.clone(), + } + } + }; + + o[j..j + HASHSIZE].copy_from_slice(&hash); + } + + Hash256::from_slice(&o[0..HASHSIZE]) +} + +fn num_sanitized_leaves(num_bytes: usize) -> usize { + let leaves = (num_bytes + HASHSIZE - 1) / HASHSIZE; + leaves.next_power_of_two() +} + +fn num_nodes(num_leaves: usize) -> usize { + 2 * num_leaves - 1 +} diff --git a/packages/tree_hash/src/sha256.rs b/packages/tree_hash/src/sha256.rs new file mode 100644 index 000000000..50f99377 --- /dev/null +++ b/packages/tree_hash/src/sha256.rs @@ -0,0 +1,36 @@ +use std::sync::LazyLock; + +use sha2::Digest; + +/// Length of a SHA256 hash in bytes. +pub const HASH_LEN: usize = 32; + +/// The max index that can be used with `ZERO_HASHES`. +pub const ZERO_HASHES_MAX_INDEX: usize = 48; + +pub fn hash_fixed(input: &[u8]) -> [u8; HASH_LEN] { + sha2::Sha256::digest(input).into() +} + +pub fn hash(input: &[u8]) -> Vec { + sha2::Sha256::digest(input).into_iter().collect() +} + +/// Cached zero hashes where `ZERO_HASHES[i]` is the hash of a Merkle tree with 2^i zero leaves. +pub static ZERO_HASHES: LazyLock> = LazyLock::new(|| { + let mut hashes = vec![[0; HASH_LEN]; ZERO_HASHES_MAX_INDEX + 1]; + + for i in 0..ZERO_HASHES_MAX_INDEX { + hashes[i + 1] = hash32_concat(&hashes[i], &hashes[i]); + } + + hashes +}); + +/// Compute the hash of two slices concatenated. +pub fn hash32_concat(h1: &[u8], h2: &[u8]) -> [u8; 32] { + let mut ctxt = sha2::Sha256::new(); + ctxt.update(h1); + ctxt.update(h2); + ctxt.finalize().into() +} diff --git a/packages/tree_hash/tests/tests.rs b/packages/tree_hash/tests/tests.rs new file mode 100644 index 000000000..11992221 --- /dev/null +++ b/packages/tree_hash/tests/tests.rs @@ -0,0 +1,180 @@ +use alloy_primitives::{Address, U128, U160, U256}; +use ssz_derive::Encode; +use tree_hash::{ + sha256::hash32_concat, Hash256, MerkleHasher, PackedEncoding, TreeHash, BYTES_PER_CHUNK, +}; +use tree_hash_derive::TreeHash; + +#[derive(Encode)] +struct HashVec { + vec: Vec, +} + +impl From> for HashVec { + fn from(vec: Vec) -> Self { + Self { vec } + } +} + +impl tree_hash::TreeHash for HashVec { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + let mut hasher = + MerkleHasher::with_leaves((self.vec.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK); + + for item in &self.vec { + hasher.write(&item.tree_hash_packed_encoding()).unwrap() + } + + let root = hasher.finish().unwrap(); + + tree_hash::mix_in_length(&root, self.vec.len()) + } +} + +fn mix_in_selector(a: Hash256, selector: u8) -> Hash256 { + let mut b = [0; 32]; + b[0] = selector; + + Hash256::from_slice(&hash32_concat(a.as_slice(), &b)) +} + +fn u8_hash_concat(v1: u8, v2: u8) -> Hash256 { + let mut a = [0; 32]; + let mut b = [0; 32]; + + a[0] = v1; + b[0] = v2; + + Hash256::from_slice(&hash32_concat(&a, &b)) +} + +fn u8_hash(x: u8) -> Hash256 { + let mut a = [0; 32]; + a[0] = x; + Hash256::from_slice(&a) +} + +#[derive(TreeHash)] +#[tree_hash(enum_behaviour = "transparent")] +enum FixedTrans { + A(u8), + B(u8), +} + +#[test] +fn fixed_trans() { + assert_eq!(FixedTrans::A(2).tree_hash_root(), u8_hash(2)); + assert_eq!(FixedTrans::B(2).tree_hash_root(), u8_hash(2)); +} + +#[derive(TreeHash)] +#[tree_hash(enum_behaviour = "union")] +enum FixedUnion { + A(u8), + B(u8), +} + +#[test] +fn fixed_union() { + assert_eq!(FixedUnion::A(2).tree_hash_root(), u8_hash_concat(2, 0)); + assert_eq!(FixedUnion::B(2).tree_hash_root(), u8_hash_concat(2, 1)); +} + +#[derive(TreeHash)] +#[tree_hash(enum_behaviour = "transparent")] +enum VariableTrans { + A(HashVec), + B(HashVec), +} + +#[test] +fn variable_trans() { + assert_eq!( + VariableTrans::A(HashVec::from(vec![2])).tree_hash_root(), + u8_hash_concat(2, 1) + ); + assert_eq!( + VariableTrans::B(HashVec::from(vec![2])).tree_hash_root(), + u8_hash_concat(2, 1) + ); +} + +#[derive(TreeHash)] +#[tree_hash(enum_behaviour = "union")] +enum VariableUnion { + A(HashVec), + B(HashVec), +} + +#[test] +fn variable_union() { + assert_eq!( + VariableUnion::A(HashVec::from(vec![2])).tree_hash_root(), + mix_in_selector(u8_hash_concat(2, 1), 0) + ); + assert_eq!( + VariableUnion::B(HashVec::from(vec![2])).tree_hash_root(), + mix_in_selector(u8_hash_concat(2, 1), 1) + ); +} + +/// Test that the packed encodings for different types are equal. +#[test] +fn packed_encoding_example() { + let val = 0xfff0eee0ddd0ccc0bbb0aaa099908880_u128; + let canonical = U256::from(val).tree_hash_packed_encoding(); + let encodings = [ + (0x8880_u16.tree_hash_packed_encoding(), 0), + (0x9990_u16.tree_hash_packed_encoding(), 2), + (0xaaa0_u16.tree_hash_packed_encoding(), 4), + (0xbbb0_u16.tree_hash_packed_encoding(), 6), + (0xccc0_u16.tree_hash_packed_encoding(), 8), + (0xddd0_u16.tree_hash_packed_encoding(), 10), + (0xeee0_u16.tree_hash_packed_encoding(), 12), + (0xfff0_u16.tree_hash_packed_encoding(), 14), + (U128::from(val).tree_hash_packed_encoding(), 0), + (U128::from(0).tree_hash_packed_encoding(), 16), + ( + Hash256::from_slice(U256::from(val).as_le_slice()).tree_hash_packed_encoding(), + 0, + ), + ( + Hash256::from_slice(U256::from(val).as_le_slice()) + .tree_hash_root() + .0 + .into(), + 0, + ), + (U256::from(val).tree_hash_root().0.into(), 0), + ( + Address::from(U160::from(val).to_le_bytes::<20>()) + .tree_hash_root() + .0 + .into(), + 0, + ), + ( + Address::from(U160::from(val).to_le_bytes::<20>()).tree_hash_packed_encoding(), + 0, + ), + ]; + for (i, (encoding, offset)) in encodings.into_iter().enumerate() { + assert_eq!( + &encoding[..], + &canonical[offset..offset + encoding.len()], + "encoding {i} is wrong" + ); + } +} diff --git a/programs/08-wasm-eth/Cargo.toml b/programs/08-wasm-eth/Cargo.toml index 63cd968e..b113600f 100644 --- a/programs/08-wasm-eth/Cargo.toml +++ b/programs/08-wasm-eth/Cargo.toml @@ -4,12 +4,15 @@ version = { workspace = true } edition = { workspace = true } repository = { workspace = true } +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] ibc-proto = { workspace = true } ethereum-light-client = { workspace = true } ethereum-utils = { workspace = true } -alloy-primitives = { workspace = true } +alloy-primitives = { workspace = true, default-features = false } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } @@ -22,6 +25,3 @@ thiserror = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } alloy-primitives = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] From 8052da9b710c166e5fee10327e101f793f0b88ee Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Thu, 5 Dec 2024 20:00:42 +0100 Subject: [PATCH 04/49] lint --- packages/tree_hash/src/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tree_hash/src/impls.rs b/packages/tree_hash/src/impls.rs index 53d28f4b..ec328aa8 100644 --- a/packages/tree_hash/src/impls.rs +++ b/packages/tree_hash/src/impls.rs @@ -230,7 +230,7 @@ mod test { ] ); assert_eq!( - int_to_hash256(u64::max_value()).as_slice(), + int_to_hash256(u64::MAX).as_slice(), &[ 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 From 699cdf454f19cb8e7f30b32621f2420d61f3b59f Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 6 Dec 2024 09:58:38 +0100 Subject: [PATCH 05/49] add run-script --- .github/workflows/rust.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c09d8db1..569d8af5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -107,10 +107,14 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - name: Checkout sources - uses: actions/checkout@v4 - - name: "Set up environment" - uses: ./.github/setup + - name: "Check out the repo" + uses: "actions/checkout@v4" + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: rustfmt, clippy, cargo-run-script - name: Build uses: actions-rs/cargo@v1 with: From 9022dfac16965386ab8234b7be21bc6263ca4d09 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 6 Dec 2024 15:20:50 +0100 Subject: [PATCH 06/49] add wasm target to cosmwasm build in ci --- .github/workflows/rust.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 569d8af5..595650a9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -114,7 +114,8 @@ jobs: with: profile: minimal toolchain: stable - components: rustfmt, clippy, cargo-run-script + components: rustfmt, clippy, run-script + target: wasm32-unknown-unknown - name: Build uses: actions-rs/cargo@v1 with: From 2387472456c85e73e5b51fa2d64fe926e2e6c7ad Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 6 Dec 2024 15:29:52 +0100 Subject: [PATCH 07/49] move features out of top-level cargo --- Cargo.lock | 12 ------------ Cargo.toml | 22 +++++++++++----------- packages/ethereum-trie-db/Cargo.toml | 10 +++++----- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2496e73a..12b1102a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,8 +231,6 @@ dependencies = [ "alloy-serde", "c-kzg", "derive_more 1.0.0", - "ethereum_ssz 0.8.0", - "ethereum_ssz_derive 0.8.0", "once_cell", "serde", "sha2 0.10.8", @@ -540,8 +538,6 @@ dependencies = [ "alloy-primitives 0.8.14", "alloy-rpc-types-engine", "alloy-serde", - "ethereum_ssz 0.8.0", - "ethereum_ssz_derive 0.8.0", "serde", "serde_with", "thiserror 2.0.4", @@ -559,8 +555,6 @@ dependencies = [ "alloy-rlp", "alloy-serde", "derive_more 1.0.0", - "ethereum_ssz 0.8.0", - "ethereum_ssz_derive 0.8.0", "serde", "strum", ] @@ -841,7 +835,6 @@ dependencies = [ "derive_more 1.0.0", "hashbrown 0.14.5", "nybbles", - "serde", "smallvec", "tracing", ] @@ -4816,7 +4809,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95f06be0417d97f81fe4e5c86d7d01b392655a9cac9c19a848aa033e18937b23" dependencies = [ "const-hex", - "serde", "smallvec", ] @@ -6043,7 +6035,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" dependencies = [ "bytes", - "rlp-derive 0.2.0", "rustc-hex", ] @@ -6779,9 +6770,6 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] [[package]] name = "snowbridge-amcl" diff --git a/Cargo.toml b/Cargo.toml index 57fb11dc..9cd85a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,10 @@ ethereum-trie-db = { path = "packages/ethereum-trie-db", defau ethereum-utils = { path = "packages/ethereum-utils", default-features = false } ethereum-light-client = { path = "packages/ethereum-light-client", default-features = false } -serde = { version = "1.0", default-features = false, features = ["derive"] } -serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false } +serde_json = { version = "1.0", default-features = false } serde_cbor = { version = "0.11", default-features = false } -serde_with = { version = "3.11", default-features = false, features = ["alloc"] } +serde_with = { version = "3.11", default-features = false } hex = { version = "0.4", default-features = false } base64 = { version = "0.22", default-features = false } prost = { version = "0.13", default-features = false } @@ -82,13 +82,13 @@ ibc-client-tendermint-types = { version = "0.56", default-features = false } alloy = { version = "0.7", default-features = false } alloy-contract = { version = "0.7", default-features = false } alloy-sol-types = { version = "0.8", default-features = false } -alloy-primitives = { version = "0.8", default-features = false, features = ["serde", "rlp"] } -alloy-trie = { version = "0.5", default-features = false, features = ["serde"] } +alloy-primitives = { version = "0.8", default-features = false } +alloy-trie = { version = "0.5", default-features = false } alloy-serde = { version = "0.7", default-features = false } alloy-rlp = { version = "0.3", default-features = false } -alloy-rpc-types-eth = { version = "0.7", default-features = false, features = ["serde"] } -alloy-rpc-types-beacon = { version = "0.7", default-features = false, features = ["ssz"] } -alloy-rpc-types-engine = { version = "0.7", default-features = false, features = ["ssz"] } +alloy-rpc-types-eth = { version = "0.7", default-features = false } +alloy-rpc-types-beacon = { version = "0.7", default-features = false } +alloy-rpc-types-engine = { version = "0.7", default-features = false } sp1-sdk = { version = "3.3", default-features = false } sp1-zkvm = { version = "3.3", default-features = false } @@ -105,13 +105,13 @@ tree_hash = { path = "packages/tree_hash", version = "0.8", default-fe tree_hash_derive = { version = "0.8", default-features = false } # The dependencies below are maintained by Parity Tech -trie-db = { version = "0.29", default-features = false, features = ["std"] } +trie-db = { version = "0.29", default-features = false } hash-db = { version = "0.16", default-features = false } memory-db = { version = "0.32", default-features = false } hash256-std-hasher = { version = "0.15", default-features = false } -rlp = { version = "0.6", default-features = false, features = ["derive", "std"] } +rlp = { version = "0.6", default-features = false } rlp-derive = { version = "0.2", default-features = false } -primitive-types = { version = "0.13", default-features = false, features = ["rlp"] } +primitive-types = { version = "0.13", default-features = false } # From union (might be removed if they are replaced by something more standard) typenum = { version = "1.17.0", default-features = false } diff --git a/packages/ethereum-trie-db/Cargo.toml b/packages/ethereum-trie-db/Cargo.toml index 8f5055e7..de778381 100644 --- a/packages/ethereum-trie-db/Cargo.toml +++ b/packages/ethereum-trie-db/Cargo.toml @@ -7,18 +7,18 @@ repository = { workspace = true } [dependencies] ethereum-utils = { workspace = true } -trie-db = { workspace = true } +trie-db = { workspace = true, features = ["std"] } hash-db = { workspace = true } memory-db = { workspace = true } hash256-std-hasher = { workspace = true } -rlp = { workspace = true } +rlp = { workspace = true, features = ["std"] } rlp-derive = { workspace = true } -primitive-types = { workspace = true } +primitive-types = { workspace = true, features = ["rlp"] } sha3 = { workspace = true } -alloy-primitives = { workspace = true, default-features = false } # Needed for alloy-based interfaces/functions +alloy-primitives = { workspace = true } # Needed for alloy-based interfaces/functions thiserror = { workspace = true } -serde = { workspace = true, features = ["derive"] } +serde = { workspace = true } From a3854e466be457681bec887e6fd6a21e0c9d46b9 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 6 Dec 2024 18:15:20 +0100 Subject: [PATCH 08/49] lint and fix just lint command --- e2e/interchaintestv8/e2esuite/light_clients.go | 6 ++++-- e2e/interchaintestv8/types/rust_fixtures.go | 4 +++- justfile | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/e2e/interchaintestv8/e2esuite/light_clients.go b/e2e/interchaintestv8/e2esuite/light_clients.go index 26c87f1c..6e0ffbcd 100644 --- a/e2e/interchaintestv8/e2esuite/light_clients.go +++ b/e2e/interchaintestv8/e2esuite/light_clients.go @@ -317,8 +317,10 @@ func (s *TestSuite) createUnionLightClient(ctx context.Context, simdRelayerUser s.Require().NoError(err) s.Require().Equal("08-wasm-0", s.EthereumLightClientID) - rustFixtureGenerator.GenerateRustFixture("initial_client_state", ethClientState) - rustFixtureGenerator.GenerateRustFixture("initial_consensus_state", ethConsensusState) + err = rustFixtureGenerator.GenerateRustFixture("initial_client_state", ethClientState) + s.Require().NoError(err) + err = rustFixtureGenerator.GenerateRustFixture("initial_consensus_state", ethConsensusState) + s.Require().NoError(err) } func (s *TestSuite) createDummyLightClient(ctx context.Context, simdRelayerUser ibc.Wallet) { diff --git a/e2e/interchaintestv8/types/rust_fixtures.go b/e2e/interchaintestv8/types/rust_fixtures.go index 60e3eb3e..a673af70 100644 --- a/e2e/interchaintestv8/types/rust_fixtures.go +++ b/e2e/interchaintestv8/types/rust_fixtures.go @@ -6,6 +6,7 @@ import ( "os" clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" + "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/testvalues" ethereumlightclient "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/ethereumlightclient" ) @@ -47,7 +48,8 @@ func (g *RustFixtureGenerator) GenerateRustFixture(name string, jsonMarshalble i fixtureName := fmt.Sprintf("%s_%d_%s", g.prefix, g.fixtureCount, name) filePath := fmt.Sprintf("%s/%s.json", testvalues.RustFixturesDir, fixtureName) - return os.WriteFile(filePath, fixturesBz, 0644) + // nolint:gosec + return os.WriteFile(filePath, fixturesBz, 0o644) } func (g *RustFixtureGenerator) ShouldGenerateFixture() bool { diff --git a/justfile b/justfile index f457f2a5..fa9be49e 100644 --- a/justfile +++ b/justfile @@ -52,7 +52,7 @@ lint: @echo "Linting the Solidity code..." forge fmt --check && bun solhint -w 0 '{scripts,contracts,test}/**/*.sol' @echo "Linting the Go code..." - cd e2e/interchaintestv8 && golangci-lint run . + cd e2e/interchaintestv8 && golangci-lint run @echo "Linting the Rust code..." cargo fmt --all -- --check && cargo clippy --all-targets --all-features -- -D warnings @echo "Linting the Protobuf files..." From ee4df25d0c4ee96c7724653f684401279773f35d Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 6 Dec 2024 18:15:34 +0100 Subject: [PATCH 09/49] fix cosmwasm ci build --- .github/workflows/rust.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 595650a9..f5488e56 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -114,8 +114,13 @@ jobs: with: profile: minimal toolchain: stable - components: rustfmt, clippy, run-script + components: rustfmt, clippy target: wasm32-unknown-unknown + - name: Install cargo-run-script + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-run-script - name: Build uses: actions-rs/cargo@v1 with: From 60730c270250ff72ff89f052d1e376c6eb2c3a14 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Fri, 6 Dec 2024 20:17:07 +0100 Subject: [PATCH 10/49] cr fixes --- Cargo.toml | 2 +- e2e/interchaintestv8/types/rust_fixtures.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9cd85a5d..8f390272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ primitive-types = { version = "0.13", default-features = false } typenum = { version = "1.17.0", default-features = false } # dev-dependencies -cw-multi-test = { version = "2.2.0"} +cw-multi-test = { version = "2.2.0", default-features = false } milagro_bls = { git = "https://github.com/Snowfork/milagro_bls", rev = "bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095", default-features = false } # Only used for testing, not to be used in production! [patch.crates-io] diff --git a/e2e/interchaintestv8/types/rust_fixtures.go b/e2e/interchaintestv8/types/rust_fixtures.go index a673af70..20f21e1e 100644 --- a/e2e/interchaintestv8/types/rust_fixtures.go +++ b/e2e/interchaintestv8/types/rust_fixtures.go @@ -38,6 +38,10 @@ func NewRustFixtureGenerator(prefix string, shouldGenerateFixture bool) *RustFix // GenerateRustFixture generates a fixture by json marshalling jsonMarshalble and saves it to a file func (g *RustFixtureGenerator) GenerateRustFixture(name string, jsonMarshalble interface{}) error { + if !g.shouldGenerateFixture { + return nil + } + fixturesBz, err := json.MarshalIndent(jsonMarshalble, "", " ") if err != nil { return err From e9c1626e10330d37d397054a6f4504d0a8265ab7 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Sat, 7 Dec 2024 12:20:43 +0100 Subject: [PATCH 11/49] cargo lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2407944e..a68cbac0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2037,7 +2037,7 @@ dependencies = [ "cw-storage-plus", "ethereum-light-client", "ethereum-utils", - "ibc-proto", + "ibc-proto 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)", "prost", "serde", "tendermint-proto", From 8577c386ec40cabe53fb901dc3fd0e4f0840e50c Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Sat, 7 Dec 2024 12:32:31 +0100 Subject: [PATCH 12/49] fix e2e test in pow mode --- e2e/interchaintestv8/ibc_eureka_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index 3100a181..4483b4d4 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -120,9 +120,9 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context, proofType operator. if os.Getenv(testvalues.EnvKeyGenerateSolidityFixtures) == testvalues.EnvValueGenerateFixtures_True { s.generateSolidityFixtures = true } - if os.Getenv(testvalues.EnvKeyGenerateRustFixtures) == testvalues.EnvValueGenerateFixtures_True { - s.rustFixtureGenerator = types.NewRustFixtureGenerator(s.GetTopLevelTestName(), true) - } + + shouldGenerateRustFixtures := os.Getenv(testvalues.EnvKeyGenerateRustFixtures) == testvalues.EnvValueGenerateFixtures_True + s.rustFixtureGenerator = types.NewRustFixtureGenerator(s.GetTopLevelTestName(), shouldGenerateRustFixtures) })) s.Require().True(s.Run("Deploy ethereum contracts", func() { From b9d0ef46a15139214ce1c69f30e9ee08c906002b Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Sat, 7 Dec 2024 12:32:48 +0100 Subject: [PATCH 13/49] add cosmwasm-check to ci --- .github/workflows/rust.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f5488e56..90101c0c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -116,15 +116,19 @@ jobs: toolchain: stable components: rustfmt, clippy target: wasm32-unknown-unknown - - name: Install cargo-run-script + - name: Install cargo-run-script and cosmwasm-check uses: actions-rs/cargo@v1 with: command: install - args: cargo-run-script + args: cargo-run-script cosmwasm-check - name: Build uses: actions-rs/cargo@v1 with: command: run-script args: build-08-wasm-eth + # checks that the wasm binary is a proper cosmwasm smart contract + # it checks for things like memories, exports, imports, available capabilities, and non-determinism + - name: Check cosmwasm file + run: cosmwasm-check artifacts/cw_08_wasm_etheruem_light_client.wasm From 2d1c2b230a0b8a40ab00c3a902f76a72c8b3c980 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Sat, 7 Dec 2024 17:09:52 +0100 Subject: [PATCH 14/49] added unit tests on cosmwasm contract --- Cargo.lock | 17 + Cargo.toml | 3 +- e2e/interchaintestv8/testvalues/values.go | 2 +- packages/ethereum-light-client/Cargo.toml | 3 +- packages/ethereum-light-client/src/error.rs | 3 + packages/ethereum-light-client/src/lib.rs | 3 - .../ethereum-light-client/src/membership.rs | 16 +- .../src/test/bls_verifier.rs | 48 --- .../src/test/fixtures.rs | 33 -- .../src/types/light_client.rs | 12 + .../src/types/sync_committee.rs | 4 +- packages/ethereum-light-client/src/verify.rs | 24 +- packages/ethereum-test-utils/Cargo.toml | 17 + .../ethereum-test-utils/src/bls_verifier.rs | 61 +++ packages/ethereum-test-utils/src/fixtures.rs | 15 + ...ndBack_Groth16_1_initial_client_state.json | 0 ...ack_Groth16_2_initial_consensus_state.json | 0 ...reumAndBack_Groth16_3_update_header_0.json | 0 ...eumAndBack_Groth16_4_commitment_proof.json | 0 ...reumAndBack_Groth16_5_update_header_0.json | 0 ...eumAndBack_Groth16_6_commitment_proof.json | 0 .../src}/fixtures/sync_committee_fixture.json | 0 .../mod.rs => ethereum-test-utils/src/lib.rs} | 0 packages/ethereum-utils/Cargo.toml | 1 + packages/ethereum-utils/src/error.rs | 12 + packages/ethereum-utils/src/lib.rs | 1 + packages/ethereum-utils/src/slot.rs | 17 + programs/08-wasm-eth/Cargo.toml | 3 +- programs/08-wasm-eth/src/contract.rs | 350 +++++++++++++++--- programs/08-wasm-eth/src/error.rs | 10 + programs/08-wasm-eth/src/state.rs | 87 ++++- 31 files changed, 586 insertions(+), 156 deletions(-) delete mode 100644 packages/ethereum-light-client/src/test/bls_verifier.rs delete mode 100644 packages/ethereum-light-client/src/test/fixtures.rs create mode 100644 packages/ethereum-test-utils/Cargo.toml create mode 100644 packages/ethereum-test-utils/src/bls_verifier.rs create mode 100644 packages/ethereum-test-utils/src/fixtures.rs rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json (100%) rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json (100%) rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json (100%) rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json (100%) rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json (100%) rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json (100%) rename packages/{ethereum-light-client/src/test => ethereum-test-utils/src}/fixtures/sync_committee_fixture.json (100%) rename packages/{ethereum-light-client/src/test/mod.rs => ethereum-test-utils/src/lib.rs} (100%) create mode 100644 packages/ethereum-utils/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index a68cbac0..9d7ac706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2036,10 +2036,12 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "ethereum-light-client", + "ethereum-test-utils", "ethereum-utils", "ibc-proto 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)", "prost", "serde", + "serde_json", "tendermint-proto", "thiserror 2.0.4", ] @@ -2609,6 +2611,7 @@ dependencies = [ "alloy-serde", "alloy-trie 0.5.3", "base64 0.22.1", + "ethereum-test-utils", "ethereum-trie-db", "ethereum-utils", "ethereum_ssz 0.8.0", @@ -2624,6 +2627,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "ethereum-test-utils" +version = "0.1.0" +dependencies = [ + "alloy-primitives 0.8.14", + "ethereum-light-client", + "ethereum-utils", + "milagro_bls", + "serde", + "serde_json", + "thiserror 2.0.4", +] + [[package]] name = "ethereum-trie-db" version = "0.1.0" @@ -2665,6 +2681,7 @@ dependencies = [ "alloy-primitives 0.8.14", "base64 0.22.1", "serde", + "thiserror 2.0.4", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 62df7763..dead7b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ sp1-ics07-tendermint-membership = { path = "programs/sp1-programs/membership" ethereum-trie-db = { path = "packages/ethereum-trie-db", default-features = false } ethereum-utils = { path = "packages/ethereum-utils", default-features = false } ethereum-light-client = { path = "packages/ethereum-light-client", default-features = false } +ethereum-test-utils = { path = "packages/ethereum-test-utils", default-features = false } +tree_hash = { path = "packages/tree_hash", default-features = false } serde = { version = "1.0", default-features = false } serde_json = { version = "1.0", default-features = false } @@ -102,7 +104,6 @@ cw-storage-plus = { version = "2.0", default-features = false } # The dependencies below are maintained by Sigma Prime (for use in Lighthouse (and the broader Ethereum ecosystem)) ethereum_ssz = { version = "0.8", default-features = false } ethereum_ssz_derive = { version = "0.8", default-features = false } -tree_hash = { path = "packages/tree_hash", version = "0.8", default-features = false } tree_hash_derive = { version = "0.8", default-features = false } # The dependencies below are maintained by Parity Tech diff --git a/e2e/interchaintestv8/testvalues/values.go b/e2e/interchaintestv8/testvalues/values.go index 4f677c8a..99dc452d 100644 --- a/e2e/interchaintestv8/testvalues/values.go +++ b/e2e/interchaintestv8/testvalues/values.go @@ -62,7 +62,7 @@ const ( // SP1ICS07FixturesDir is the directory where the SP1ICS07 fixtures are stored. SP1ICS07FixturesDir = "test/sp1-ics07/fixtures" // RustFixturesDir is the directory where the Rust fixtures are stored. - RustFixturesDir = "packages/ethereum-light-client/src/test/fixtures" + RustFixturesDir = "packages/ethereum-test-utils/src/test/fixtures" // RelayerConfigFilePath is the path to generate the relayer config file. RelayerConfigFilePath = "programs/relayer/config.json" // E2EDeployScriptPath is the path to the E2E deploy script. diff --git a/packages/ethereum-light-client/Cargo.toml b/packages/ethereum-light-client/Cargo.toml index 4ea002f8..bbe4c28e 100644 --- a/packages/ethereum-light-client/Cargo.toml +++ b/packages/ethereum-light-client/Cargo.toml @@ -8,7 +8,7 @@ repository = { workspace = true } ethereum-trie-db = { workspace = true } ethereum-utils = { workspace = true } -alloy-primitives = { workspace = true, default-features = false } +alloy-primitives = { workspace = true } alloy-rpc-types-eth = { workspace = true } alloy-rpc-types-beacon = { workspace = true } alloy-rpc-types-engine = { workspace = true } @@ -31,3 +31,4 @@ typenum = { workspace = true, features = ["const-generics", "no_std"] } [dev-dependencies] milagro_bls = { workspace = true } base64 = { workspace = true } +ethereum-test-utils = { workspace = true } diff --git a/packages/ethereum-light-client/src/error.rs b/packages/ethereum-light-client/src/error.rs index f168f434..c5032dfc 100644 --- a/packages/ethereum-light-client/src/error.rs +++ b/packages/ethereum-light-client/src/error.rs @@ -3,6 +3,9 @@ use alloy_rpc_types_beacon::BlsPublicKey; #[derive(thiserror::Error, Debug, Clone, PartialEq)] pub enum EthereumIBCError { + #[error(transparent)] + EthereumUtilsError(#[from] ethereum_utils::error::EthereumUtilsError), + #[error("IBC path is empty")] EmptyPath, diff --git a/packages/ethereum-light-client/src/lib.rs b/packages/ethereum-light-client/src/lib.rs index ea68bf2c..a4099324 100644 --- a/packages/ethereum-light-client/src/lib.rs +++ b/packages/ethereum-light-client/src/lib.rs @@ -9,6 +9,3 @@ pub mod verify; pub mod types; pub use typenum; // re-export (for some weird macro stuff in config.rs) - -#[cfg(test)] -mod test; diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs index 119c4d0b..e6e35bb3 100644 --- a/packages/ethereum-light-client/src/membership.rs +++ b/packages/ethereum-light-client/src/membership.rs @@ -86,17 +86,29 @@ mod test { use crate::{ client_state::ClientState, consensus_state::ConsensusState, - test::fixtures::{load_fixture, CommitmentProofFixture}, - types::{storage_proof::StorageProof, wrappers::WrappedBytes}, + types::{height::Height, storage_proof::StorageProof, wrappers::WrappedBytes}, }; + use alloy_primitives::{ hex::{self, FromHex}, Bytes, B256, U256, }; + use ethereum_test_utils::fixtures::load_fixture; use ethereum_utils::hex::FromBeHex; + use serde::{Deserialize, Serialize}; use super::verify_membership; + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] + pub struct CommitmentProofFixture { + #[serde(with = "ethereum_utils::base64")] + pub path: Vec, + pub storage_proof: StorageProof, + pub proof_height: Height, + pub client_state: ClientState, + pub consensus_state: ConsensusState, + } + #[test] fn test_with_fixture() { let commitment_proof_fixture: CommitmentProofFixture = load_fixture( diff --git a/packages/ethereum-light-client/src/test/bls_verifier.rs b/packages/ethereum-light-client/src/test/bls_verifier.rs deleted file mode 100644 index df5a890f..000000000 --- a/packages/ethereum-light-client/src/test/bls_verifier.rs +++ /dev/null @@ -1,48 +0,0 @@ -use thiserror::Error; - -use crate::types::bls::BlsVerify; - -pub struct TestBlsVerifier; - -#[derive(Error, Debug)] -pub enum BlsError { - #[error("bls error: {0}")] - Bls(String), -} - -impl BlsVerify for TestBlsVerifier { - type Error = BlsError; - - fn fast_aggregate_verify( - &self, - public_keys: Vec<&crate::types::bls::BlsPublicKey>, - msg: alloy_primitives::B256, - signature: crate::types::bls::BlsSignature, - ) -> Result<(), Self::Error> { - let public_keys = public_keys - .iter() - .cloned() - .map(|pk| milagro_bls::PublicKey::from_bytes(pk.as_ref())) - .collect::, _>>() - .map_err(|_| { - BlsError::Bls("failed to convert to milagro_bls public keys".to_string()) - })?; - - let public_keys: Vec<&milagro_bls::PublicKey> = public_keys.iter().collect(); - - let signature = milagro_bls::Signature::from_bytes(signature.as_slice()) - .map_err(|_| BlsError::Bls("failed to convert to milagro_bls signature".to_string()))?; - - let aggregate_signature = milagro_bls::AggregateSignature::aggregate(&[&signature]); - let aggregate_pubkey = milagro_bls::AggregatePublicKey::aggregate(&public_keys) - .map_err(|_| BlsError::Bls("failed to aggregate public keys".to_string()))?; - - let res = aggregate_signature - .fast_aggregate_verify_pre_aggregated(msg.as_slice(), &aggregate_pubkey); - if res { - Ok(()) - } else { - Err(BlsError::Bls("failed to verify signature".to_string())) - } - } -} diff --git a/packages/ethereum-light-client/src/test/fixtures.rs b/packages/ethereum-light-client/src/test/fixtures.rs deleted file mode 100644 index c833f9b4..000000000 --- a/packages/ethereum-light-client/src/test/fixtures.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -use crate::{ - client_state::ClientState, - consensus_state::ConsensusState, - types::{height::Height, storage_proof::StorageProof}, -}; - -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] -pub struct CommitmentProofFixture { - #[serde(with = "ethereum_utils::base64")] - pub path: Vec, - pub storage_proof: StorageProof, - pub proof_height: Height, - pub client_state: ClientState, - pub consensus_state: ConsensusState, -} - -pub fn load_fixture(name: &str) -> T -where - T: serde::de::DeserializeOwned, -{ - // Construct the path relative to the Cargo manifest directory - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("src/test/fixtures"); - path.push(format!("{}.json", name)); - - // Open the file and deserialize its contents - let file = std::fs::File::open(path).unwrap(); - serde_json::from_reader(file).unwrap() -} diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs index b43312d1..a3bb3bd9 100644 --- a/packages/ethereum-light-client/src/types/light_client.rs +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -22,6 +22,18 @@ pub struct Header { pub account_update: AccountUpdate, } +impl From> for Header { + fn from(value: Vec) -> Self { + serde_json::from_slice(&value).unwrap() + } +} + +impl From
for Vec { + fn from(value: Header) -> Self { + serde_json::to_vec(&value).unwrap() + } +} + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct LightClientUpdate { /// Header attested to by the sync committee diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index d6c7bd84..58e56e9a 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -103,8 +103,10 @@ pub fn compute_sync_committee_period_at_slot( #[cfg(test)] mod test { - use crate::{test::fixtures::load_fixture, types::sync_committee::SyncCommittee}; + use crate::types::sync_committee::SyncCommittee; + use alloy_primitives::{hex::FromHex, B256}; + use ethereum_test_utils::fixtures::load_fixture; use tree_hash::TreeHash; #[test] diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index 4d9f979a..0616ac17 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -44,7 +44,7 @@ pub fn verify_header( client_state.seconds_per_slot, current_timestamp, ) - .unwrap(); + .map_err(EthereumIBCError::EthereumUtilsError)?; validate_light_client_update::( client_state, @@ -342,10 +342,30 @@ pub fn get_lc_execution_root(client_state: &ClientState, header: &LightClientHea #[cfg(test)] mod test { - use crate::test::{bls_verifier::TestBlsVerifier, fixtures::load_fixture}; + use crate::types::bls::{BlsPublicKey, BlsSignature}; use super::*; + use ethereum_test_utils::{ + bls_verifier::{fast_aggregate_verify, BlsError}, + fixtures::load_fixture, + }; + + struct TestBlsVerifier; + + impl BlsVerify for TestBlsVerifier { + type Error = BlsError; + + fn fast_aggregate_verify( + &self, + public_keys: Vec<&BlsPublicKey>, + msg: B256, + signature: BlsSignature, + ) -> Result<(), BlsError> { + fast_aggregate_verify(public_keys, msg, signature) + } + } + #[test] fn test_verify_header() { let bls_verifier = TestBlsVerifier; diff --git a/packages/ethereum-test-utils/Cargo.toml b/packages/ethereum-test-utils/Cargo.toml new file mode 100644 index 000000000..ffaaa157 --- /dev/null +++ b/packages/ethereum-test-utils/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ethereum-test-utils" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } + +[dependencies] +ethereum-light-client = { workspace = true } +ethereum-utils = { workspace = true } + +alloy-primitives = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +milagro_bls = { workspace = true } +thiserror = { workspace = true } + + diff --git a/packages/ethereum-test-utils/src/bls_verifier.rs b/packages/ethereum-test-utils/src/bls_verifier.rs new file mode 100644 index 000000000..eb91f282 --- /dev/null +++ b/packages/ethereum-test-utils/src/bls_verifier.rs @@ -0,0 +1,61 @@ +use thiserror::Error; + +use ethereum_light_client::types::bls::{BlsPublicKey, BlsSignature}; + +pub struct TestBlsVerifier; + +#[derive(Error, Debug)] +pub enum BlsError { + #[error("bls error: {0}")] + Bls(String), +} + +pub fn fast_aggregate_verify( + public_keys: Vec<&BlsPublicKey>, + msg: alloy_primitives::B256, + signature: BlsSignature, +) -> Result<(), BlsError> { + let public_keys = public_keys + .iter() + .cloned() + .map(|pk| milagro_bls::PublicKey::from_bytes(pk.as_ref())) + .collect::, _>>() + .map_err(|_| BlsError::Bls("failed to convert to milagro_bls public keys".to_string()))?; + + let public_keys: Vec<&milagro_bls::PublicKey> = public_keys.iter().collect(); + + let signature = milagro_bls::Signature::from_bytes(signature.as_slice()) + .map_err(|_| BlsError::Bls("failed to convert to milagro_bls signature".to_string()))?; + + let aggregate_signature = milagro_bls::AggregateSignature::aggregate(&[&signature]); + let aggregate_pubkey = milagro_bls::AggregatePublicKey::aggregate(&public_keys) + .map_err(|_| BlsError::Bls("failed to aggregate public keys".to_string()))?; + + let res = + aggregate_signature.fast_aggregate_verify_pre_aggregated(msg.as_slice(), &aggregate_pubkey); + if res { + Ok(()) + } else { + Err(BlsError::Bls("failed to verify signature".to_string())) + } +} + +pub fn aggreagate(public_keys: Vec<&BlsPublicKey>) -> Result { + let public_keys = public_keys + .iter() + .cloned() + .map(|pk| milagro_bls::PublicKey::from_bytes(pk.as_ref())) + .collect::, _>>() + .map_err(|_| BlsError::Bls("failed to convert to milagro_bls public keys".to_string()))?; + + let public_keys: Vec<&milagro_bls::PublicKey> = public_keys.iter().collect(); + + let aggregate_pubkey = milagro_bls::AggregatePublicKey::aggregate(&public_keys) + .map_err(|_| BlsError::Bls("failed to aggregate public keys".to_string()))?; + + let pubkey = milagro_bls::PublicKey { + point: aggregate_pubkey.point, + }; + + Ok(BlsPublicKey::from(pubkey.as_bytes())) +} diff --git a/packages/ethereum-test-utils/src/fixtures.rs b/packages/ethereum-test-utils/src/fixtures.rs new file mode 100644 index 000000000..4c506a9a --- /dev/null +++ b/packages/ethereum-test-utils/src/fixtures.rs @@ -0,0 +1,15 @@ +use std::path::PathBuf; + +pub fn load_fixture(name: &str) -> T +where + T: serde::de::DeserializeOwned, +{ + // Construct the path relative to the Cargo manifest directory + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("src/fixtures"); + path.push(format!("{}.json", name)); + + // Open the file and deserialize its contents + let file = std::fs::File::open(path).unwrap(); + serde_json::from_reader(file).unwrap() +} diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json rename to packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json rename to packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json rename to packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json rename to packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json rename to packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json diff --git a/packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json rename to packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json diff --git a/packages/ethereum-light-client/src/test/fixtures/sync_committee_fixture.json b/packages/ethereum-test-utils/src/fixtures/sync_committee_fixture.json similarity index 100% rename from packages/ethereum-light-client/src/test/fixtures/sync_committee_fixture.json rename to packages/ethereum-test-utils/src/fixtures/sync_committee_fixture.json diff --git a/packages/ethereum-light-client/src/test/mod.rs b/packages/ethereum-test-utils/src/lib.rs similarity index 100% rename from packages/ethereum-light-client/src/test/mod.rs rename to packages/ethereum-test-utils/src/lib.rs diff --git a/packages/ethereum-utils/Cargo.toml b/packages/ethereum-utils/Cargo.toml index 95618c2a..6e808638 100644 --- a/packages/ethereum-utils/Cargo.toml +++ b/packages/ethereum-utils/Cargo.toml @@ -8,3 +8,4 @@ repository = { workspace = true } alloy-primitives = { workspace = true, default-features = false } base64 = { workspace = true } serde = { workspace = true } +thiserror = { workspace = true } diff --git a/packages/ethereum-utils/src/error.rs b/packages/ethereum-utils/src/error.rs new file mode 100644 index 000000000..377f57bc --- /dev/null +++ b/packages/ethereum-utils/src/error.rs @@ -0,0 +1,12 @@ +#[derive(thiserror::Error, Debug, Clone, PartialEq)] +pub enum EthereumUtilsError { + #[error("failed to compute slot at timestamp with \ + (timestamp ({timestamp}) - genesis ({genesis})) / seconds_per_slot ({seconds_per_slot}) + genesis_slot ({genesis_slot})" + )] + FailedToComputeSlotAtTimestamp { + timestamp: u64, + genesis: u64, + seconds_per_slot: u64, + genesis_slot: u64, + }, +} diff --git a/packages/ethereum-utils/src/lib.rs b/packages/ethereum-utils/src/lib.rs index a3f92e29..7f3328f9 100644 --- a/packages/ethereum-utils/src/lib.rs +++ b/packages/ethereum-utils/src/lib.rs @@ -1,4 +1,5 @@ pub mod base64; pub mod ensure; +pub mod error; pub mod hex; pub mod slot; diff --git a/packages/ethereum-utils/src/slot.rs b/packages/ethereum-utils/src/slot.rs index baf0ce23..055fc6ea 100644 --- a/packages/ethereum-utils/src/slot.rs +++ b/packages/ethereum-utils/src/slot.rs @@ -1,9 +1,26 @@ +use crate::error::EthereumUtilsError; + pub const GENESIS_SLOT: u64 = 0; pub fn compute_slot_at_timestamp( genesis_time: u64, seconds_per_slot: u64, timestamp_seconds: u64, +) -> Result { + checked_compute_slot_at_timestamp(genesis_time, seconds_per_slot, timestamp_seconds).ok_or( + EthereumUtilsError::FailedToComputeSlotAtTimestamp { + timestamp: timestamp_seconds, + genesis: genesis_time, + seconds_per_slot, + genesis_slot: GENESIS_SLOT, + }, + ) +} + +fn checked_compute_slot_at_timestamp( + genesis_time: u64, + seconds_per_slot: u64, + timestamp_seconds: u64, ) -> Option { timestamp_seconds .checked_sub(genesis_time)? diff --git a/programs/08-wasm-eth/Cargo.toml b/programs/08-wasm-eth/Cargo.toml index b113600f..294ce24b 100644 --- a/programs/08-wasm-eth/Cargo.toml +++ b/programs/08-wasm-eth/Cargo.toml @@ -20,8 +20,9 @@ cw-storage-plus = { workspace = true } tendermint-proto = { workspace = true } prost = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } thiserror = { workspace = true } [dev-dependencies] cw-multi-test = { workspace = true } -alloy-primitives = { workspace = true } +ethereum-test-utils = { workspace = true } diff --git a/programs/08-wasm-eth/src/contract.rs b/programs/08-wasm-eth/src/contract.rs index 5d857794..a714f0dc 100644 --- a/programs/08-wasm-eth/src/contract.rs +++ b/programs/08-wasm-eth/src/contract.rs @@ -1,5 +1,8 @@ +use std::convert::Into; + use cosmwasm_std::entry_point; use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use ethereum_light_client::types::light_client::Header; use ethereum_light_client::{client_state::ClientState, consensus_state::ConsensusState}; use ibc_proto::ibc::{ core::client::v1::Height as IbcProtoHeight, @@ -10,16 +13,20 @@ use ibc_proto::ibc::{ use prost::Message; use tendermint_proto::google::protobuf::Any; +use crate::custom_query::{BlsVerifier, EthereumCustomQuery}; use crate::error::ContractError; use crate::msg::{ CheckForMisbehaviourResult, ExecuteMsg, ExportMetadataResult, Height, InstantiateMsg, QueryMsg, - StatusResult, SudoMsg, TimestampAtHeightResult, UpdateStateResult, + StatusResult, SudoMsg, TimestampAtHeightResult, UpdateStateResult, VerifyClientMessageMsg, + VerifyMembershipMsg, VerifyNonMembershipMsg, +}; +use crate::state::{ + consensus_db_key, get_eth_client_state, get_eth_consensus_state, HOST_CLIENT_STATE_KEY, }; -use crate::state::{consensus_db_key, HOST_CLIENT_STATE_KEY}; #[entry_point] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg, @@ -59,10 +66,18 @@ pub fn instantiate( } #[entry_point] -pub fn sudo(_deps: DepsMut, _env: Env, msg: SudoMsg) -> Result { +pub fn sudo( + deps: DepsMut, + _env: Env, + msg: SudoMsg, +) -> Result { let result = match msg { - SudoMsg::VerifyMembership(_) => verify_membership()?, - SudoMsg::VerifyNonMembership(_) => verify_non_membership()?, + SudoMsg::VerifyMembership(verify_membership_msg) => { + verify_membership(deps.as_ref(), verify_membership_msg)? + } + SudoMsg::VerifyNonMembership(verify_non_membership_msg) => { + verify_non_membership(deps.as_ref(), verify_non_membership_msg)? + } SudoMsg::UpdateState(_) => update_state()?, SudoMsg::UpdateStateOnMisbehaviour(_) => unimplemented!(), SudoMsg::VerifyUpgradeAndUpdateState(_) => unimplemented!(), @@ -72,11 +87,51 @@ pub fn sudo(_deps: DepsMut, _env: Env, msg: SudoMsg) -> Result Result { +pub fn verify_membership( + deps: Deps, + verify_membership_msg: VerifyMembershipMsg, +) -> Result { + let eth_client_state = get_eth_client_state(deps); + let eth_consensus_state = get_eth_consensus_state(deps, &verify_membership_msg.height); + + ethereum_light_client::membership::verify_membership( + eth_consensus_state, + eth_client_state, + verify_membership_msg.proof.into(), + verify_membership_msg + .merkle_path + .key_path + .into_iter() + .map(Into::into) + .collect(), + Some(verify_membership_msg.value.into()), + ) + .map_err(ContractError::VerifyMembershipFailed)?; + Ok(to_json_binary(&Ok::<(), ()>(()))?) } -pub fn verify_non_membership() -> Result { +pub fn verify_non_membership( + deps: Deps, + verify_non_membership_msg: VerifyNonMembershipMsg, +) -> Result { + let eth_client_state = get_eth_client_state(deps); + let eth_consensus_state = get_eth_consensus_state(deps, &verify_non_membership_msg.height); + + ethereum_light_client::membership::verify_membership( + eth_consensus_state, + eth_client_state, + verify_non_membership_msg.proof.into(), + verify_non_membership_msg + .merkle_path + .key_path + .into_iter() + .map(Into::into) + .collect(), + None, + ) + .map_err(ContractError::VerifyNonMembershipFailed)?; + Ok(to_json_binary(&Ok::<(), ()>(()))?) } @@ -95,9 +150,15 @@ pub fn execute( } #[entry_point] -pub fn query(_deps: Deps, env: Env, msg: QueryMsg) -> Result { +pub fn query( + deps: Deps, + env: Env, + msg: QueryMsg, +) -> Result { match msg { - QueryMsg::VerifyClientMessage(_) => verify_client_message(), + QueryMsg::VerifyClientMessage(verify_client_message_msg) => { + verify_client_message(deps, env, verify_client_message_msg) + } QueryMsg::CheckForMisbehaviour(_) => check_for_misbehaviour(), QueryMsg::TimestampAtHeight(_) => timestamp_at_height(env), QueryMsg::Status(_) => status(), @@ -105,7 +166,33 @@ pub fn query(_deps: Deps, env: Env, msg: QueryMsg) -> Result Result { +pub fn verify_client_message( + deps: Deps, + env: Env, + verify_client_message_msg: VerifyClientMessageMsg, +) -> Result { + let eth_client_state = get_eth_client_state(deps); + let eth_consensus_state = get_eth_consensus_state( + deps, + &Height { + revision_number: 0, + revision_height: eth_client_state.latest_slot, + }, + ); + let header = Header::from(Into::>::into( + verify_client_message_msg.client_message, + )); + let bls_verifier = BlsVerifier { deps }; + + ethereum_light_client::verify::verify_header( + ð_consensus_state, + ð_client_state, + env.block.time.seconds(), + &header, + bls_verifier, + ) + .map_err(ContractError::VerifyClientMessageFailed)?; + Ok(to_json_binary(&Ok::<(), ()>(()))?) } @@ -134,16 +221,38 @@ pub fn export_metadata() -> Result { #[cfg(test)] mod tests { + use std::marker::PhantomData; + + use alloy_primitives::B256; + use cosmwasm_std::{ + testing::{ + mock_dependencies, MockApi, MockQuerier, MockQuerierCustomHandlerResult, MockStorage, + }, + Binary, OwnedDeps, SystemResult, + }; + use ethereum_light_client::{ + client_state::ClientState as EthClientState, + consensus_state::ConsensusState as EthConsensusState, + types::{ + bls::{BlsPublicKey, BlsSignature}, + storage_proof::StorageProof, + }, + }; + use ethereum_test_utils::bls_verifier::{aggreagate, fast_aggregate_verify}; + use serde::{Deserialize, Serialize}; + + use crate::custom_query::EthereumCustomQuery; + mod instantiate_tests { use alloy_primitives::{aliases::B32, FixedBytes, B256, U256}; use cosmwasm_std::{ coins, - testing::{message_info, mock_dependencies, mock_env}, + testing::{message_info, mock_env}, Storage, }; use ethereum_light_client::{ - client_state::ClientState, - consensus_state::ConsensusState, + client_state::ClientState as EthClientState, + consensus_state::ConsensusState as EthConsensusState, types::{fork::Fork, fork_parameters::ForkParameters, wrappers::WrappedVersion}, }; use ibc_proto::ibc::lightclients::wasm::v1::{ @@ -153,18 +262,18 @@ mod tests { use tendermint_proto::google::protobuf::Any; use crate::{ - contract::instantiate, + contract::{instantiate, tests::mk_deps}, msg::{Height, InstantiateMsg}, state::{consensus_db_key, HOST_CLIENT_STATE_KEY}, }; #[test] fn test_instantiate() { - let mut deps = mock_dependencies(); + let mut deps = mk_deps(); let creator = deps.api.addr_make("creator"); let info = message_info(&creator, &coins(1, "uatom")); - let client_state = ClientState { + let client_state = EthClientState { chain_id: 0, genesis_validators_root: B256::from([0; 32]), min_sync_committee_participants: 0, @@ -199,7 +308,7 @@ mod tests { }; let client_state_bz: Vec = client_state.clone().into(); - let consensus_state = ConsensusState { + let consensus_state = EthConsensusState { slot: 0, state_root: B256::from([0; 32]), storage_root: B256::from([0; 32]), @@ -264,56 +373,85 @@ mod tests { mod sudo_tests { use cosmwasm_std::{ - testing::{mock_dependencies, mock_env}, + coins, + testing::{message_info, mock_env}, Binary, }; + use ethereum_test_utils::fixtures::load_fixture; use crate::{ - contract::sudo, - msg::{ - Height, MerklePath, SudoMsg, UpdateStateMsg, VerifyMembershipMsg, - VerifyNonMembershipMsg, + contract::{ + instantiate, sudo, + tests::{mk_deps, CommitmentProofFixture}, }, + msg::{Height, MerklePath, SudoMsg, UpdateStateMsg, VerifyMembershipMsg}, }; #[test] fn test_verify_membership() { - let mut deps = mock_dependencies(); + let mut deps = mk_deps(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &coins(1, "uatom")); + + let commitment_proof_fixture: CommitmentProofFixture = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof", + ); + + let client_state = commitment_proof_fixture.client_state; + let client_state_bz: Vec = client_state.clone().into(); + let consensus_state = commitment_proof_fixture.consensus_state; + let consensus_state_bz: Vec = consensus_state.clone().into(); + + let msg = crate::msg::InstantiateMsg { + client_state: Binary::from(client_state_bz), + consensus_state: Binary::from(consensus_state_bz), + checksum: "checksum".as_bytes().into(), // TODO: Real checksum important? + }; + instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let proof = commitment_proof_fixture.storage_proof; + let proof_bz = serde_json::to_vec(&proof).unwrap(); + let path = commitment_proof_fixture.path; + let value = proof.value; + let value_bz = value.to_be_bytes_vec(); + let msg = SudoMsg::VerifyMembership(VerifyMembershipMsg { height: Height { revision_number: 0, - revision_height: 1, + revision_height: commitment_proof_fixture.proof_height.revision_height, }, delay_time_period: 0, delay_block_period: 0, - proof: Binary::default(), - merkle_path: MerklePath { key_path: vec![] }, - value: Binary::default(), - }); - let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); - assert_eq!(0, res.messages.len()); - } - - #[test] - fn test_verify_non_membership() { - let mut deps = mock_dependencies(); - let msg = SudoMsg::VerifyNonMembership(VerifyNonMembershipMsg { - height: Height { - revision_number: 0, - revision_height: 1, + proof: Binary::from(proof_bz), + merkle_path: MerklePath { + key_path: vec![Binary::from(path)], }, - delay_time_period: 0, - delay_block_period: 0, - proof: Binary::default(), - merkle_path: MerklePath { key_path: vec![] }, + value: Binary::from(value_bz), }); let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); assert_eq!(0, res.messages.len()); } + //#[test] + //fn test_verify_non_membership() { + // let mut deps = mk_deps(); + // let msg = SudoMsg::VerifyNonMembership(VerifyNonMembershipMsg { + // height: Height { + // revision_number: 0, + // revision_height: 1, + // }, + // delay_time_period: 0, + // delay_block_period: 0, + // proof: Binary::default(), + // merkle_path: MerklePath { key_path: vec![] }, + // }); + // let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); + // assert_eq!(0, res.messages.len()); + //} + #[test] fn test_update_state() { - let mut deps = mock_dependencies(); + let mut deps = mk_deps(); let msg = SudoMsg::UpdateState(UpdateStateMsg { client_message: Binary::default(), }); @@ -324,13 +462,18 @@ mod tests { mod query_tests { use cosmwasm_std::{ - from_json, - testing::{mock_dependencies, mock_env}, - Binary, + coins, from_json, + testing::{message_info, mock_env}, + Binary, Timestamp, + }; + use ethereum_light_client::{ + client_state::ClientState as EthClientState, + consensus_state::ConsensusState as EthConsensusState, types::light_client::Header, }; + use ethereum_test_utils::fixtures::load_fixture; use crate::{ - contract::query, + contract::{instantiate, query, tests::mk_deps}, msg::{ CheckForMisbehaviourMsg, CheckForMisbehaviourResult, ExportMetadataMsg, ExportMetadataResult, Height, QueryMsg, StatusMsg, StatusResult, @@ -340,12 +483,44 @@ mod tests { #[test] fn test_verify_client_message() { - let deps = mock_dependencies(); + let mut deps = mk_deps(); + let creator = deps.api.addr_make("creator"); + let info = message_info(&creator, &coins(1, "uatom")); + + let client_state: EthClientState = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state", + ); + + let consensus_state: EthConsensusState = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", + ); + + let client_state_bz: Vec = client_state.clone().into(); + let consensus_state_bz: Vec = consensus_state.clone().into(); + + let msg = crate::msg::InstantiateMsg { + client_state: Binary::from(client_state_bz), + consensus_state: Binary::from(consensus_state_bz), + checksum: "checksum".as_bytes().into(), // TODO: Real checksum important? + }; + + instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let header: Header = load_fixture( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", + ); + let header_bz: Vec = header.clone().into(); + + let mut env = mock_env(); + env.block.time = Timestamp::from_seconds( + header.consensus_update.attested_header.execution.timestamp + 1000, + ); + query( deps.as_ref(), - mock_env(), + env, QueryMsg::VerifyClientMessage(VerifyClientMessageMsg { - client_message: Binary::default(), + client_message: Binary::from(header_bz), }), ) .unwrap(); @@ -353,7 +528,7 @@ mod tests { #[test] fn test_check_for_misbehaviour() { - let deps = mock_dependencies(); + let deps = mk_deps(); let res = query( deps.as_ref(), mock_env(), @@ -368,7 +543,7 @@ mod tests { #[test] fn test_timestamp_at_height() { - let deps = mock_dependencies(); + let deps = mk_deps(); let res = query( deps.as_ref(), mock_env(), @@ -389,7 +564,7 @@ mod tests { #[test] fn test_status() { - let deps = mock_dependencies(); + let deps = mk_deps(); let res = query(deps.as_ref(), mock_env(), QueryMsg::Status(StatusMsg {})).unwrap(); let status_response: StatusResult = from_json(&res).unwrap(); assert_eq!("Active", status_response.status); @@ -397,7 +572,7 @@ mod tests { #[test] fn test_export_metadata() { - let deps = mock_dependencies(); + let deps = mk_deps(); let res = query( deps.as_ref(), mock_env(), @@ -408,4 +583,67 @@ mod tests { assert_eq!(0, export_metadata_result.genesis_metadata.len()); } } + + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] + pub struct CommitmentProofFixture { + #[serde(with = "ethereum_utils::base64")] + pub path: Vec, + pub storage_proof: StorageProof, + pub proof_height: ethereum_light_client::types::height::Height, + pub client_state: EthClientState, + pub consensus_state: EthConsensusState, + } + + // TODO: Find a way to reuse the test handling code that already exists in the + // ethereum-light-client package + pub fn custom_query_handler(query: &EthereumCustomQuery) -> MockQuerierCustomHandlerResult { + match query { + EthereumCustomQuery::AggregateVerify { + public_keys, + message, + signature, + } => { + let public_keys = public_keys + .iter() + .map(|pk| pk.as_ref().try_into().unwrap()) + .collect::>(); + let message = B256::try_from(message.as_slice()).unwrap(); + let signature = BlsSignature::try_from(signature.as_slice()).unwrap(); + + fast_aggregate_verify(public_keys, message, signature).unwrap(); + + SystemResult::Ok(cosmwasm_std::ContractResult::Ok::( + serde_json::to_vec(&true).unwrap().into(), + )) + } + EthereumCustomQuery::Aggregate { public_keys } => { + let public_keys = public_keys + .iter() + .map(|pk| pk.as_ref().try_into().unwrap()) + .collect::>(); + + let aggregate_pubkey = aggreagate(public_keys).unwrap(); + + SystemResult::Ok(cosmwasm_std::ContractResult::Ok::( + serde_json::to_vec(&Binary::from(aggregate_pubkey.as_slice())) + .unwrap() + .into(), + )) + } + } + } + + fn mk_deps( + ) -> OwnedDeps, EthereumCustomQuery> + { + let deps = mock_dependencies(); + + OwnedDeps { + storage: deps.storage, + api: deps.api, + querier: MockQuerier::::new(&[]) + .with_custom_handler(custom_query_handler), + custom_query_type: PhantomData, + } + } } diff --git a/programs/08-wasm-eth/src/error.rs b/programs/08-wasm-eth/src/error.rs index 840de74f..41e4c521 100644 --- a/programs/08-wasm-eth/src/error.rs +++ b/programs/08-wasm-eth/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::StdError; +use ethereum_light_client::error::EthereumIBCError; use thiserror::Error; #[derive(Error, Debug)] @@ -8,4 +9,13 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized, + + #[error("Verify membership failed")] + VerifyMembershipFailed(#[source] EthereumIBCError), + + #[error("Verify non-membership failed")] + VerifyNonMembershipFailed(#[source] EthereumIBCError), + + #[error("Verify client message failed")] + VerifyClientMessageFailed(#[source] EthereumIBCError), } diff --git a/programs/08-wasm-eth/src/state.rs b/programs/08-wasm-eth/src/state.rs index 29c599bb..86a3523b 100644 --- a/programs/08-wasm-eth/src/state.rs +++ b/programs/08-wasm-eth/src/state.rs @@ -1,19 +1,92 @@ -use cosmwasm_schema::cw_serde; +use cosmwasm_std::Deps; +use ethereum_light_client::client_state::ClientState as EthClientState; +use ethereum_light_client::consensus_state::ConsensusState as EthConsensusState; +use ibc_proto::{ + google::protobuf::Any, + ibc::lightclients::wasm::v1::{ + ClientState as WasmClientState, ConsensusState as WasmConsensusState, + }, +}; +use prost::Message; -use crate::msg::Height; +use crate::{custom_query::EthereumCustomQuery, msg::Height}; // Client state that is stored by the host pub const HOST_CLIENT_STATE_KEY: &str = "clientState"; pub const HOST_CONSENSUS_STATES_KEY: &str = "consensusStates"; -#[cw_serde] -pub struct ClientState { - pub latest_height: u64, -} - pub fn consensus_db_key(height: &Height) -> String { format!( "{}/{}-{}", HOST_CONSENSUS_STATES_KEY, height.revision_number, height.revision_height ) } + +// TODO: Proper errors +pub fn get_eth_client_state(deps: Deps) -> EthClientState { + let wasm_client_state_any_bz = deps.storage.get(HOST_CLIENT_STATE_KEY.as_bytes()).unwrap(); + let wasm_client_state_any = Any::decode(wasm_client_state_any_bz.as_slice()).unwrap(); + let wasm_client_state = + WasmClientState::decode(wasm_client_state_any.value.as_slice()).unwrap(); + + EthClientState::from(wasm_client_state.data) +} + +// TODO: Proper errors +pub fn get_eth_consensus_state( + deps: Deps, + height: &Height, +) -> EthConsensusState { + let wasm_consensus_state_any_bz = deps + .storage + .get(consensus_db_key(height).as_bytes()) + .unwrap(); + let wasm_consensus_state_any = Any::decode(wasm_consensus_state_any_bz.as_slice()).unwrap(); + let wasm_consensus_state = + WasmConsensusState::decode(wasm_consensus_state_any.value.as_slice()).unwrap(); + + EthConsensusState::from(wasm_consensus_state.data) +} + +/* +* let actual_wasm_client_state_any_bz = + deps.storage.get(HOST_CLIENT_STATE_KEY.as_bytes()).unwrap(); + let actual_wasm_client_state_any = + Any::decode(actual_wasm_client_state_any_bz.as_slice()).unwrap(); + assert_eq!( + WasmClientState::type_url(), + actual_wasm_client_state_any.type_url + ); + let actual_client_state = + WasmClientState::decode(actual_wasm_client_state_any.value.as_slice()).unwrap(); + assert_eq!(msg.checksum, actual_client_state.checksum); + assert_eq!(msg.client_state, actual_client_state.data); + assert_eq!( + 0, + actual_client_state.latest_height.unwrap().revision_number + ); + assert_eq!( + client_state.latest_slot, + actual_client_state.latest_height.unwrap().revision_height + ); + + let actual_wasm_consensus_state_any_bz = deps + .storage + .get( + consensus_db_key(&Height { + revision_number: 0, + revision_height: consensus_state.slot, + }) + .as_bytes(), + ) + .unwrap(); + let actual_wasm_consensus_state_any = + Any::decode(actual_wasm_consensus_state_any_bz.as_slice()).unwrap(); + assert_eq!( + WasmConsensusState::type_url(), + actual_wasm_consensus_state_any.type_url + ); + let actual_consensus_state = + WasmConsensusState::decode(actual_wasm_consensus_state_any.value.as_slice()) + .unwrap(); +*/ From abf2ef9b215906492dddfad05810bb036f8d3d88 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Sat, 7 Dec 2024 18:30:32 +0100 Subject: [PATCH 15/49] some minor cleanup before review --- Cargo.toml | 2 +- packages/ethereum-light-client/Cargo.toml | 2 +- packages/ethereum-light-client/src/client_state.rs | 4 ++-- packages/ethereum-light-client/src/trie.rs | 7 ------- packages/ethereum-light-client/src/types/fork.rs | 2 +- packages/ethereum-light-client/src/types/light_client.rs | 4 +--- packages/ethereum-test-utils/Cargo.toml | 2 -- packages/ethereum-trie-db/Cargo.toml | 4 ---- 8 files changed, 6 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dead7b3e..c979462a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,7 @@ rlp = { version = "0.6", default-features = false } rlp-derive = { version = "0.2", default-features = false } primitive-types = { version = "0.13", default-features = false } -# From union (might be removed if they are replaced by something more standard) +# TODO: From union (might be removed if they are replaced by something more standard). #147 typenum = { version = "1.17.0", default-features = false } # dev-dependencies diff --git a/packages/ethereum-light-client/Cargo.toml b/packages/ethereum-light-client/Cargo.toml index bbe4c28e..b20ed697 100644 --- a/packages/ethereum-light-client/Cargo.toml +++ b/packages/ethereum-light-client/Cargo.toml @@ -25,7 +25,7 @@ thiserror = { workspace = true } sha2 = { workspace = true } serde_with = { workspace = true } -# From union (might be removed if they are replaced by something more standard) +# TODO: From union (might be removed if they are replaced by something more standard). #147 typenum = { workspace = true, features = ["const-generics", "no_std"] } [dev-dependencies] diff --git a/packages/ethereum-light-client/src/client_state.rs b/packages/ethereum-light-client/src/client_state.rs index 284640c5..98d8e9f3 100644 --- a/packages/ethereum-light-client/src/client_state.rs +++ b/packages/ethereum-light-client/src/client_state.rs @@ -11,14 +11,14 @@ pub struct ClientState { pub chain_id: u64, #[serde(with = "ethereum_utils::base64::fixed_size")] pub genesis_validators_root: B256, - pub min_sync_committee_participants: u64, // TODO: Needs be added to e2e tests + pub min_sync_committee_participants: u64, // TODO: Needs be added to e2e tests #143 pub genesis_time: u64, pub fork_parameters: ForkParameters, pub seconds_per_slot: u64, pub slots_per_epoch: u64, pub epochs_per_sync_committee_period: u64, pub latest_slot: u64, - // TODO: Should this be frozen_slot? + // TODO: Should this be frozen_slot? Consider this in #143 pub frozen_height: Height, #[serde(with = "ethereum_utils::base64::uint256")] pub ibc_commitment_slot: U256, diff --git a/packages/ethereum-light-client/src/trie.rs b/packages/ethereum-light-client/src/trie.rs index c39c2876..a0d135bb 100644 --- a/packages/ethereum-light-client/src/trie.rs +++ b/packages/ethereum-light-client/src/trie.rs @@ -45,7 +45,6 @@ pub fn validate_merkle_branch( #[cfg(test)] mod test { - use alloy_primitives::{hex::FromHex, Address, Bloom, Bytes, B256, U256}; use crate::{ @@ -127,12 +126,6 @@ mod test { let index = get_subtree_index(EXECUTION_PAYLOAD_INDEX); let root = header.beacon.body_root; - println!("Leaf: {:?}", leaf); - println!("Branch: {:?}", header.execution_branch); - println!("Depth: {:?}", depth); - println!("Index: {:?}", index); - println!("Root: {:?}", root); - validate_merkle_branch(leaf, header.execution_branch.0.into(), depth, index, root).unwrap(); } } diff --git a/packages/ethereum-light-client/src/types/fork.rs b/packages/ethereum-light-client/src/types/fork.rs index 29a8e0e7..f4904743 100644 --- a/packages/ethereum-light-client/src/types/fork.rs +++ b/packages/ethereum-light-client/src/types/fork.rs @@ -5,6 +5,6 @@ use super::wrappers::WrappedVersion; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct Fork { pub version: WrappedVersion, - #[serde(default)] // TODO: REMOVE AND FIX IN E2E + #[serde(default)] // TODO: Remove this when doing e2e integration #143 pub epoch: u64, } diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs index a3bb3bd9..1fe21d68 100644 --- a/packages/ethereum-light-client/src/types/light_client.rs +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -39,9 +39,7 @@ pub struct LightClientUpdate { /// Header attested to by the sync committee pub attested_header: LightClientHeader, /// Next sync committee corresponding to `attested_header.state_root` - // NOTE: These fields aren't actually optional, they are just because of the current structure of the ethereum Header. - // TODO: Remove the Option and improve ethereum::header::Header to be an enum, instead of using optional fields and bools. - #[serde(default)] + #[serde(default)] // TODO: Check if this can be removed in #143 pub next_sync_committee: Option, pub next_sync_committee_branch: Option>, /// Finalized header corresponding to `attested_header.state_root` diff --git a/packages/ethereum-test-utils/Cargo.toml b/packages/ethereum-test-utils/Cargo.toml index ffaaa157..c87c9ed6 100644 --- a/packages/ethereum-test-utils/Cargo.toml +++ b/packages/ethereum-test-utils/Cargo.toml @@ -13,5 +13,3 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } milagro_bls = { workspace = true } thiserror = { workspace = true } - - diff --git a/packages/ethereum-trie-db/Cargo.toml b/packages/ethereum-trie-db/Cargo.toml index de778381..fbfae598 100644 --- a/packages/ethereum-trie-db/Cargo.toml +++ b/packages/ethereum-trie-db/Cargo.toml @@ -15,10 +15,6 @@ rlp = { workspace = true, features = ["std"] } rlp-derive = { workspace = true } primitive-types = { workspace = true, features = ["rlp"] } sha3 = { workspace = true } - alloy-primitives = { workspace = true } # Needed for alloy-based interfaces/functions thiserror = { workspace = true } serde = { workspace = true } - - - From 34df628a8ae226877968e34412ad6b093d6af59f Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 11:39:52 +0000 Subject: [PATCH 16/49] Update packages/ethereum-light-client/src/types/sync_committee.rs Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> --- packages/ethereum-light-client/src/types/sync_committee.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 58e56e9a..59c13a62 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -67,8 +67,8 @@ impl SyncAggregate { pub fn num_sync_committe_participants(&self) -> usize { self.sync_committee_bits .iter() - .map(|byte| byte.count_ones() as usize) - .sum() + .map(|byte| byte.count_ones()) + .sum::() as usize } // TODO: Unit test From 1026bd208388e2eeff89f40e359dddc2790b511e Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 11:46:47 +0000 Subject: [PATCH 17/49] Update programs/08-wasm-eth/src/msg.rs Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> --- programs/08-wasm-eth/src/msg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/08-wasm-eth/src/msg.rs b/programs/08-wasm-eth/src/msg.rs index 4d4f129c..591d28ff 100644 --- a/programs/08-wasm-eth/src/msg.rs +++ b/programs/08-wasm-eth/src/msg.rs @@ -110,7 +110,7 @@ pub struct Height { /// the revision that the client is currently on #[serde(default)] pub revision_number: u64, - /// **height** is a height of remote chain + /// **revision_height** is a height of remote chain #[serde(default)] pub revision_height: u64, } From 001807f3d71eb6118737344dceaf90a5efa546d3 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 11:57:07 +0000 Subject: [PATCH 18/49] add source errors to error messages --- packages/ethereum-light-client/src/error.rs | 4 ++-- programs/08-wasm-eth/src/error.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-light-client/src/error.rs b/packages/ethereum-light-client/src/error.rs index c5032dfc..e4500c4c 100644 --- a/packages/ethereum-light-client/src/error.rs +++ b/packages/ethereum-light-client/src/error.rs @@ -112,10 +112,10 @@ pub enum EthereumIBCError { #[error("not enough signatures")] NotEnoughSignatures, - #[error("failed to verify finalized_header is finalized")] + #[error("failed to verify finalized_header is finalized: {0}")] ValidateFinalizedHeaderFailed(#[source] Box), - #[error("failed to verify next sync committee against attested header")] + #[error("failed to verify next sync committee against attested header: {0}")] ValidateNextSyncCommitteeFailed(#[source] Box), } diff --git a/programs/08-wasm-eth/src/error.rs b/programs/08-wasm-eth/src/error.rs index 41e4c521..d2ebeaeb 100644 --- a/programs/08-wasm-eth/src/error.rs +++ b/programs/08-wasm-eth/src/error.rs @@ -10,12 +10,12 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized, - #[error("Verify membership failed")] + #[error("Verify membership failed: {0}")] VerifyMembershipFailed(#[source] EthereumIBCError), - #[error("Verify non-membership failed")] + #[error("Verify non-membership failed: {0}")] VerifyNonMembershipFailed(#[source] EthereumIBCError), - #[error("Verify client message failed")] + #[error("Verify client message failed: {0}")] VerifyClientMessageFailed(#[source] EthereumIBCError), } From 4021f54a3a7e47f62016e93d63dd8e9cea58efb2 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 14:38:43 +0000 Subject: [PATCH 19/49] remove From trait for json serializaiton --- .../ethereum-light-client/src/client_state.rs | 12 ------ .../src/consensus_state.rs | 12 ------ .../src/types/light_client.rs | 12 ------ programs/08-wasm-eth/src/contract.rs | 40 ++++++++++--------- programs/08-wasm-eth/src/error.rs | 6 +++ programs/08-wasm-eth/src/state.rs | 5 ++- 6 files changed, 31 insertions(+), 56 deletions(-) diff --git a/packages/ethereum-light-client/src/client_state.rs b/packages/ethereum-light-client/src/client_state.rs index 98d8e9f3..54e77f86 100644 --- a/packages/ethereum-light-client/src/client_state.rs +++ b/packages/ethereum-light-client/src/client_state.rs @@ -25,15 +25,3 @@ pub struct ClientState { #[serde(with = "ethereum_utils::base64::fixed_size")] pub ibc_contract_address: Address, } - -impl From> for ClientState { - fn from(value: Vec) -> Self { - serde_json::from_slice(&value).unwrap() - } -} - -impl From for Vec { - fn from(value: ClientState) -> Self { - serde_json::to_vec(&value).unwrap() - } -} diff --git a/packages/ethereum-light-client/src/consensus_state.rs b/packages/ethereum-light-client/src/consensus_state.rs index 14d507a6..2792fa3d 100644 --- a/packages/ethereum-light-client/src/consensus_state.rs +++ b/packages/ethereum-light-client/src/consensus_state.rs @@ -19,18 +19,6 @@ pub struct ConsensusState { pub next_sync_committee: Option>, } -impl From> for ConsensusState { - fn from(value: Vec) -> Self { - serde_json::from_slice(&value).unwrap() - } -} - -impl From for Vec { - fn from(value: ConsensusState) -> Self { - serde_json::to_vec(&value).unwrap() - } -} - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct TrustedConsensusState { pub state: ConsensusState, diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs index 1fe21d68..5033abfd 100644 --- a/packages/ethereum-light-client/src/types/light_client.rs +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -22,18 +22,6 @@ pub struct Header { pub account_update: AccountUpdate, } -impl From> for Header { - fn from(value: Vec) -> Self { - serde_json::from_slice(&value).unwrap() - } -} - -impl From
for Vec { - fn from(value: Header) -> Self { - serde_json::to_vec(&value).unwrap() - } -} - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct LightClientUpdate { /// Header attested to by the sync committee diff --git a/programs/08-wasm-eth/src/contract.rs b/programs/08-wasm-eth/src/contract.rs index a714f0dc..f98ce351 100644 --- a/programs/08-wasm-eth/src/contract.rs +++ b/programs/08-wasm-eth/src/contract.rs @@ -1,9 +1,12 @@ use std::convert::Into; -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; -use ethereum_light_client::types::light_client::Header; -use ethereum_light_client::{client_state::ClientState, consensus_state::ConsensusState}; +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, +}; +use ethereum_light_client::{ + client_state::ClientState as EthClientState, + consensus_state::ConsensusState as EthConsensusState, +}; use ibc_proto::ibc::{ core::client::v1::Height as IbcProtoHeight, lightclients::wasm::v1::{ @@ -32,10 +35,11 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { let client_state_bz: Vec = msg.client_state.into(); - let client_state = ClientState::from(client_state_bz); + let client_state: EthClientState = serde_json::from_slice(&client_state_bz) + .map_err(ContractError::DeserializeClientStateFailed)?; let wasm_client_state = WasmClientState { checksum: msg.checksum.into(), - data: client_state.clone().into(), + data: client_state_bz, latest_height: Some(IbcProtoHeight { revision_number: 0, revision_height: client_state.latest_slot, @@ -48,9 +52,10 @@ pub fn instantiate( ); let consensus_state_bz: Vec = msg.consensus_state.into(); - let consensus_state = ConsensusState::from(consensus_state_bz); + let consensus_state: EthConsensusState = serde_json::from_slice(&consensus_state_bz) + .map_err(ContractError::DeserializeClientStateFailed)?; let wasm_consensus_state = WasmConsensusState { - data: consensus_state.clone().into(), + data: consensus_state_bz, }; let wasm_consensus_state_any = Any::from_msg(&wasm_consensus_state).unwrap(); let height = Height { @@ -179,9 +184,8 @@ pub fn verify_client_message( revision_height: eth_client_state.latest_slot, }, ); - let header = Header::from(Into::>::into( - verify_client_message_msg.client_message, - )); + let header = serde_json::from_slice(&verify_client_message_msg.client_message) + .map_err(ContractError::DeserializeClientStateFailed)?; let bls_verifier = BlsVerifier { deps }; ethereum_light_client::verify::verify_header( @@ -306,7 +310,7 @@ mod tests { ibc_contract_address: Default::default(), frozen_height: ethereum_light_client::types::height::Height::default(), }; - let client_state_bz: Vec = client_state.clone().into(); + let client_state_bz: Vec = serde_json::to_vec(&client_state).unwrap(); let consensus_state = EthConsensusState { slot: 0, @@ -316,7 +320,7 @@ mod tests { current_sync_committee: FixedBytes::<48>::from([0; 48]), next_sync_committee: None, }; - let consensus_state_bz: Vec = consensus_state.clone().into(); + let consensus_state_bz: Vec = serde_json::to_vec(&consensus_state).unwrap(); let msg = InstantiateMsg { client_state: client_state_bz.into(), @@ -398,9 +402,9 @@ mod tests { ); let client_state = commitment_proof_fixture.client_state; - let client_state_bz: Vec = client_state.clone().into(); + let client_state_bz: Vec = serde_json::to_vec(&client_state).unwrap(); let consensus_state = commitment_proof_fixture.consensus_state; - let consensus_state_bz: Vec = consensus_state.clone().into(); + let consensus_state_bz: Vec = serde_json::to_vec(&consensus_state).unwrap(); let msg = crate::msg::InstantiateMsg { client_state: Binary::from(client_state_bz), @@ -495,8 +499,8 @@ mod tests { "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", ); - let client_state_bz: Vec = client_state.clone().into(); - let consensus_state_bz: Vec = consensus_state.clone().into(); + let client_state_bz: Vec = serde_json::to_vec(&client_state).unwrap(); + let consensus_state_bz: Vec = serde_json::to_vec(&consensus_state).unwrap(); let msg = crate::msg::InstantiateMsg { client_state: Binary::from(client_state_bz), @@ -509,7 +513,7 @@ mod tests { let header: Header = load_fixture( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", ); - let header_bz: Vec = header.clone().into(); + let header_bz: Vec = serde_json::to_vec(&header).unwrap(); let mut env = mock_env(); env.block.time = Timestamp::from_seconds( diff --git a/programs/08-wasm-eth/src/error.rs b/programs/08-wasm-eth/src/error.rs index d2ebeaeb..0f7715b6 100644 --- a/programs/08-wasm-eth/src/error.rs +++ b/programs/08-wasm-eth/src/error.rs @@ -10,6 +10,12 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized, + #[error("deserializing client state failed: {0}")] + DeserializeClientStateFailed(#[source] serde_json::Error), + + #[error("deserializing consensus state failed: {0}")] + DeserializeConsensusStateFailed(#[source] serde_json::Error), + #[error("Verify membership failed: {0}")] VerifyMembershipFailed(#[source] EthereumIBCError), diff --git a/programs/08-wasm-eth/src/state.rs b/programs/08-wasm-eth/src/state.rs index 86a3523b..5e798855 100644 --- a/programs/08-wasm-eth/src/state.rs +++ b/programs/08-wasm-eth/src/state.rs @@ -29,7 +29,8 @@ pub fn get_eth_client_state(deps: Deps) -> EthClientState { let wasm_client_state = WasmClientState::decode(wasm_client_state_any.value.as_slice()).unwrap(); - EthClientState::from(wasm_client_state.data) + // TODO: map to ContractError + serde_json::from_slice(&wasm_client_state.data).unwrap() } // TODO: Proper errors @@ -45,7 +46,7 @@ pub fn get_eth_consensus_state( let wasm_consensus_state = WasmConsensusState::decode(wasm_consensus_state_any.value.as_slice()).unwrap(); - EthConsensusState::from(wasm_consensus_state.data) + serde_json::from_slice(&wasm_consensus_state.data).unwrap() } /* From b4de27781db4f31051a7a4e9b093720533d1c47c Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 14:48:56 +0000 Subject: [PATCH 20/49] remove no_std feature from typenum --- packages/ethereum-light-client/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-light-client/Cargo.toml b/packages/ethereum-light-client/Cargo.toml index b20ed697..b74a1712 100644 --- a/packages/ethereum-light-client/Cargo.toml +++ b/packages/ethereum-light-client/Cargo.toml @@ -26,7 +26,7 @@ sha2 = { workspace = true } serde_with = { workspace = true } # TODO: From union (might be removed if they are replaced by something more standard). #147 -typenum = { workspace = true, features = ["const-generics", "no_std"] } +typenum = { workspace = true, features = ["const-generics"] } [dev-dependencies] milagro_bls = { workspace = true } From 2f6b5dabda18ea85a1ba46bfc69bc6504dbc9baf Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 16:16:18 +0000 Subject: [PATCH 21/49] fix spacing issue --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c979462a..f18bfe8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ tokio = { version = "1.0", default-features = false } axum = { version = "0.7", default-features = false } tonic = { version = "0.12", default-features = false } tonic-build = { version = "0.12", default-features = false } -reqwest = { version = "0.12", default-features = false } +reqwest = { version = "0.12", default-features = false } tracing = { version = "0.1", default-features = false } tracing-subscriber = { version = "0.3", default-features = false } From 5ec6b9bbc34a319f7ec07359b776b43b6fe62cbe Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 16:22:33 +0000 Subject: [PATCH 22/49] tree_hash using workspace --- Cargo.lock | 33 +++++---------------------------- Cargo.toml | 4 +++- packages/tree_hash/Cargo.toml | 23 +++++++++++------------ 3 files changed, 19 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d7ac706..1e1b14de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2614,8 +2614,8 @@ dependencies = [ "ethereum-test-utils", "ethereum-trie-db", "ethereum-utils", - "ethereum_ssz 0.8.0", - "ethereum_ssz_derive 0.8.0", + "ethereum_ssz", + "ethereum_ssz_derive", "milagro_bls", "serde", "serde_json", @@ -2697,17 +2697,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "ethereum_ssz" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e999563461faea0ab9bc0024e5e66adcee35881f3d5062f52f31a4070fe1522" -dependencies = [ - "alloy-primitives 0.8.14", - "itertools 0.13.0", - "smallvec", -] - [[package]] name = "ethereum_ssz" version = "0.8.0" @@ -2724,18 +2713,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ethereum_ssz_derive" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3deae99c8e74829a00ba7a92d49055732b3c1f093f2ccfa3cbc621679b6fa91" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "ethereum_ssz_derive" version = "0.8.0" @@ -8188,11 +8165,11 @@ dependencies = [ [[package]] name = "tree_hash" -version = "0.8.0" +version = "0.1.0" dependencies = [ "alloy-primitives 0.8.14", - "ethereum_ssz 0.7.1", - "ethereum_ssz_derive 0.7.1", + "ethereum_ssz", + "ethereum_ssz_derive", "rand", "sha2 0.10.8", "smallvec", diff --git a/Cargo.toml b/Cargo.toml index f18bfe8a..bad130ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ subtle-encoding = { version = "0.5", default-features = false } sha2 = { version = "0.10", default-features = false } sha3 = { version = "0.8", default-features = false } +rand = { version = "0.8.5", default-features = false } tokio = { version = "1.0", default-features = false } axum = { version = "0.7", default-features = false } @@ -62,7 +63,7 @@ futures = { version = "0.3", default-features = false } clap = { version = "4.5", default-features = false, features = ["std"] } # std feature is required for clap time = { version = "0.3", default-features = false } dotenv = { version = "0.15", default-features = false } -thiserror = { version = "2.0", default-features = false } +thiserror = { version = "2.0", default-features = false } tendermint = { version = "0.40", default-features = false } tendermint-rpc = { version = "0.40", default-features = false } @@ -121,6 +122,7 @@ typenum = { version = "1.17.0", default-features = false } # dev-dependencies cw-multi-test = { version = "2.2.0", default-features = false } milagro_bls = { git = "https://github.com/Snowfork/milagro_bls", rev = "bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095", default-features = false } # Only used for testing, not to be used in production! +smallvec = { version = "1.6.1", default-features = false } [patch.crates-io] sha2-v0-9-8 = { git = "https://github.com/sp1-patches/RustCrypto-hashes", package = "sha2", branch = "patch-v0.9.9" } diff --git a/packages/tree_hash/Cargo.toml b/packages/tree_hash/Cargo.toml index f39b498c..348136d9 100644 --- a/packages/tree_hash/Cargo.toml +++ b/packages/tree_hash/Cargo.toml @@ -1,25 +1,24 @@ [package] name = "tree_hash" -version = "0.8.0" -edition = "2021" +version = { workspace = true } +edition = { workspace = true } +repository = { workspace = true } description = "Efficient Merkle-hashing as used in Ethereum consensus" -license = "Apache-2.0" -readme = "../README.md" -repository = "https://github.com/sigp/tree_hash" +license = { workspace = true } documentation = "https://docs.rs/tree_hash" keywords = ["ethereum"] categories = ["cryptography::cryptocurrencies"] [dependencies] -alloy-primitives = "0.8.0" -smallvec = "1.6.1" -sha2 = "0.10" +alloy-primitives = { workspace = true } +smallvec = { workspace = true } +sha2 = { workspace = true } [dev-dependencies] -rand = "0.8.5" -tree_hash_derive = "0.8.0" -ethereum_ssz = "0.7" -ethereum_ssz_derive = "0.7" +rand = { workspace = true } +tree_hash_derive = { workspace = true } +ethereum_ssz = { workspace = true } +ethereum_ssz_derive = { workspace = true } [features] arbitrary = ["alloy-primitives/arbitrary"] From d0b7eaccedf8ee49ab6a209bef0f8c6d366ffeb0 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 16:32:18 +0000 Subject: [PATCH 23/49] Update programs/08-wasm-eth/src/state.rs Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> --- programs/08-wasm-eth/src/state.rs | 43 ------------------------------- 1 file changed, 43 deletions(-) diff --git a/programs/08-wasm-eth/src/state.rs b/programs/08-wasm-eth/src/state.rs index 5e798855..a924ada5 100644 --- a/programs/08-wasm-eth/src/state.rs +++ b/programs/08-wasm-eth/src/state.rs @@ -48,46 +48,3 @@ pub fn get_eth_consensus_state( serde_json::from_slice(&wasm_consensus_state.data).unwrap() } - -/* -* let actual_wasm_client_state_any_bz = - deps.storage.get(HOST_CLIENT_STATE_KEY.as_bytes()).unwrap(); - let actual_wasm_client_state_any = - Any::decode(actual_wasm_client_state_any_bz.as_slice()).unwrap(); - assert_eq!( - WasmClientState::type_url(), - actual_wasm_client_state_any.type_url - ); - let actual_client_state = - WasmClientState::decode(actual_wasm_client_state_any.value.as_slice()).unwrap(); - assert_eq!(msg.checksum, actual_client_state.checksum); - assert_eq!(msg.client_state, actual_client_state.data); - assert_eq!( - 0, - actual_client_state.latest_height.unwrap().revision_number - ); - assert_eq!( - client_state.latest_slot, - actual_client_state.latest_height.unwrap().revision_height - ); - - let actual_wasm_consensus_state_any_bz = deps - .storage - .get( - consensus_db_key(&Height { - revision_number: 0, - revision_height: consensus_state.slot, - }) - .as_bytes(), - ) - .unwrap(); - let actual_wasm_consensus_state_any = - Any::decode(actual_wasm_consensus_state_any_bz.as_slice()).unwrap(); - assert_eq!( - WasmConsensusState::type_url(), - actual_wasm_consensus_state_any.type_url - ); - let actual_consensus_state = - WasmConsensusState::decode(actual_wasm_consensus_state_any.value.as_slice()) - .unwrap(); -*/ From 199184581661b452a0918206053bd0d160536ff9 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 16:34:28 +0000 Subject: [PATCH 24/49] consistent returns macro usage --- programs/08-wasm-eth/src/msg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/08-wasm-eth/src/msg.rs b/programs/08-wasm-eth/src/msg.rs index 591d28ff..4837f8be 100644 --- a/programs/08-wasm-eth/src/msg.rs +++ b/programs/08-wasm-eth/src/msg.rs @@ -77,7 +77,7 @@ pub enum QueryMsg { #[returns[TimestampAtHeightResult]] TimestampAtHeight(TimestampAtHeightMsg), - #[returns(StatusResult)] + #[returns[StatusResult]] Status(StatusMsg), #[returns[ExportMetadataResult]] From 8ca15791b263d4b1920ed017fbbc212d62df08b5 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 17:02:24 +0000 Subject: [PATCH 25/49] move optimized wasm build to justfile --- .github/workflows/rust.yml | 9 ++++----- Cargo.toml | 6 ------ justfile | 3 +++ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 90101c0c..75802950 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -121,11 +121,10 @@ jobs: with: command: install args: cargo-run-script cosmwasm-check - - name: Build - uses: actions-rs/cargo@v1 - with: - command: run-script - args: build-08-wasm-eth + - name: Install just + uses: extractions/setup-just@v2 + - name: Build optimized wasm binary + run: just build-optimized-wasm # checks that the wasm binary is a proper cosmwasm smart contract # it checks for things like memories, exports, imports, available capabilities, and non-determinism - name: Check cosmwasm file diff --git a/Cargo.toml b/Cargo.toml index bad130ab..3b6df6b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,12 +16,6 @@ license = "MIT" repository = "https://github.com/cosmos/solidity-ibc-eureka" keywords = ["cosmos", "ibc", "sp1", "tendermint", "ethereum", "bridge", "solidity", "eureka"] -[workspace.metadata.scripts] -build-08-wasm-eth = """docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.16.1 ./programs/08-wasm-eth""" - [workspace.dependencies] ibc-eureka-solidity-types = { path = "packages/solidity", default-features = false } ibc-eureka-relayer-lib = { path = "packages/relayer-lib", default-features = false } diff --git a/justfile b/justfile index fa9be49e..80422258 100644 --- a/justfile +++ b/justfile @@ -23,6 +23,9 @@ build-sp1-programs: cd programs/sp1-programs/misbehaviour && ~/.sp1/bin/cargo-prove prove build --elf-name misbehaviour-riscv32im-succinct-zkvm-elf @echo "ELF created at 'elf/misbehaviour-riscv32im-succinct-zkvm-elf'" +build-optimized-wasm: + docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/optimizer:0.16.1 ./programs/08-wasm-eth + # Clean up the cache and out directories clean: @echo "Cleaning up cache and out directories" From 6b5acb15f3ba60e89fa413cc8934335d94d28bf9 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 17:27:13 +0000 Subject: [PATCH 26/49] use div_ceil --- packages/ethereum-light-client/src/types/wrappers.rs | 6 +++--- packages/tree_hash/Cargo.toml | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs index 46dbd367..55299d4d 100644 --- a/packages/ethereum-light-client/src/types/wrappers.rs +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -42,7 +42,7 @@ impl TreeHash for WrappedBytes { } fn tree_hash_root(&self) -> tree_hash::Hash256 { - let leaves = (self.0.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + let leaves = self.0.len().div_ceil(BYTES_PER_CHUNK); let mut hasher = MerkleHasher::with_leaves(leaves); @@ -77,7 +77,7 @@ impl TreeHash for WrappedBloom { } fn tree_hash_root(&self) -> tree_hash::Hash256 { - let leaves = (self.0.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + let leaves = self.0.len().div_ceil(BYTES_PER_CHUNK); let mut hasher = MerkleHasher::with_leaves(leaves); @@ -108,7 +108,7 @@ impl TreeHash for WrappedBranch { } fn tree_hash_root(&self) -> tree_hash::Hash256 { - let leaves = (self.0.len() + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + let leaves = self.0.len().div_ceil(BYTES_PER_CHUNK); let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { diff --git a/packages/tree_hash/Cargo.toml b/packages/tree_hash/Cargo.toml index 348136d9..f198ee0c 100644 --- a/packages/tree_hash/Cargo.toml +++ b/packages/tree_hash/Cargo.toml @@ -6,8 +6,6 @@ repository = { workspace = true } description = "Efficient Merkle-hashing as used in Ethereum consensus" license = { workspace = true } documentation = "https://docs.rs/tree_hash" -keywords = ["ethereum"] -categories = ["cryptography::cryptocurrencies"] [dependencies] alloy-primitives = { workspace = true } From 6192e7e7875823ae3c8829d7faf67d2e7acb7fac Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 17:29:15 +0000 Subject: [PATCH 27/49] Update packages/ethereum-light-client/src/types/wrappers.rs Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> --- packages/ethereum-light-client/src/types/wrappers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs index 55299d4d..db30665b 100644 --- a/packages/ethereum-light-client/src/types/wrappers.rs +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -45,7 +45,6 @@ impl TreeHash for WrappedBytes { let leaves = self.0.len().div_ceil(BYTES_PER_CHUNK); let mut hasher = MerkleHasher::with_leaves(leaves); - for item in &self.0 { hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() } From 82a4493097770c6dae64fccb922380d73fe495cd Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 17:30:42 +0000 Subject: [PATCH 28/49] consistent line breaks for wrapper tree_hash fns --- packages/ethereum-light-client/src/types/wrappers.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs index db30665b..e9bb1bf4 100644 --- a/packages/ethereum-light-client/src/types/wrappers.rs +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -79,7 +79,6 @@ impl TreeHash for WrappedBloom { let leaves = self.0.len().div_ceil(BYTES_PER_CHUNK); let mut hasher = MerkleHasher::with_leaves(leaves); - for item in &self.0 { hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() } @@ -108,8 +107,8 @@ impl TreeHash for WrappedBranch { fn tree_hash_root(&self) -> tree_hash::Hash256 { let leaves = self.0.len().div_ceil(BYTES_PER_CHUNK); - let mut hasher = MerkleHasher::with_leaves(leaves); + let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() } @@ -150,8 +149,8 @@ impl TreeHash for WrappedVecBlsPublicKey { fn tree_hash_root(&self) -> tree_hash::Hash256 { let leaves = self.0.len(); - let mut hasher = MerkleHasher::with_leaves(leaves); + let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { hasher.write(item.tree_hash_root().as_ref()).unwrap() } From c346d0b322883d8e0dbcbf49c58756a70c40a663 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 18:45:47 +0000 Subject: [PATCH 29/49] Update packages/ethereum-light-client/src/types/sync_committee.rs Co-authored-by: srdtrk <59252793+srdtrk@users.noreply.github.com> --- packages/ethereum-light-client/src/types/sync_committee.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 59c13a62..86fc25f8 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -76,7 +76,7 @@ impl SyncAggregate { // // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#process_light_client_update pub fn validate_signature_supermajority(&self) -> bool { - self.num_sync_committe_participants() * 3 >= self.sync_committee_bits.len() * 2 + self.num_sync_committe_participants() * 3 >= self.sync_committee_bits.len() * 8 * 2 } } From afe0db906eb7139b9cda91abf295e09b56184bc8 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 18:56:36 +0000 Subject: [PATCH 30/49] rename load_fixture --- packages/ethereum-light-client/src/membership.rs | 4 ++-- .../src/types/sync_committee.rs | 6 ++++-- packages/ethereum-light-client/src/verify.rs | 9 ++++----- packages/ethereum-test-utils/src/fixtures.rs | 2 +- programs/08-wasm-eth/src/contract.rs | 12 ++++++------ 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs index e6e35bb3..316d4b46 100644 --- a/packages/ethereum-light-client/src/membership.rs +++ b/packages/ethereum-light-client/src/membership.rs @@ -93,7 +93,7 @@ mod test { hex::{self, FromHex}, Bytes, B256, U256, }; - use ethereum_test_utils::fixtures::load_fixture; + use ethereum_test_utils::fixtures; use ethereum_utils::hex::FromBeHex; use serde::{Deserialize, Serialize}; @@ -111,7 +111,7 @@ mod test { #[test] fn test_with_fixture() { - let commitment_proof_fixture: CommitmentProofFixture = load_fixture( + let commitment_proof_fixture: CommitmentProofFixture = fixtures::load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof", ); diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 86fc25f8..6898827d 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -39,6 +39,8 @@ pub struct TrustedSyncCommittee { } impl TrustedSyncCommittee { + // TODO: should this actually return default at any point? If not, panic or error + // also, if not returning default, remove the impl Default pub fn get_active_sync_committee(&self) -> ActiveSyncCommittee { if let Some(sync_committee) = &self.current_sync_committee { ActiveSyncCommittee::Current(sync_committee.clone()) @@ -106,12 +108,12 @@ mod test { use crate::types::sync_committee::SyncCommittee; use alloy_primitives::{hex::FromHex, B256}; - use ethereum_test_utils::fixtures::load_fixture; + use ethereum_test_utils::fixtures; use tree_hash::TreeHash; #[test] fn test_sync_committee_tree_hash_root() { - let sync_committee: SyncCommittee = load_fixture("sync_committee_fixture"); + let sync_committee: SyncCommittee = fixtures::load("sync_committee_fixture"); assert_ne!(sync_committee, SyncCommittee::default()); let actual_tree_hash_root = sync_committee.tree_hash_root(); diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index 0616ac17..5b631d6a 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -207,7 +207,6 @@ pub fn validate_light_client_update( // Verify that the `finality_branch`, if present, confirms `finalized_header` // to match the finalized checkpoint root saved in the state of `attested_header`. - // NOTE(aeryz): We always expect to get `finalized_header` and it's embedded into the type definition. is_valid_light_client_header(client_state, &update.finalized_header)?; let finalized_root = update.finalized_header.beacon.tree_hash_root(); @@ -348,7 +347,7 @@ mod test { use ethereum_test_utils::{ bls_verifier::{fast_aggregate_verify, BlsError}, - fixtures::load_fixture, + fixtures, }; struct TestBlsVerifier; @@ -370,17 +369,17 @@ mod test { fn test_verify_header() { let bls_verifier = TestBlsVerifier; - let client_state: ClientState = load_fixture( + let client_state: ClientState = fixtures::load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state", ); assert_ne!(client_state, ClientState::default()); - let consensus_state: ConsensusState = load_fixture( + let consensus_state: ConsensusState = fixtures::load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", ); assert_ne!(consensus_state, ConsensusState::default()); - let header: Header = load_fixture( + let header: Header = fixtures::load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", ); assert_ne!(header, Header::default()); diff --git a/packages/ethereum-test-utils/src/fixtures.rs b/packages/ethereum-test-utils/src/fixtures.rs index 4c506a9a..44fa9b5f 100644 --- a/packages/ethereum-test-utils/src/fixtures.rs +++ b/packages/ethereum-test-utils/src/fixtures.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -pub fn load_fixture(name: &str) -> T +pub fn load(name: &str) -> T where T: serde::de::DeserializeOwned, { diff --git a/programs/08-wasm-eth/src/contract.rs b/programs/08-wasm-eth/src/contract.rs index f98ce351..39145d2a 100644 --- a/programs/08-wasm-eth/src/contract.rs +++ b/programs/08-wasm-eth/src/contract.rs @@ -381,7 +381,7 @@ mod tests { testing::{message_info, mock_env}, Binary, }; - use ethereum_test_utils::fixtures::load_fixture; + use ethereum_test_utils::fixtures; use crate::{ contract::{ @@ -397,7 +397,7 @@ mod tests { let creator = deps.api.addr_make("creator"); let info = message_info(&creator, &coins(1, "uatom")); - let commitment_proof_fixture: CommitmentProofFixture = load_fixture( + let commitment_proof_fixture: CommitmentProofFixture = fixtures::load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof", ); @@ -474,7 +474,7 @@ mod tests { client_state::ClientState as EthClientState, consensus_state::ConsensusState as EthConsensusState, types::light_client::Header, }; - use ethereum_test_utils::fixtures::load_fixture; + use ethereum_test_utils::fixtures::load; use crate::{ contract::{instantiate, query, tests::mk_deps}, @@ -491,11 +491,11 @@ mod tests { let creator = deps.api.addr_make("creator"); let info = message_info(&creator, &coins(1, "uatom")); - let client_state: EthClientState = load_fixture( + let client_state: EthClientState = load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state", ); - let consensus_state: EthConsensusState = load_fixture( + let consensus_state: EthConsensusState = load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", ); @@ -510,7 +510,7 @@ mod tests { instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - let header: Header = load_fixture( + let header: Header = load( "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", ); let header_bz: Vec = serde_json::to_vec(&header).unwrap(); From f20590836c999407584d3ac211d04183e9ee8f42 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 19:04:11 +0000 Subject: [PATCH 31/49] move and rename cw light client program --- Cargo.lock | 2 +- Cargo.toml | 2 +- justfile | 2 +- programs/{08-wasm-eth => cw-ics08-wasm-eth}/Cargo.toml | 2 +- programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/contract.rs | 0 programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/custom_query.rs | 0 programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/error.rs | 0 programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/lib.rs | 0 programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/msg.rs | 0 programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/state.rs | 0 programs/relayer/README.md | 2 +- 11 files changed, 5 insertions(+), 5 deletions(-) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/Cargo.toml (94%) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/contract.rs (100%) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/custom_query.rs (100%) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/error.rs (100%) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/lib.rs (100%) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/msg.rs (100%) rename programs/{08-wasm-eth => cw-ics08-wasm-eth}/src/state.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 1e1b14de..76a24c1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2027,7 +2027,7 @@ dependencies = [ ] [[package]] -name = "cw-08-wasm-etheruem-light-client" +name = "cw-ics08-wasm-eth" version = "0.1.0" dependencies = [ "alloy-primitives 0.8.14", diff --git a/Cargo.toml b/Cargo.toml index 3b6df6b7..ed816d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "programs/relayer", "programs/operator", "programs/sp1-programs/*", - "programs/08-wasm-eth" + "programs/cw-ics08-wasm-eth" ] resolver = "2" diff --git a/justfile b/justfile index 80422258..9c805b53 100644 --- a/justfile +++ b/justfile @@ -24,7 +24,7 @@ build-sp1-programs: @echo "ELF created at 'elf/misbehaviour-riscv32im-succinct-zkvm-elf'" build-optimized-wasm: - docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/optimizer:0.16.1 ./programs/08-wasm-eth + docker run --rm -v "$(pwd)":/code --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/optimizer:0.16.1 ./programs/cw-ics08-wasm-eth # Clean up the cache and out directories clean: diff --git a/programs/08-wasm-eth/Cargo.toml b/programs/cw-ics08-wasm-eth/Cargo.toml similarity index 94% rename from programs/08-wasm-eth/Cargo.toml rename to programs/cw-ics08-wasm-eth/Cargo.toml index 294ce24b..185ca778 100644 --- a/programs/08-wasm-eth/Cargo.toml +++ b/programs/cw-ics08-wasm-eth/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cw-08-wasm-etheruem-light-client" +name = "cw-ics08-wasm-eth" version = { workspace = true } edition = { workspace = true } repository = { workspace = true } diff --git a/programs/08-wasm-eth/src/contract.rs b/programs/cw-ics08-wasm-eth/src/contract.rs similarity index 100% rename from programs/08-wasm-eth/src/contract.rs rename to programs/cw-ics08-wasm-eth/src/contract.rs diff --git a/programs/08-wasm-eth/src/custom_query.rs b/programs/cw-ics08-wasm-eth/src/custom_query.rs similarity index 100% rename from programs/08-wasm-eth/src/custom_query.rs rename to programs/cw-ics08-wasm-eth/src/custom_query.rs diff --git a/programs/08-wasm-eth/src/error.rs b/programs/cw-ics08-wasm-eth/src/error.rs similarity index 100% rename from programs/08-wasm-eth/src/error.rs rename to programs/cw-ics08-wasm-eth/src/error.rs diff --git a/programs/08-wasm-eth/src/lib.rs b/programs/cw-ics08-wasm-eth/src/lib.rs similarity index 100% rename from programs/08-wasm-eth/src/lib.rs rename to programs/cw-ics08-wasm-eth/src/lib.rs diff --git a/programs/08-wasm-eth/src/msg.rs b/programs/cw-ics08-wasm-eth/src/msg.rs similarity index 100% rename from programs/08-wasm-eth/src/msg.rs rename to programs/cw-ics08-wasm-eth/src/msg.rs diff --git a/programs/08-wasm-eth/src/state.rs b/programs/cw-ics08-wasm-eth/src/state.rs similarity index 100% rename from programs/08-wasm-eth/src/state.rs rename to programs/cw-ics08-wasm-eth/src/state.rs diff --git a/programs/relayer/README.md b/programs/relayer/README.md index b06a31c2..70e024ea 100644 --- a/programs/relayer/README.md +++ b/programs/relayer/README.md @@ -23,7 +23,7 @@ This is a work-in-progress implementation, and the relayer is not yet usuable. T | **Source Chain** | **Target Chain** | **Light Client** | **Development Status** | |:---:|:---:|:---:|:---:| | Cosmos SDK | EVM | `sp1-ics07-tendermint` | ✅ | -| EVM | Cosmos SDK | `08-wasm-ethereum` | ❌ | +| EVM | Cosmos SDK | `cw-ics08-wasm-eth` | ❌ | ## Usage From d1e91991de99f41c60654e0ed7ae81eb0b7b2b51 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 19:06:38 +0000 Subject: [PATCH 32/49] remove tendermint proto crate --- Cargo.lock | 1 - Cargo.toml | 1 - programs/cw-ics08-wasm-eth/Cargo.toml | 1 - programs/cw-ics08-wasm-eth/src/contract.rs | 20 ++++++++++++-------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76a24c1b..f73ee339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2042,7 +2042,6 @@ dependencies = [ "prost", "serde", "serde_json", - "tendermint-proto", "thiserror 2.0.4", ] diff --git a/Cargo.toml b/Cargo.toml index ed816d42..92cfb7e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,6 @@ tendermint-rpc = { version = "0.40", default-features = false tendermint-light-client-verifier = { version = "0.40", default-features = false } cosmos-sdk-proto = { version = "0.26", default-features = false } -tendermint-proto = { version = "0.40", default-features = false } ibc-proto = { version = "0.51", default-features = false } ibc-proto-eureka = { package = "ibc-proto", git = "https://github.com/srdtrk/ibc-proto-rs", branch = "feat/ibc-eureka", default-features = false } diff --git a/programs/cw-ics08-wasm-eth/Cargo.toml b/programs/cw-ics08-wasm-eth/Cargo.toml index 185ca778..0d5d26d8 100644 --- a/programs/cw-ics08-wasm-eth/Cargo.toml +++ b/programs/cw-ics08-wasm-eth/Cargo.toml @@ -17,7 +17,6 @@ alloy-primitives = { workspace = true, default-features = false } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } -tendermint-proto = { workspace = true } prost = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/programs/cw-ics08-wasm-eth/src/contract.rs b/programs/cw-ics08-wasm-eth/src/contract.rs index 39145d2a..314124ed 100644 --- a/programs/cw-ics08-wasm-eth/src/contract.rs +++ b/programs/cw-ics08-wasm-eth/src/contract.rs @@ -7,14 +7,16 @@ use ethereum_light_client::{ client_state::ClientState as EthClientState, consensus_state::ConsensusState as EthConsensusState, }; -use ibc_proto::ibc::{ - core::client::v1::Height as IbcProtoHeight, - lightclients::wasm::v1::{ - ClientState as WasmClientState, ConsensusState as WasmConsensusState, +use ibc_proto::{ + google::protobuf::Any, + ibc::{ + core::client::v1::Height as IbcProtoHeight, + lightclients::wasm::v1::{ + ClientState as WasmClientState, ConsensusState as WasmConsensusState, + }, }, }; use prost::Message; -use tendermint_proto::google::protobuf::Any; use crate::custom_query::{BlsVerifier, EthereumCustomQuery}; use crate::error::ContractError; @@ -259,11 +261,13 @@ mod tests { consensus_state::ConsensusState as EthConsensusState, types::{fork::Fork, fork_parameters::ForkParameters, wrappers::WrappedVersion}, }; - use ibc_proto::ibc::lightclients::wasm::v1::{ - ClientState as WasmClientState, ConsensusState as WasmConsensusState, + use ibc_proto::{ + google::protobuf::Any, + ibc::lightclients::wasm::v1::{ + ClientState as WasmClientState, ConsensusState as WasmConsensusState, + }, }; use prost::{Message, Name}; - use tendermint_proto::google::protobuf::Any; use crate::{ contract::{instantiate, tests::mk_deps}, From 9dd789a844fbe72f646b5ff90984bdf29a7ab84d Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Mon, 9 Dec 2024 19:11:33 +0000 Subject: [PATCH 33/49] remove export metadata --- programs/cw-ics08-wasm-eth/src/contract.rs | 53 ++++------------------ programs/cw-ics08-wasm-eth/src/msg.rs | 17 ------- 2 files changed, 8 insertions(+), 62 deletions(-) diff --git a/programs/cw-ics08-wasm-eth/src/contract.rs b/programs/cw-ics08-wasm-eth/src/contract.rs index 314124ed..2152735c 100644 --- a/programs/cw-ics08-wasm-eth/src/contract.rs +++ b/programs/cw-ics08-wasm-eth/src/contract.rs @@ -21,8 +21,8 @@ use prost::Message; use crate::custom_query::{BlsVerifier, EthereumCustomQuery}; use crate::error::ContractError; use crate::msg::{ - CheckForMisbehaviourResult, ExecuteMsg, ExportMetadataResult, Height, InstantiateMsg, QueryMsg, - StatusResult, SudoMsg, TimestampAtHeightResult, UpdateStateResult, VerifyClientMessageMsg, + CheckForMisbehaviourResult, ExecuteMsg, Height, InstantiateMsg, QueryMsg, StatusResult, + SudoMsg, TimestampAtHeightResult, UpdateStateResult, VerifyClientMessageMsg, VerifyMembershipMsg, VerifyNonMembershipMsg, }; use crate::state::{ @@ -86,9 +86,9 @@ pub fn sudo( verify_non_membership(deps.as_ref(), verify_non_membership_msg)? } SudoMsg::UpdateState(_) => update_state()?, - SudoMsg::UpdateStateOnMisbehaviour(_) => unimplemented!(), - SudoMsg::VerifyUpgradeAndUpdateState(_) => unimplemented!(), - SudoMsg::MigrateClientStore(_) => unimplemented!(), + SudoMsg::UpdateStateOnMisbehaviour(_) => todo!(), + SudoMsg::VerifyUpgradeAndUpdateState(_) => todo!(), + SudoMsg::MigrateClientStore(_) => todo!(), }; Ok(Response::default().set_data(result)) @@ -169,7 +169,6 @@ pub fn query( QueryMsg::CheckForMisbehaviour(_) => check_for_misbehaviour(), QueryMsg::TimestampAtHeight(_) => timestamp_at_height(env), QueryMsg::Status(_) => status(), - QueryMsg::ExportMetadata(_) => export_metadata(), } } @@ -219,12 +218,6 @@ pub fn status() -> Result { })?) } -pub fn export_metadata() -> Result { - Ok(to_json_binary(&ExportMetadataResult { - genesis_metadata: vec![], - })?) -} - #[cfg(test)] mod tests { use std::marker::PhantomData; @@ -440,23 +433,6 @@ mod tests { assert_eq!(0, res.messages.len()); } - //#[test] - //fn test_verify_non_membership() { - // let mut deps = mk_deps(); - // let msg = SudoMsg::VerifyNonMembership(VerifyNonMembershipMsg { - // height: Height { - // revision_number: 0, - // revision_height: 1, - // }, - // delay_time_period: 0, - // delay_block_period: 0, - // proof: Binary::default(), - // merkle_path: MerklePath { key_path: vec![] }, - // }); - // let res = sudo(deps.as_mut(), mock_env(), msg).unwrap(); - // assert_eq!(0, res.messages.len()); - //} - #[test] fn test_update_state() { let mut deps = mk_deps(); @@ -483,9 +459,9 @@ mod tests { use crate::{ contract::{instantiate, query, tests::mk_deps}, msg::{ - CheckForMisbehaviourMsg, CheckForMisbehaviourResult, ExportMetadataMsg, - ExportMetadataResult, Height, QueryMsg, StatusMsg, StatusResult, - TimestampAtHeightMsg, TimestampAtHeightResult, VerifyClientMessageMsg, + CheckForMisbehaviourMsg, CheckForMisbehaviourResult, Height, QueryMsg, StatusMsg, + StatusResult, TimestampAtHeightMsg, TimestampAtHeightResult, + VerifyClientMessageMsg, }, }; @@ -577,19 +553,6 @@ mod tests { let status_response: StatusResult = from_json(&res).unwrap(); assert_eq!("Active", status_response.status); } - - #[test] - fn test_export_metadata() { - let deps = mk_deps(); - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::ExportMetadata(ExportMetadataMsg {}), - ) - .unwrap(); - let export_metadata_result: ExportMetadataResult = from_json(&res).unwrap(); - assert_eq!(0, export_metadata_result.genesis_metadata.len()); - } } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] diff --git a/programs/cw-ics08-wasm-eth/src/msg.rs b/programs/cw-ics08-wasm-eth/src/msg.rs index 4837f8be..4e68e810 100644 --- a/programs/cw-ics08-wasm-eth/src/msg.rs +++ b/programs/cw-ics08-wasm-eth/src/msg.rs @@ -79,9 +79,6 @@ pub enum QueryMsg { #[returns[StatusResult]] Status(StatusMsg), - - #[returns[ExportMetadataResult]] - ExportMetadata(ExportMetadataMsg), } #[cw_serde] @@ -102,9 +99,6 @@ pub struct TimestampAtHeightMsg { #[cw_serde] pub struct StatusMsg {} -#[cw_serde] -pub struct ExportMetadataMsg {} - #[cw_serde] pub struct Height { /// the revision that the client is currently on @@ -139,14 +133,3 @@ pub struct CheckForMisbehaviourResult { pub struct TimestampAtHeightResult { pub timestamp: u64, } - -#[cw_serde] -pub struct GenesisMetadata { - pub key: Vec, - pub value: Vec, -} - -#[cw_serde] -pub struct ExportMetadataResult { - pub genesis_metadata: Vec, -} From e0d216e1d4eabf0d3a89ab655306d887f7e33694 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 10 Dec 2024 08:02:38 +0000 Subject: [PATCH 34/49] update wasm ci file name for check --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 75802950..b621b98a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -128,6 +128,6 @@ jobs: # checks that the wasm binary is a proper cosmwasm smart contract # it checks for things like memories, exports, imports, available capabilities, and non-determinism - name: Check cosmwasm file - run: cosmwasm-check artifacts/cw_08_wasm_etheruem_light_client.wasm + run: cosmwasm-check artifacts/cw_ics08_wasm_eth.wasm From 68e99f19d3420b303676dbc73eccf3bbaa9ee5c9 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 10 Dec 2024 10:51:50 +0000 Subject: [PATCH 35/49] move and use branch consts everywhere --- packages/ethereum-light-client/src/config.rs | 5 +++++ .../src/types/light_client.rs | 14 +++++++------- packages/ethereum-light-client/src/verify.rs | 14 ++++++++------ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/ethereum-light-client/src/config.rs b/packages/ethereum-light-client/src/config.rs index 532855a5..a3279377 100644 --- a/packages/ethereum-light-client/src/config.rs +++ b/packages/ethereum-light-client/src/config.rs @@ -162,6 +162,11 @@ pub mod consts { pub const NEXT_SYNC_COMMITTEE_INDEX: u64 = 55; /// `get_generalized_index(BeaconBlockBody, "execution_payload")` pub const EXECUTION_PAYLOAD_INDEX: u64 = 25; + + // Branch depths for different merkle trees related to ethereum consensus + pub const EXECUTION_BRANCH_DEPTH: usize = floorlog2(EXECUTION_PAYLOAD_INDEX); + pub const NEXT_SYNC_COMMITTEE_BRANCH_DEPTH: usize = floorlog2(NEXT_SYNC_COMMITTEE_INDEX); + pub const FINALITY_BRANCH_DEPTH: usize = floorlog2(FINALIZED_ROOT_INDEX); } pub mod preset { diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs index 5033abfd..b98c6170 100644 --- a/packages/ethereum-light-client/src/types/light_client.rs +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use tree_hash_derive::TreeHash; use crate::config::consts::{ - floorlog2, EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX, + EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX, }; use super::{ @@ -11,9 +11,9 @@ use super::{ wrappers::{WrappedBloom, WrappedBranch, WrappedBytes}, }; -const EXECUTION_BRANCH_SIZE: usize = floorlog2(EXECUTION_PAYLOAD_INDEX); -const NEXT_SYNC_COMMITTEE_BRANCH_SIZE: usize = floorlog2(NEXT_SYNC_COMMITTEE_INDEX); -const FINALITY_BRANCH_SIZE: usize = floorlog2(FINALIZED_ROOT_INDEX); +pub const EXECUTION_BRANCH_DEPTH: usize = EXECUTION_PAYLOAD_INDEX.ilog2() as usize; +pub const NEXT_SYNC_COMMITTEE_BRANCH_DEPTH: usize = NEXT_SYNC_COMMITTEE_INDEX.ilog2() as usize; +pub const FINALITY_BRANCH_DEPTH: usize = FINALIZED_ROOT_INDEX.ilog2() as usize; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] pub struct Header { @@ -29,10 +29,10 @@ pub struct LightClientUpdate { /// Next sync committee corresponding to `attested_header.state_root` #[serde(default)] // TODO: Check if this can be removed in #143 pub next_sync_committee: Option, - pub next_sync_committee_branch: Option>, + pub next_sync_committee_branch: Option>, /// Finalized header corresponding to `attested_header.state_root` pub finalized_header: LightClientHeader, - pub finality_branch: WrappedBranch, + pub finality_branch: WrappedBranch, /// Sync committee aggregate signature pub sync_aggregate: SyncAggregate, /// Slot at which the aggregate signature was created (untrusted) @@ -55,7 +55,7 @@ pub struct AccountProof { pub struct LightClientHeader { pub beacon: BeaconBlockHeader, pub execution: ExecutionPayloadHeader, - pub execution_branch: WrappedBranch, + pub execution_branch: WrappedBranch, } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index 5b631d6a..e5f2aa17 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -10,8 +10,7 @@ use tree_hash::TreeHash; use crate::{ client_state::ClientState, config::consts::{ - floorlog2, get_subtree_index, EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, - NEXT_SYNC_COMMITTEE_INDEX, + get_subtree_index, EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX, }, consensus_state::{ConsensusState, TrustedConsensusState}, error::EthereumIBCError, @@ -20,7 +19,10 @@ use crate::{ bls::BlsVerify, domain::{compute_domain, DomainType}, fork_parameters::compute_fork_version, - light_client::{Header, LightClientHeader, LightClientUpdate}, + light_client::{ + Header, LightClientHeader, LightClientUpdate, EXECUTION_BRANCH_DEPTH, + FINALITY_BRANCH_DEPTH, NEXT_SYNC_COMMITTEE_BRANCH_DEPTH, + }, signing_data::compute_signing_root, sync_committee::compute_sync_committee_period_at_slot, }, @@ -214,7 +216,7 @@ pub fn validate_light_client_update( validate_merkle_branch( finalized_root, update.finality_branch.clone().into(), - floorlog2(FINALIZED_ROOT_INDEX), + FINALITY_BRANCH_DEPTH, get_subtree_index(FINALIZED_ROOT_INDEX), update.attested_header.beacon.state_root, ) @@ -243,7 +245,7 @@ pub fn validate_light_client_update( .clone() .unwrap_or_default() .into(), - floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + NEXT_SYNC_COMMITTEE_BRANCH_DEPTH, get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), update.attested_header.beacon.state_root, ) @@ -321,7 +323,7 @@ pub fn is_valid_light_client_header( validate_merkle_branch( get_lc_execution_root(client_state, header), header.execution_branch.0.into(), - floorlog2(EXECUTION_PAYLOAD_INDEX), + EXECUTION_BRANCH_DEPTH, get_subtree_index(EXECUTION_PAYLOAD_INDEX), header.beacon.body_root, ) From c9de44d98a100595b45aa08a47e319bc21a6c2f0 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 10 Dec 2024 10:52:08 +0000 Subject: [PATCH 36/49] add readme with WIP warnings and acknowledgements --- README.md | 10 +++++++++- packages/ethereum-light-client/README.md | 8 ++++++++ programs/cw-ics08-wasm-eth/README.md | 8 ++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/ethereum-light-client/README.md create mode 100644 programs/cw-ics08-wasm-eth/README.md diff --git a/README.md b/README.md index 700ec0f1..5a379278 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ This project is structered as a [foundry](https://getfoundry.sh/) project with t - `relayer/`: Contains the relayer implementation. - `operator/`: Contains the operator for the SP1 light client. - `sp1-programs/`: Contains the SP1 programs for the light client. + - `cw-ics08-wasm-eth/`: Contains the (WIP) CosmWasm 08-wasm light client for Ethereum - `packages/`: Contains the Rust packages for the project. ### Contracts @@ -223,10 +224,17 @@ Note: These gas benchmarks are with Groth16. RUST_LOG=info cargo run --bin operator --release -- start ``` +## Etheruem Light Client +> ⚠ The Ethereum Light Client is currently under heavy development, is expected to change and is not ready for production use. + +This repository contains an Ethereum light client which is implemented as two separate layers: +* A CosmWasm contract that supports the 08-wasm light client interface in `programs/cw-ics08-wasm-eth` +* A stateless light client verification package in `packages/ethereum-light-client` + ## License This project is licensed under MIT. ## Acknowledgements -This project was bootstrapped with this [template](https://github.com/PaulRBerg/foundry-template). Implementations of IBC specifications in [solidity](https://github.com/hyperledger-labs/yui-ibc-solidity/), [CosmWasm](https://github.com/srdtrk/cw-ibc-lite), [golang](https://github.com/cosmos/ibc-go), and [rust](https://github.com/cosmos/ibc-rs) were used as references. We are also grateful to [unionlabs](https://github.com/unionlabs/union/) for their `08-wasm` ethereum light client implementation for ibc-go. +This project was bootstrapped with this [template](https://github.com/PaulRBerg/foundry-template). Implementations of IBC specifications in [solidity](https://github.com/hyperledger-labs/yui-ibc-solidity/), [CosmWasm](https://github.com/srdtrk/cw-ibc-lite), [golang](https://github.com/cosmos/ibc-go), and [rust](https://github.com/cosmos/ibc-rs) were used as references. We are also grateful to [unionlabs](https://github.com/unionlabs/union/) for their `08-wasm` ethereum light client implementation for ibc-go which our own implementation is based on. diff --git a/packages/ethereum-light-client/README.md b/packages/ethereum-light-client/README.md new file mode 100644 index 000000000..b9019958 --- /dev/null +++ b/packages/ethereum-light-client/README.md @@ -0,0 +1,8 @@ +# ethereum-light-client +> ⚠ The Ethereum Light Client is currently under heavy development, is expected to change and is not ready for production use. + +This is the stateless verification implementation of the ethereum light client. It contains all the core logic for verifying ethereum consensus, proving state (verify (non)memebership) and the headers submitted to update the light client. +The state is handled by the CosmWasm implemention in `programs/cw-ics08-wasm-eth`. + +## Acknowledgements +This work is based on the ethereum light client created by [Union](http://github.com/unionlabs/union/) diff --git a/programs/cw-ics08-wasm-eth/README.md b/programs/cw-ics08-wasm-eth/README.md new file mode 100644 index 000000000..f51085d0 --- /dev/null +++ b/programs/cw-ics08-wasm-eth/README.md @@ -0,0 +1,8 @@ +# cw-ics08-wasm-eth +> ⚠ The Ethereum Light Client is currently under heavy development, is expected to change and is not ready for production use. + +This is the CosmWasm implementation that can be used with ibc-go's 08-wasm light client wrapper. +It handles the client and consensus state, and calls into `packages/ethereum-light-client` for all the light client related logic. + +## Acknowledgements +This work is based on the ethereum light client created by [Union](http://github.com/unionlabs/union/) From fc9958aab9acec222269ab17432cc8ab7703d020 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 10 Dec 2024 11:08:43 +0000 Subject: [PATCH 37/49] change ensure to macro --- packages/ethereum-light-client/src/trie.rs | 3 +- packages/ethereum-light-client/src/verify.rs | 86 ++++++++++--------- packages/ethereum-trie-db/src/trie_db.rs | 9 +- packages/ethereum-utils/src/ensure.rs | 9 +- packages/ethereum-utils/src/lib.rs | 4 +- .../cw-ics08-wasm-eth/src/custom_query.rs | 13 +-- 6 files changed, 69 insertions(+), 55 deletions(-) diff --git a/packages/ethereum-light-client/src/trie.rs b/packages/ethereum-light-client/src/trie.rs index a0d135bb..5d494c8c 100644 --- a/packages/ethereum-light-client/src/trie.rs +++ b/packages/ethereum-light-client/src/trie.rs @@ -121,7 +121,8 @@ mod test { ..Default::default() }, &header, - ); + ) + .unwrap(); let depth = floorlog2(EXECUTION_PAYLOAD_INDEX); let index = get_subtree_index(EXECUTION_PAYLOAD_INDEX); let root = header.beacon.body_root; diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index e5f2aa17..84d83853 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -2,7 +2,7 @@ use alloy_primitives::B256; use ethereum_trie_db::trie_db::verify_account_storage_root; use ethereum_utils::{ - ensure::ensure, + ensure, slot::{compute_epoch_at_slot, compute_slot_at_timestamp, GENESIS_SLOT}, }; use tree_hash::TreeHash; @@ -57,13 +57,13 @@ pub fn verify_header( )?; // check whether at least 2/3 of the sync committee signed - ensure( + ensure!( header .consensus_update .sync_aggregate .validate_signature_supermajority(), - EthereumIBCError::NotEnoughSignatures, - )?; + EthereumIBCError::NotEnoughSignatures + ); let proof_data = header.account_update.account_proof.clone(); @@ -103,7 +103,7 @@ pub fn validate_light_client_update( bls_verifier: V, ) -> Result<(), EthereumIBCError> { // Verify sync committee has sufficient participants - ensure( + ensure!( update.sync_aggregate.num_sync_committe_participants() >= client_state .min_sync_committee_participants @@ -111,8 +111,8 @@ pub fn validate_light_client_update( .unwrap(), EthereumIBCError::InsufficientSyncCommitteeParticipants( update.sync_aggregate.num_sync_committe_participants(), - ), - )?; + ) + ); is_valid_light_client_header(client_state, &update.attested_header)?; @@ -120,28 +120,28 @@ pub fn validate_light_client_update( let update_attested_slot = update.attested_header.beacon.slot; let update_finalized_slot = update.finalized_header.beacon.slot; - ensure( + ensure!( update_finalized_slot != GENESIS_SLOT, - EthereumIBCError::FinalizedSlotIsGenesis, - )?; + EthereumIBCError::FinalizedSlotIsGenesis + ); - ensure( + ensure!( current_slot >= update.signature_slot, EthereumIBCError::UpdateMoreRecentThanCurrentSlot { current_slot, - update_signature_slot: update.signature_slot, - }, - )?; + update_signature_slot: update.signature_slot + } + ); - ensure( + ensure!( update.signature_slot > update_attested_slot && update_attested_slot >= update_finalized_slot, EthereumIBCError::InvalidSlots { update_signature_slot: update.signature_slot, update_attested_slot, update_finalized_slot, - }, - )?; + } + ); // Let's say N is the signature period of the header we store, we can only do updates with // the following settings: @@ -161,21 +161,21 @@ pub fn validate_light_client_update( ); if trusted_consensus_state.next_sync_committee().is_some() { - ensure( + ensure!( signature_period == stored_period || signature_period == stored_period + 1, EthereumIBCError::InvalidSignaturePeriodWhenNextSyncCommitteeExists { signature_period, stored_period, - }, - )?; + } + ); } else { - ensure( + ensure!( signature_period == stored_period, EthereumIBCError::InvalidSignaturePeriodWhenNextSyncCommitteeDoesNotExist { signature_period, stored_period, - }, - )?; + } + ); } // Verify update is relevant @@ -190,7 +190,7 @@ pub fn validate_light_client_update( // 2. We haven't set the next sync committee yet and we can use any attested header within the same // signature period to set the next sync committee. This means that the stored header could be larger. // The light client implementation needs to take care of it. - ensure( + ensure!( update_attested_slot > trusted_consensus_state.finalized_slot() || (update_attested_period == stored_period && update.next_sync_committee.is_some() @@ -204,8 +204,8 @@ pub fn validate_light_client_update( trusted_next_sync_committee_is_set: trusted_consensus_state .next_sync_committee() .is_some(), - }, - )?; + } + ); // Verify that the `finality_branch`, if present, confirms `finalized_header` // to match the finalized checkpoint root saved in the state of `attested_header`. @@ -229,13 +229,13 @@ pub fn validate_light_client_update( trusted_consensus_state.next_sync_committee(), ) { if update_attested_period == stored_period { - ensure( + ensure!( next_sync_committee == stored_next_sync_committee, EthereumIBCError::NextSyncCommitteeMismatch { expected: stored_next_sync_committee.aggregate_pubkey, found: next_sync_committee.aggregate_pubkey, - }, - )?; + } + ); } // This validates the given next sync committee against the attested header's state root. validate_merkle_branch( @@ -309,19 +309,19 @@ pub fn is_valid_light_client_header( let epoch = compute_epoch_at_slot(client_state.slots_per_epoch, header.beacon.slot); if epoch < client_state.fork_parameters.deneb.epoch { - ensure( + ensure!( header.execution.blob_gas_used == 0 && header.execution.excess_blob_gas == 0, - EthereumIBCError::MustBeDeneb, - )?; + EthereumIBCError::MustBeDeneb + ); } - ensure( + ensure!( epoch >= client_state.fork_parameters.capella.epoch, - EthereumIBCError::InvalidChainVersion, - )?; + EthereumIBCError::InvalidChainVersion + ); validate_merkle_branch( - get_lc_execution_root(client_state, header), + get_lc_execution_root(client_state, header)?, header.execution_branch.0.into(), EXECUTION_BRANCH_DEPTH, get_subtree_index(EXECUTION_PAYLOAD_INDEX), @@ -329,16 +329,18 @@ pub fn is_valid_light_client_header( ) } -pub fn get_lc_execution_root(client_state: &ClientState, header: &LightClientHeader) -> B256 { +pub fn get_lc_execution_root( + client_state: &ClientState, + header: &LightClientHeader, +) -> Result { let epoch = compute_epoch_at_slot(client_state.slots_per_epoch, header.beacon.slot); - ensure( + ensure!( epoch >= client_state.fork_parameters.deneb.epoch, - "only deneb or higher epochs are supported", - ) - .unwrap(); + EthereumIBCError::MustBeDeneb + ); - header.execution.tree_hash_root() + Ok(header.execution.tree_hash_root()) } #[cfg(test)] diff --git a/packages/ethereum-trie-db/src/trie_db.rs b/packages/ethereum-trie-db/src/trie_db.rs index 1b21eb67..e61da6ed 100644 --- a/packages/ethereum-trie-db/src/trie_db.rs +++ b/packages/ethereum-trie-db/src/trie_db.rs @@ -1,5 +1,5 @@ use alloy_primitives::{Address, B256}; -use ethereum_utils::ensure::ensure; +use ethereum_utils::ensure; use hash_db::HashDB; use memory_db::{HashKey, MemoryDB}; use primitive_types::{H160, H256, U256}; @@ -36,17 +36,18 @@ pub fn verify_account_storage_root( let storage_root: H256 = H256(storage_root.into()); let address: H160 = H160(address.into()); + print!("test"); match get_node(root, address.as_ref(), proof)? { Some(account) => { let account = rlp::decode::(account.as_ref()).map_err(TrieDBError::RlpDecode)?; - ensure( + ensure!( account.storage_root == storage_root, TrieDBError::ValueMismatch { expected: storage_root.as_ref().into(), actual: account.storage_root.as_ref().into(), - }, - )?; + } + ); Ok(()) } None => Err(TrieDBError::ValueMissing { diff --git a/packages/ethereum-utils/src/ensure.rs b/packages/ethereum-utils/src/ensure.rs index 72cd8bd5..08e6e2a7 100644 --- a/packages/ethereum-utils/src/ensure.rs +++ b/packages/ethereum-utils/src/ensure.rs @@ -1,3 +1,8 @@ -pub fn ensure(expr: bool, err: E) -> Result<(), E> { - expr.then_some(()).ok_or(err) +#[macro_export] +macro_rules! ensure { + ($cond:expr, $err:expr) => { + if !$cond { + return Err($err); + } + }; } diff --git a/packages/ethereum-utils/src/lib.rs b/packages/ethereum-utils/src/lib.rs index 7f3328f9..5e03d58f 100644 --- a/packages/ethereum-utils/src/lib.rs +++ b/packages/ethereum-utils/src/lib.rs @@ -1,5 +1,7 @@ pub mod base64; -pub mod ensure; pub mod error; pub mod hex; pub mod slot; + +#[macro_use] +pub mod ensure; diff --git a/programs/cw-ics08-wasm-eth/src/custom_query.rs b/programs/cw-ics08-wasm-eth/src/custom_query.rs index 5005573a..218ea1ec 100644 --- a/programs/cw-ics08-wasm-eth/src/custom_query.rs +++ b/programs/cw-ics08-wasm-eth/src/custom_query.rs @@ -1,7 +1,7 @@ use alloy_primitives::B256; use cosmwasm_std::{Binary, CustomQuery, Deps, QueryRequest}; use ethereum_light_client::types::bls::{BlsPublicKey, BlsSignature, BlsVerify}; -use ethereum_utils::{ensure::ensure, hex::to_hex}; +use ethereum_utils::{ensure, hex::to_hex}; use thiserror::Error; #[derive(serde::Serialize, serde::Deserialize, Clone)] @@ -61,19 +61,22 @@ impl<'a> BlsVerify for BlsVerifier<'a> { message: Binary::from(msg.to_vec()), signature: Binary::from(signature.to_vec()), }); - let is_valid = self + + let is_valid: bool = self .deps .querier .query(&request) .map_err(|e| BlsVerifierError::FastAggregateVerify(e.to_string()))?; - ensure( + ensure!( is_valid, BlsVerifierError::InvalidSignature(InvalidSignatureErr { public_keys: public_keys.into_iter().copied().collect(), msg, signature, - }), - ) + }) + ); + + Ok(()) } } From a4ac4aadca3e7ee57afd27cafa67ffd4618c080a Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 10 Dec 2024 11:38:17 +0000 Subject: [PATCH 38/49] added unit tests for supermajority check --- packages/ethereum-light-client/src/lib.rs | 3 + .../ethereum-light-client/src/membership.rs | 14 +---- .../src/test/commitment_proof_fixture.rs | 17 ++++++ .../ethereum-light-client/src/test/mod.rs | 1 + .../src/types/sync_committee.rs | 58 ++++++++++++++++++- 5 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 packages/ethereum-light-client/src/test/commitment_proof_fixture.rs create mode 100644 packages/ethereum-light-client/src/test/mod.rs diff --git a/packages/ethereum-light-client/src/lib.rs b/packages/ethereum-light-client/src/lib.rs index a4099324..ea68bf2c 100644 --- a/packages/ethereum-light-client/src/lib.rs +++ b/packages/ethereum-light-client/src/lib.rs @@ -9,3 +9,6 @@ pub mod verify; pub mod types; pub use typenum; // re-export (for some weird macro stuff in config.rs) + +#[cfg(test)] +mod test; diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs index 316d4b46..e674142a 100644 --- a/packages/ethereum-light-client/src/membership.rs +++ b/packages/ethereum-light-client/src/membership.rs @@ -86,7 +86,8 @@ mod test { use crate::{ client_state::ClientState, consensus_state::ConsensusState, - types::{height::Height, storage_proof::StorageProof, wrappers::WrappedBytes}, + test::commitment_proof_fixture::CommitmentProofFixture, + types::{storage_proof::StorageProof, wrappers::WrappedBytes}, }; use alloy_primitives::{ @@ -95,20 +96,9 @@ mod test { }; use ethereum_test_utils::fixtures; use ethereum_utils::hex::FromBeHex; - use serde::{Deserialize, Serialize}; use super::verify_membership; - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] - pub struct CommitmentProofFixture { - #[serde(with = "ethereum_utils::base64")] - pub path: Vec, - pub storage_proof: StorageProof, - pub proof_height: Height, - pub client_state: ClientState, - pub consensus_state: ConsensusState, - } - #[test] fn test_with_fixture() { let commitment_proof_fixture: CommitmentProofFixture = fixtures::load( diff --git a/packages/ethereum-light-client/src/test/commitment_proof_fixture.rs b/packages/ethereum-light-client/src/test/commitment_proof_fixture.rs new file mode 100644 index 000000000..529b51f4 --- /dev/null +++ b/packages/ethereum-light-client/src/test/commitment_proof_fixture.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + client_state::ClientState, + consensus_state::ConsensusState, + types::{height::Height, storage_proof::StorageProof}, +}; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct CommitmentProofFixture { + #[serde(with = "ethereum_utils::base64")] + pub path: Vec, + pub storage_proof: StorageProof, + pub proof_height: Height, + pub client_state: ClientState, + pub consensus_state: ConsensusState, +} diff --git a/packages/ethereum-light-client/src/test/mod.rs b/packages/ethereum-light-client/src/test/mod.rs new file mode 100644 index 000000000..0975f98f --- /dev/null +++ b/packages/ethereum-light-client/src/test/mod.rs @@ -0,0 +1 @@ +pub mod commitment_proof_fixture; diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 6898827d..509b11e9 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -73,12 +73,16 @@ impl SyncAggregate { .sum::() as usize } + pub fn sync_committee_size(&self) -> usize { + self.sync_committee_bits.len() * 8 + } + // TODO: Unit test // Returns if at least 2/3 of the sync committee signed // // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#process_light_client_update pub fn validate_signature_supermajority(&self) -> bool { - self.num_sync_committe_participants() * 3 >= self.sync_committee_bits.len() * 8 * 2 + self.num_sync_committe_participants() * 3 >= self.sync_committee_size() * 2 } } @@ -105,9 +109,13 @@ pub fn compute_sync_committee_period_at_slot( #[cfg(test)] mod test { - use crate::types::sync_committee::SyncCommittee; + use crate::types::{ + light_client::Header, + sync_committee::{SyncAggregate, SyncCommittee}, + }; use alloy_primitives::{hex::FromHex, B256}; + use alloy_rpc_types_beacon::BlsSignature; use ethereum_test_utils::fixtures; use tree_hash::TreeHash; @@ -123,4 +131,50 @@ mod test { assert_eq!(expected_tree_hash_root, actual_tree_hash_root); } + + #[test] + fn test_validate_signature_supermajority() { + // not supermajority + let sync_aggregate = SyncAggregate { + sync_committee_bits: vec![0b10001001].into(), + sync_committee_signature: BlsSignature::default(), + }; + assert_eq!(sync_aggregate.num_sync_committe_participants(), 3); + assert_eq!(sync_aggregate.sync_committee_size(), 8); + assert!(!sync_aggregate.validate_signature_supermajority()); + + // not supermajority + let sync_aggregate = SyncAggregate { + sync_committee_bits: vec![0b10000001, 0b11111111, 0b00010000, 0b00000000].into(), + sync_committee_signature: BlsSignature::default(), + }; + assert_eq!(sync_aggregate.num_sync_committe_participants(), 11); + assert_eq!(sync_aggregate.sync_committee_size(), 32); + assert!(!sync_aggregate.validate_signature_supermajority()); + + // not supermajority + let sync_aggregate = SyncAggregate { + sync_committee_bits: vec![0b11101001, 0b11111111, 0b01010000, 0b01111110].into(), + sync_committee_signature: BlsSignature::default(), + }; + assert_eq!(sync_aggregate.num_sync_committe_participants(), 21); + assert_eq!(sync_aggregate.sync_committee_size(), 32); + assert!(!sync_aggregate.validate_signature_supermajority()); + + // supermajority + let sync_aggregate = SyncAggregate { + sync_committee_bits: vec![0b11101001, 0b11111111, 0b01011000, 0b01111110].into(), + sync_committee_signature: BlsSignature::default(), + }; + assert_eq!(sync_aggregate.num_sync_committe_participants(), 22); + assert_eq!(sync_aggregate.sync_committee_size(), 32); + assert!(sync_aggregate.validate_signature_supermajority()); + + // valid sync aggregate from fixtures with supermajority + let fixture: Header = fixtures::load( + "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", + ); + let sync_aggregate = fixture.consensus_update.sync_aggregate; + assert!(sync_aggregate.validate_signature_supermajority()); + } } From 68a50d2dfc9ce911397e4981033c959b26c01a3e Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 10 Dec 2024 19:24:33 +0000 Subject: [PATCH 39/49] move rust fixtures into a single file per test --- .../e2esuite/light_clients.go | 21 +- e2e/interchaintestv8/go.mod | 6 +- e2e/interchaintestv8/go.sum | 4 +- e2e/interchaintestv8/ibc_eureka_test.go | 10 +- e2e/interchaintestv8/testvalues/values.go | 2 +- e2e/interchaintestv8/types/rust_fixtures.go | 79 ++- .../ethereum-light-client/src/membership.rs | 9 +- .../src/test/commitment_proof_fixture.rs | 17 - .../src/test/fixture_types.rs | 39 ++ .../ethereum-light-client/src/test/mod.rs | 2 +- .../src/types/sync_committee.rs | 17 +- packages/ethereum-light-client/src/verify.rs | 25 +- packages/ethereum-test-utils/src/fixtures.rs | 23 + ...eCosmosCoinsToEthereumAndBack_Groth16.json | 612 ++++++++++++++++++ ...ndBack_Groth16_1_initial_client_state.json | 31 - ...ack_Groth16_2_initial_consensus_state.json | 8 - ...reumAndBack_Groth16_3_update_header_0.json | 176 ----- ...eumAndBack_Groth16_4_commitment_proof.json | 54 -- ...reumAndBack_Groth16_5_update_header_0.json | 176 ----- ...eumAndBack_Groth16_6_commitment_proof.json | 54 -- programs/cw-ics08-wasm-eth/src/contract.rs | 60 +- programs/cw-ics08-wasm-eth/src/lib.rs | 5 +- .../src/test/fixture_types.rs | 38 ++ programs/cw-ics08-wasm-eth/src/test/mod.rs | 1 + 24 files changed, 852 insertions(+), 617 deletions(-) delete mode 100644 packages/ethereum-light-client/src/test/commitment_proof_fixture.rs create mode 100644 packages/ethereum-light-client/src/test/fixture_types.rs create mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16.json delete mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json delete mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json delete mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json delete mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json delete mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json delete mode 100644 packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json create mode 100644 programs/cw-ics08-wasm-eth/src/test/fixture_types.rs create mode 100644 programs/cw-ics08-wasm-eth/src/test/mod.rs diff --git a/e2e/interchaintestv8/e2esuite/light_clients.go b/e2e/interchaintestv8/e2esuite/light_clients.go index 6e0ffbcd..92208a68 100644 --- a/e2e/interchaintestv8/e2esuite/light_clients.go +++ b/e2e/interchaintestv8/e2esuite/light_clients.go @@ -180,10 +180,11 @@ func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress stri headers = []ethereumligthclient.Header{} } - wasmClientState, _ := s.GetUnionClientState(ctx, simd, s.EthereumLightClientID) + wasmClientState, unionClientState := s.GetUnionClientState(ctx, simd, s.EthereumLightClientID) _, unionConsensusState = s.GetUnionConsensusState(ctx, simd, s.EthereumLightClientID, wasmClientState.LatestHeight) - for i, header := range headers { + var updatedHeaders []ethereumligthclient.Header + for _, header := range headers { logHeader("Updating eth light client", header) headerBz := simd.Config().EncodingConfig.Codec.MustMarshal(&header) wasmHeader := ibcwasmtypes.ClientMessage{ @@ -202,8 +203,7 @@ func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress stri s.LastEtheruemLightClientUpdate = header.ConsensusUpdate.AttestedHeader.Beacon.Slot fmt.Println("Updated eth light client to block number", s.LastEtheruemLightClientUpdate) - err = rustFixtureGenerator.GenerateRustFixture(fmt.Sprintf("update_header_%d", i), header) - s.Require().NoError(err) + updatedHeaders = append(updatedHeaders, header) time.Sleep(10 * time.Second) @@ -212,6 +212,11 @@ func (s *TestSuite) UpdateEthClient(ctx context.Context, ibcContractAddress stri break } } + rustFixtureGenerator.AddFixtureStep("updated_light_client", types.UpdateClientFixture{ + ClientState: unionClientState, + ConsensusState: unionConsensusState, + Updates: updatedHeaders, + }) s.Require().Greater(s.LastEtheruemLightClientUpdate, uint64(minimumUpdateTo)) } @@ -317,10 +322,10 @@ func (s *TestSuite) createUnionLightClient(ctx context.Context, simdRelayerUser s.Require().NoError(err) s.Require().Equal("08-wasm-0", s.EthereumLightClientID) - err = rustFixtureGenerator.GenerateRustFixture("initial_client_state", ethClientState) - s.Require().NoError(err) - err = rustFixtureGenerator.GenerateRustFixture("initial_consensus_state", ethConsensusState) - s.Require().NoError(err) + rustFixtureGenerator.AddFixtureStep("initial_state", types.InitialStateFixture{ + ClientState: ethClientState, + ConsensusState: ethConsensusState, + }) } func (s *TestSuite) createDummyLightClient(ctx context.Context, simdRelayerUser ibc.Wallet) { diff --git a/e2e/interchaintestv8/go.mod b/e2e/interchaintestv8/go.mod index bc4f5097..8f65cbf5 100644 --- a/e2e/interchaintestv8/go.mod +++ b/e2e/interchaintestv8/go.mod @@ -6,6 +6,7 @@ toolchain go1.23.2 require ( cosmossdk.io/api v0.7.6 + cosmossdk.io/collections v0.4.0 cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.3.0 cosmossdk.io/x/tx v0.13.5 @@ -19,7 +20,7 @@ require ( github.com/cosmos/solidity-ibc-eureka/abigen v0.0.0 github.com/docker/docker v24.0.9+incompatible github.com/ethereum/go-ethereum v1.14.12 - github.com/kurtosis-tech/kurtosis/api/golang v1.4.1 + github.com/kurtosis-tech/kurtosis/api/golang v1.4.2 github.com/rs/zerolog v1.33.0 github.com/strangelove-ventures/interchaintest/v8 v8.3.0 github.com/stretchr/testify v1.10.0 @@ -35,7 +36,6 @@ require ( cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/iam v1.1.9 // indirect cloud.google.com/go/storage v1.41.0 // indirect - cosmossdk.io/collections v0.4.0 // indirect cosmossdk.io/core v0.11.1 // indirect cosmossdk.io/depinject v1.0.0 // indirect cosmossdk.io/log v1.4.1 // indirect @@ -307,8 +307,8 @@ require ( replace github.com/cosmos/solidity-ibc-eureka/abigen => ../../abigen replace ( - github.com/cosmos/ibc-go/v9 => github.com/cosmos/ibc-go/v9 v9.0.0-20241123151201-3d84b47307b9 github.com/cosmos/ibc-go/modules/light-clients/08-wasm => github.com/cosmos/ibc-go/modules/light-clients/08-wasm v0.0.0-20241123151201-3d84b47307b9 + github.com/cosmos/ibc-go/v9 => github.com/cosmos/ibc-go/v9 v9.0.0-20241123151201-3d84b47307b9 ) replace ( diff --git a/e2e/interchaintestv8/go.sum b/e2e/interchaintestv8/go.sum index d80df083..e0fcdd6c 100644 --- a/e2e/interchaintestv8/go.sum +++ b/e2e/interchaintestv8/go.sum @@ -855,8 +855,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2 h1:izciXrFyFR+ihJ7nLTOkoIX5GzBPIp8gVKlw94gIc98= github.com/kurtosis-tech/kurtosis-portal/api/golang v0.0.0-20230818182330-1a86869414d2/go.mod h1:bWSMQK3WHVTGHX9CjxPAb/LtzcmfOxID2wdzakSWQxo= -github.com/kurtosis-tech/kurtosis/api/golang v1.4.1 h1:V/T5k7t1iKgFof1cGhyLh396YKdTehUqO97AsTPDy+k= -github.com/kurtosis-tech/kurtosis/api/golang v1.4.1/go.mod h1:9T22P7Vv3j5g6sbm78DxHQ4s9C4Cj3s9JjFQ7DFyYpM= +github.com/kurtosis-tech/kurtosis/api/golang v1.4.2 h1:x9jpXBGuLTWuILVUZWZtgDYY9amhyhzRVHxDFlYEJB4= +github.com/kurtosis-tech/kurtosis/api/golang v1.4.2/go.mod h1:9T22P7Vv3j5g6sbm78DxHQ4s9C4Cj3s9JjFQ7DFyYpM= github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b h1:hMoIM99QKcYQqsnK4AF7Lovi9ZD9ac6lZLZ5D/jx2x8= github.com/kurtosis-tech/kurtosis/contexts-config-store v0.0.0-20230818184218-f4e3e773463b/go.mod h1:4pFdrRwDz5R+Fov2ZuTaPhAVgjA2jhGh1Izf832sX7A= github.com/kurtosis-tech/kurtosis/grpc-file-transfer/golang v0.0.0-20230803130419-099ee7a4e3dc h1:7IlEpSehmWcNXOFpNP24Cu5HQI3af7GCBQw//m+LnvQ= diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index 4483b4d4..cca1898a 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -88,6 +88,7 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context, proofType operator. eth, simd := s.ChainA, s.ChainB var prover string + shouldGenerateRustFixtures := false s.Require().True(s.Run("Set up environment", func() { err := os.Chdir("../..") s.Require().NoError(err) @@ -121,10 +122,12 @@ func (s *IbcEurekaTestSuite) SetupSuite(ctx context.Context, proofType operator. s.generateSolidityFixtures = true } - shouldGenerateRustFixtures := os.Getenv(testvalues.EnvKeyGenerateRustFixtures) == testvalues.EnvValueGenerateFixtures_True - s.rustFixtureGenerator = types.NewRustFixtureGenerator(s.GetTopLevelTestName(), shouldGenerateRustFixtures) + shouldGenerateRustFixtures = os.Getenv(testvalues.EnvKeyGenerateRustFixtures) == testvalues.EnvValueGenerateFixtures_True })) + // Needs to be added here so the cleanup is called after the test suite is done + s.rustFixtureGenerator = types.NewRustFixtureGenerator(&s.Suite, shouldGenerateRustFixtures) + s.Require().True(s.Run("Deploy ethereum contracts", func() { args := append([]string{ "--trust-level", testvalues.DefaultTrustLevel.String(), @@ -1214,7 +1217,7 @@ func (s *IbcEurekaTestSuite) getCommitmentProof(ctx context.Context, path []byte RevisionHeight: s.LastEtheruemLightClientUpdate, }) - err = s.rustFixtureGenerator.GenerateRustFixture("commitment_proof", &types.CommitmentProofFixture{ + s.rustFixtureGenerator.AddFixtureStep("commitment_proof", &types.CommitmentProofFixture{ Path: path, StorageProof: storageProof, ProofHeight: clienttypes.Height{ @@ -1224,7 +1227,6 @@ func (s *IbcEurekaTestSuite) getCommitmentProof(ctx context.Context, path []byte ClientState: unionClientState, ConsensusState: unionConsensusState, }) - s.Require().NoError(err) } return simd.Config().EncodingConfig.Codec.MustMarshal(&storageProof) diff --git a/e2e/interchaintestv8/testvalues/values.go b/e2e/interchaintestv8/testvalues/values.go index 99dc452d..3bcdb81d 100644 --- a/e2e/interchaintestv8/testvalues/values.go +++ b/e2e/interchaintestv8/testvalues/values.go @@ -62,7 +62,7 @@ const ( // SP1ICS07FixturesDir is the directory where the SP1ICS07 fixtures are stored. SP1ICS07FixturesDir = "test/sp1-ics07/fixtures" // RustFixturesDir is the directory where the Rust fixtures are stored. - RustFixturesDir = "packages/ethereum-test-utils/src/test/fixtures" + RustFixturesDir = "packages/ethereum-test-utils/src/fixtures" // RelayerConfigFilePath is the path to generate the relayer config file. RelayerConfigFilePath = "programs/relayer/config.json" // E2EDeployScriptPath is the path to the E2E deploy script. diff --git a/e2e/interchaintestv8/types/rust_fixtures.go b/e2e/interchaintestv8/types/rust_fixtures.go index 20f21e1e..45e0d0b9 100644 --- a/e2e/interchaintestv8/types/rust_fixtures.go +++ b/e2e/interchaintestv8/types/rust_fixtures.go @@ -4,6 +4,9 @@ import ( "encoding/json" "fmt" "os" + "strings" + + "github.com/stretchr/testify/suite" clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types" @@ -11,6 +14,11 @@ import ( ethereumlightclient "github.com/srdtrk/solidity-ibc-eureka/e2e/v8/types/ethereumlightclient" ) +type InitialStateFixture struct { + ClientState ethereumlightclient.ClientState `json:"client_state"` + ConsensusState ethereumlightclient.ConsensusState `json:"consensus_state"` +} + type CommitmentProofFixture struct { Path []byte `json:"path"` StorageProof ethereumlightclient.StorageProof `json:"storage_proof"` @@ -19,43 +27,84 @@ type CommitmentProofFixture struct { ConsensusState ethereumlightclient.ConsensusState `json:"consensus_state"` } +type UpdateClientFixture struct { + ClientState ethereumlightclient.ClientState `json:"client_state"` + ConsensusState ethereumlightclient.ConsensusState `json:"consensus_state"` + Updates []ethereumlightclient.Header `json:"updates"` +} + +type Step struct { + Name string `json:"name"` + Data interface{} `json:"data"` +} + +type RustFixture struct { + Steps []Step `json:"steps"` +} + type RustFixtureGenerator struct { shouldGenerateFixture bool - prefix string - // fixtureCount is used to create a clear order of fixtures - fixtureCount uint + fixture RustFixture } // NewRustFixtureGenerator creates a new RustFixtureGenerator // If shouldGenerateFixture is false, the generator will not generate any fixtures -func NewRustFixtureGenerator(prefix string, shouldGenerateFixture bool) *RustFixtureGenerator { - return &RustFixtureGenerator{ - prefix: prefix, +func NewRustFixtureGenerator(s *suite.Suite, shouldGenerateFixture bool) *RustFixtureGenerator { + rustFixtureGenerator := &RustFixtureGenerator{ shouldGenerateFixture: shouldGenerateFixture, } + + fixtureName := getTopLevelTestName(s) + + if shouldGenerateFixture { + s.T().Cleanup(func() { + s.T().Logf("Writing fixtures for %s", fixtureName) + if err := rustFixtureGenerator.writeFixtures(fixtureName); err != nil { + s.T().Logf("Error writing fixtures: %v", err) + } + }) + } + + return rustFixtureGenerator } // GenerateRustFixture generates a fixture by json marshalling jsonMarshalble and saves it to a file -func (g *RustFixtureGenerator) GenerateRustFixture(name string, jsonMarshalble interface{}) error { +func (g *RustFixtureGenerator) AddFixtureStep(stepName string, jsonMarshalble interface{}) { + if !g.shouldGenerateFixture { + return + } + + g.fixture.Steps = append(g.fixture.Steps, Step{ + Name: stepName, + Data: jsonMarshalble, + }) +} + +func (g *RustFixtureGenerator) ShouldGenerateFixture() bool { + return g.shouldGenerateFixture +} + +func (g *RustFixtureGenerator) writeFixtures(fixtureName string) error { if !g.shouldGenerateFixture { return nil } + filePath := fmt.Sprintf("%s/%s.json", testvalues.RustFixturesDir, fixtureName) - fixturesBz, err := json.MarshalIndent(jsonMarshalble, "", " ") + fixturesBz, err := json.MarshalIndent(g.fixture, "", " ") if err != nil { return err } - g.fixtureCount++ - - fixtureName := fmt.Sprintf("%s_%d_%s", g.prefix, g.fixtureCount, name) - filePath := fmt.Sprintf("%s/%s.json", testvalues.RustFixturesDir, fixtureName) - // nolint:gosec return os.WriteFile(filePath, fixturesBz, 0o644) } -func (g *RustFixtureGenerator) ShouldGenerateFixture() bool { - return g.shouldGenerateFixture +func getTopLevelTestName(s *suite.Suite) string { + parts := strings.Split(s.T().Name(), "/") + if len(parts) >= 2 { + return parts[1] + } + + return s.T().Name() } diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs index e674142a..23a19ebd 100644 --- a/packages/ethereum-light-client/src/membership.rs +++ b/packages/ethereum-light-client/src/membership.rs @@ -86,7 +86,7 @@ mod test { use crate::{ client_state::ClientState, consensus_state::ConsensusState, - test::commitment_proof_fixture::CommitmentProofFixture, + test::fixture_types::CommitmentProof, types::{storage_proof::StorageProof, wrappers::WrappedBytes}, }; @@ -101,9 +101,10 @@ mod test { #[test] fn test_with_fixture() { - let commitment_proof_fixture: CommitmentProofFixture = fixtures::load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof", - ); + let fixture: fixtures::StepFixture = + fixtures::load("TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16"); + + let commitment_proof_fixture: CommitmentProof = fixture.get_data_at_step(2); let trusted_consensus_state = commitment_proof_fixture.consensus_state; let client_state = commitment_proof_fixture.client_state; diff --git a/packages/ethereum-light-client/src/test/commitment_proof_fixture.rs b/packages/ethereum-light-client/src/test/commitment_proof_fixture.rs deleted file mode 100644 index 529b51f4..000000000 --- a/packages/ethereum-light-client/src/test/commitment_proof_fixture.rs +++ /dev/null @@ -1,17 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - client_state::ClientState, - consensus_state::ConsensusState, - types::{height::Height, storage_proof::StorageProof}, -}; - -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] -pub struct CommitmentProofFixture { - #[serde(with = "ethereum_utils::base64")] - pub path: Vec, - pub storage_proof: StorageProof, - pub proof_height: Height, - pub client_state: ClientState, - pub consensus_state: ConsensusState, -} diff --git a/packages/ethereum-light-client/src/test/fixture_types.rs b/packages/ethereum-light-client/src/test/fixture_types.rs new file mode 100644 index 000000000..81a52bcc --- /dev/null +++ b/packages/ethereum-light-client/src/test/fixture_types.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + client_state::ClientState, + consensus_state::ConsensusState, + types::{height::Height, light_client::Header, storage_proof::StorageProof}, +}; + +// TODO: Remove this file once these types are in a separate package #143 + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub enum DataType { + InitialState(Box), + CommitmentProof(Box), + UpdateClient(Box), +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct InitialState { + pub client_state: ClientState, + pub consensus_state: ConsensusState, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct CommitmentProof { + #[serde(with = "ethereum_utils::base64")] + pub path: Vec, + pub storage_proof: StorageProof, + pub proof_height: Height, + pub client_state: ClientState, + pub consensus_state: ConsensusState, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct UpdateClient { + pub client_state: ClientState, + pub consensus_state: ConsensusState, + pub updates: Vec
, +} diff --git a/packages/ethereum-light-client/src/test/mod.rs b/packages/ethereum-light-client/src/test/mod.rs index 0975f98f..11036963 100644 --- a/packages/ethereum-light-client/src/test/mod.rs +++ b/packages/ethereum-light-client/src/test/mod.rs @@ -1 +1 @@ -pub mod commitment_proof_fixture; +pub mod fixture_types; diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 509b11e9..1d39d562 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -109,10 +109,8 @@ pub fn compute_sync_committee_period_at_slot( #[cfg(test)] mod test { - use crate::types::{ - light_client::Header, - sync_committee::{SyncAggregate, SyncCommittee}, - }; + use crate::test::fixture_types::UpdateClient; + use crate::types::sync_committee::{SyncAggregate, SyncCommittee}; use alloy_primitives::{hex::FromHex, B256}; use alloy_rpc_types_beacon::BlsSignature; @@ -171,10 +169,13 @@ mod test { assert!(sync_aggregate.validate_signature_supermajority()); // valid sync aggregate from fixtures with supermajority - let fixture: Header = fixtures::load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", - ); - let sync_aggregate = fixture.consensus_update.sync_aggregate; + let fixture: fixtures::StepFixture = + fixtures::load("TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16"); + let client_update: UpdateClient = fixture.get_data_at_step(1); + let sync_aggregate = client_update.updates[0] + .consensus_update + .sync_aggregate + .clone(); assert!(sync_aggregate.validate_signature_supermajority()); } } diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index 84d83853..127934bb 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -345,7 +345,10 @@ pub fn get_lc_execution_root( #[cfg(test)] mod test { - use crate::types::bls::{BlsPublicKey, BlsSignature}; + use crate::{ + test::fixture_types::{InitialState, UpdateClient}, + types::bls::{BlsPublicKey, BlsSignature}, + }; use super::*; @@ -373,21 +376,17 @@ mod test { fn test_verify_header() { let bls_verifier = TestBlsVerifier; - let client_state: ClientState = fixtures::load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state", - ); - assert_ne!(client_state, ClientState::default()); + let fixture: fixtures::StepFixture = + fixtures::load("TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16"); - let consensus_state: ConsensusState = fixtures::load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", - ); - assert_ne!(consensus_state, ConsensusState::default()); + let initial_state: InitialState = fixture.get_data_at_step(0); - let header: Header = fixtures::load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", - ); - assert_ne!(header, Header::default()); + let client_state = initial_state.client_state; + let consensus_state = initial_state.consensus_state; + + let update_state: UpdateClient = fixture.get_data_at_step(1); + let header = update_state.updates[0].clone(); verify_header( &consensus_state, &client_state, diff --git a/packages/ethereum-test-utils/src/fixtures.rs b/packages/ethereum-test-utils/src/fixtures.rs index 44fa9b5f..77180e4a 100644 --- a/packages/ethereum-test-utils/src/fixtures.rs +++ b/packages/ethereum-test-utils/src/fixtures.rs @@ -1,5 +1,28 @@ use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct StepFixture { + pub steps: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct Step { + pub name: String, + pub data: Value, +} + +impl StepFixture { + pub fn get_data_at_step(&self, step: usize) -> T + where + T: serde::de::DeserializeOwned, + { + serde_json::from_value(self.steps[step].data.clone()).unwrap() + } +} + pub fn load(name: &str) -> T where T: serde::de::DeserializeOwned, diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16.json new file mode 100644 index 000000000..63caddf9 --- /dev/null +++ b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16.json @@ -0,0 +1,612 @@ +{ + "steps": [ + { + "name": "intial_state", + "data": { + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733855756, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 32, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "bw0opyzI85ngM8T7HEvuR7nsyaQ=", + "min_sync_committee_participants": 32 + + }, + "consensus_state": { + "slot": 32, + "state_root": "QLfsmERVtybnotdLY/E0RpipsKGzFghmdRhlkglq34Q=", + "storage_root": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "timestamp": 1733855948000000000, + "current_sync_committee": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v", + "next_sync_committee": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v" + } + } + }, + { + "name": "updated_light_client", + "data": { + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733855756, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 32, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "bw0opyzI85ngM8T7HEvuR7nsyaQ=", + "min_sync_committee_participants": 32 + }, + "consensus_state": { + "slot": 32, + "state_root": "QLfsmERVtybnotdLY/E0RpipsKGzFghmdRhlkglq34Q=", + "storage_root": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "timestamp": 1733855948000000000, + "current_sync_committee": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v", + "next_sync_committee": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v" + }, + "updates": [ + { + "trusted_sync_committee": { + "trusted_height": { + "revision_number": 0, + "revision_height": 32 + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v" + } + }, + "consensus_update": { + "attested_header": { + "beacon": { + "slot": 80, + "proposer_index": 42, + "parent_root": "1O7lDzEE5PxbXw1fow084phX2E3ASFKvEg5mYae4s1U=", + "state_root": "2aQwWA5cAU2P6ZHla5SUGhiIJfV3tbtNF7nQjS+soNg=", + "body_root": "xqsG0dPX9F6EcxPCOH1fNCxSf++0jDF2E0q3Gyukh+A=" + }, + "execution": { + "parent_hash": "ru6Z9zOSaJ+wsLMggEkS8j0wQcXWhVUXIP8aj66l5vg=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "LAG0VZsJAMaMlNvMZwgtgdBXFwG1seMi1oQ2Az6C/cM=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "GIyUtj3YLIpxoI8O9/LoqXJVwX0Al6LVKzT2+ESmXbA=", + "block_number": 80, + "gas_limit": 30000000, + "timestamp": 1733856236, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ74=", + "block_hash": "wlDETVSwhPXLtJSgOuU+gGcwOtCh2wRwUJLsDGF3f0E=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "iHWRb/HnHxdZ5aPr2jTGL5RND6Nh3UAmNUqVHRF4eJw=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "h1JnSIM8gJKzW/g8wB2fmTfN02XPyP0eb+qKjFQjZF8=" + ] + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "piW74PYzIdk0DnJi+7tM60knsusPNbNaao5uh3gQRHEqnItRtIZWrwrUPD+fTggu" + }, + "next_sync_committee_branch": [ + "CwBU3peDu2QTGV9J3v71dAPfPjZHWNE0mz2A+zCjnZA=", + "lwZkHK6SUzdlSMoFdZE1Tkt2Do5CGUP7YqXfZeUqYsQ=", + "BV9rD4Us8DDhOy4RR0zIFmiWReVrqrSoUUwCKioFFcA=", + "P4XiedemFlpmMiGID7+2ceKCA9zRIk7xJ81cXNrSCtg=", + "iQpbs2186fOH6WMYYRF+NEWN/nZfcfCg7lEwnUx4kYY=" + ], + "finalized_header": { + "beacon": { + "slot": 64, + "proposer_index": 50, + "parent_root": "KiLRWMkzOpvoBBeSfdhhDEFNKg/H8gkVEgODLIF2mmk=", + "state_root": "p+SRTw53W6vOjDwYc9TzgF3k92ROJ5hcAKus4Zl5qrE=", + "body_root": "ZnPqkQmmW0tfCkZEpotUdZBk5k2PYw0jWJGsbfukx/k=" + }, + "execution": { + "parent_hash": "RIgrTRvdvgS1Xt4wteEutMowgLQkGm1draydEKrpNi8=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "4ONwA+OSvHfeAwEYD3lY1dbZ8rW0uGyaiFxbMCEIyP4=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "N4VrKZF3d8uOIDoIm/ZB6z3Lf6M1dqio0kTFazd4MPg=", + "block_number": 64, + "gas_limit": 30000000, + "timestamp": 1733856140, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADYuU=", + "block_hash": "Os3NEkyiKm8149HAOSx2mqnzh1zOpZNMlcn85qwmeF4=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "Nwt0yDluQVKBlp6j99315FRZf4vQu8GEmvlfww5OhGM=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "gMub4iCaz4g0suRKADDwA3zkZ39YCCth9c/VHVPtElA=" + ] + }, + "finality_branch": [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", + "7DwX1mZPgtSvOmOopjT4GmPGZus71j5IoOmsksT13Nc=", + "BV9rD4Us8DDhOy4RR0zIFmiWReVrqrSoUUwCKioFFcA=", + "P4XiedemFlpmMiGID7+2ceKCA9zRIk7xJ81cXNrSCtg=", + "iQpbs2186fOH6WMYYRF+NEWN/nZfcfCg7lEwnUx4kYY=" + ], + "sync_aggregate": { + "sync_committee_bits": "/////w==", + "sync_committee_signature": "toeilMFMq5SCh6IUiR4zCkdF6+QylRaOSXSDFPROp2N/VpN0KVpi7tR1mZviK2AMGQQZ6WTJBsm/W8dyEkobMmEl6r+6sgJt1RtG9R+xTZnp/AkDGPERxEjzhqGA11wR" + }, + "signature_slot": 81 + }, + "account_update": { + "account_proof": { + "storage_root": "FQyCzO+Kf1o+8uOU1/zh9I50iBGqiCG3tQxMoLZEE0E=", + "proof": [ + "+QIRoEqvnXgQSE6uML1+jas4PVF1tiIo22EFAKi/9NHndXGooAbUdhbfR5tGswLyqLftA8tTf2z3xVHBVCHGXbTgD6l/oLuHKSNDinwlyI+JmecT3RQCSrT57vDoTVHjUNnDx813oNi8oPl9vaMHj97bp7mRjBEbLF3LR35QM+JPyKJlJVrvoGkq5BIeaVESyepPbBR0D5TQeXhOmQ9B0YsXpxlmuhdFoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foF1h7zrBxkEiigQalRUjHhVeP1E8WXQ42q5bWqZOuy5noGKSYZjiqQ7UeRdBYSZA+2XGPZ8ZDOZjyDX9mR1LdHVJoPnRr2R9fhhE6aKm3r7yCLaNREEdGZYSchixFYITIyXkoL6I5HJDJjgqi1biMo7u8K1R8Y1brg6EKWr+FMQCjEr5oA8y0WYWCPn0aXZh4EtQpjT+GU3aZpXUGDTyYdHqQ4DCoBeWYXQn5n7RDN+KcrAmiacAunHrkxhqGxIMmtCw5W6uoK0LuGtHGGwEIj6FqcM90ch91uXBf3U/T9ClZ3LYp4OZoFb+tXLIDCe36wVG4xP7Lp2Nv8bXo57vQKwx+UNVMd1UoJYvD3zLQ8eLoj+ZvmPX++/JrK+7O+hycOuNhQ5QkGz/oHgwS2TetxpcoSkco3l58MFOK7j4GhbaIQf7CREw8PeygA==", + "+QFRoD6TiwBD2rB8FpUnqq6OVXeMwdUN+zOByLq5YzOe8vh6gKC1zPy8Mgo5tAriiI73XuIFDClU/iUkiVrJjNQ10NUgGqDfWkJW/yX4deayShK82g9hCqUUs0cmbxEr2eZx3aWk+4CAoBg5Oha5L8XhP/JZovIotNuHSnwuLv3qmQ2yQzStrHZegKDzjJXLv3w4rfQGDl5MDol0PdW9yGP4iuh+EQ0RpVwsbKBl3iIXsq26GR0iZfo522mo2yhfEBuxKdaTM9WxhuAY5KCFrWasFfAWK346oyhshu96L08yD/W9DEUFK7X2UYxLBICg64B8mYyBIQllkJFDYEN+MFEUZh4CEbyzacUHt5mjEdegCAZwUy7i7pfJeAL2UPTFjLvTywKQ+x0xPGKqI2HirLmAoFi5pM785JAZiqlJ8+HEM+8x5Yr49QTGmR8VRhAlBAtHgA==", + "+HGAgICgWg82zhYEL4F3O3LZtw85nBcPD1+N+sHPhqaCO5rWf+6AoNv83llVsfz8cCghl+uNr0UluUBHabf45yAv5gVUtLZrgICAgICgYpsKqgTf1yVgMfDQyfWmYg5ZMa4nWI5is6fHD88LP2iAgICAgA==", + "+GifM1FcvC/LYwS9lHNetkjkXluqZ/eS8pyZh1A8MpYcc7hG+EQBgKAVDILM74p/Wj7y45TX/OH0jnSIEaqIIbe1DEygtkQTQaC1UBwqmIhZLr9DrTkL8TkHuD14NINNNaH/HPGnOXRcaQ==" + ] + } + } + } + ] + } + }, + { + "name": "commitment_proof", + "data": { + "path": "MDctdGVuZGVybWludC0wAwAAAAAAAAAB", + "storage_proof": { + "key": "z4MIXTcym36YbZwkQWDschTHgBp83kz7bYZ/zELHO3A=", + "value": "hGDiH3O1PXeeSzKRzTUzjpL66ZmHNfGgtxUMB0wHMaY=", + "proof": [ + "+HGAgKBn+HTtJcYaG/oWCy2naUfMpetX7uGWMwVxW59RjHDv+oCAgICAgICAgKDZ7DPZ9gv/axP5sWNsOgrHy/HIZt5y428gaQ/rczBJC6AqTaH7tpp+wcsZefHTE7thiOuIYkLUlzAwZezXeCxqcoCAgA==", + "+EOgOCBn1SkRXM4FoKixmPRs60ule4noP5PH5oOlpmmmXRahoIRg4h9ztT13nksykc01M46S+umZhzXxoLcVDAdMBzGm" + ] + }, + "proof_height": { + "revision_number": 0, + "revision_height": 80 + }, + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733855756, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 80, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "bw0opyzI85ngM8T7HEvuR7nsyaQ=", + "min_sync_committee_participants": 32 + + }, + "consensus_state": { + "slot": 80, + "state_root": "LAG0VZsJAMaMlNvMZwgtgdBXFwG1seMi1oQ2Az6C/cM=", + "storage_root": "FQyCzO+Kf1o+8uOU1/zh9I50iBGqiCG3tQxMoLZEE0E=", + "timestamp": 1733856236000000000, + "current_sync_committee": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v", + "next_sync_committee": "piW74PYzIdk0DnJi+7tM60knsusPNbNaao5uh3gQRHEqnItRtIZWrwrUPD+fTggu" + } + } + }, + { + "name": "updated_light_client", + "data": { + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733855756, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 80, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "bw0opyzI85ngM8T7HEvuR7nsyaQ=", + "min_sync_committee_participants": 32 + + }, + "consensus_state": { + "slot": 80, + "state_root": "LAG0VZsJAMaMlNvMZwgtgdBXFwG1seMi1oQ2Az6C/cM=", + "storage_root": "FQyCzO+Kf1o+8uOU1/zh9I50iBGqiCG3tQxMoLZEE0E=", + "timestamp": 1733856236000000000, + "current_sync_committee": "gpEz/QdWaEoK7HttvrDVrsFuWqP7jHroafNH8NoS2NdwzHN+kHwqYxHUI0oEfN+v", + "next_sync_committee": "piW74PYzIdk0DnJi+7tM60knsusPNbNaao5uh3gQRHEqnItRtIZWrwrUPD+fTggu" + }, + "updates": [ + { + "trusted_sync_committee": { + "trusted_height": { + "revision_number": 0, + "revision_height": 80 + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "piW74PYzIdk0DnJi+7tM60knsusPNbNaao5uh3gQRHEqnItRtIZWrwrUPD+fTggu" + } + }, + "consensus_update": { + "attested_header": { + "beacon": { + "slot": 144, + "proposer_index": 58, + "parent_root": "mF2KL1zqv4gZ2N++yAloFZXcTbVr0i1ELFXLzxdk5ww=", + "state_root": "aUW8ax9pw1HINE+QO9/ByUH9Y3gmZRBfHtmlGranjmQ=", + "body_root": "fafDMFnlAGT7C/wASqo+hNL3FN1RiCERClXVbU/yFtg=" + }, + "execution": { + "parent_hash": "qFoCQLx7yYrcmX5a2usJYRo1ocXSnsrP3TF5gtASHLA=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "+s5Ah5zXVKIu+I4QQ4TPdcxgcRhuwKLf3igMostLKG8=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "1RbFW2Lq8FKb2ddLIgaGkWDXvUiu3qS3FiTVSmGtPQM=", + "block_number": 144, + "gas_limit": 30000000, + "timestamp": 1733856620, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk=", + "block_hash": "bOMaCWf9lobnDErYND5uyaOhi8dGNh5yFvE+fe9cviE=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "ggg/zbZvdsdS7WG7wAx+vpH+dcbRnK6ZrIE7t8+6dMo=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "S8o2yFqYtFoPxGKkwb1jQmfBMLDG9TceN862Ba4DNXw=" + ] + }, + "next_sync_committee": { + "pubkeys": [ + "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" + ], + "aggregate_pubkey": "llDboaDTFWpT9oYHHw3JlS1FDDDxrU2RQQ5sVYT7qgvSFACKddjo5IXB5fUYrOBc" + }, + "next_sync_committee_branch": [ + "AGP1OJjuol6AlkzYVgpoVm1bsWReyqgg3NLKTOxctzY=", + "MgBhmm/UIND/X5sksVSDj8d0hr39DRF3Dc0R5++fia0=", + "wFZZieYdauxSNTVpVyEoIvuTfTEgu5teJovC8CX4E04=", + "rO+Pqm4gF/0zoIYv1Oj/6hC4Z0TY9F3rwAJM9hJk4y4=", + "UOXxLnocL0iqw4Ql1oJGpqppr9KLHwaTWPAqBUxR13M=" + ], + "finalized_header": { + "beacon": { + "slot": 128, + "proposer_index": 60, + "parent_root": "wosYEYU9l1ooOcUYG1eZFQFJvtPlruyVGOIpILTkm/U=", + "state_root": "Lq2yQ+CO065pLdqv6E2MBoG+UkGCKn+pvtEu6XiXjZA=", + "body_root": "b3OH6+XxIwlg7HjBTF7vgF34KXaik0JQ6T5xeGf3NNs=" + }, + "execution": { + "parent_hash": "L0j248QGmkMl+uv8mCIrroyqE4XI+CV4AMSyEOI1OC8=", + "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", + "state_root": "HLfYes1lNuLDHxw8ABcNUJaVMHezicmAr1BYeMt1EBI=", + "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", + "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "prev_randao": "4wxl64GBXfNYyGOXRbtcVGdQ3YvsXBoG3LSFlgfP78I=", + "block_number": 128, + "gas_limit": 30000000, + "timestamp": 1733856524, + "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", + "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA=", + "block_hash": "bamfdcRuhojTnkYzLo7HO2rlih9Nz3NDsMuj2BKu28g=", + "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", + "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" + }, + "execution_branch": [ + "VevPI+wqOmK1kSOuJWtdU3Drmevw9+R0LnTN5Ctkwys=", + "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", + "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", + "ljGYWEISnj6hg/eg5+QK/lfQ7Lcb1i5TDOw8QiBDhT0=" + ] + }, + "finality_branch": [ + "EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", + "NcLtbn1GXhlgXseD9Nblrp6v+1IYLaoTXPm7hs3LpJQ=", + "wFZZieYdauxSNTVpVyEoIvuTfTEgu5teJovC8CX4E04=", + "rO+Pqm4gF/0zoIYv1Oj/6hC4Z0TY9F3rwAJM9hJk4y4=", + "UOXxLnocL0iqw4Ql1oJGpqppr9KLHwaTWPAqBUxR13M=" + ], + "sync_aggregate": { + "sync_committee_bits": "/////w==", + "sync_committee_signature": "h2oyGhzIYwwYF/OiblaBLnkFqZncQFqZ5PUZX6yAGj6b/4weHLykYCJH4qOeQbQPF6uC8wXNc5h+7evtWdpdToIwP/NlEK9aQSkCVfgvrG+iOPG/ctjZqfNStjqf2amY" + }, + "signature_slot": 145 + }, + "account_update": { + "account_proof": { + "storage_root": "OSSDYp1SYQqEE62BFntoqQKVKGw3XHklNAg3RuwW3xE=", + "proof": [ + "+QIRoEqvnXgQSE6uML1+jas4PVF1tiIo22EFAKi/9NHndXGooAbUdhbfR5tGswLyqLftA8tTf2z3xVHBVCHGXbTgD6l/oLuHKSNDinwlyI+JmecT3RQCSrT57vDoTVHjUNnDx813oMeymH8lQgoK6PLOJGcRV73/G2KbuXGDU47XuTOdvPVroGkq5BIeaVESyepPbBR0D5TQeXhOmQ9B0YsXpxlmuhdFoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foF1h7zrBxkEiigQalRUjHhVeP1E8WXQ42q5bWqZOuy5noJyNzdK8koRQrscuwTJHpsUKqZ7OBxduwGktNXlb2zNPoINUfExmxDP+E61Jq7AgBy7diT9AGcMZrPIEN3JTz4RaoL6I5HJDJjgqi1biMo7u8K1R8Y1brg6EKWr+FMQCjEr5oKJBg+4k4qx6ERsHe7Ns4Is8ZPBToCFAIG1ecwc+G+z9oBeWYXQn5n7RDN+KcrAmiacAunHrkxhqGxIMmtCw5W6uoK0LuGtHGGwEIj6FqcM90ch91uXBf3U/T9ClZ3LYp4OZoFb+tXLIDCe36wVG4xP7Lp2Nv8bXo57vQKwx+UNVMd1UoJYvD3zLQ8eLoj+ZvmPX++/JrK+7O+hycOuNhQ5QkGz/oHgwS2TetxpcoSkco3l58MFOK7j4GhbaIQf7CREw8PeygA==", + "+QFRoNSe0WFknOy5ppfsxg071vwNegMDDi9eSsDeQy4QXXEqgKC1zPy8Mgo5tAriiI73XuIFDClU/iUkiVrJjNQ10NUgGqDfWkJW/yX4deayShK82g9hCqUUs0cmbxEr2eZx3aWk+4CAoBg5Oha5L8XhP/JZovIotNuHSnwuLv3qmQ2yQzStrHZegKDzjJXLv3w4rfQGDl5MDol0PdW9yGP4iuh+EQ0RpVwsbKBl3iIXsq26GR0iZfo522mo2yhfEBuxKdaTM9WxhuAY5KCFrWasFfAWK346oyhshu96L08yD/W9DEUFK7X2UYxLBICg64B8mYyBIQllkJFDYEN+MFEUZh4CEbyzacUHt5mjEdegCAZwUy7i7pfJeAL2UPTFjLvTywKQ+x0xPGKqI2HirLmAoFi5pM785JAZiqlJ8+HEM+8x5Yr49QTGmR8VRhAlBAtHgA==", + "+HGAgICgWg82zhYEL4F3O3LZtw85nBcPD1+N+sHPhqaCO5rWf+6AoAbsEfbGy1GbewvrgWX5DFLBcgsUfs6Z5WrTgx9O3UYNgICAgICgYpsKqgTf1yVgMfDQyfWmYg5ZMa4nWI5is6fHD88LP2iAgICAgA==", + "+GifM1FcvC/LYwS9lHNetkjkXluqZ/eS8pyZh1A8MpYcc7hG+EQBgKA5JINinVJhCoQTrYEWe2ipApUobDdceSU0CDdG7BbfEaC1UBwqmIhZLr9DrTkL8TkHuD14NINNNaH/HPGnOXRcaQ==" + ] + } + } + } + ] + } + }, + { + "name": "commitment_proof", + "data": { + "path": "MDctdGVuZGVybWludC0wAQAAAAAAAAAB", + "storage_proof": { + "key": "dddBHLAdqtFncTtam3IZZw8OUAZTy7zUXP4b/gQiJFk=", + "value": "o6k/8ZjQOClNcSihI2CKlxE5D7j12ZPz6UAdtyMI97A=", + "proof": [ + "+LGAgKBn+HTtJcYaG/oWCy2naUfMpetX7uGWMwVxW59RjHDv+qA3G1OM0aXfARdggPsZmFNPtqjUk9m9025RegfZsaIN64CAoFexbZo7uy0Qa00bEtyjUE9hiZx8ZgsDaEhRFCbtNC3WgICAgICg2ewz2fYL/2sT+bFjbDoKx8vxyGbecuNvIGkP63MwSQugKk2h+7aafsHLGXnx0xO7YYjriGJC1JcwMGXs13gsanKAgIA=", + "+EOgPTw7zwMABq/qKmd6b/W/P38RHodGHIhIzwYqV1bRqIihoKOpP/GY0DgpTXEooSNgipcROQ+49dmT8+lAHbcjCPew" + ] + }, + "proof_height": { + "revision_number": 0, + "revision_height": 144 + }, + "client_state": { + "chain_id": "3151908", + "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", + "genesis_time": 1733855756, + "fork_parameters": { + "genesis_fork_version": "EAAAOA==", + "altair": { + "version": "IAAAOA==" + }, + "bellatrix": { + "version": "MAAAOA==" + }, + "capella": { + "version": "QAAAOA==" + }, + "deneb": { + "version": "UAAAOA==" + } + }, + "seconds_per_slot": 6, + "slots_per_epoch": 8, + "epochs_per_sync_committee_period": 8, + "latest_slot": 144, + "frozen_height": { + "revision_number": 0, + "revision_height": 0 + }, + "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", + "ibc_contract_address": "bw0opyzI85ngM8T7HEvuR7nsyaQ=", + "min_sync_committee_participants": 32 + + }, + "consensus_state": { + "slot": 144, + "state_root": "+s5Ah5zXVKIu+I4QQ4TPdcxgcRhuwKLf3igMostLKG8=", + "storage_root": "OSSDYp1SYQqEE62BFntoqQKVKGw3XHklNAg3RuwW3xE=", + "timestamp": 1733856620000000000, + "current_sync_committee": "piW74PYzIdk0DnJi+7tM60knsusPNbNaao5uh3gQRHEqnItRtIZWrwrUPD+fTggu", + "next_sync_committee": "llDboaDTFWpT9oYHHw3JlS1FDDDxrU2RQQ5sVYT7qgvSFACKddjo5IXB5fUYrOBc" + } + } + } + ] +} diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json deleted file mode 100644 index bb215567..000000000 --- a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "chain_id": "3151908", - "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", - "genesis_time": 1733407803, - "fork_parameters": { - "genesis_fork_version": "EAAAOA==", - "altair": { - "version": "IAAAOA==" - }, - "bellatrix": { - "version": "MAAAOA==" - }, - "capella": { - "version": "QAAAOA==" - }, - "deneb": { - "version": "UAAAOA==" - } - }, - "seconds_per_slot": 6, - "slots_per_epoch": 8, - "epochs_per_sync_committee_period": 8, - "latest_slot": 32, - "frozen_height": { - "revision_number": 0, - "revision_height": 0 - }, - "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", - "ibc_contract_address": "Pus1Pj9GbHF70jfiXjd1yeYfhpY=", - "min_sync_committee_participants": 32 -} diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json deleted file mode 100644 index 1da1c746..000000000 --- a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "slot": 32, - "state_root": "yIADlPrcUUOqTri8ZYeoYh9e0nQndM7TrJVp4gQPD0k=", - "storage_root": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "timestamp": 1733407995000000000, - "current_sync_committee": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD", - "next_sync_committee": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD" -} \ No newline at end of file diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json deleted file mode 100644 index d8df8d8a..000000000 --- a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "trusted_sync_committee": { - "trusted_height": { - "revision_number": 0, - "revision_height": 32 - }, - "next_sync_committee": { - "pubkeys": [ - "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" - ], - "aggregate_pubkey": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD" - } - }, - "consensus_update": { - "attested_header": { - "beacon": { - "slot": 80, - "proposer_index": 25, - "parent_root": "NlekTueD21MkMXOIh+1MmF/J9CrkmZv66spYesCVsJQ=", - "state_root": "xuF1/mikzEFmF1Pc5yrD1n40GsbPGmp9M5/gqp/S0ho=", - "body_root": "7rtnE/VXD/CMMnt3ftCZBbqak9OS6Utmeeo2So+Q3gU=" - }, - "execution": { - "parent_hash": "wJC+gZETcSKHSj7Yu0reQWCcNkSvAInT4CiaZggu5lo=", - "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "LiJXxsCQiY7Py+Moq31eYwxPNPhl9yJ0bGlmAzvtwHw=", - "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", - "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "1jC5ZU0vKvjYcxEMFJDXIazRcwp/bgE6SJNHYo1JDd8=", - "block_number": 80, - "gas_limit": 30000000, - "timestamp": 1733408283, - "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", - "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ2c=", - "block_hash": "79sNb+MH27Eg+aJBAtgrZwtAsfGPnQQwoR+vD/UePmU=", - "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", - "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" - }, - "execution_branch": [ - "LmEeN0VFfprSbSzqEJ1d4jRMk4jiilxpyr6uRgUU+SU=", - "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", - "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "UgvNn2R4SQFIlQ1VBsGxb/ZHIU1AlpEUeOCR0iqYCJY=" - ] - }, - "next_sync_committee": { - "pubkeys": [ - "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" - ], - "aggregate_pubkey": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe" - }, - "next_sync_committee_branch": [ - "YTWQEU/vCtSt0LPg5NtUfs6Uwfz7fIqmBZNY4pyZuBM=", - "sN4B29I4KUX3o9pmjH4SIkQ14VKztRixQo73PrS4PZQ=", - "OsO00eQO+jeWCS0gfXgUdEJPI1+ZAIg1kfXAAUcPsDQ=", - "sCek+pC9ySDXdCjT1t8QySt68QWaXOD3iqm12W2Jlbc=", - "7cuAvzpPr9OW7N06mldF5gn16N/5cuD28t2YXLNPZnk=" - ], - "finalized_header": { - "beacon": { - "slot": 64, - "proposer_index": 39, - "parent_root": "i/FPE5oXcsSYF0xLZtWiPx7cnoBZo0Kc/HscjMVizO4=", - "state_root": "wobedKhB7hSl5kUeBzhI/KkTv7wynUESa/1KSyQNWtU=", - "body_root": "IoxUVJZ73fdRF9bhGvEcWjtkMafFPSFei5k68TX3VJo=" - }, - "execution": { - "parent_hash": "7W5Stq/gIqVRTjGxjXfczqEyS2U73FokKoA6Dh+NK7s=", - "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "RIquC1ijfk4hNvprzGZ8gJC/+/A794md4WqkN5kHtQ4=", - "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", - "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "mLIiuZeTtjH0ZCzXCrHYqerfIwnfjK+o2ZiUPTpdFkw=", - "block_number": 64, - "gas_limit": 30000000, - "timestamp": 1733408187, - "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", - "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX/4=", - "block_hash": "yqb7iCDzjqlbyiS/ccGag6WMEB/AT8YnCiPCKfwt9uw=", - "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", - "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" - }, - "execution_branch": [ - "xDEyxC+53LW9gybVGPp0Tfmjl62SjojALtkVQz9s9Is=", - "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", - "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "kCPZTUkwMbHNDxsNIgG4YFrEznahbokZe/vNmJAy3Bs=" - ] - }, - "finality_branch": [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", - "9GI7ErmLRdu448f3+EsD+dXYrkYuINl8F/ysuorSMm0=", - "OsO00eQO+jeWCS0gfXgUdEJPI1+ZAIg1kfXAAUcPsDQ=", - "sCek+pC9ySDXdCjT1t8QySt68QWaXOD3iqm12W2Jlbc=", - "7cuAvzpPr9OW7N06mldF5gn16N/5cuD28t2YXLNPZnk=" - ], - "sync_aggregate": { - "sync_committee_bits": "/////w==", - "sync_committee_signature": "hEM3Bv7mWv3oslH8317lojgRJuaZvtps80uylnLueVvlzP5of2sQnG9msQZcD3TYGfpxvX0hnAy9m/NrNd8fc4QXwnrkLgFAWqh+47Pn7EjbK9HqWVLnvB45KlwX79w0" - }, - "signature_slot": 81 - }, - "account_update": { - "account_proof": { - "storage_root": "UMSy+0fN7u9iWv60HXAmZ+xmF+Vr7Bhkrc5R/s5k43M=", - "proof": [ - "+QIRoEqvnXgQSE6uML1+jas4PVF1tiIo22EFAKi/9NHndXGooEPbBKBSO87PpVSt/n2mvRYGYQD53ttV/CIukxCPRFrToKsZigoIFy1+rpw72dqLstEaNIs0OHAvt/LB8bVw60dAoE2zRjhvz8XG+0ubo1Al5PWOVE3thPFgEUvRtFh1uxqMoN00IIObq67nYefqo4rV9Zaxqbhxbn6bkmGUmpZKWn1hoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foLdsee8YaGUh9w2QReAmz+ikM3vwYV+0vwu4llu4xiFboIPGl55GPAKBj/6truuKvJ8vUedn+5FRp/yJmJ60C1esoMvNwdImpUDFDLHmFeevmfFx1DZbRXNJQOItR+xKojoUoJmNmYDMJMjLe1v5yT9zr59e+vNHDKFWpagulbksSpEPoPy/d7p1MmcO5NywlRj54vQBdBjeJHDNd/HzriIPCVjYoBeWYXQn5n7RDN+KcrAmiacAunHrkxhqGxIMmtCw5W6uoK0LuGtHGGwEIj6FqcM90ch91uXBf3U/T9ClZ3LYp4OZoGm3Si0sJG+LQGhv/VfDzx+p7jJASdBUX8seZ84WVsEGoHzaprc7+/5FwSGnCUk3uJukyupPXR7ue6s5TcnN9GlUoOGhHHPfIMepd0ImI5qOoTyl7v5SUngNaHzuWqQY/MT6gA==", - "+QGRoAW/OheSLPSH4l2/4k9e4Gqcu6AIFeeUxVgmfV348TJggKC1Dd8AKIzIwTNSUbBLymVjI1RWNN3SIw8XhNPyF2m7U6ChpXt8UzlXsptRWcjDUno50TVNuwuqpI+nBaNUdlL5pKCZjxpgZi8W4bfVYmdSvqX/emcnaVEpIdkA69F1jFBLJ6COn69uca0GKG4WS94nlNllxcmAAkzIo6i5lCYJr1Z+v6Cz7/jDJ2RT50/6Uz9iqlGmT/ptcN6EMvkYguUn6yvwRICgW4c0sG9Yfjxp/QRldX24lk/lnljNycWZQzRf6OGXyRagI249OXiMI7N+u6Uip6+fED8MfU/kLgn+WDJre6pyIrCgmeYaf7+xnVndxQtXJssAH72qha3wIK27GL+ui87ytt2AgKCiJCLqmRojruVuNbkH5tFGPMmaWMr+5hDuVtL2SUram6AqT1h3lSJKnVY1mgcfNXPZOAyq3Um8obY6G6fiDZ5mZKAYkkbHr4+u0sfT+H3ItaUdeH9vitZZrG+RcilLPf/tTIA=", - "+GmgIBIndIQYPU8oMKp/nGRX2TS5REap2KbZyPAYf55t07S4RvhEAYCgUMSy+0fN7u9iWv60HXAmZ+xmF+Vr7Bhkrc5R/s5k43Ogsq72HnOsOpO18+6lYsWneIteOOTR+++IIQbOr8OvLdc=" - ] - } - } -} \ No newline at end of file diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json deleted file mode 100644 index 0fdff101..000000000 --- a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "path": "MDctdGVuZGVybWludC0wAwAAAAAAAAAB", - "storage_proof": { - "key": "z4MIXTcym36YbZwkQWDschTHgBp83kz7bYZ/zELHO3A=", - "value": "hGDiH3O1PXeeSzKRzTUzjpL66ZmHNfGgtxUMB0wHMaY=", - "proof": [ - "+HGAgKA6BiC+LtHLi1QUcOvXp/4Ey0uNco6kzwhuY+dIrcntAICAgICAgICAgKDZ7DPZ9gv/axP5sWNsOgrHy/HIZt5y428gaQ/rczBJC6AqTaH7tpp+wcsZefHTE7thiOuIYkLUlzAwZezXeCxqcoCAgA==", - "+EOgOCBn1SkRXM4FoKixmPRs60ule4noP5PH5oOlpmmmXRahoIRg4h9ztT13nksykc01M46S+umZhzXxoLcVDAdMBzGm" - ] - }, - "proof_height": { - "revision_number": 0, - "revision_height": 80 - }, - "client_state": { - "chain_id": "3151908", - "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", - "genesis_time": 1733407803, - "fork_parameters": { - "genesis_fork_version": "EAAAOA==", - "altair": { - "version": "IAAAOA==" - }, - "bellatrix": { - "version": "MAAAOA==" - }, - "capella": { - "version": "QAAAOA==" - }, - "deneb": { - "version": "UAAAOA==" - } - }, - "seconds_per_slot": 6, - "slots_per_epoch": 8, - "epochs_per_sync_committee_period": 8, - "latest_slot": 80, - "frozen_height": { - "revision_number": 0, - "revision_height": 0 - }, - "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", - "ibc_contract_address": "Pus1Pj9GbHF70jfiXjd1yeYfhpY=", - "min_sync_committee_participants": 32 - }, - "consensus_state": { - "slot": 80, - "state_root": "LiJXxsCQiY7Py+Moq31eYwxPNPhl9yJ0bGlmAzvtwHw=", - "storage_root": "UMSy+0fN7u9iWv60HXAmZ+xmF+Vr7Bhkrc5R/s5k43M=", - "timestamp": 1733408283000000000, - "current_sync_committee": "p8wWXo3JZ8KBDBoaJFolrp7yjc3lWcpaeoOds4cUb4SD5NmwbJc8DuRWqAbyVYCD", - "next_sync_committee": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe" - } -} diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json deleted file mode 100644 index b08efede..000000000 --- a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_5_update_header_0.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "trusted_sync_committee": { - "trusted_height": { - "revision_number": 0, - "revision_height": 80 - }, - "next_sync_committee": { - "pubkeys": [ - "tj8yffaFgc3AKmbBxl6Qagaho6jXpuOPe22pROjmzC24X87VMn2MEpRc6zMBgnLK", - "q2T5AMdw4rmd5rhrQ5C70Veb1I3M7FWACtvPUuAG8iEo6ZcbvzqSzAEFsJdISZNa", - "jQKKAhxcMaGqHhjtp0z68PuhxFTBfC4PxzDdB6GdDHf3qQXVQBcpLz6ADKBraXfN", - "r2HyY63ftBxG1m5g7PtZillC9kj1hxi2tOTJIBn9sSMo77/5hwMTS88o6cH6tLtg", - "skORqpe//ymtyTXQaittWDQzyvgvkt4ZgOAZLTsnAyO9vyS4bcYVIKQMQZ3ePfSz", - "p1n2vMqPNfyq3EBsxLgowBbA7SOIKYenn1Lykztc7e/iTjHfb9DTjoqALbr9dQ0B", - "tyyxBre8HsriGeCuGDClCe0YoEK1aid59AM0Gd5puoroAXCQyu0fU3e/poUGFXNg", - "p1ypRH3KOjdFraNnMRh93R9qFSzxXXRGt4Xqs4HlyFYsEgKm56JAgLxrYZoWERPb", - "jUbpqgwZhgVuQH78cBO38nECfTyYzpZmf6qYB0qwWIphaB+veGRMEYGaRZqVaJ2r", - "hyMUIaCO0o59NX4rN6JqRYFVyNgi2Ck0S9ECnl0XW17fqnjxb3hPckosrvEklExP", - "rlMCeWz+ymherzf/1brrMhIfLwdBW+4mzABR7lE/85MtLDZePZ+HsJSaWYBEXLZM", - "hNCNWMMbzTzd+T4T1vUCA4lzhK+jRkS/8RNe/o4ByBxqkcpsI0ux5RyjLkG4KKr5", - "qPo1hKkrB5yMc+0VU+XhYaCyEyX8L8TiSokjVKiZx/wL+0Nql6ftH8cbzNpDjqcV", - "teiYofwG1RxpVxKSj0RkbRVFE0DRs+SApA8DJQFgvAfTtmkeyUNh3VJNWdnff3bT", - "q0DcHP4nOtDacAxk+PyU+R2yU8o6zyDjNtm9Cd5n7sXH01Bihdg8e7agjWS3fl8t", - "snrROvyP8w4Id5ezRMg4K7CoREdUnxsCdAWd3WUiduexSLqICKEMxFdGdilX1O++", - "mRhDO48LxeEm2j/e+Ne3FFZJLa5tLQfy4Qx6f4UgRvhO0M5tO/7EIgBnDbJ9zzA3", - "piwCBfsi34U1wLcAdkhuad+pCP7drnnkqUqdR7l+0ZDSKOHGIX6EpZiCu5ktrK4w", - "j9pmuGB6+HP0wsghjdP/x5QNQRBH6xmbXNAQFWr0hF0h3S5lsORM//teeCcem7Kd", - "hKaH/98hoK11TQFk0eLAMDVhOrdjWef1z1HqSkJabuAmcl7AoNvTNvfat1lZbwv4", - "iqW77iHpjHueekyOpFqpn4niKZL6T8LXOGnXfaTMigWyW2GTH/UhmGZ33X9xWejm", - "oEhdcfH14Xf31bydmMUkimotDeRVTC6vAquuSPWj4nOy7ndleEzypMt9+E9hcXfJ", - "huAUdHx5Isz8K51L9sHs8NyAAZcDeFjQuFqxlEtMPBS5Xg7TJbxCpvRnvEfsJ7x7", - "qt2wy2nKGPFK7XBU6Yok3w/2Bq7/kZ1In3iE/RvRg7y0bqVLw2MUbhqI2zbcIKek", - "qvbBJR5z+2AGJJN3YP7yGKrOWyU78GjtRTmK6ynYIeTSiZND3cu+N8s/bPUA3/Js", - "oVhN/hVz347IjHt012cmtIIb/oS/iG3TwOP3TC6hiqYspEyHH7HGOXH8z2k35lAf", - "qATk+o0TkanQeKqTmFoSUDuEzk9vH55wq3/KQh4c+XJThmYpnUwb/Dkye0abLbeo", - "oDwqgjdOBLLgWUxM4U+z8iW0bxMYjw2AAqUjx9z7k5rkhWBTwsnGlTdNfDaF3xyl", - "r4mrAKDqsRMWRSkqnPulg6aaHjrFiyEOJiSUhT5nOFrrUNSvQovdV3uTmdqpbYsg", - "sJyxVdryAir9GBFKNS5QaoQGXIBXPLDHwxDL6S4nBs3PkfdLvZ5GT3Tj2DE4bVAz", - "l2Pd4bgCgTaj/9ba/R9FDiyvsoGcf6kB98bpzejyiX7n6aRdppR/3hrQ04NhiOq1", - "iWpR4LDeDykCmvOLeW2x8ebQ+fkIWt5AoxOmDLcj+j1Y9lhxdVcAhsT78P5TMfHI" - ], - "aggregate_pubkey": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe" - } - }, - "consensus_update": { - "attested_header": { - "beacon": { - "slot": 144, - "proposer_index": 19, - "parent_root": "noItn1+uyy+W/92z4oVJFK8ac/T80CZo/K5TktxISIk=", - "state_root": "GCjDO3cPt2zkO4y4Y2nxWL2FBaOyCKGlDCtix4hWckY=", - "body_root": "JwAXlihnGZ7+bNu0MN3YhFUnt+qXOuQVc0co9xVghLc=" - }, - "execution": { - "parent_hash": "pcqjSudk77UOc80zBjNwvLwTThxio3CbTpwhz0U/dio=", - "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "JO+3htK/8MDf+cf6sh5XaZJML5L66wuPkQG39BRIT6w=", - "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", - "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "D9NU6oRyAq5uSSJGYR90uSydzEa//IsgDviMj/LY1T4=", - "block_number": 144, - "gas_limit": 30000000, - "timestamp": 1733408667, - "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", - "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAk=", - "block_hash": "2mKLFw/r+90+6w1Vfro7rM3l9W9KVpirwQKnBHEKJj0=", - "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", - "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" - }, - "execution_branch": [ - "RwWU2OtzImFiVHzy6rQYORkIUUYQxmZT99p5sHGdZ5s=", - "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", - "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "X7xxqqZgbddcXEN5PrWwYBu3NFge3O8gqUdEdOxVEJk=" - ] - }, - "next_sync_committee": { - "pubkeys": [ - "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" - ], - "aggregate_pubkey": "llqLwJDEnKUubFmaF2HQO7oeFKuIoYxG9bAdYQ6C8Akk9M0T8jci8TNk15UoEyIl" - }, - "next_sync_committee_branch": [ - "eB34gOFsk1/HX2I1CV9Ih3bIb269+feRs8mJ1K7AxhU=", - "o6OxugUBZG1wksq0DufSy+IR3kOcqgRpwq4nfVXjlok=", - "sNQYjZCbNKTXGSRAsighUnkD4O1GMfQHv71WepfOjnI=", - "9rDXZu1xo5PfVwNIwg/2nXHxWn61l9dIXHq6t8H2IZI=", - "qdXDOUrODU9WLHckIdaWx8tgc86RfKzD8YuBxRnpxCM=" - ], - "finalized_header": { - "beacon": { - "slot": 128, - "proposer_index": 61, - "parent_root": "ICVxBTiY1i6ytSfAkr8BZgsslIZEAfBCWhrhFhrKJZM=", - "state_root": "trqHeiATdBKzeFROSpnnW+Ggf+LaWus2qMG3Gjua/uU=", - "body_root": "qZRSMS6Ek7wo5ASFM+AM1kNIc3V7jNWS7WIZhViOTLA=" - }, - "execution": { - "parent_hash": "Z05CPblHpIQuL1NmX2MmoRv7IvTIQ+lIYxJ75L2fwVk=", - "fee_recipient": "iUNUUXeAbtF7nyPwoh7llI7Kp3Y=", - "state_root": "djI7S1KUcpaVjyTbeHvZzH1tSvM2Rv7Sma/pgkeh16A=", - "receipts_root": "VugfFxvMVab/g0XmksD4bltI4BuZbK3AAWIvteNjtCE=", - "logs_bloom": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", - "prev_randao": "m9MEiShQfTIEaaj6bhbamf/cyQqTem2y1UhITTGCieY=", - "block_number": 128, - "gas_limit": 30000000, - "timestamp": 1733408571, - "extra_data": "2IMBDgaEZ2V0aIhnbzEuMjIuNIVsaW51eA==", - "base_fee_per_gas": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA=", - "block_hash": "PvaxoPdifciaZTdRx9dyRndGzZJypSlZOfSwHumolDg=", - "transactions_root": "f/4kHqYBh/2wGHv6It410fm+16sGHZQB/UfjSlT77eE=", - "withdrawals_root": "KLoYNKOntldGDOefo6HZCauIKP1VdlnU0FVKm9vA7DA=" - }, - "execution_branch": [ - "LQJDppP4UKvZ/LUNKw7vNci2hdPYN7za2MdgnvGh+ns=", - "bG3WNlZjnRU6LoapyrKR56JulXrWNf7IctKDbpI0DCM=", - "21YRTgD91MH4XIkr81rJqJKJquyx69CpbN5ganSLXXE=", - "9mIbi+txOKdH2pZbRQfJGheSuT93Lbl4leyLBBRtSek=" - ] - }, - "finality_branch": [ - "EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "X28CrykhgpLSGmm2SnlKfAhzs+D1RhGXKGNwboy983E=", - "lEBRQFpyoqod+eoAePD3fIIy3eZ0DVv3dIFrnLwnCWw=", - "sNQYjZCbNKTXGSRAsighUnkD4O1GMfQHv71WepfOjnI=", - "9rDXZu1xo5PfVwNIwg/2nXHxWn61l9dIXHq6t8H2IZI=", - "qdXDOUrODU9WLHckIdaWx8tgc86RfKzD8YuBxRnpxCM=" - ], - "sync_aggregate": { - "sync_committee_bits": "/////w==", - "sync_committee_signature": "jcaUvHR7oJhe+pjOWNZ1B2ZYQNgzjf+By+FZl+RuEB+D6G8Fw7r10+K/DobC/OTKDmZp8qnJlwR65cnN2qZqh0iQWgMMuXWxqq9q18Jt/NgWh9B2slJbD0+mUORFv7vY" - }, - "signature_slot": 145 - }, - "account_update": { - "account_proof": { - "storage_root": "LNIdwMCrMXD0A6mAkgxkZ5kST7VB89Te2PorkMrErVg=", - "proof": [ - "+QIRoEqvnXgQSE6uML1+jas4PVF1tiIo22EFAKi/9NHndXGooOVBk+ODbAmW/oiCKL8aKzwF+qyHbl2o5Qb/ozbCM6ojoKsZigoIFy1+rpw72dqLstEaNIs0OHAvt/LB8bVw60dAoDvNV7PEQbn+RElhWDWK0yBVFWSUAPie+UUbicsNLvdHoN00IIObq67nYefqo4rV9Zaxqbhxbn6bkmGUmpZKWn1hoO6mQ3QFKsRglXu8NKBxy4sl3N9E2WeFpVs0JCkUyD+foLzvA+KpdGPjRsdJlB8ejvxTiOShmR0HnpitUrrUAhK5oIPGl55GPAKBj/6truuKvJ8vUedn+5FRp/yJmJ60C1esoMvNwdImpUDFDLHmFeevmfFx1DZbRXNJQOItR+xKojoUoJmNmYDMJMjLe1v5yT9zr59e+vNHDKFWpagulbksSpEPoPy/d7p1MmcO5NywlRj54vQBdBjeJHDNd/HzriIPCVjYoBeWYXQn5n7RDN+KcrAmiacAunHrkxhqGxIMmtCw5W6uoK0LuGtHGGwEIj6FqcM90ch91uXBf3U/T9ClZ3LYp4OZoGm3Si0sJG+LQGhv/VfDzx+p7jJASdBUX8seZ84WVsEGoHzaprc7+/5FwSGnCUk3uJukyupPXR7ue6s5TcnN9GlUoOHGC5EFoWlZIcDp/qj04TjxsiFJhAF6o7uBH1Ul9ge/gA==", - "+QGRoAW/OheSLPSH4l2/4k9e4Gqcu6AIFeeUxVgmfV348TJggKC1Dd8AKIzIwTNSUbBLymVjI1RWNN3SIw8XhNPyF2m7U6ChpXt8UzlXsptRWcjDUno50TVNuwuqpI+nBaNUdlL5pKCZjxpgZi8W4bfVYmdSvqX/emcnaVEpIdkA69F1jFBLJ6COn69uca0GKG4WS94nlNllxcmAAkzIo6i5lCYJr1Z+v6Cz7/jDJ2RT50/6Uz9iqlGmT/ptcN6EMvkYguUn6yvwRICgW4c0sG9Yfjxp/QRldX24lk/lnljNycWZQzRf6OGXyRagDWgrEDC2jbKjJ5P2IvLvPfFKiv+ZUvlBO9vNnN1bLHmgmeYaf7+xnVndxQtXJssAH72qha3wIK27GL+ui87ytt2AgKCiJCLqmRojruVuNbkH5tFGPMmaWMr+5hDuVtL2SUram6AqT1h3lSJKnVY1mgcfNXPZOAyq3Um8obY6G6fiDZ5mZKAYkkbHr4+u0sfT+H3ItaUdeH9vitZZrG+RcilLPf/tTIA=", - "+GmgIBIndIQYPU8oMKp/nGRX2TS5REap2KbZyPAYf55t07S4RvhEAYCgLNIdwMCrMXD0A6mAkgxkZ5kST7VB89Te2PorkMrErVigsq72HnOsOpO18+6lYsWneIteOOTR+++IIQbOr8OvLdc=" - ] - } - } -} \ No newline at end of file diff --git a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json b/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json deleted file mode 100644 index 612e6944..000000000 --- a/packages/ethereum-test-utils/src/fixtures/TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_6_commitment_proof.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "path": "MDctdGVuZGVybWludC0wAQAAAAAAAAAB", - "storage_proof": { - "key": "dddBHLAdqtFncTtam3IZZw8OUAZTy7zUXP4b/gQiJFk=", - "value": "gGNSwJ98jIunXa1eK/lC2dnaArcna4V5tYLEW99U3aY=", - "proof": [ - "+LGAgKA6BiC+LtHLi1QUcOvXp/4Ey0uNco6kzwhuY+dIrcntAKAfa/LQO28G+qbqc0zEwwvN1KA/O6AU1SfXJG3qwHyYZICAoFexbZo7uy0Qa00bEtyjUE9hiZx8ZgsDaEhRFCbtNC3WgICAgICg2ewz2fYL/2sT+bFjbDoKx8vxyGbecuNvIGkP63MwSQugKk2h+7aafsHLGXnx0xO7YYjriGJC1JcwMGXs13gsanKAgIA=", - "+EOgPTw7zwMABq/qKmd6b/W/P38RHodGHIhIzwYqV1bRqIihoIBjUsCffIyLp12tXiv5QtnZ2gK3J2uFebWCxFvfVN2m" - ] - }, - "proof_height": { - "revision_number": 0, - "revision_height": 144 - }, - "client_state": { - "chain_id": "3151908", - "genesis_validators_root": "1h6khP66z65SmNUqK1gfPjBaUfMRKpJBuWjczwGfexE=", - "genesis_time": 1733407803, - "fork_parameters": { - "genesis_fork_version": "EAAAOA==", - "altair": { - "version": "IAAAOA==" - }, - "bellatrix": { - "version": "MAAAOA==" - }, - "capella": { - "version": "QAAAOA==" - }, - "deneb": { - "version": "UAAAOA==" - } - }, - "seconds_per_slot": 6, - "slots_per_epoch": 8, - "epochs_per_sync_committee_period": 8, - "latest_slot": 144, - "frozen_height": { - "revision_number": 0, - "revision_height": 0 - }, - "ibc_commitment_slot": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE=", - "ibc_contract_address": "Pus1Pj9GbHF70jfiXjd1yeYfhpY=", - "min_sync_committee_participants": 32 - }, - "consensus_state": { - "slot": 144, - "state_root": "JO+3htK/8MDf+cf6sh5XaZJML5L66wuPkQG39BRIT6w=", - "storage_root": "LNIdwMCrMXD0A6mAkgxkZ5kST7VB89Te2PorkMrErVg=", - "timestamp": 1733408667000000000, - "current_sync_committee": "rKar8i9Bg0tfMHObnk6NGsab3Taf0NFQkQ+JVFVpQ48HibSDWcsqKmbOD+qQ5LMe", - "next_sync_committee": "llqLwJDEnKUubFmaF2HQO7oeFKuIoYxG9bAdYQ6C8Akk9M0T8jci8TNk15UoEyIl" - } -} diff --git a/programs/cw-ics08-wasm-eth/src/contract.rs b/programs/cw-ics08-wasm-eth/src/contract.rs index 2152735c..34d47310 100644 --- a/programs/cw-ics08-wasm-eth/src/contract.rs +++ b/programs/cw-ics08-wasm-eth/src/contract.rs @@ -229,16 +229,8 @@ mod tests { }, Binary, OwnedDeps, SystemResult, }; - use ethereum_light_client::{ - client_state::ClientState as EthClientState, - consensus_state::ConsensusState as EthConsensusState, - types::{ - bls::{BlsPublicKey, BlsSignature}, - storage_proof::StorageProof, - }, - }; + use ethereum_light_client::types::bls::{BlsPublicKey, BlsSignature}; use ethereum_test_utils::bls_verifier::{aggreagate, fast_aggregate_verify}; - use serde::{Deserialize, Serialize}; use crate::custom_query::EthereumCustomQuery; @@ -378,14 +370,12 @@ mod tests { testing::{message_info, mock_env}, Binary, }; - use ethereum_test_utils::fixtures; + use ethereum_test_utils::fixtures::{self, StepFixture}; use crate::{ - contract::{ - instantiate, sudo, - tests::{mk_deps, CommitmentProofFixture}, - }, + contract::{instantiate, sudo, tests::mk_deps}, msg::{Height, MerklePath, SudoMsg, UpdateStateMsg, VerifyMembershipMsg}, + test::fixture_types::CommitmentProof, }; #[test] @@ -394,9 +384,10 @@ mod tests { let creator = deps.api.addr_make("creator"); let info = message_info(&creator, &coins(1, "uatom")); - let commitment_proof_fixture: CommitmentProofFixture = fixtures::load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_4_commitment_proof", - ); + let fixture: StepFixture = + fixtures::load("TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16"); + + let commitment_proof_fixture: CommitmentProof = fixture.get_data_at_step(2); let client_state = commitment_proof_fixture.client_state; let client_state_bz: Vec = serde_json::to_vec(&client_state).unwrap(); @@ -450,11 +441,7 @@ mod tests { testing::{message_info, mock_env}, Binary, Timestamp, }; - use ethereum_light_client::{ - client_state::ClientState as EthClientState, - consensus_state::ConsensusState as EthConsensusState, types::light_client::Header, - }; - use ethereum_test_utils::fixtures::load; + use ethereum_test_utils::fixtures::{self, StepFixture}; use crate::{ contract::{instantiate, query, tests::mk_deps}, @@ -463,6 +450,7 @@ mod tests { StatusResult, TimestampAtHeightMsg, TimestampAtHeightResult, VerifyClientMessageMsg, }, + test::fixture_types::{InitialState, UpdateClient}, }; #[test] @@ -471,13 +459,14 @@ mod tests { let creator = deps.api.addr_make("creator"); let info = message_info(&creator, &coins(1, "uatom")); - let client_state: EthClientState = load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_1_initial_client_state", - ); + let fixture: StepFixture = + fixtures::load("TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16"); - let consensus_state: EthConsensusState = load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_2_initial_consensus_state", - ); + let initial_state: InitialState = fixture.get_data_at_step(0); + + let client_state = initial_state.client_state; + + let consensus_state = initial_state.consensus_state; let client_state_bz: Vec = serde_json::to_vec(&client_state).unwrap(); let consensus_state_bz: Vec = serde_json::to_vec(&consensus_state).unwrap(); @@ -490,9 +479,8 @@ mod tests { instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - let header: Header = load( - "TestICS20TransferNativeCosmosCoinsToEthereumAndBack_Groth16_3_update_header_0", - ); + let update_client: UpdateClient = fixture.get_data_at_step(1); + let header = update_client.updates[0].clone(); let header_bz: Vec = serde_json::to_vec(&header).unwrap(); let mut env = mock_env(); @@ -555,16 +543,6 @@ mod tests { } } - #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] - pub struct CommitmentProofFixture { - #[serde(with = "ethereum_utils::base64")] - pub path: Vec, - pub storage_proof: StorageProof, - pub proof_height: ethereum_light_client::types::height::Height, - pub client_state: EthClientState, - pub consensus_state: EthConsensusState, - } - // TODO: Find a way to reuse the test handling code that already exists in the // ethereum-light-client package pub fn custom_query_handler(query: &EthereumCustomQuery) -> MockQuerierCustomHandlerResult { diff --git a/programs/cw-ics08-wasm-eth/src/lib.rs b/programs/cw-ics08-wasm-eth/src/lib.rs index 025f28ab..b6004c71 100644 --- a/programs/cw-ics08-wasm-eth/src/lib.rs +++ b/programs/cw-ics08-wasm-eth/src/lib.rs @@ -1,5 +1,8 @@ pub mod contract; pub mod custom_query; -mod error; +pub mod error; pub mod msg; pub mod state; + +#[cfg(test)] +mod test; diff --git a/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs b/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs new file mode 100644 index 000000000..fddee001 --- /dev/null +++ b/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs @@ -0,0 +1,38 @@ +use ethereum_light_client::{ + client_state::ClientState, + consensus_state::ConsensusState, + types::{height::Height, light_client::Header, storage_proof::StorageProof}, +}; +use serde::{Deserialize, Serialize}; + +// TODO: Remove this file once these types are in a separate package #143 + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub enum DataType { + InitialState(Box), + CommitmentProof(Box), + UpdateClient(Box), +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct InitialState { + pub client_state: ClientState, + pub consensus_state: ConsensusState, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct CommitmentProof { + #[serde(with = "ethereum_utils::base64")] + pub path: Vec, + pub storage_proof: StorageProof, + pub proof_height: Height, + pub client_state: ClientState, + pub consensus_state: ConsensusState, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +pub struct UpdateClient { + pub client_state: ClientState, + pub consensus_state: ConsensusState, + pub updates: Vec
, +} diff --git a/programs/cw-ics08-wasm-eth/src/test/mod.rs b/programs/cw-ics08-wasm-eth/src/test/mod.rs new file mode 100644 index 000000000..11036963 --- /dev/null +++ b/programs/cw-ics08-wasm-eth/src/test/mod.rs @@ -0,0 +1 @@ +pub mod fixture_types; From 9cae4fa15a07da4d5f130644b8b580aa82d9af0a Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 12:05:45 +0800 Subject: [PATCH 40/49] docs: updated readmes --- README.md | 9 ++++++--- programs/cw-ics08-wasm-eth/README.md | 11 +++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5a379278..61e310f8 100644 --- a/README.md +++ b/README.md @@ -225,11 +225,14 @@ Note: These gas benchmarks are with Groth16. ``` ## Etheruem Light Client -> ⚠ The Ethereum Light Client is currently under heavy development, is expected to change and is not ready for production use. + +> [!CAUTION] +> ⚠ The Ethereum Light Client is currently under heavy development, and is not ready for use. This repository contains an Ethereum light client which is implemented as two separate layers: -* A CosmWasm contract that supports the 08-wasm light client interface in `programs/cw-ics08-wasm-eth` -* A stateless light client verification package in `packages/ethereum-light-client` + +- A CosmWasm contract that supports the 08-wasm light client interface in `programs/cw-ics08-wasm-eth` +- A stateless light client verification package in `packages/ethereum-light-client` ## License diff --git a/programs/cw-ics08-wasm-eth/README.md b/programs/cw-ics08-wasm-eth/README.md index f51085d0..c9f82020 100644 --- a/programs/cw-ics08-wasm-eth/README.md +++ b/programs/cw-ics08-wasm-eth/README.md @@ -1,8 +1,11 @@ -# cw-ics08-wasm-eth -> ⚠ The Ethereum Light Client is currently under heavy development, is expected to change and is not ready for production use. +# CosmWasm ICS08 Wasm Ethereum Light Client -This is the CosmWasm implementation that can be used with ibc-go's 08-wasm light client wrapper. +> [!CAUTION] +> ⚠ The Ethereum Light Client is currently under heavy development, and is not ready for use. + +This is the CosmWasm implementation that can be used with ibc-go's `08-wasm` light client wrapper. It handles the client and consensus state, and calls into `packages/ethereum-light-client` for all the light client related logic. ## Acknowledgements -This work is based on the ethereum light client created by [Union](http://github.com/unionlabs/union/) + +This work is based on the ethereum light client created by [Union](http://github.com/unionlabs/union/). From a56859d69582f0fb898ca012fd60cee7835357c6 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 12:46:43 +0800 Subject: [PATCH 41/49] docs: fully linted and documented wasm client --- programs/cw-ics08-wasm-eth/README.md | 4 +- programs/cw-ics08-wasm-eth/src/contract.rs | 259 ++++++++++-------- .../cw-ics08-wasm-eth/src/custom_query.rs | 21 +- programs/cw-ics08-wasm-eth/src/error.rs | 9 + programs/cw-ics08-wasm-eth/src/lib.rs | 8 +- programs/cw-ics08-wasm-eth/src/msg.rs | 70 ++++- programs/cw-ics08-wasm-eth/src/state.rs | 33 ++- 7 files changed, 272 insertions(+), 132 deletions(-) diff --git a/programs/cw-ics08-wasm-eth/README.md b/programs/cw-ics08-wasm-eth/README.md index c9f82020..afbd3326 100644 --- a/programs/cw-ics08-wasm-eth/README.md +++ b/programs/cw-ics08-wasm-eth/README.md @@ -1,9 +1,9 @@ -# CosmWasm ICS08 Wasm Ethereum Light Client +# `CosmWasm` ICS08 Wasm Ethereum Light Client > [!CAUTION] > ⚠ The Ethereum Light Client is currently under heavy development, and is not ready for use. -This is the CosmWasm implementation that can be used with ibc-go's `08-wasm` light client wrapper. +This is the `CosmWasm` implementation that can be used with ibc-go's `08-wasm` light client wrapper. It handles the client and consensus state, and calls into `packages/ethereum-light-client` for all the light client related logic. ## Acknowledgements diff --git a/programs/cw-ics08-wasm-eth/src/contract.rs b/programs/cw-ics08-wasm-eth/src/contract.rs index 34d47310..cf10489b 100644 --- a/programs/cw-ics08-wasm-eth/src/contract.rs +++ b/programs/cw-ics08-wasm-eth/src/contract.rs @@ -1,3 +1,5 @@ +//! This module handles the execution logic of the contract. + use std::convert::Into; use cosmwasm_std::{ @@ -18,18 +20,18 @@ use ibc_proto::{ }; use prost::Message; -use crate::custom_query::{BlsVerifier, EthereumCustomQuery}; -use crate::error::ContractError; -use crate::msg::{ - CheckForMisbehaviourResult, ExecuteMsg, Height, InstantiateMsg, QueryMsg, StatusResult, - SudoMsg, TimestampAtHeightResult, UpdateStateResult, VerifyClientMessageMsg, - VerifyMembershipMsg, VerifyNonMembershipMsg, -}; +use crate::custom_query::EthereumCustomQuery; +use crate::msg::{ExecuteMsg, Height, InstantiateMsg, QueryMsg, SudoMsg}; use crate::state::{ consensus_db_key, get_eth_client_state, get_eth_consensus_state, HOST_CLIENT_STATE_KEY, }; +use crate::ContractError; +/// The instantiate entry point for the CosmWasm contract. +/// # Errors +/// Will return an error if the client state or consensus state cannot be deserialized. #[entry_point] +#[allow(clippy::needless_pass_by_value)] pub fn instantiate( deps: DepsMut, _env: Env, @@ -47,7 +49,7 @@ pub fn instantiate( revision_height: client_state.latest_slot, }), }; - let wasm_client_state_any = Any::from_msg(&wasm_client_state).unwrap(); + let wasm_client_state_any = Any::from_msg(&wasm_client_state)?; deps.storage.set( HOST_CLIENT_STATE_KEY.as_bytes(), wasm_client_state_any.encode_to_vec().as_slice(), @@ -59,7 +61,7 @@ pub fn instantiate( let wasm_consensus_state = WasmConsensusState { data: consensus_state_bz, }; - let wasm_consensus_state_any = Any::from_msg(&wasm_consensus_state).unwrap(); + let wasm_consensus_state_any = Any::from_msg(&wasm_consensus_state)?; let height = Height { revision_number: 0, revision_height: consensus_state.slot, @@ -72,7 +74,12 @@ pub fn instantiate( Ok(Response::default()) } +/// The sudo entry point for the CosmWasm contract. +/// It routes the message to the appropriate handler. +/// # Errors +/// Will return an error if the handler returns an error. #[entry_point] +#[allow(clippy::needless_pass_by_value)] pub fn sudo( deps: DepsMut, _env: Env, @@ -80,12 +87,12 @@ pub fn sudo( ) -> Result { let result = match msg { SudoMsg::VerifyMembership(verify_membership_msg) => { - verify_membership(deps.as_ref(), verify_membership_msg)? + sudo::verify_membership(deps.as_ref(), verify_membership_msg)? } SudoMsg::VerifyNonMembership(verify_non_membership_msg) => { - verify_non_membership(deps.as_ref(), verify_non_membership_msg)? + sudo::verify_non_membership(deps.as_ref(), verify_non_membership_msg)? } - SudoMsg::UpdateState(_) => update_state()?, + SudoMsg::UpdateState(_) => sudo::update_state()?, SudoMsg::UpdateStateOnMisbehaviour(_) => todo!(), SudoMsg::VerifyUpgradeAndUpdateState(_) => todo!(), SudoMsg::MigrateClientStore(_) => todo!(), @@ -94,59 +101,72 @@ pub fn sudo( Ok(Response::default().set_data(result)) } -pub fn verify_membership( - deps: Deps, - verify_membership_msg: VerifyMembershipMsg, -) -> Result { - let eth_client_state = get_eth_client_state(deps); - let eth_consensus_state = get_eth_consensus_state(deps, &verify_membership_msg.height); - - ethereum_light_client::membership::verify_membership( - eth_consensus_state, - eth_client_state, - verify_membership_msg.proof.into(), - verify_membership_msg - .merkle_path - .key_path - .into_iter() - .map(Into::into) - .collect(), - Some(verify_membership_msg.value.into()), - ) - .map_err(ContractError::VerifyMembershipFailed)?; - - Ok(to_json_binary(&Ok::<(), ()>(()))?) -} +mod sudo { + use crate::msg::{UpdateStateResult, VerifyMembershipMsg, VerifyNonMembershipMsg}; -pub fn verify_non_membership( - deps: Deps, - verify_non_membership_msg: VerifyNonMembershipMsg, -) -> Result { - let eth_client_state = get_eth_client_state(deps); - let eth_consensus_state = get_eth_consensus_state(deps, &verify_non_membership_msg.height); - - ethereum_light_client::membership::verify_membership( - eth_consensus_state, - eth_client_state, - verify_non_membership_msg.proof.into(), - verify_non_membership_msg - .merkle_path - .key_path - .into_iter() - .map(Into::into) - .collect(), - None, - ) - .map_err(ContractError::VerifyNonMembershipFailed)?; - - Ok(to_json_binary(&Ok::<(), ()>(()))?) -} + use super::{ + get_eth_client_state, get_eth_consensus_state, to_json_binary, Binary, ContractError, Deps, + EthereumCustomQuery, + }; + + pub fn verify_membership( + deps: Deps, + verify_membership_msg: VerifyMembershipMsg, + ) -> Result { + let eth_client_state = get_eth_client_state(deps.storage); + let eth_consensus_state = + get_eth_consensus_state(deps.storage, &verify_membership_msg.height); + + ethereum_light_client::membership::verify_membership( + eth_consensus_state, + eth_client_state, + verify_membership_msg.proof.into(), + verify_membership_msg + .merkle_path + .key_path + .into_iter() + .map(Into::into) + .collect(), + Some(verify_membership_msg.value.into()), + ) + .map_err(ContractError::VerifyMembershipFailed)?; + + Ok(to_json_binary(&Ok::<(), ()>(()))?) + } + + pub fn verify_non_membership( + deps: Deps, + verify_non_membership_msg: VerifyNonMembershipMsg, + ) -> Result { + let eth_client_state = get_eth_client_state(deps.storage); + let eth_consensus_state = + get_eth_consensus_state(deps.storage, &verify_non_membership_msg.height); + + ethereum_light_client::membership::verify_membership( + eth_consensus_state, + eth_client_state, + verify_non_membership_msg.proof.into(), + verify_non_membership_msg + .merkle_path + .key_path + .into_iter() + .map(Into::into) + .collect(), + None, + ) + .map_err(ContractError::VerifyNonMembershipFailed)?; + + Ok(to_json_binary(&Ok::<(), ()>(()))?) + } -pub fn update_state() -> Result { - Ok(to_json_binary(&UpdateStateResult { heights: vec![] })?) + pub fn update_state() -> Result { + Ok(to_json_binary(&UpdateStateResult { heights: vec![] })?) + } } +/// Execute entry point is not used in this contract. #[entry_point] +#[allow(clippy::needless_pass_by_value, clippy::missing_errors_doc)] pub fn execute( _deps: DepsMut, _env: Env, @@ -156,6 +176,10 @@ pub fn execute( unimplemented!() } +/// The query entry point for the CosmWasm contract. +/// It routes the message to the appropriate handler. +/// # Errors +/// Will return an error if the handler returns an error. #[entry_point] pub fn query( deps: Deps, @@ -164,58 +188,75 @@ pub fn query( ) -> Result { match msg { QueryMsg::VerifyClientMessage(verify_client_message_msg) => { - verify_client_message(deps, env, verify_client_message_msg) + query::verify_client_message(deps, env, verify_client_message_msg) } - QueryMsg::CheckForMisbehaviour(_) => check_for_misbehaviour(), - QueryMsg::TimestampAtHeight(_) => timestamp_at_height(env), - QueryMsg::Status(_) => status(), + QueryMsg::CheckForMisbehaviour(_) => query::check_for_misbehaviour(), + QueryMsg::TimestampAtHeight(_) => query::timestamp_at_height(env), + QueryMsg::Status(_) => query::status(), } } -pub fn verify_client_message( - deps: Deps, - env: Env, - verify_client_message_msg: VerifyClientMessageMsg, -) -> Result { - let eth_client_state = get_eth_client_state(deps); - let eth_consensus_state = get_eth_consensus_state( - deps, - &Height { - revision_number: 0, - revision_height: eth_client_state.latest_slot, +mod query { + use crate::{ + custom_query::BlsVerifier, + msg::{ + CheckForMisbehaviourResult, Height, StatusResult, TimestampAtHeightResult, + VerifyClientMessageMsg, }, - ); - let header = serde_json::from_slice(&verify_client_message_msg.client_message) - .map_err(ContractError::DeserializeClientStateFailed)?; - let bls_verifier = BlsVerifier { deps }; - - ethereum_light_client::verify::verify_header( - ð_consensus_state, - ð_client_state, - env.block.time.seconds(), - &header, - bls_verifier, - ) - .map_err(ContractError::VerifyClientMessageFailed)?; - - Ok(to_json_binary(&Ok::<(), ()>(()))?) -} + }; -pub fn check_for_misbehaviour() -> Result { - Ok(to_json_binary(&CheckForMisbehaviourResult { - found_misbehaviour: false, - })?) -} + use super::{ + get_eth_client_state, get_eth_consensus_state, to_json_binary, Binary, ContractError, Deps, + Env, EthereumCustomQuery, + }; -pub fn timestamp_at_height(env: Env) -> Result { - let now = env.block.time.seconds(); - Ok(to_json_binary(&TimestampAtHeightResult { timestamp: now })?) -} + #[allow(clippy::needless_pass_by_value)] + pub fn verify_client_message( + deps: Deps, + env: Env, + verify_client_message_msg: VerifyClientMessageMsg, + ) -> Result { + let eth_client_state = get_eth_client_state(deps.storage); + let eth_consensus_state = get_eth_consensus_state( + deps.storage, + &Height { + revision_number: 0, + revision_height: eth_client_state.latest_slot, + }, + ); + let header = serde_json::from_slice(&verify_client_message_msg.client_message) + .map_err(ContractError::DeserializeClientStateFailed)?; + let bls_verifier = BlsVerifier { deps }; + + ethereum_light_client::verify::verify_header( + ð_consensus_state, + ð_client_state, + env.block.time.seconds(), + &header, + bls_verifier, + ) + .map_err(ContractError::VerifyClientMessageFailed)?; + + Ok(to_json_binary(&Ok::<(), ()>(()))?) + } + + pub fn check_for_misbehaviour() -> Result { + Ok(to_json_binary(&CheckForMisbehaviourResult { + found_misbehaviour: false, + })?) + } + + #[allow(clippy::needless_pass_by_value)] + pub fn timestamp_at_height(env: Env) -> Result { + let now = env.block.time.seconds(); + Ok(to_json_binary(&TimestampAtHeightResult { timestamp: now })?) + } -pub fn status() -> Result { - Ok(to_json_binary(&StatusResult { - status: "Active".to_string(), - })?) + pub fn status() -> Result { + Ok(to_json_binary(&StatusResult { + status: "Active".to_string(), + })?) + } } #[cfg(test)] @@ -235,7 +276,7 @@ mod tests { use crate::custom_query::EthereumCustomQuery; mod instantiate_tests { - use alloy_primitives::{aliases::B32, FixedBytes, B256, U256}; + use alloy_primitives::{aliases::B32, Address, FixedBytes, B256, U256}; use cosmwasm_std::{ coins, testing::{message_info, mock_env}, @@ -296,7 +337,7 @@ mod tests { epochs_per_sync_committee_period: 0, latest_slot: 42, ibc_commitment_slot: U256::from(0), - ibc_contract_address: Default::default(), + ibc_contract_address: Address::default(), frozen_height: ethereum_light_client::types::height::Height::default(), }; let client_state_bz: Vec = serde_json::to_vec(&client_state).unwrap(); @@ -314,7 +355,7 @@ mod tests { let msg = InstantiateMsg { client_state: client_state_bz.into(), consensus_state: consensus_state_bz.into(), - checksum: "also does not matter yet".as_bytes().into(), + checksum: b"also does not matter yet".into(), }; let res = instantiate(deps.as_mut(), mock_env(), info, msg.clone()).unwrap(); @@ -397,7 +438,7 @@ mod tests { let msg = crate::msg::InstantiateMsg { client_state: Binary::from(client_state_bz), consensus_state: Binary::from(consensus_state_bz), - checksum: "checksum".as_bytes().into(), // TODO: Real checksum important? + checksum: b"checksum".into(), // TODO: Real checksum important? }; instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); @@ -474,7 +515,7 @@ mod tests { let msg = crate::msg::InstantiateMsg { client_state: Binary::from(client_state_bz), consensus_state: Binary::from(consensus_state_bz), - checksum: "checksum".as_bytes().into(), // TODO: Real checksum important? + checksum: b"checksum".into(), // TODO: Real checksum important? }; instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); diff --git a/programs/cw-ics08-wasm-eth/src/custom_query.rs b/programs/cw-ics08-wasm-eth/src/custom_query.rs index 218ea1ec..d094856a 100644 --- a/programs/cw-ics08-wasm-eth/src/custom_query.rs +++ b/programs/cw-ics08-wasm-eth/src/custom_query.rs @@ -1,37 +1,56 @@ +//! This module contains the custom `CosmWasm` query for the Ethereum light client + use alloy_primitives::B256; use cosmwasm_std::{Binary, CustomQuery, Deps, QueryRequest}; use ethereum_light_client::types::bls::{BlsPublicKey, BlsSignature, BlsVerify}; use ethereum_utils::{ensure, hex::to_hex}; use thiserror::Error; +/// The custom query for the Ethereum light client +/// This is used to verify BLS signatures in `CosmosSDK` #[derive(serde::Serialize, serde::Deserialize, Clone)] #[serde(rename_all = "snake_case")] +#[allow(clippy::module_name_repetitions)] pub enum EthereumCustomQuery { + /// Verify a BLS signature AggregateVerify { + /// The public keys to verify the signature public_keys: Vec, + /// The message to verify message: Binary, + /// The signature to verify signature: Binary, }, + /// Aggregate public keys Aggregate { + /// The public keys to aggregate public_keys: Vec, }, } impl CustomQuery for EthereumCustomQuery {} +/// The BLS verifier via [`EthereumCustomQuery`] pub struct BlsVerifier<'a> { + /// The `CosmWasm` deps used to query the custom query pub deps: Deps<'a, EthereumCustomQuery>, } -#[derive(Debug, PartialEq, thiserror::Error, Clone)] +/// The error type for invalid signatures +#[derive(Debug, PartialEq, Eq, thiserror::Error, Clone)] #[error("signature cannot be verified (public_keys: {public_keys:?}, msg: {msg}, signature: {signature})", msg = to_hex(.msg))] pub struct InvalidSignatureErr { + /// The public keys used to verify the signature pub public_keys: Vec, + /// The message that was signed pub msg: B256, + /// The signature that was verified pub signature: BlsSignature, } +/// The error type for the BLS verifier #[derive(Error, Debug)] +#[allow(missing_docs)] pub enum BlsVerifierError { #[error("fast aggregate verify error: {0}")] FastAggregateVerify(String), diff --git a/programs/cw-ics08-wasm-eth/src/error.rs b/programs/cw-ics08-wasm-eth/src/error.rs index 0f7715b6..c23247ed 100644 --- a/programs/cw-ics08-wasm-eth/src/error.rs +++ b/programs/cw-ics08-wasm-eth/src/error.rs @@ -1,8 +1,11 @@ +//! Defines the [`ContractError`] type. + use cosmwasm_std::StdError; use ethereum_light_client::error::EthereumIBCError; use thiserror::Error; #[derive(Error, Debug)] +#[allow(missing_docs, clippy::module_name_repetitions)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), @@ -24,4 +27,10 @@ pub enum ContractError { #[error("Verify client message failed: {0}")] VerifyClientMessageFailed(#[source] EthereumIBCError), + + #[error("prost encoding error: {0}")] + ProstEncodeError(#[from] prost::EncodeError), + + #[error("prost decoding error: {0}")] + ProstDecodeError(#[from] prost::DecodeError), } diff --git a/programs/cw-ics08-wasm-eth/src/lib.rs b/programs/cw-ics08-wasm-eth/src/lib.rs index b6004c71..2a1714eb 100644 --- a/programs/cw-ics08-wasm-eth/src/lib.rs +++ b/programs/cw-ics08-wasm-eth/src/lib.rs @@ -1,8 +1,14 @@ +#![doc = include_str!("../README.md")] +#![deny(clippy::nursery, clippy::pedantic, warnings, missing_docs)] + pub mod contract; pub mod custom_query; -pub mod error; +mod error; +#[allow(clippy::module_name_repetitions)] pub mod msg; pub mod state; +pub use error::ContractError; + #[cfg(test)] mod test; diff --git a/programs/cw-ics08-wasm-eth/src/msg.rs b/programs/cw-ics08-wasm-eth/src/msg.rs index 4e68e810..ac4009f4 100644 --- a/programs/cw-ics08-wasm-eth/src/msg.rs +++ b/programs/cw-ics08-wasm-eth/src/msg.rs @@ -1,135 +1,193 @@ +//! The messages that are passed between the contract and the ibc-go module + use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Binary; +/// The message to instantiate the contract #[cw_serde] pub struct InstantiateMsg { + /// The client state pub client_state: Binary, + /// The consensus state pub consensus_state: Binary, + /// The checksum of this wasm code pub checksum: Binary, } +/// The unused message to execute the contract #[cw_serde] pub enum ExecuteMsg {} +/// The sudo messages called by `ibc-go` #[cw_serde] pub enum SudoMsg { + /// The message to verify membership VerifyMembership(VerifyMembershipMsg), - + /// The message to verify non-membership VerifyNonMembership(VerifyNonMembershipMsg), - UpdateState(UpdateStateMsg), + /// The message to update the client state + UpdateState(UpdateStateMsg), + /// The message to update the client state on misbehaviour UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsg), - + /// The message to verify the upgrade and update the state VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsg), - + /// The message to migrate the client store MigrateClientStore(MigrateClientStoreMsg), } +/// Verify membership message #[cw_serde] pub struct VerifyMembershipMsg { + /// The proof height pub height: Height, + /// The delay time period (unused) pub delay_time_period: u64, + /// The delay block period (unused) pub delay_block_period: u64, + /// The proof bytes pub proof: Binary, + /// The path to the value pub merkle_path: MerklePath, + /// The value to verify pub value: Binary, } +/// Verify non-membership message #[cw_serde] pub struct VerifyNonMembershipMsg { + /// The proof height pub height: Height, + /// The delay time period (unused) pub delay_time_period: u64, + /// The delay block period (unused) pub delay_block_period: u64, + /// The proof bytes pub proof: Binary, + /// The path to the empty value pub merkle_path: MerklePath, } +/// Update state message #[cw_serde] pub struct UpdateStateMsg { + /// The client message pub client_message: Binary, } +/// Update state on misbehaviour message #[cw_serde] pub struct UpdateStateOnMisbehaviourMsg { + /// The client message pub client_message: Binary, } +/// Verify upgrade and update state message #[cw_serde] pub struct VerifyUpgradeAndUpdateStateMsg { + /// The upgraded client state pub upgrade_client_state: Binary, + /// The upgraded consensus state pub upgrade_consensus_state: Binary, + /// The proof of the upgraded client state pub proof_upgrade_client: Binary, + /// The proof of the upgraded consensus state pub proof_upgrade_consensus_state: Binary, } +/// Migrate client store message #[cw_serde] pub struct MigrateClientStoreMsg {} +/// The query messages called by `ibc-go` #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + /// The message to verify the client message #[returns[()]] VerifyClientMessage(VerifyClientMessageMsg), + /// The message to check for misbehaviour #[returns[CheckForMisbehaviourResult]] CheckForMisbehaviour(CheckForMisbehaviourMsg), + /// The message to get the timestamp at height #[returns[TimestampAtHeightResult]] TimestampAtHeight(TimestampAtHeightMsg), + /// The message to get the status #[returns[StatusResult]] Status(StatusMsg), } +/// The message to verify the client message #[cw_serde] pub struct VerifyClientMessageMsg { + /// The client message to verify pub client_message: Binary, } +/// The message to check for misbehaviour #[cw_serde] pub struct CheckForMisbehaviourMsg { + /// The client message to check pub client_message: Binary, } +/// The message to get the timestamp at height #[cw_serde] pub struct TimestampAtHeightMsg { + /// The height to get the timestamp at pub height: Height, } +/// The status query message #[cw_serde] pub struct StatusMsg {} +/// Height of the ethereum chain #[cw_serde] pub struct Height { - /// the revision that the client is currently on + /// The revision that the client is currently on + /// Always zero in the ethereum light client #[serde(default)] pub revision_number: u64, - /// **revision_height** is a height of remote chain + /// The execution height of ethereum chain #[serde(default)] pub revision_height: u64, } +/// The result of updating the client state #[cw_serde] pub struct UpdateStateResult { + /// The updated client state heights pub heights: Vec, } +/// The merkle path #[cw_serde] pub struct MerklePath { + /// The key path pub key_path: Vec, } +/// The response to the status query #[cw_serde] pub struct StatusResult { + /// The status of the client pub status: String, } +/// The response to the check for misbehaviour query #[cw_serde] pub struct CheckForMisbehaviourResult { + /// Whether the client has found misbehaviour pub found_misbehaviour: bool, } +/// The response to the timestamp at height query #[cw_serde] pub struct TimestampAtHeightResult { + /// The timestamp at the height (in nanoseconds) pub timestamp: u64, } diff --git a/programs/cw-ics08-wasm-eth/src/state.rs b/programs/cw-ics08-wasm-eth/src/state.rs index a924ada5..89519c34 100644 --- a/programs/cw-ics08-wasm-eth/src/state.rs +++ b/programs/cw-ics08-wasm-eth/src/state.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::Deps; +//! State management for the Ethereum light client + +use cosmwasm_std::Storage; use ethereum_light_client::client_state::ClientState as EthClientState; use ethereum_light_client::consensus_state::ConsensusState as EthConsensusState; use ibc_proto::{ @@ -9,12 +11,15 @@ use ibc_proto::{ }; use prost::Message; -use crate::{custom_query::EthereumCustomQuery, msg::Height}; +use crate::msg::Height; -// Client state that is stored by the host +/// The store key used by `ibc-go` to store the client state pub const HOST_CLIENT_STATE_KEY: &str = "clientState"; +/// The store key used by `ibc-go` to store the consensus states pub const HOST_CONSENSUS_STATES_KEY: &str = "consensusStates"; +/// The key used to store the consensus states by height +#[must_use] pub fn consensus_db_key(height: &Height) -> String { format!( "{}/{}-{}", @@ -23,8 +28,12 @@ pub fn consensus_db_key(height: &Height) -> String { } // TODO: Proper errors -pub fn get_eth_client_state(deps: Deps) -> EthClientState { - let wasm_client_state_any_bz = deps.storage.get(HOST_CLIENT_STATE_KEY.as_bytes()).unwrap(); +/// Get the Ethereum client state +/// # Panics +/// Panics if the client state is not found or cannot be deserialized +#[allow(clippy::module_name_repetitions)] +pub fn get_eth_client_state(storage: &dyn Storage) -> EthClientState { + let wasm_client_state_any_bz = storage.get(HOST_CLIENT_STATE_KEY.as_bytes()).unwrap(); let wasm_client_state_any = Any::decode(wasm_client_state_any_bz.as_slice()).unwrap(); let wasm_client_state = WasmClientState::decode(wasm_client_state_any.value.as_slice()).unwrap(); @@ -34,14 +43,12 @@ pub fn get_eth_client_state(deps: Deps) -> EthClientState { } // TODO: Proper errors -pub fn get_eth_consensus_state( - deps: Deps, - height: &Height, -) -> EthConsensusState { - let wasm_consensus_state_any_bz = deps - .storage - .get(consensus_db_key(height).as_bytes()) - .unwrap(); +/// Get the Ethereum consensus state at a given height +/// # Panics +/// Panics if the consensus state is not found or cannot be deserialized +#[allow(clippy::module_name_repetitions)] +pub fn get_eth_consensus_state(storage: &dyn Storage, height: &Height) -> EthConsensusState { + let wasm_consensus_state_any_bz = storage.get(consensus_db_key(height).as_bytes()).unwrap(); let wasm_consensus_state_any = Any::decode(wasm_consensus_state_any_bz.as_slice()).unwrap(); let wasm_consensus_state = WasmConsensusState::decode(wasm_consensus_state_any.value.as_slice()).unwrap(); From c792160f68ce3109432ff9c4444649d0b351928b Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 12:54:51 +0800 Subject: [PATCH 42/49] refactor: custom query --- programs/cw-ics08-wasm-eth/src/contract.rs | 4 ++- .../cw-ics08-wasm-eth/src/custom_query.rs | 34 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/programs/cw-ics08-wasm-eth/src/contract.rs b/programs/cw-ics08-wasm-eth/src/contract.rs index cf10489b..e0ec8750 100644 --- a/programs/cw-ics08-wasm-eth/src/contract.rs +++ b/programs/cw-ics08-wasm-eth/src/contract.rs @@ -226,7 +226,9 @@ mod query { ); let header = serde_json::from_slice(&verify_client_message_msg.client_message) .map_err(ContractError::DeserializeClientStateFailed)?; - let bls_verifier = BlsVerifier { deps }; + let bls_verifier = BlsVerifier { + querier: deps.querier, + }; ethereum_light_client::verify::verify_header( ð_consensus_state, diff --git a/programs/cw-ics08-wasm-eth/src/custom_query.rs b/programs/cw-ics08-wasm-eth/src/custom_query.rs index d094856a..5034dc8d 100644 --- a/programs/cw-ics08-wasm-eth/src/custom_query.rs +++ b/programs/cw-ics08-wasm-eth/src/custom_query.rs @@ -1,7 +1,7 @@ //! This module contains the custom `CosmWasm` query for the Ethereum light client use alloy_primitives::B256; -use cosmwasm_std::{Binary, CustomQuery, Deps, QueryRequest}; +use cosmwasm_std::{Binary, CustomQuery, QuerierWrapper, QueryRequest}; use ethereum_light_client::types::bls::{BlsPublicKey, BlsSignature, BlsVerify}; use ethereum_utils::{ensure, hex::to_hex}; use thiserror::Error; @@ -32,20 +32,8 @@ impl CustomQuery for EthereumCustomQuery {} /// The BLS verifier via [`EthereumCustomQuery`] pub struct BlsVerifier<'a> { - /// The `CosmWasm` deps used to query the custom query - pub deps: Deps<'a, EthereumCustomQuery>, -} - -/// The error type for invalid signatures -#[derive(Debug, PartialEq, Eq, thiserror::Error, Clone)] -#[error("signature cannot be verified (public_keys: {public_keys:?}, msg: {msg}, signature: {signature})", msg = to_hex(.msg))] -pub struct InvalidSignatureErr { - /// The public keys used to verify the signature - pub public_keys: Vec, - /// The message that was signed - pub msg: B256, - /// The signature that was verified - pub signature: BlsSignature, + /// The `CosmWasm` querier + pub querier: QuerierWrapper<'a, EthereumCustomQuery>, } /// The error type for the BLS verifier @@ -55,8 +43,15 @@ pub enum BlsVerifierError { #[error("fast aggregate verify error: {0}")] FastAggregateVerify(String), - #[error("invalid signature: {0}")] - InvalidSignature(InvalidSignatureErr), + #[error("signature cannot be verified (public_keys: {public_keys:?}, msg: {msg}, signature: {signature})", msg = to_hex(.msg))] + InvalidSignature { + /// The public keys used to verify the signature + public_keys: Vec, + /// The message that was signed + msg: B256, + /// The signature that was verified + signature: BlsSignature, + }, } impl<'a> BlsVerify for BlsVerifier<'a> { @@ -82,18 +77,17 @@ impl<'a> BlsVerify for BlsVerifier<'a> { }); let is_valid: bool = self - .deps .querier .query(&request) .map_err(|e| BlsVerifierError::FastAggregateVerify(e.to_string()))?; ensure!( is_valid, - BlsVerifierError::InvalidSignature(InvalidSignatureErr { + BlsVerifierError::InvalidSignature { public_keys: public_keys.into_iter().copied().collect(), msg, signature, - }) + } ); Ok(()) From b99018fc0c61913cc4a8954724f756c375a13871 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 12:56:54 +0800 Subject: [PATCH 43/49] docs: update README --- packages/ethereum-light-client/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ethereum-light-client/README.md b/packages/ethereum-light-client/README.md index b9019958..84381323 100644 --- a/packages/ethereum-light-client/README.md +++ b/packages/ethereum-light-client/README.md @@ -1,8 +1,11 @@ -# ethereum-light-client -> ⚠ The Ethereum Light Client is currently under heavy development, is expected to change and is not ready for production use. +# `ethereum-light-client` + +> [!CAUTION] +> ⚠ The Ethereum Light Client is currently under heavy development, and is not ready for use. This is the stateless verification implementation of the ethereum light client. It contains all the core logic for verifying ethereum consensus, proving state (verify (non)memebership) and the headers submitted to update the light client. -The state is handled by the CosmWasm implemention in `programs/cw-ics08-wasm-eth`. +The state is handled by the `CosmWasm` implemention in `programs/cw-ics08-wasm-eth`. ## Acknowledgements + This work is based on the ethereum light client created by [Union](http://github.com/unionlabs/union/) From 52dfc3105484338e7cdd24ad13e8671f1b3070b7 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 14:51:12 +0800 Subject: [PATCH 44/49] docs: refactored and added docs to all of `ethereum-light-client` --- .../ethereum-light-client/src/client_state.rs | 21 +++++- packages/ethereum-light-client/src/config.rs | 72 ++++++++++++++----- .../src/consensus_state.rs | 27 +++++-- packages/ethereum-light-client/src/error.rs | 28 +++++++- packages/ethereum-light-client/src/lib.rs | 3 + .../ethereum-light-client/src/membership.rs | 15 ++-- .../src/test/fixture_types.rs | 8 +-- packages/ethereum-light-client/src/trie.rs | 33 ++++----- .../ethereum-light-client/src/types/bls.rs | 14 +++- .../ethereum-light-client/src/types/domain.rs | 13 +++- .../ethereum-light-client/src/types/fork.rs | 7 +- .../src/types/fork_data.rs | 8 ++- .../src/types/fork_parameters.rs | 44 +++++++----- .../ethereum-light-client/src/types/height.rs | 8 ++- .../src/types/light_client.rs | 66 +++++++++++++---- .../ethereum-light-client/src/types/mod.rs | 2 + .../src/types/signing_data.rs | 4 +- .../src/types/storage_proof.rs | 8 ++- .../src/types/sync_committee.rs | 55 ++++++++++---- .../src/types/wrappers.rs | 28 +++++--- packages/ethereum-light-client/src/verify.rs | 37 +++++++--- .../src/test/fixture_types.rs | 8 +-- 22 files changed, 381 insertions(+), 128 deletions(-) diff --git a/packages/ethereum-light-client/src/client_state.rs b/packages/ethereum-light-client/src/client_state.rs index 54e77f86..49fce0dd 100644 --- a/packages/ethereum-light-client/src/client_state.rs +++ b/packages/ethereum-light-client/src/client_state.rs @@ -1,27 +1,42 @@ +//! This module defines [`ClientState`]. + use alloy_primitives::{Address, B256, U256}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; use crate::types::{fork_parameters::ForkParameters, height::Height}; +/// The ethereum client state #[serde_as] -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct ClientState { + /// The chain ID #[serde_as(as = "DisplayFromStr")] pub chain_id: u64, + /// The genesis validators root #[serde(with = "ethereum_utils::base64::fixed_size")] pub genesis_validators_root: B256, + /// The minimum number of participants in the sync committee pub min_sync_committee_participants: u64, // TODO: Needs be added to e2e tests #143 + /// The time of genesis pub genesis_time: u64, + /// The fork parameters pub fork_parameters: ForkParameters, + /// The slot duration in seconds pub seconds_per_slot: u64, + /// The number of slots per epoch pub slots_per_epoch: u64, + /// The number of epochs per sync committee period pub epochs_per_sync_committee_period: u64, + /// The latest slot of this client pub latest_slot: u64, + /// The height at which the client was frozen // TODO: Should this be frozen_slot? Consider this in #143 pub frozen_height: Height, - #[serde(with = "ethereum_utils::base64::uint256")] - pub ibc_commitment_slot: U256, + /// The address of the IBC contract being tracked on Ethereum #[serde(with = "ethereum_utils::base64::fixed_size")] pub ibc_contract_address: Address, + /// The storage slot of the IBC commitment in the Ethereum contract + #[serde(with = "ethereum_utils::base64::uint256")] + pub ibc_commitment_slot: U256, } diff --git a/packages/ethereum-light-client/src/config.rs b/packages/ethereum-light-client/src/config.rs index a3279377..715762eb 100644 --- a/packages/ethereum-light-client/src/config.rs +++ b/packages/ethereum-light-client/src/config.rs @@ -1,3 +1,5 @@ +//! The configuration module contains the configurations and presets for the beacon chains. + use core::{ fmt::{self, Debug}, str::FromStr, @@ -16,18 +18,21 @@ pub struct Minimal; #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Mainnet; +/// The base kind of the preset. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PresetBaseKind { + /// Minimal preset Minimal, + /// Mainnet preset Mainnet, } impl fmt::Display for PresetBaseKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - PresetBaseKind::Minimal => "minimal", - PresetBaseKind::Mainnet => "mainnet", + Self::Minimal => "minimal", + Self::Mainnet => "mainnet", }) } } @@ -55,7 +60,7 @@ macro_rules! with_dollar_sign { macro_rules! consts_traits { ($($CONST:ident $(,)?),+) => { $( - #[allow(non_camel_case_types)] + #[allow(non_camel_case_types, missing_docs)] pub trait $CONST: Send + Sync + Unpin + 'static { // Extra traits are required because the builtin derives bound all generic // types unconditionally @@ -63,6 +68,7 @@ macro_rules! consts_traits { } )+ + #[allow(missing_docs, clippy::trait_duplication_in_bounds)] pub trait ChainSpec: 'static + Debug + Clone + PartialEq + Eq + Default + Send + Sync + Unpin + Default + $($CONST+)+ { const PRESET: preset::Preset; // const PRESET_BASE_KIND: PresetBaseKind; @@ -164,51 +170,77 @@ pub mod consts { pub const EXECUTION_PAYLOAD_INDEX: u64 = 25; // Branch depths for different merkle trees related to ethereum consensus + + /// The depth of the merkle tree for execution payloads. pub const EXECUTION_BRANCH_DEPTH: usize = floorlog2(EXECUTION_PAYLOAD_INDEX); + /// The depth of the merkle tree for the next sync committee. pub const NEXT_SYNC_COMMITTEE_BRANCH_DEPTH: usize = floorlog2(NEXT_SYNC_COMMITTEE_INDEX); + /// The depth of the merkle tree for the finalized root. pub const FINALITY_BRANCH_DEPTH: usize = floorlog2(FINALIZED_ROOT_INDEX); } +/// This module contains the preset values for the different configurations. pub mod preset { + /// The preset #[allow(non_snake_case)] #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Preset { - /// Misc - /// --------------------------------------------------------------- + // Misc + // --------------------------------------------------------------- + /// The depth of the deposit contract tree. pub DEPOSIT_CONTRACT_TREE_DEPTH: usize, + /// The max number of validators per committee. pub MAX_VALIDATORS_PER_COMMITTEE: usize, - /// Time parameters - /// --------------------------------------------------------------- + // Time parameters + // --------------------------------------------------------------- + /// The number of seconds per slot. pub SECONDS_PER_SLOT: usize, + /// The number of slots per epoch. pub SLOTS_PER_EPOCH: usize, - /// Max operations per block - /// --------------------------------------------------------------- + // Max operations per block + // --------------------------------------------------------------- + /// The max number of proposer slashings per block. pub MAX_PROPOSER_SLASHINGS: usize, + /// The max number of attester slashings per block. pub MAX_ATTESTER_SLASHINGS: usize, + /// The max number of attestations per block. pub MAX_ATTESTATIONS: usize, + /// The max number of deposits per block. pub MAX_DEPOSITS: usize, + /// The max number of voluntary exits per block. pub MAX_VOLUNTARY_EXITS: usize, + /// The max number of BLS to execution changes per block. pub MAX_BLS_TO_EXECUTION_CHANGES: usize, + /// The max number of blob commitments per block. pub MAX_BLOB_COMMITMENTS_PER_BLOCK: usize, - /// Execution - /// --------------------------------------------------------------- + // Execution + // --------------------------------------------------------------- + /// The max number of bytes per transaction. pub MAX_BYTES_PER_TRANSACTION: usize, + /// The max number of transactions per payload. pub MAX_TRANSACTIONS_PER_PAYLOAD: usize, + /// The number of bytes per logs bloom. pub BYTES_PER_LOGS_BLOOM: usize, + /// The max number of extra data bytes. pub MAX_EXTRA_DATA_BYTES: usize, + /// The max number of withdrawals per payload. pub MAX_WITHDRAWALS_PER_PAYLOAD: usize, - /// Sync committee - /// --------------------------------------------------------------- + // Sync committee + // --------------------------------------------------------------- + /// The size of the sync committee. pub SYNC_COMMITTEE_SIZE: usize, + /// The number of epochs per sync committee period. pub EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize, - /// Sync protocol - /// --------------------------------------------------------------- + // Sync protocol + // --------------------------------------------------------------- + /// The min number of sync committee participants. pub MIN_SYNC_COMMITTEE_PARTICIPANTS: usize, + /// The update timeout. pub UPDATE_TIMEOUT: usize, } @@ -268,13 +300,18 @@ pub mod preset { }; } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +/// The configuration for the beacon chain. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Config { + /// The preset pub preset: preset::Preset, + /// The fork parameters pub fork_parameters: ForkParameters, + /// The minimum genesis time pub min_genesis_time: u64, } +/// The Goerli testnet configuration. pub const GOERLI: Config = Config { preset: preset::MAINNET, fork_parameters: ForkParameters { @@ -300,6 +337,7 @@ pub const GOERLI: Config = Config { min_genesis_time: 1_614_588_812, }; +/// The mainnet configuration. pub const MAINNET: Config = Config { preset: preset::MAINNET, fork_parameters: ForkParameters { @@ -327,6 +365,7 @@ pub const MAINNET: Config = Config { min_genesis_time: 1_606_824_000, }; +/// The minimal configuration. pub const MINIMAL: Config = Config { preset: preset::MINIMAL, fork_parameters: ForkParameters { @@ -357,6 +396,7 @@ pub const MINIMAL: Config = Config { min_genesis_time: 1_578_009_600, }; +/// The sepolia testnet configuration. pub const SEPOLIA: Config = Config { preset: preset::MAINNET, fork_parameters: ForkParameters { diff --git a/packages/ethereum-light-client/src/consensus_state.rs b/packages/ethereum-light-client/src/consensus_state.rs index 2792fa3d..b4dc2d23 100644 --- a/packages/ethereum-light-client/src/consensus_state.rs +++ b/packages/ethereum-light-client/src/consensus_state.rs @@ -1,15 +1,23 @@ +//! This module defines [`ConsensusState`] and [`TrustedConsensusState`]. + use alloy_primitives::{FixedBytes, B256}; use serde::{Deserialize, Serialize}; use crate::types::sync_committee::{ActiveSyncCommittee, SyncCommittee}; -#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)] +/// The consensus state of the Ethereum light client +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct ConsensusState { + /// The slot number pub slot: u64, + /// The state merkle root #[serde(with = "ethereum_utils::base64::fixed_size")] pub state_root: B256, + /// The storage merkle root #[serde(with = "ethereum_utils::base64::fixed_size")] pub storage_root: B256, + /// The timestamp of the consensus state + // TODO: document the timestamp format (seconds since epoch?) pub timestamp: u64, /// aggregate public key of current sync committee #[serde(with = "ethereum_utils::base64::fixed_size")] @@ -19,8 +27,11 @@ pub struct ConsensusState { pub next_sync_committee: Option>, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The trusted consensus state of the Ethereum light client +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] pub struct TrustedConsensusState { + /// The consensus state pub state: ConsensusState, /// Full sync committee data which corresponds to the aggregate key that we /// store at the client. @@ -32,11 +43,15 @@ pub struct TrustedConsensusState { } impl TrustedConsensusState { - pub fn finalized_slot(&self) -> u64 { + /// Returns the finalized slot of the trusted consensus state + #[must_use] + pub const fn finalized_slot(&self) -> u64 { self.state.slot } - pub fn current_sync_committee(&self) -> Option<&SyncCommittee> { + /// Returns the current slot of the trusted consensus state if it is available + #[must_use] + pub const fn current_sync_committee(&self) -> Option<&SyncCommittee> { if let ActiveSyncCommittee::Current(committee) = &self.sync_committee { Some(committee) } else { @@ -44,7 +59,9 @@ impl TrustedConsensusState { } } - pub fn next_sync_committee(&self) -> Option<&SyncCommittee> { + /// Returns the next sync committee if it is available + #[must_use] + pub const fn next_sync_committee(&self) -> Option<&SyncCommittee> { if let ActiveSyncCommittee::Next(committee) = &self.sync_committee { Some(committee) } else { diff --git a/packages/ethereum-light-client/src/error.rs b/packages/ethereum-light-client/src/error.rs index e4500c4c..1b8c9492 100644 --- a/packages/ethereum-light-client/src/error.rs +++ b/packages/ethereum-light-client/src/error.rs @@ -1,7 +1,10 @@ +//! This module defines [`EthereumIBCError`]. + use alloy_primitives::B256; use alloy_rpc_types_beacon::BlsPublicKey; #[derive(thiserror::Error, Debug, Clone, PartialEq)] +#[allow(missing_docs, clippy::module_name_repetitions)] pub enum EthereumIBCError { #[error(transparent)] EthereumUtilsError(#[from] ethereum_utils::error::EthereumUtilsError), @@ -119,12 +122,13 @@ pub enum EthereumIBCError { ValidateNextSyncCommitteeFailed(#[source] Box), } -#[derive(Debug, PartialEq, Clone, thiserror::Error)] +#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] #[error("invalid merkle branch \ (leaf: {leaf}, branch: [{branch}], \ depth: {depth}, index: {index}, root: {root}, found: {found})", branch = .branch.iter().map(ToString::to_string).collect::>().join(", ") )] +#[allow(missing_docs)] pub struct InvalidMerkleBranch { pub leaf: B256, pub branch: Vec, @@ -133,3 +137,25 @@ pub struct InvalidMerkleBranch { pub root: B256, pub found: B256, } + +impl EthereumIBCError { + /// Constructs an [`EthereumIBCError::InvalidMerkleBranch`] variant. + #[must_use] + pub fn invalid_merkle_branch( + leaf: B256, + branch: Vec, + depth: usize, + index: u64, + root: B256, + found: B256, + ) -> Self { + Self::InvalidMerkleBranch(Box::new(InvalidMerkleBranch { + leaf, + branch, + depth, + index, + root, + found, + })) + } +} diff --git a/packages/ethereum-light-client/src/lib.rs b/packages/ethereum-light-client/src/lib.rs index ea68bf2c..df6e6138 100644 --- a/packages/ethereum-light-client/src/lib.rs +++ b/packages/ethereum-light-client/src/lib.rs @@ -1,3 +1,6 @@ +#![doc = include_str!("../README.md")] +#![deny(clippy::nursery, clippy::pedantic, warnings, missing_docs)] + pub mod client_state; pub mod config; pub mod consensus_state; diff --git a/packages/ethereum-light-client/src/membership.rs b/packages/ethereum-light-client/src/membership.rs index 23a19ebd..4fc867be 100644 --- a/packages/ethereum-light-client/src/membership.rs +++ b/packages/ethereum-light-client/src/membership.rs @@ -1,3 +1,6 @@ +//! This module provides [`verify_membership`] function to verify the membership of a key in the +//! storage trie. + use alloy_primitives::{keccak256, Bytes, Keccak256, U256}; use alloy_rlp::encode_fixed_size; use alloy_trie::{proof::verify_proof, Nibbles}; @@ -8,6 +11,10 @@ use crate::{ types::storage_proof::StorageProof, }; +/// Verifies the membership of a key in the storage trie. +/// # Errors +/// Returns an error if the proof cannot be verified. +#[allow(clippy::module_name_repetitions, clippy::needless_pass_by_value)] pub fn verify_membership( trusted_consensus_state: ConsensusState, client_state: ClientState, @@ -21,7 +28,7 @@ pub fn verify_membership( .map_err(|_| EthereumIBCError::StorageProofDecode)?; check_commitment_key( - path.to_vec(), + path.clone(), client_state.ibc_commitment_slot, storage_proof.key.into(), )?; @@ -59,13 +66,13 @@ fn check_commitment_key( let expected_commitment_key = ibc_commitment_key_v2(path, ibc_commitment_slot); // Data MUST be stored to the commitment path that is defined in ICS23. - if expected_commitment_key != key { + if expected_commitment_key == key { + Ok(()) + } else { Err(EthereumIBCError::InvalidCommitmentKey( format!("0x{expected_commitment_key:x}"), format!("0x{key:x}"), )) - } else { - Ok(()) } } diff --git a/packages/ethereum-light-client/src/test/fixture_types.rs b/packages/ethereum-light-client/src/test/fixture_types.rs index 81a52bcc..a7ebcb9b 100644 --- a/packages/ethereum-light-client/src/test/fixture_types.rs +++ b/packages/ethereum-light-client/src/test/fixture_types.rs @@ -8,20 +8,20 @@ use crate::{ // TODO: Remove this file once these types are in a separate package #143 -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub enum DataType { InitialState(Box), CommitmentProof(Box), UpdateClient(Box), } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct InitialState { pub client_state: ClientState, pub consensus_state: ConsensusState, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct CommitmentProof { #[serde(with = "ethereum_utils::base64")] pub path: Vec, @@ -31,7 +31,7 @@ pub struct CommitmentProof { pub consensus_state: ConsensusState, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct UpdateClient { pub client_state: ClientState, pub consensus_state: ConsensusState, diff --git a/packages/ethereum-light-client/src/trie.rs b/packages/ethereum-light-client/src/trie.rs index 5d494c8c..74aeba72 100644 --- a/packages/ethereum-light-client/src/trie.rs +++ b/packages/ethereum-light-client/src/trie.rs @@ -1,8 +1,15 @@ +//! This module provides [`validate_merkle_branch`] function to validate a merkle branch. + use alloy_primitives::B256; use sha2::{Digest, Sha256}; -use crate::error::{EthereumIBCError, InvalidMerkleBranch}; +use crate::error::EthereumIBCError; // https://github.com/ethereum/consensus-specs/blob/efb554f4c4848f8bfc260fcf3ff4b806971716f6/specs/phase0/beacon-chain.md#is_valid_merkle_branch +/// Validates a merkle branch. +/// # Errors +/// Returns an error if the merkle branch is invalid. +/// # Panics +/// Panics if the depth of the merkle branch is too large. pub fn validate_merkle_branch( leaf: B256, branch: Vec, @@ -12,38 +19,28 @@ pub fn validate_merkle_branch( ) -> Result<(), EthereumIBCError> { let mut value = leaf; for (i, branch_node) in branch.iter().take(depth).enumerate() { - if (index / 2u64.checked_pow(i as u32).unwrap()) % 2 != 0 { - let mut hasher = Sha256::new(); + let mut hasher = Sha256::new(); + if (index / 2u64.checked_pow(u32::try_from(i).unwrap()).unwrap()) % 2 != 0 { hasher.update(branch_node); hasher.update(value); - - value = B256::from_slice(&hasher.finalize()[..]); } else { - let mut hasher = Sha256::new(); hasher.update(value); hasher.update(branch_node); - - value = B256::from_slice(&hasher.finalize()[..]); } + value = B256::from_slice(&hasher.finalize()[..]); } if value == root { Ok(()) } else { - Err(EthereumIBCError::InvalidMerkleBranch(Box::new( - InvalidMerkleBranch { - leaf, - branch, - depth, - index, - root, - found: value, - }, - ))) + Err(EthereumIBCError::invalid_merkle_branch( + leaf, branch, depth, index, root, value, + )) } } #[cfg(test)] +#[allow(clippy::pedantic)] mod test { use alloy_primitives::{hex::FromHex, Address, Bloom, Bytes, B256, U256}; diff --git a/packages/ethereum-light-client/src/types/bls.rs b/packages/ethereum-light-client/src/types/bls.rs index b9e10160..bc1675ba 100644 --- a/packages/ethereum-light-client/src/types/bls.rs +++ b/packages/ethereum-light-client/src/types/bls.rs @@ -1,6 +1,8 @@ +//! BLS12-381 signature types for Ethereum beacon chain. + use alloy_primitives::{FixedBytes, B256}; -/// The Domain Separation Tag for hash_to_point in Ethereum beacon chain BLS12-381 signatures. +/// The Domain Separation Tag for `hash_to_point` in Ethereum beacon chain BLS12-381 signatures. /// /// This is also the name of the ciphersuite that defines beacon chain BLS signatures. /// @@ -18,12 +20,22 @@ pub const BLS_SECRET_KEY_BYTES_LEN: usize = 32; /// The number of bytes in a BLS12-381 signature. pub const BLS_SIGNATURE_BYTES_LEN: usize = 96; +/// The bytes representing a BLS12-381 public key. +#[allow(clippy::module_name_repetitions)] pub type BlsPublicKey = FixedBytes; +/// The bytes representing a BLS12-381 signature. +#[allow(clippy::module_name_repetitions)] pub type BlsSignature = FixedBytes; +/// The BLS verifier trait. +#[allow(clippy::module_name_repetitions)] pub trait BlsVerify { + /// The error type for the BLS verifier. type Error: std::fmt::Display; + /// Verify a BLS signature. + /// # Errors + /// Returns an error if the signature cannot be verified. fn fast_aggregate_verify( &self, public_keys: Vec<&BlsPublicKey>, diff --git a/packages/ethereum-light-client/src/types/domain.rs b/packages/ethereum-light-client/src/types/domain.rs index 21750de3..4f43d300 100644 --- a/packages/ethereum-light-client/src/types/domain.rs +++ b/packages/ethereum-light-client/src/types/domain.rs @@ -1,10 +1,17 @@ +//! This module defines types and functions related to the signature domain. + use alloy_primitives::{hex, FixedBytes, B256}; use serde::{Deserialize, Serialize}; use super::{fork_data::compute_fork_data_root, wrappers::WrappedVersion}; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The signature domain type. +/// Defined in +/// +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] pub struct DomainType(pub [u8; 4]); +#[allow(missing_docs)] impl DomainType { pub const BEACON_PROPOSER: Self = Self(hex!("00000000")); pub const BEACON_ATTESTER: Self = Self(hex!("01000000")); @@ -23,6 +30,8 @@ impl DomainType { /// Return the domain for the `domain_type` and `fork_version`. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_domain) +#[allow(clippy::module_name_repetitions, clippy::needless_pass_by_value)] +#[must_use] pub fn compute_domain( domain_type: DomainType, fork_version: Option, @@ -96,7 +105,7 @@ mod test { ) ); - let fork_data_root = compute_fork_data_root(genesis_version.clone(), Default::default()); + let fork_data_root = compute_fork_data_root(genesis_version.clone(), FixedBytes::default()); let mut domain = B256::default(); domain.0[..4].copy_from_slice(&domain_type.0); domain.0[4..].copy_from_slice(&fork_data_root[..28]); diff --git a/packages/ethereum-light-client/src/types/fork.rs b/packages/ethereum-light-client/src/types/fork.rs index f4904743..7dc3569e 100644 --- a/packages/ethereum-light-client/src/types/fork.rs +++ b/packages/ethereum-light-client/src/types/fork.rs @@ -1,10 +1,15 @@ +//! This module defines [`Fork`]. + use serde::{Deserialize, Serialize}; use super::wrappers::WrappedVersion; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The fork data +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct Fork { + /// The version of the fork pub version: WrappedVersion, + /// The epoch at which this fork is activated #[serde(default)] // TODO: Remove this when doing e2e integration #143 pub epoch: u64, } diff --git a/packages/ethereum-light-client/src/types/fork_data.rs b/packages/ethereum-light-client/src/types/fork_data.rs index be7e79f5..bf26cade 100644 --- a/packages/ethereum-light-client/src/types/fork_data.rs +++ b/packages/ethereum-light-client/src/types/fork_data.rs @@ -1,3 +1,5 @@ +//! This module defines [`compute_fork_data_root`]. + use alloy_primitives::B256; use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; @@ -5,9 +7,12 @@ use tree_hash_derive::TreeHash; use crate::types::wrappers::WrappedVersion; +/// The fork data #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] -pub struct ForkData { +struct ForkData { + /// The current version pub current_version: WrappedVersion, + /// The genesis validators root pub genesis_validators_root: B256, } @@ -15,6 +20,7 @@ pub struct ForkData { /// This is used primarily in signature domains to avoid collisions across forks/chains. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_fork_data_root) +#[must_use] pub fn compute_fork_data_root( current_version: WrappedVersion, genesis_validators_root: B256, diff --git a/packages/ethereum-light-client/src/types/fork_parameters.rs b/packages/ethereum-light-client/src/types/fork_parameters.rs index ef3bd131..f8b5f094 100644 --- a/packages/ethereum-light-client/src/types/fork_parameters.rs +++ b/packages/ethereum-light-client/src/types/fork_parameters.rs @@ -1,32 +1,44 @@ +//! This module defines [`ForkParameters`]. + use serde::{Deserialize, Serialize}; use super::{fork::Fork, wrappers::WrappedVersion}; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The fork parameters +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct ForkParameters { + /// The genesis fork version pub genesis_fork_version: WrappedVersion, + /// The genesis slot #[serde(default)] // TODO: REMOVE AND FIX IN E2E pub genesis_slot: u64, + /// The altair fork pub altair: Fork, + /// The bellatrix fork pub bellatrix: Fork, + /// The capella fork pub capella: Fork, + /// The deneb fork pub deneb: Fork, } -/// Returns the fork version based on the `epoch` and `fork_parameters`. -/// NOTE: This implementation is based on capella. -/// -/// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/fork.md#modified-compute_fork_version) -pub fn compute_fork_version(fork_parameters: &ForkParameters, epoch: u64) -> WrappedVersion { - if epoch >= fork_parameters.deneb.epoch { - fork_parameters.deneb.version.clone() - } else if epoch >= fork_parameters.capella.epoch { - fork_parameters.capella.version.clone() - } else if epoch >= fork_parameters.bellatrix.epoch { - fork_parameters.bellatrix.version.clone() - } else if epoch >= fork_parameters.altair.epoch { - fork_parameters.altair.version.clone() - } else { - fork_parameters.genesis_fork_version.clone() +impl ForkParameters { + /// Returns the fork version based on the `epoch`. + /// NOTE: This implementation is based on capella. + /// + /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/fork.md#modified-compute_fork_version) + #[must_use] + pub fn compute_fork_version(&self, epoch: u64) -> WrappedVersion { + if epoch >= self.deneb.epoch { + self.deneb.version.clone() + } else if epoch >= self.capella.epoch { + self.capella.version.clone() + } else if epoch >= self.bellatrix.epoch { + self.bellatrix.version.clone() + } else if epoch >= self.altair.epoch { + self.altair.version.clone() + } else { + self.genesis_fork_version.clone() + } } } diff --git a/packages/ethereum-light-client/src/types/height.rs b/packages/ethereum-light-client/src/types/height.rs index 630bfb18..75d40f0c 100644 --- a/packages/ethereum-light-client/src/types/height.rs +++ b/packages/ethereum-light-client/src/types/height.rs @@ -1,8 +1,14 @@ +//! This module defines [`Height`]. + use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// Height +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct Height { + /// The revision number + /// This is always 0 in the current implementation #[serde(default)] pub revision_number: u64, + /// The block height pub revision_height: u64, } diff --git a/packages/ethereum-light-client/src/types/light_client.rs b/packages/ethereum-light-client/src/types/light_client.rs index b98c6170..b03dd2f0 100644 --- a/packages/ethereum-light-client/src/types/light_client.rs +++ b/packages/ethereum-light-client/src/types/light_client.rs @@ -1,9 +1,11 @@ +//! This module defines the types used in the light client updates + use alloy_primitives::{Address, B256, U256}; use serde::{Deserialize, Serialize}; use tree_hash_derive::TreeHash; use crate::config::consts::{ - EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX, + EXECUTION_BRANCH_DEPTH, FINALITY_BRANCH_DEPTH, NEXT_SYNC_COMMITTEE_BRANCH_DEPTH, }; use super::{ @@ -11,27 +13,31 @@ use super::{ wrappers::{WrappedBloom, WrappedBranch, WrappedBytes}, }; -pub const EXECUTION_BRANCH_DEPTH: usize = EXECUTION_PAYLOAD_INDEX.ilog2() as usize; -pub const NEXT_SYNC_COMMITTEE_BRANCH_DEPTH: usize = NEXT_SYNC_COMMITTEE_INDEX.ilog2() as usize; -pub const FINALITY_BRANCH_DEPTH: usize = FINALIZED_ROOT_INDEX.ilog2() as usize; - -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The header of a light client update +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct Header { + /// The trusted sync committee pub trusted_sync_committee: TrustedSyncCommittee, + /// The consensus update pub consensus_update: LightClientUpdate, + /// The account update pub account_update: AccountUpdate, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// A light client update +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] pub struct LightClientUpdate { /// Header attested to by the sync committee pub attested_header: LightClientHeader, /// Next sync committee corresponding to `attested_header.state_root` #[serde(default)] // TODO: Check if this can be removed in #143 pub next_sync_committee: Option, + /// The branch of the next sync committee pub next_sync_committee_branch: Option>, /// Finalized header corresponding to `attested_header.state_root` pub finalized_header: LightClientHeader, + /// Branch of the finalized header pub finality_branch: WrappedBranch, /// Sync committee aggregate signature pub sync_aggregate: SyncAggregate, @@ -39,68 +45,100 @@ pub struct LightClientUpdate { pub signature_slot: u64, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The account update +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct AccountUpdate { + /// The account proof pub account_proof: AccountProof, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The account proof +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct AccountProof { + /// The account storage root #[serde(with = "ethereum_utils::base64::fixed_size")] pub storage_root: B256, + /// The account proof pub proof: Vec, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +/// The header of a light client +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default, TreeHash)] +#[allow(clippy::module_name_repetitions)] pub struct LightClientHeader { + /// The beacon block header pub beacon: BeaconBlockHeader, + /// The execution payload header pub execution: ExecutionPayloadHeader, + /// The execution branch pub execution_branch: WrappedBranch, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +/// The beacon block header +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default, TreeHash)] pub struct BeaconBlockHeader { + /// The slot to which this block corresponds pub slot: u64, + /// The index of validator in validator registry pub proposer_index: u64, + /// The signing merkle root of the parent `BeaconBlock` #[serde(with = "ethereum_utils::base64::fixed_size")] pub parent_root: B256, + /// The tree hash merkle root of the `BeaconState` for the `BeaconBlock` #[serde(with = "ethereum_utils::base64::fixed_size")] pub state_root: B256, + /// The tree hash merkle root of the `BeaconBlockBody` for the `BeaconBlock` #[serde(with = "ethereum_utils::base64::fixed_size")] pub body_root: B256, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +/// Header to track the execution block +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default, TreeHash)] pub struct ExecutionPayloadHeader { + /// The parent hash of the execution payload header #[serde(with = "ethereum_utils::base64::fixed_size")] pub parent_hash: B256, + /// Block fee recipient #[serde(with = "ethereum_utils::base64::fixed_size")] pub fee_recipient: Address, + /// The state root #[serde(with = "ethereum_utils::base64::fixed_size")] pub state_root: B256, + /// The root of the receipts trie #[serde(with = "ethereum_utils::base64::fixed_size")] pub receipts_root: B256, + /// The logs bloom filter pub logs_bloom: WrappedBloom, + /// The previous Randao value, used to compute the randomness on the execution layer. #[serde(with = "ethereum_utils::base64::fixed_size")] pub prev_randao: B256, + /// The block number of the execution payload pub block_number: u64, + /// Execution block gas limit pub gas_limit: u64, + /// Execution block gas used #[serde(default)] pub gas_used: u64, + /// The timestamp of the execution payload pub timestamp: u64, + /// The extra data of the execution payload pub extra_data: WrappedBytes, + /// Block base fee per gas #[serde(with = "ethereum_utils::base64::uint256")] pub base_fee_per_gas: U256, + /// The block hash #[serde(with = "ethereum_utils::base64::fixed_size")] pub block_hash: B256, + /// SSZ hash tree root of the transaction list #[serde(with = "ethereum_utils::base64::fixed_size")] pub transactions_root: B256, + /// Tree root of the withdrawals list #[serde(with = "ethereum_utils::base64::fixed_size")] pub withdrawals_root: B256, - // new in Deneb + /// Blob gas used (new in Deneb) #[serde(default)] pub blob_gas_used: u64, - // new in Deneb + /// Excess blob gas (new in Deneb) #[serde(default)] pub excess_blob_gas: u64, } diff --git a/packages/ethereum-light-client/src/types/mod.rs b/packages/ethereum-light-client/src/types/mod.rs index 2954b14d..3b848571 100644 --- a/packages/ethereum-light-client/src/types/mod.rs +++ b/packages/ethereum-light-client/src/types/mod.rs @@ -1,3 +1,5 @@ +//! This module contains the types that are used in the Ethereum light client + pub mod bls; pub mod domain; pub mod fork; diff --git a/packages/ethereum-light-client/src/types/signing_data.rs b/packages/ethereum-light-client/src/types/signing_data.rs index e2cc398e..63398324 100644 --- a/packages/ethereum-light-client/src/types/signing_data.rs +++ b/packages/ethereum-light-client/src/types/signing_data.rs @@ -1,10 +1,12 @@ +//! This module defines [`compute_signing_root`]. + use alloy_primitives::B256; use serde::{Deserialize, Serialize}; use tree_hash::TreeHash; use tree_hash_derive::TreeHash; #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] -pub struct SigningData { +struct SigningData { pub object_root: B256, pub domain: B256, } diff --git a/packages/ethereum-light-client/src/types/storage_proof.rs b/packages/ethereum-light-client/src/types/storage_proof.rs index af3855f3..ff4d2ac1 100644 --- a/packages/ethereum-light-client/src/types/storage_proof.rs +++ b/packages/ethereum-light-client/src/types/storage_proof.rs @@ -1,13 +1,19 @@ +//! This module defines [`StorageProof`]. + use alloy_primitives::{B256, U256}; use serde::{Deserialize, Serialize}; use super::wrappers::WrappedBytes; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The key-value storage proof for a smart contract account +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct StorageProof { + /// The key of the storage #[serde(with = "ethereum_utils::base64::fixed_size")] pub key: B256, + /// The value of the storage #[serde(with = "ethereum_utils::base64::uint256")] pub value: U256, + /// The proof of the storage pub proof: Vec, } diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 1d39d562..85edaf93 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -1,3 +1,5 @@ +//! Types related to the sync committee + use alloy_primitives::Bytes; use ethereum_utils::slot::compute_epoch_at_slot; use serde::{Deserialize, Serialize}; @@ -9,50 +11,66 @@ use super::{ wrappers::WrappedVecBlsPublicKey, }; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default, TreeHash)] +/// The sync committee data +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default, TreeHash)] pub struct SyncCommittee { + /// The public keys of the sync committee pub pubkeys: WrappedVecBlsPublicKey, + /// The aggregate public key of the sync committee #[serde(with = "ethereum_utils::base64::fixed_size")] pub aggregate_pubkey: BlsPublicKey, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +/// The active sync committee +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] +#[allow(clippy::module_name_repetitions)] pub enum ActiveSyncCommittee { + /// The current sync committee Current(SyncCommittee), + /// The next sync committee Next(SyncCommittee), } +// TODO: should this actually return default at any point? If not, panic or error impl Default for ActiveSyncCommittee { fn default() -> Self { - ActiveSyncCommittee::Current(SyncCommittee { + Self::Current(SyncCommittee { pubkeys: WrappedVecBlsPublicKey::default(), aggregate_pubkey: BlsPublicKey::default(), }) } } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The trusted sync committee +// TODO: Could we use a enum to represent the current and next sync committee like +// `ActiveSyncCommittee`? +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] +#[allow(clippy::module_name_repetitions)] pub struct TrustedSyncCommittee { + /// The trusted height pub trusted_height: Height, + /// The current sync committee pub current_sync_committee: Option, + /// The next sync committee pub next_sync_committee: Option, } impl TrustedSyncCommittee { + /// Returns the active sync committee // TODO: should this actually return default at any point? If not, panic or error // also, if not returning default, remove the impl Default + #[must_use] pub fn get_active_sync_committee(&self) -> ActiveSyncCommittee { - if let Some(sync_committee) = &self.current_sync_committee { - ActiveSyncCommittee::Current(sync_committee.clone()) - } else if let Some(sync_committee) = &self.next_sync_committee { - ActiveSyncCommittee::Next(sync_committee.clone()) - } else { - ActiveSyncCommittee::default() + match (&self.current_sync_committee, &self.next_sync_committee) { + (Some(sync_committee), _) => ActiveSyncCommittee::Current(sync_committee.clone()), + (_, Some(sync_committee)) => ActiveSyncCommittee::Next(sync_committee.clone()), + _ => ActiveSyncCommittee::default(), } } } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// The sync committee aggregate +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct SyncAggregate { /// The bits representing the sync committee's participation. #[serde(with = "ethereum_utils::base64")] @@ -73,14 +91,15 @@ impl SyncAggregate { .sum::() as usize } + /// Returns the size of the sync committee. pub fn sync_committee_size(&self) -> usize { self.sync_committee_bits.len() * 8 } + /// Returns if at least 2/3 of the sync committee signed + /// + /// // TODO: Unit test - // Returns if at least 2/3 of the sync committee signed - // - // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#process_light_client_update pub fn validate_signature_supermajority(&self) -> bool { self.num_sync_committe_participants() * 3 >= self.sync_committee_size() * 2 } @@ -89,13 +108,18 @@ impl SyncAggregate { /// Returns the sync committee period at a given `epoch`. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/validator.md#sync-committee) -pub fn compute_sync_committee_period(epochs_per_sync_committee_period: u64, epoch: u64) -> u64 { +#[must_use] +pub const fn compute_sync_committee_period( + epochs_per_sync_committee_period: u64, + epoch: u64, +) -> u64 { epoch / epochs_per_sync_committee_period } /// Returns the sync committee period at a given `slot`. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#compute_sync_committee_period_at_slot) +#[must_use] pub fn compute_sync_committee_period_at_slot( slots_per_epoch: u64, epochs_per_sync_committee_period: u64, @@ -108,6 +132,7 @@ pub fn compute_sync_committee_period_at_slot( } #[cfg(test)] +#[allow(clippy::pedantic)] mod test { use crate::test::fixture_types::UpdateClient; use crate::types::sync_committee::{SyncAggregate, SyncCommittee}; diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs index e9bb1bf4..a0dbdf34 100644 --- a/packages/ethereum-light-client/src/types/wrappers.rs +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -1,10 +1,14 @@ +//! Wrappers around types that implement `TreeHash` to provide custom serialization and encoding. + use alloy_primitives::{aliases::B32, Bloom, Bytes, FixedBytes, B256}; use serde::{Deserialize, Serialize}; use tree_hash::{MerkleHasher, TreeHash, BYTES_PER_CHUNK}; use super::bls::BlsPublicKey; -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// A wrapper around a `B32` that represents a version, implements [`TreeHash`], and uses a +/// fixed-size base64 encoding. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct WrappedVersion(#[serde(with = "ethereum_utils::base64::fixed_size")] pub B32); impl TreeHash for WrappedVersion { @@ -25,7 +29,8 @@ impl TreeHash for WrappedVersion { } } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// A wrapper around [`Bytes`] that implements [`TreeHash`] and uses a base64 encoding. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct WrappedBytes(#[serde(with = "ethereum_utils::base64")] pub Bytes); impl TreeHash for WrappedBytes { @@ -46,7 +51,7 @@ impl TreeHash for WrappedBytes { let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { - hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() + hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap(); } tree_hash::mix_in_length(&hasher.finish().unwrap(), self.0.len()) @@ -59,7 +64,8 @@ impl AsRef<[u8]> for WrappedBytes { } } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// A wrapper around [`Bloom`] that implements [`TreeHash`] and uses a fixed-size base64 encoding. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct WrappedBloom(#[serde(with = "ethereum_utils::base64::fixed_size")] pub Bloom); impl TreeHash for WrappedBloom { @@ -80,14 +86,16 @@ impl TreeHash for WrappedBloom { let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { - hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() + hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap(); } hasher.finish().unwrap() } } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +/// A fixed size wrapper around a list of [`B256`] that implements [`TreeHash`] and uses a +/// fixed-size base64 encoding to represent a branch of a tree. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct WrappedBranch( #[serde(with = "ethereum_utils::base64::fixed_size::vec::fixed_size")] pub [B256; N], ); @@ -110,7 +118,7 @@ impl TreeHash for WrappedBranch { let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { - hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap() + hasher.write(item.tree_hash_root()[..1].as_ref()).unwrap(); } hasher.finish().unwrap() @@ -129,7 +137,9 @@ impl From> for Vec { } } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Default)] +/// A wrapper around a list of [`BlsPublicKey`] that implements [`TreeHash`] and uses a fixed-size +/// base64 encoding. +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct WrappedVecBlsPublicKey( #[serde(with = "ethereum_utils::base64::fixed_size::vec")] pub Vec, ); @@ -152,7 +162,7 @@ impl TreeHash for WrappedVecBlsPublicKey { let mut hasher = MerkleHasher::with_leaves(leaves); for item in &self.0 { - hasher.write(item.tree_hash_root().as_ref()).unwrap() + hasher.write(item.tree_hash_root().as_ref()).unwrap(); } hasher.finish().unwrap() diff --git a/packages/ethereum-light-client/src/verify.rs b/packages/ethereum-light-client/src/verify.rs index 127934bb..f9ea5028 100644 --- a/packages/ethereum-light-client/src/verify.rs +++ b/packages/ethereum-light-client/src/verify.rs @@ -1,3 +1,5 @@ +//! This module contains functions to verify the header of the light client. + use alloy_primitives::B256; use ethereum_trie_db::trie_db::verify_account_storage_root; @@ -10,7 +12,8 @@ use tree_hash::TreeHash; use crate::{ client_state::ClientState, config::consts::{ - get_subtree_index, EXECUTION_PAYLOAD_INDEX, FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_INDEX, + get_subtree_index, EXECUTION_BRANCH_DEPTH, EXECUTION_PAYLOAD_INDEX, FINALITY_BRANCH_DEPTH, + FINALIZED_ROOT_INDEX, NEXT_SYNC_COMMITTEE_BRANCH_DEPTH, NEXT_SYNC_COMMITTEE_INDEX, }, consensus_state::{ConsensusState, TrustedConsensusState}, error::EthereumIBCError, @@ -18,16 +21,16 @@ use crate::{ types::{ bls::BlsVerify, domain::{compute_domain, DomainType}, - fork_parameters::compute_fork_version, - light_client::{ - Header, LightClientHeader, LightClientUpdate, EXECUTION_BRANCH_DEPTH, - FINALITY_BRANCH_DEPTH, NEXT_SYNC_COMMITTEE_BRANCH_DEPTH, - }, + light_client::{Header, LightClientHeader, LightClientUpdate}, signing_data::compute_signing_root, sync_committee::compute_sync_committee_period_at_slot, }, }; +/// Verifies the header of the light client. +/// # Errors +/// Returns an error if the header cannot be verified. +#[allow(clippy::module_name_repetitions)] pub fn verify_header( consensus_state: &ConsensusState, client_state: &ClientState, @@ -76,7 +79,6 @@ pub fn verify_header( .map_err(|err| EthereumIBCError::VerifyStorageProof(err.to_string())) } -// TODO: Update comments /// Verifies if the light client `update` is valid. /// /// * `update`: The light client update we want to verify. @@ -95,6 +97,12 @@ pub fn verify_header( /// to be changed or removed. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md#validate_light_client_update) +/// # Errors +/// Returns an error if the update cannot be verified. +/// # Panics +/// If the minimum sync committee participants is not a valid usize. +// TODO: Update comments +#[allow(clippy::too_many_lines, clippy::needless_pass_by_value)] pub fn validate_light_client_update( client_state: &ClientState, trusted_consensus_state: &TrustedConsensusState, @@ -275,10 +283,12 @@ pub fn validate_light_client_update( .collect::>(); let fork_version_slot = std::cmp::max(update.signature_slot, 1) - 1; - let fork_version = compute_fork_version( - &client_state.fork_parameters, - compute_epoch_at_slot(client_state.slots_per_epoch, fork_version_slot), - ); + let fork_version = client_state + .fork_parameters + .compute_fork_version(compute_epoch_at_slot( + client_state.slots_per_epoch, + fork_version_slot, + )); let domain = compute_domain( DomainType::SYNC_COMMITTEE, @@ -302,6 +312,8 @@ pub fn validate_light_client_update( /// Validates a light client header. /// /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/light-client/sync-protocol.md#modified-is_valid_light_client_header) +/// # Errors +/// Returns an error if the header cannot be validated. pub fn is_valid_light_client_header( client_state: &ClientState, header: &LightClientHeader, @@ -329,6 +341,9 @@ pub fn is_valid_light_client_header( ) } +/// Returns the execution root of the light client header. +/// # Errors +/// Returns an error if the epoch is less than the Deneb epoch. pub fn get_lc_execution_root( client_state: &ClientState, header: &LightClientHeader, diff --git a/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs b/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs index fddee001..27f20a65 100644 --- a/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs +++ b/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs @@ -7,20 +7,20 @@ use serde::{Deserialize, Serialize}; // TODO: Remove this file once these types are in a separate package #143 -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub enum DataType { InitialState(Box), CommitmentProof(Box), UpdateClient(Box), } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct InitialState { pub client_state: ClientState, pub consensus_state: ConsensusState, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct CommitmentProof { #[serde(with = "ethereum_utils::base64")] pub path: Vec, @@ -30,7 +30,7 @@ pub struct CommitmentProof { pub consensus_state: ConsensusState, } -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct UpdateClient { pub client_state: ClientState, pub consensus_state: ConsensusState, From 13caeb193814fae60b163a027236b2f351360fc9 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 14:57:21 +0800 Subject: [PATCH 45/49] refactor: use match instead of if --- .../src/types/fork_parameters.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/ethereum-light-client/src/types/fork_parameters.rs b/packages/ethereum-light-client/src/types/fork_parameters.rs index f8b5f094..45841d9f 100644 --- a/packages/ethereum-light-client/src/types/fork_parameters.rs +++ b/packages/ethereum-light-client/src/types/fork_parameters.rs @@ -29,16 +29,12 @@ impl ForkParameters { /// [See in consensus-spec](https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/fork.md#modified-compute_fork_version) #[must_use] pub fn compute_fork_version(&self, epoch: u64) -> WrappedVersion { - if epoch >= self.deneb.epoch { - self.deneb.version.clone() - } else if epoch >= self.capella.epoch { - self.capella.version.clone() - } else if epoch >= self.bellatrix.epoch { - self.bellatrix.version.clone() - } else if epoch >= self.altair.epoch { - self.altair.version.clone() - } else { - self.genesis_fork_version.clone() + match epoch { + _ if epoch >= self.deneb.epoch => self.deneb.version.clone(), + _ if epoch >= self.capella.epoch => self.capella.version.clone(), + _ if epoch >= self.bellatrix.epoch => self.bellatrix.version.clone(), + _ if epoch >= self.altair.epoch => self.altair.version.clone(), + _ => self.genesis_fork_version.clone(), } } } From 04423753afa0d0da5be9691d1edd99707c2a472e Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 15:05:39 +0800 Subject: [PATCH 46/49] lint: fixed clippy complaint --- programs/cw-ics08-wasm-eth/src/custom_query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/cw-ics08-wasm-eth/src/custom_query.rs b/programs/cw-ics08-wasm-eth/src/custom_query.rs index 5034dc8d..0c41ef09 100644 --- a/programs/cw-ics08-wasm-eth/src/custom_query.rs +++ b/programs/cw-ics08-wasm-eth/src/custom_query.rs @@ -54,7 +54,7 @@ pub enum BlsVerifierError { }, } -impl<'a> BlsVerify for BlsVerifier<'a> { +impl BlsVerify for BlsVerifier<'_> { type Error = BlsVerifierError; fn fast_aggregate_verify( From 373d0947d9b8492b3a345aca2c441826537e3e57 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 15:45:37 +0800 Subject: [PATCH 47/49] docs: added linting and docs to triedb --- packages/ethereum-trie-db/src/error.rs | 5 ++++- packages/ethereum-trie-db/src/lib.rs | 8 +++++++- packages/ethereum-trie-db/src/trie_db.rs | 14 +++++++++++--- packages/ethereum-trie-db/src/types.rs | 12 +++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/ethereum-trie-db/src/error.rs b/packages/ethereum-trie-db/src/error.rs index ec80657a..83637f25 100644 --- a/packages/ethereum-trie-db/src/error.rs +++ b/packages/ethereum-trie-db/src/error.rs @@ -1,6 +1,9 @@ +//! This module defines [`TrieDBError`]. + use ethereum_utils::hex; -#[derive(Debug, PartialEq, thiserror::Error, Clone)] +#[derive(Debug, PartialEq, Eq, thiserror::Error, Clone)] +#[allow(missing_docs, clippy::module_name_repetitions)] pub enum TrieDBError { #[error("get trie node failed: {0}")] GetTrieNodeFailed(String), diff --git a/packages/ethereum-trie-db/src/lib.rs b/packages/ethereum-trie-db/src/lib.rs index 051c2615..68bf41a3 100644 --- a/packages/ethereum-trie-db/src/lib.rs +++ b/packages/ethereum-trie-db/src/lib.rs @@ -1,3 +1,9 @@ -pub mod error; +//! Ethereum trie database utilities. + +#![deny(clippy::nursery, clippy::pedantic, warnings, missing_docs)] + +mod error; pub mod trie_db; pub mod types; + +pub use error::TrieDBError; diff --git a/packages/ethereum-trie-db/src/trie_db.rs b/packages/ethereum-trie-db/src/trie_db.rs index e61da6ed..6c50745f 100644 --- a/packages/ethereum-trie-db/src/trie_db.rs +++ b/packages/ethereum-trie-db/src/trie_db.rs @@ -1,3 +1,5 @@ +//! Defines the account trie and the account type. + use alloy_primitives::{Address, B256}; use ethereum_utils::ensure; use hash_db::HashDB; @@ -7,15 +9,20 @@ use rlp_derive::RlpDecodable; use trie_db::{Trie, TrieDBBuilder}; use crate::{ - error::TrieDBError, types::{keccak_256, EthLayout, KeccakHasher}, + TrieDBError, }; +/// A smart contract account. #[derive(Debug, Clone, RlpDecodable)] pub struct Account { + /// The nonce of the account. pub nonce: u64, + /// The balance of the account. pub balance: U256, + /// The storage root of the account. pub storage_root: H256, + /// The code hash of the account. pub code_hash: H256, } @@ -26,7 +33,9 @@ pub struct Account { /// * `proof`: Proof of storage. /// * `storage_root`: Storage root of the contract. /// -/// NOTE: You must not trust the `root` unless you've verified it. +/// WARNING: You must not trust the `root` unless you've verified it. +/// # Errors +/// Returns an error if the verification fails. pub fn verify_account_storage_root( root: B256, address: Address, @@ -36,7 +45,6 @@ pub fn verify_account_storage_root( let storage_root: H256 = H256(storage_root.into()); let address: H160 = H160(address.into()); - print!("test"); match get_node(root, address.as_ref(), proof)? { Some(account) => { let account = diff --git a/packages/ethereum-trie-db/src/types.rs b/packages/ethereum-trie-db/src/types.rs index 0993977d..0cadc1e4 100644 --- a/packages/ethereum-trie-db/src/types.rs +++ b/packages/ethereum-trie-db/src/types.rs @@ -27,6 +27,7 @@ use trie_db::{ ChildReference, NodeCodec, TrieLayout, }; +/// Concrete implementation of `TrieLayout` for Ethereum #[derive(Default, Clone)] pub struct EthLayout; @@ -55,6 +56,7 @@ impl Hasher for KeccakHasher { } /// Performs a Keccak-256 hash on the given input. +#[must_use] pub fn keccak_256(input: &[u8]) -> [u8; 32] { let mut hasher = Keccak256::new(); hasher.input(input); @@ -99,12 +101,14 @@ fn encode_partial_inner_iter<'a>( std::iter::once(first).chain(partial_remaining) } +#[allow(clippy::needless_pass_by_value)] fn decode_value_range(rlp: Rlp, mut offset: usize) -> Result, DecoderError> { let payload = rlp.payload_info()?; offset += payload.header_len; Ok(offset..(offset + payload.value_len)) } +#[allow(clippy::needless_pass_by_value)] fn decode_child_handle_plan( child_rlp: Rlp, mut offset: usize, @@ -219,10 +223,9 @@ impl NodeCodec for RlpNodeCodec { fn leaf_node(partial: impl Iterator, _: usize, value: Value) -> Vec { let mut stream = RlpStream::new_list(2); - stream.append_iter(partial.collect::>()); + stream.append_iter(partial); stream.append(&match value { - Value::Inline(v) => v, - Value::Node(v) => v, + Value::Inline(v) | Value::Node(v) => v, }); stream.out().into() } @@ -269,8 +272,7 @@ impl NodeCodec for RlpNodeCodec { } if let Some(value) = value { stream.append(&match value { - Value::Inline(v) => v, - Value::Node(v) => v, + Value::Inline(v) | Value::Node(v) => v, }); } else { stream.append_empty_data(); From 2b5eaa0ae2038629620c5a5160975ee5c7ddbc52 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 15:46:31 +0800 Subject: [PATCH 48/49] deps: added std feature for error impl --- programs/cw-ics08-wasm-eth/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/cw-ics08-wasm-eth/Cargo.toml b/programs/cw-ics08-wasm-eth/Cargo.toml index 0d5d26d8..08dfaf1b 100644 --- a/programs/cw-ics08-wasm-eth/Cargo.toml +++ b/programs/cw-ics08-wasm-eth/Cargo.toml @@ -17,7 +17,7 @@ alloy-primitives = { workspace = true, default-features = false } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } -prost = { workspace = true } +prost = { workspace = true, features = ["std"] } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } From b1e90e216d275e12c49ac2fc2c7e559fd1c52422 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 11 Dec 2024 16:07:07 +0800 Subject: [PATCH 49/49] imp: removed ethereum-utils::base64 --- Cargo.lock | 1 + packages/ethereum-light-client/Cargo.toml | 2 +- .../src/test/fixture_types.rs | 4 +++- .../src/types/sync_committee.rs | 4 +++- .../src/types/wrappers.rs | 4 +++- packages/ethereum-utils/src/base64.rs | 18 ------------------ programs/cw-ics08-wasm-eth/Cargo.toml | 1 + .../src/test/fixture_types.rs | 4 +++- 8 files changed, 15 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f73ee339..2a2d3bd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2042,6 +2042,7 @@ dependencies = [ "prost", "serde", "serde_json", + "serde_with", "thiserror 2.0.4", ] diff --git a/packages/ethereum-light-client/Cargo.toml b/packages/ethereum-light-client/Cargo.toml index b74a1712..1fd97b9b 100644 --- a/packages/ethereum-light-client/Cargo.toml +++ b/packages/ethereum-light-client/Cargo.toml @@ -23,7 +23,7 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } sha2 = { workspace = true } -serde_with = { workspace = true } +serde_with = { workspace = true, features = ["base64"] } # TODO: From union (might be removed if they are replaced by something more standard). #147 typenum = { workspace = true, features = ["const-generics"] } diff --git a/packages/ethereum-light-client/src/test/fixture_types.rs b/packages/ethereum-light-client/src/test/fixture_types.rs index a7ebcb9b..84c6df75 100644 --- a/packages/ethereum-light-client/src/test/fixture_types.rs +++ b/packages/ethereum-light-client/src/test/fixture_types.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use serde_with::{base64::Base64, serde_as}; use crate::{ client_state::ClientState, @@ -21,9 +22,10 @@ pub struct InitialState { pub consensus_state: ConsensusState, } +#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct CommitmentProof { - #[serde(with = "ethereum_utils::base64")] + #[serde_as(as = "Base64")] pub path: Vec, pub storage_proof: StorageProof, pub proof_height: Height, diff --git a/packages/ethereum-light-client/src/types/sync_committee.rs b/packages/ethereum-light-client/src/types/sync_committee.rs index 85edaf93..3dcb5aa6 100644 --- a/packages/ethereum-light-client/src/types/sync_committee.rs +++ b/packages/ethereum-light-client/src/types/sync_committee.rs @@ -3,6 +3,7 @@ use alloy_primitives::Bytes; use ethereum_utils::slot::compute_epoch_at_slot; use serde::{Deserialize, Serialize}; +use serde_with::{base64::Base64, serde_as}; use tree_hash_derive::TreeHash; use super::{ @@ -70,10 +71,11 @@ impl TrustedSyncCommittee { } /// The sync committee aggregate +#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] pub struct SyncAggregate { /// The bits representing the sync committee's participation. - #[serde(with = "ethereum_utils::base64")] + #[serde_as(as = "Base64")] pub sync_committee_bits: Bytes, // TODO: Consider changing this to a BitVector /// The aggregated signature of the sync committee. #[serde(with = "ethereum_utils::base64::fixed_size")] diff --git a/packages/ethereum-light-client/src/types/wrappers.rs b/packages/ethereum-light-client/src/types/wrappers.rs index a0dbdf34..c15f4e6a 100644 --- a/packages/ethereum-light-client/src/types/wrappers.rs +++ b/packages/ethereum-light-client/src/types/wrappers.rs @@ -2,6 +2,7 @@ use alloy_primitives::{aliases::B32, Bloom, Bytes, FixedBytes, B256}; use serde::{Deserialize, Serialize}; +use serde_with::{base64::Base64, serde_as}; use tree_hash::{MerkleHasher, TreeHash, BYTES_PER_CHUNK}; use super::bls::BlsPublicKey; @@ -30,8 +31,9 @@ impl TreeHash for WrappedVersion { } /// A wrapper around [`Bytes`] that implements [`TreeHash`] and uses a base64 encoding. +#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)] -pub struct WrappedBytes(#[serde(with = "ethereum_utils::base64")] pub Bytes); +pub struct WrappedBytes(#[serde_as(as = "Base64")] pub Bytes); impl TreeHash for WrappedBytes { fn tree_hash_type() -> tree_hash::TreeHashType { diff --git a/packages/ethereum-utils/src/base64.rs b/packages/ethereum-utils/src/base64.rs index ee9f4942..d7a0809f 100644 --- a/packages/ethereum-utils/src/base64.rs +++ b/packages/ethereum-utils/src/base64.rs @@ -1,6 +1,5 @@ use alloy_primitives::B256; use base64::{prelude::*, DecodeError}; -use serde::{de, Deserialize, Deserializer}; pub trait FromBase64: Sized { fn from_base64(s: &str) -> Result; @@ -17,23 +16,6 @@ impl FromBase64 for B256 { } } -pub fn serialize>(data: T, serializer: S) -> Result -where - S: serde::Serializer, -{ - serializer.serialize_str(&BASE64_STANDARD.encode(data)) -} - -pub fn deserialize<'de, D, T>(deserializer: D) -> Result -where - D: Deserializer<'de>, - T: TryFrom>, -{ - let s = String::deserialize(deserializer)?; - let decoded = from_base64(&s).map_err(de::Error::custom)?; - T::try_from(decoded).map_err(|_| de::Error::custom("Invalid base64 data")) -} - pub mod fixed_size { use base64::prelude::*; use serde::{de, Deserialize, Deserializer}; diff --git a/programs/cw-ics08-wasm-eth/Cargo.toml b/programs/cw-ics08-wasm-eth/Cargo.toml index 08dfaf1b..e715ca4d 100644 --- a/programs/cw-ics08-wasm-eth/Cargo.toml +++ b/programs/cw-ics08-wasm-eth/Cargo.toml @@ -20,6 +20,7 @@ cw-storage-plus = { workspace = true } prost = { workspace = true, features = ["std"] } serde = { workspace = true } serde_json = { workspace = true } +serde_with = { workspace = true, features = ["base64"] } thiserror = { workspace = true } [dev-dependencies] diff --git a/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs b/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs index 27f20a65..e83e01dc 100644 --- a/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs +++ b/programs/cw-ics08-wasm-eth/src/test/fixture_types.rs @@ -4,6 +4,7 @@ use ethereum_light_client::{ types::{height::Height, light_client::Header, storage_proof::StorageProof}, }; use serde::{Deserialize, Serialize}; +use serde_with::{base64::Base64, serde_as}; // TODO: Remove this file once these types are in a separate package #143 @@ -20,9 +21,10 @@ pub struct InitialState { pub consensus_state: ConsensusState, } +#[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] pub struct CommitmentProof { - #[serde(with = "ethereum_utils::base64")] + #[serde_as(as = "Base64")] pub path: Vec, pub storage_proof: StorageProof, pub proof_height: Height,