diff --git a/Cargo.lock b/Cargo.lock index d8c79095ba..1f7de73479 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1460,6 +1460,9 @@ dependencies = [ "hex", "hex-literal", "ics23", + "near-jsonrpc-client 0.10.1", + "near-primitives 0.23.0", + "near-primitives-core 0.23.0", "num-rational 0.4.2", "num_enum", "prost 0.12.6", @@ -1758,7 +1761,7 @@ dependencies = [ ] [[package]] -name = "cometbls-light-client" +name = "cometbls-ics08" version = "0.1.0" dependencies = [ "base64 0.21.7", @@ -1784,6 +1787,23 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "cometbls-near" +version = "0.1.0" +dependencies = [ + "borsh 1.5.1", + "hex", + "hex-literal", + "ibc-vm-rs", + "ics23", + "near-contract-standards", + "near-primitives-core 0.21.2", + "near-sdk", + "near-sdk-contract-tools", + "thiserror", + "unionlabs", +] + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -4623,6 +4643,22 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.5" @@ -5625,10 +5661,10 @@ dependencies = [ "actix", "derive_more", "futures", - "near-async-derive", + "near-async-derive 0.0.0", "near-o11y 0.0.0", - "near-performance-metrics", - "near-time", + "near-performance-metrics 0.0.0", + "near-time 0.0.0", "once_cell", "serde", "serde_json", @@ -5637,6 +5673,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-async" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754fd9af13f1241520e8fc4831ff5c582ee00a6b1221c0cbd8bf43d059ed6e04" +dependencies = [ + "actix", + "derive_more", + "futures", + "near-async-derive 0.23.0", + "near-o11y 0.23.0", + "near-performance-metrics 0.23.0", + "near-time 0.23.0", + "once_cell", + "serde", + "serde_json", + "time", + "tokio", +] + [[package]] name = "near-async-derive" version = "0.0.0" @@ -5647,6 +5703,17 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "near-async-derive" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a47ae519ceed7636e3d9328fd7d1bcbda9a28eccee73315e0a3139e99aa1232" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "near-chain-configs" version = "0.0.0" @@ -5656,7 +5723,7 @@ dependencies = [ "bytesize", "chrono", "derive_more", - "near-async", + "near-async 0.0.0", "near-config-utils 0.0.0", "near-crypto 0.0.0", "near-o11y 0.0.0", @@ -5695,6 +5762,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-chain-configs" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75447355021100158c2e5fc151a0f6728e794b98cb411874f89fc5fb9f386cd2" +dependencies = [ + "anyhow", + "bytesize", + "chrono", + "derive_more", + "near-async 0.23.0", + "near-config-utils 0.23.0", + "near-crypto 0.23.0", + "near-parameters 0.23.0", + "near-primitives 0.23.0", + "num-rational 0.3.2", + "once_cell", + "serde", + "serde_json", + "sha2 0.10.8", + "smart-default", + "time", + "tracing", +] + [[package]] name = "near-config-utils" version = "0.0.0" @@ -5718,6 +5810,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-config-utils" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b3db4ac2d4340caef06b6363c3fd16c0be1f70267908dfa53e2e6241649b0c" +dependencies = [ + "anyhow", + "json_comments", + "thiserror", + "tracing", +] + [[package]] name = "near-contract-standards" version = "5.1.0" @@ -5778,6 +5882,31 @@ dependencies = [ "thiserror", ] +[[package]] +name = "near-crypto" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9807fb257f7dda41383bb33e14cfd4a8840ffa7932cb972db9eabff19ce3bf4" +dependencies = [ + "blake2 0.10.6", + "borsh 1.5.1", + "bs58 0.4.0", + "curve25519-dalek 4.1.3", + "derive_more", + "ed25519-dalek", + "hex", + "near-account-id", + "near-config-utils 0.23.0", + "near-stdx 0.23.0", + "once_cell", + "primitive-types 0.10.1", + "secp256k1", + "serde", + "serde_json", + "subtle 2.5.0", + "thiserror", +] + [[package]] name = "near-fmt" version = "0.0.0" @@ -5795,6 +5924,15 @@ dependencies = [ "near-primitives-core 0.20.1", ] +[[package]] +name = "near-fmt" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00ce363e4078b870775e2a5a5189feae22f0870ca673f6409b1974922dada0c4" +dependencies = [ + "near-primitives-core 0.23.0", +] + [[package]] name = "near-gas" version = "0.2.5" @@ -5834,8 +5972,8 @@ dependencies = [ "ibc-vm-rs", "near-contract-standards", "near-crypto 0.20.1", - "near-jsonrpc-client", - "near-jsonrpc-primitives", + "near-jsonrpc-client 0.8.0", + "near-jsonrpc-primitives 0.20.1", "near-primitives 0.20.1", "near-primitives-core 0.21.2", "near-sdk", @@ -5851,6 +5989,39 @@ dependencies = [ "unionlabs", ] +[[package]] +name = "near-ics08" +version = "0.1.0" +dependencies = [ + "base64 0.21.7", + "borsh 1.5.1", + "bytes", + "cosmwasm-std 1.5.2", + "cw-storage-plus 1.2.0", + "dlmalloc", + "hex", + "ics008-wasm-client", + "lazy_static", + "near-crypto 0.23.0", + "near-jsonrpc-client 0.10.1", + "near-primitives 0.23.0", + "near-primitives-core 0.23.0", + "near-verifier", + "prost 0.12.6", + "protos", + "ripemd", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "serde-utils", + "serde_json", + "sha2 0.10.8", + "sha3", + "thiserror", + "tokio", + "unionlabs", +] + [[package]] name = "near-jsonrpc-client" version = "0.8.0" @@ -5862,7 +6033,7 @@ dependencies = [ "log", "near-chain-configs 0.20.1", "near-crypto 0.20.1", - "near-jsonrpc-primitives", + "near-jsonrpc-primitives 0.20.1", "near-primitives 0.20.1", "reqwest 0.11.27", "serde", @@ -5870,6 +6041,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "near-jsonrpc-client" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b40b427519fcbf71be4de4cab4f6c201aa3e3fb212a4a54976596fa44b05711" +dependencies = [ + "borsh 1.5.1", + "lazy_static", + "log", + "near-chain-configs 0.23.0", + "near-crypto 0.23.0", + "near-jsonrpc-primitives 0.23.0", + "near-primitives 0.23.0", + "reqwest 0.12.5", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "near-jsonrpc-primitives" version = "0.20.1" @@ -5886,6 +6076,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "near-jsonrpc-primitives" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30db3829a8880847d7f3f64828f11133ba9102d2df382a2d916ec1553270df" +dependencies = [ + "arbitrary", + "near-chain-configs 0.23.0", + "near-crypto 0.23.0", + "near-primitives 0.23.0", + "near-rpc-error-macro 0.23.0", + "serde", + "serde_json", + "thiserror", + "time", +] + [[package]] name = "near-light-client" version = "0.1.0" @@ -5898,6 +6105,7 @@ dependencies = [ "near-sdk", "near-sdk-contract-tools", "near-units", + "near-verifier", "serde", "serde_json", "tokio", @@ -5958,6 +6166,33 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "near-o11y" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2847f81f7a4c996d583c377f8196a8d05d74ee7535678c20beef0f80304c48b1" +dependencies = [ + "actix", + "base64 0.21.7", + "clap 4.5.4", + "near-crypto 0.23.0", + "near-primitives-core 0.23.0", + "once_cell", + "opentelemetry 0.22.0", + "opentelemetry-otlp 0.15.0", + "opentelemetry-semantic-conventions 0.14.0", + "opentelemetry_sdk", + "prometheus", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-appender", + "tracing-opentelemetry 0.23.0", + "tracing-subscriber", +] + [[package]] name = "near-parameters" version = "0.0.0" @@ -5994,6 +6229,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "near-parameters" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fddf39f5f729976a791d86e0e30a71ec4d8e8dcf58117c8694e7b22fb3f50ee6" +dependencies = [ + "borsh 1.5.1", + "enum-map", + "near-account-id", + "near-primitives-core 0.23.0", + "num-rational 0.3.2", + "serde", + "serde_repr", + "serde_yaml", + "strum 0.24.1", + "thiserror", +] + [[package]] name = "near-performance-metrics" version = "0.0.0" @@ -6010,6 +6263,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-performance-metrics" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42166c93c35457d16a3a6c7b9b511bea790bcb4a35a41d4c5218b1fb9a46702a" +dependencies = [ + "actix", + "bitflags 1.3.2", + "bytes", + "futures", + "libc", + "once_cell", + "tokio", + "tokio-util 0.7.10", + "tracing", +] + [[package]] name = "near-primitives" version = "0.0.0" @@ -6033,7 +6303,7 @@ dependencies = [ "near-primitives-core 0.0.0", "near-rpc-error-macro 0.0.0", "near-stdx 0.0.0", - "near-time", + "near-time 0.0.0", "num-rational 0.3.2", "once_cell", "primitive-types 0.10.1", @@ -6093,6 +6363,48 @@ dependencies = [ "tracing", ] +[[package]] +name = "near-primitives" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c175262923db9885ed0347e96ec3bcbec57825e3b6d7de03da220f5e14ef5" +dependencies = [ + "arbitrary", + "base64 0.21.7", + "borsh 1.5.1", + "bytes", + "bytesize", + "cfg-if 1.0.0", + "chrono", + "derive_more", + "easy-ext", + "enum-map", + "hex", + "itertools 0.10.5", + "near-crypto 0.23.0", + "near-fmt 0.23.0", + "near-parameters 0.23.0", + "near-primitives-core 0.23.0", + "near-rpc-error-macro 0.23.0", + "near-stdx 0.23.0", + "near-time 0.23.0", + "num-rational 0.3.2", + "once_cell", + "primitive-types 0.10.1", + "rand 0.8.5", + "rand_chacha 0.3.1", + "reed-solomon-erasure 4.0.2", + "serde", + "serde_json", + "serde_with", + "sha3", + "smart-default", + "strum 0.24.1", + "thiserror", + "tracing", + "zstd 0.13.1", +] + [[package]] name = "near-primitives-core" version = "0.0.0" @@ -6156,6 +6468,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "near-primitives-core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45de00d413f5bb890a3912f32fcd0974b2b0a975cc7874012e2c4c4fa7f28917" +dependencies = [ + "arbitrary", + "base64 0.21.7", + "borsh 1.5.1", + "bs58 0.4.0", + "derive_more", + "enum-map", + "near-account-id", + "num-rational 0.3.2", + "serde", + "serde_repr", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "near-rpc-error-core" version = "0.0.0" @@ -6177,6 +6509,17 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "near-rpc-error-core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf41b149dcc1f5a35d6a96fbcd8c28c92625c05b52025a72ee7378c72bcd68ce" +dependencies = [ + "quote", + "serde", + "syn 2.0.60", +] + [[package]] name = "near-rpc-error-macro" version = "0.0.0" @@ -6199,6 +6542,17 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "near-rpc-error-macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c7f0f12f426792dd2c9d83df43d73c3b15d80f6e99e8d0e16ff3e024d0f9ba" +dependencies = [ + "near-rpc-error-core 0.23.0", + "serde", + "syn 2.0.60", +] + [[package]] name = "near-sandbox-utils" version = "0.7.1" @@ -6288,6 +6642,12 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "855fd5540e3b4ff6fedf12aba2db1ee4b371b36f465da1363a6d022b27cb43b8" +[[package]] +name = "near-stdx" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e1897481272eb144328abd51ca9f59b5b558e7a6dc6e2177c8c9bb18fbd818" + [[package]] name = "near-store" version = "0.0.0" @@ -6306,7 +6666,7 @@ dependencies = [ "itertools 0.10.5", "itoa", "lru 0.12.3", - "near-async", + "near-async 0.0.0", "near-chain-configs 0.0.0", "near-crypto 0.0.0", "near-fmt 0.0.0", @@ -6349,6 +6709,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "near-time" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56db32f26b089441c1a7c5451f0d68637afa9d66f6d8f6a6f2d6c2f7953520a" +dependencies = [ + "once_cell", + "serde", + "time", + "tokio", +] + [[package]] name = "near-token" version = "0.2.0" @@ -6392,6 +6764,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "near-verifier" +version = "0.1.0" +dependencies = [ + "borsh 1.5.1", + "near-account-id", + "near-primitives-core 0.23.0", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", + "unionlabs", +] + [[package]] name = "near-vm-runner" version = "0.0.0" @@ -6467,8 +6853,8 @@ dependencies = [ "near-account-id", "near-crypto 0.20.1", "near-gas", - "near-jsonrpc-client", - "near-jsonrpc-primitives", + "near-jsonrpc-client 0.8.0", + "near-jsonrpc-primitives 0.20.1", "near-primitives 0.20.1", "near-sandbox-utils", "near-token", @@ -7406,6 +7792,34 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poc-relayer" +version = "0.1.0" +dependencies = [ + "base64 0.21.7", + "borsh 1.5.1", + "chain-utils", + "cometbft-rpc", + "hex", + "ibc-vm-rs", + "near-crypto 0.23.0", + "near-jsonrpc-client 0.10.1", + "near-jsonrpc-primitives 0.23.0", + "near-primitives 0.23.0", + "near-primitives-core 0.23.0", + "near-verifier", + "num-bigint 0.4.4", + "prost 0.12.6", + "protos", + "serde", + "serde_json", + "tendermint", + "tendermint-proto", + "tendermint-rpc", + "tokio", + "unionlabs", +] + [[package]] name = "portable-atomic" version = "1.6.0" @@ -8186,7 +8600,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.28", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -8230,11 +8644,13 @@ dependencies = [ "http-body-util", "hyper 1.3.1", "hyper-rustls 0.27.2", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -8247,6 +8663,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.0", "tower-service", "url", @@ -10034,6 +10451,35 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-circuit" +version = "0.1.0" +dependencies = [ + "base64 0.21.7", + "borsh 1.5.1", + "env_logger", + "hex", + "hex-literal", + "ibc-vm-rs", + "near-contract-standards", + "near-crypto 0.20.1", + "near-jsonrpc-client 0.8.0", + "near-jsonrpc-primitives 0.20.1", + "near-primitives 0.20.1", + "near-primitives-core 0.21.2", + "near-sdk", + "near-sdk-contract-tools", + "near-store", + "near-units", + "near-workspaces", + "serde", + "serde_json", + "sha2 0.10.8", + "time", + "tokio", + "unionlabs", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -11071,8 +11517,8 @@ dependencies = [ "k256", "macros", "milagro_bls", - "near-primitives-core 0.21.2", - "near-sdk", + "near-account-id", + "near-primitives-core 0.23.0", "paste", "primitive-types 0.12.2", "prost 0.12.6", diff --git a/Cargo.toml b/Cargo.toml index 225312a8d1..dd69b7f5e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ members = [ "light-clients/arbitrum-light-client", "light-clients/berachain-light-client", - "light-clients/cometbls-light-client", + "light-clients/cometbls/ics08", "light-clients/ethereum-light-client", "light-clients/scroll-light-client", "light-clients/tendermint-light-client", @@ -70,11 +70,16 @@ members = [ "unionvisor", "voyager", "near/near-ibc", - "near/near-light-client", + "light-clients/near/near", "near/dummy-ibc-app", - "near/near-ibc-tests", "drip", + "near/near-ibc-tests", + "light-clients/near/ics08-near", + "lib/near-verifier", + "light-clients/cometbls/near", + "poc-relayer", + "near/test-circuit", ] [workspace.package] @@ -110,6 +115,7 @@ ics23 = { path = "lib/ics23", default-features = false } linea-verifier = { path = "lib/linea-verifier", default-features = false } linea-zktrie = { path = "lib/linea-zktrie", default-features = false } macros = { path = "lib/macros", default-features = false } +near-verifier = { path = "lib/near-verifier", default-features = false } pg-queue = { path = "lib/pg-queue", default-features = false } poseidon-rs = { path = "lib/poseidon-rs", default-features = false } protos = { path = "generated/rust/protos", default-features = false } @@ -172,6 +178,7 @@ go-parse-duration = { version = "0.1.1", default-features = false } hex = { version = "0.4.3", default-features = false } hex-literal = { version = "0.4.1", default-features = false } lazy_static = { version = "1.4.0", default-features = false } +near-account-id = { version = "1.0.0", default-features = false } near-contract-standards = { version = "5.1.0", default-features = false } near-sdk = { version = "5.1.0", default-features = false } near-sdk-contract-tools = { version = "3.0.2", default-features = false } diff --git a/flake.nix b/flake.nix index 0378422c72..b40592f0ce 100644 --- a/flake.nix +++ b/flake.nix @@ -257,7 +257,7 @@ ./site/openapi.nix ./near/near.nix ./light-clients/ethereum-light-client/ethereum-light-client.nix - ./light-clients/cometbls-light-client/cometbls-light-client.nix + ./light-clients/cometbls/cometbls-light-client.nix ./light-clients/tendermint-light-client/tendermint-light-client.nix ./light-clients/scroll-light-client/scroll-light-client.nix ./light-clients/arbitrum-light-client/arbitrum-light-client.nix @@ -333,11 +333,13 @@ goPkgs = import inputs.nixpkgs-go { inherit system; }; unstablePkgs = import inputs.nixpkgs-unstable { inherit system; }; + packageOverrides = pkgs.callPackage ./python-packages.nix { }; + python = pkgs.python3.override { inherit packageOverrides; }; in { _module = { args = { - inherit nixpkgs dbg get-flake uniondBundleVersions goPkgs unstablePkgs mkCi; + inherit nixpkgs dbg get-flake uniondBundleVersions goPkgs unstablePkgs mkCi python; gitRev = if (builtins.hasAttr "rev" self) then self.rev else "dirty"; @@ -525,13 +527,26 @@ postgresql emmet-language-server nodePackages.graphqurl + nodePackages_latest.near-cli nodePackages_latest.nodejs nodePackages_latest.svelte-language-server nodePackages_latest."@astrojs/language-server" nodePackages_latest."@tailwindcss/language-server" nodePackages_latest.typescript-language-server nodePackages_latest.vscode-langservers-extracted - ]) + ]) ++ + (with pkgs; [ + go + gopls + go-tools + gotools + ]) + ++ [ + (python.withPackages (py-pkgs: [ + py-pkgs.nearup + ])) + ] + ++ (with goPkgs; [ go gopls diff --git a/generated/rust/protos/Cargo.toml b/generated/rust/protos/Cargo.toml index 9873504b70..92d0a7a73c 100644 --- a/generated/rust/protos/Cargo.toml +++ b/generated/rust/protos/Cargo.toml @@ -283,6 +283,7 @@ proto_full = [ "union+ibc+lightclients+cometbls+v1", "union+ibc+lightclients+ethereum+v1", "union+ibc+lightclients+linea+v1", + "union+ibc+lightclients+near+v1", "union+ibc+lightclients+scroll+v1", "union+ics23+v1", "union+staking+v1", @@ -326,6 +327,7 @@ proto_full = [ ] "union+ibc+lightclients+ethereum+v1" = ["ibc+core+client+v1"] "union+ibc+lightclients+linea+v1" = ["ibc+core+client+v1", "union+ibc+lightclients+ethereum+v1"] +"union+ibc+lightclients+near+v1" = [] "union+ibc+lightclients+scroll+v1" = ["ibc+core+client+v1", "union+ibc+lightclients+ethereum+v1"] "union+ics23+v1" = [] "union+staking+v1" = ["cosmos+staking+v1beta1"] diff --git a/generated/rust/protos/src/lib.rs b/generated/rust/protos/src/lib.rs index c507d2f05a..f4bf147b0e 100644 --- a/generated/rust/protos/src/lib.rs +++ b/generated/rust/protos/src/lib.rs @@ -891,6 +891,14 @@ pub mod union { // @@protoc_insertion_point(union.ibc.lightclients.linea.v1) } } + pub mod near { + #[cfg(feature = "union+ibc+lightclients+near+v1")] + // @@protoc_insertion_point(attribute:union.ibc.lightclients.near.v1) + pub mod v1 { + include!("union.ibc.lightclients.near.v1.rs"); + // @@protoc_insertion_point(union.ibc.lightclients.near.v1) + } + } pub mod scroll { #[cfg(feature = "union+ibc+lightclients+scroll+v1")] // @@protoc_insertion_point(attribute:union.ibc.lightclients.scroll.v1) diff --git a/generated/rust/protos/src/union.ibc.lightclients.near.v1.rs b/generated/rust/protos/src/union.ibc.lightclients.near.v1.rs new file mode 100644 index 0000000000..580758e681 --- /dev/null +++ b/generated/rust/protos/src/union.ibc.lightclients.near.v1.rs @@ -0,0 +1,190 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Header { + #[prost(message, optional, tag = "1")] + pub new_state: ::core::option::Option, + #[prost(uint64, tag = "2")] + pub trusted_height: u64, + #[prost(message, repeated, tag = "3")] + pub prev_state_root_proof: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "4")] + pub prev_state_root: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for Header { + const NAME: &'static str = "Header"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MerklePathItem { + #[prost(bytes = "vec", tag = "1")] + pub hash: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "2")] + pub direction: u64, +} +impl ::prost::Name for MerklePathItem { + const NAME: &'static str = "MerklePathItem"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LightClientBlockView { + #[prost(bytes = "vec", tag = "1")] + pub prev_block_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "2")] + pub next_block_inner_hash: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "3")] + pub inner_lite: ::core::option::Option, + #[prost(bytes = "vec", tag = "4")] + pub inner_rest_hash: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "5")] + pub next_bps: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "6")] + pub approvals_after_next: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for LightClientBlockView { + const NAME: &'static str = "LightClientBlockView"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Signature { + #[prost(oneof = "signature::Signature", tags = "1, 2")] + pub signature: ::core::option::Option, +} +/// Nested message and enum types in `Signature`. +pub mod signature { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Signature { + #[prost(bytes, tag = "1")] + Ed25519(::prost::alloc::vec::Vec), + #[prost(bytes, tag = "2")] + Secp256k1(::prost::alloc::vec::Vec), + } +} +impl ::prost::Name for Signature { + const NAME: &'static str = "Signature"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ValidatorStakeView { + #[prost(string, tag = "1")] + pub account_id: ::prost::alloc::string::String, + /// TODO(aeryz): u128 + #[prost(bytes = "vec", tag = "4")] + pub balance: ::prost::alloc::vec::Vec, + #[prost(oneof = "validator_stake_view::PublicKey", tags = "2, 3")] + pub public_key: ::core::option::Option, +} +/// Nested message and enum types in `ValidatorStakeView`. +pub mod validator_stake_view { + #[allow(clippy::derive_partial_eq_without_eq)] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum PublicKey { + #[prost(bytes, tag = "2")] + Ed25519(::prost::alloc::vec::Vec), + #[prost(bytes, tag = "3")] + Secp256k1(::prost::alloc::vec::Vec), + } +} +impl ::prost::Name for ValidatorStakeView { + const NAME: &'static str = "ValidatorStakeView"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PublicKey { + #[prost(bytes = "vec", tag = "1")] + pub ed25519: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "2")] + pub secp256k1: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for PublicKey { + const NAME: &'static str = "PublicKey"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockHeaderInnerLiteView { + #[prost(uint64, tag = "1")] + pub height: u64, + #[prost(bytes = "vec", tag = "2")] + pub epoch_id: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "3")] + pub next_epoch_id: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "4")] + pub prev_state_root: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "5")] + pub outcome_root: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "6")] + pub timestamp: u64, + #[prost(uint64, tag = "7")] + pub timestamp_nanosec: u64, + #[prost(bytes = "vec", tag = "8")] + pub next_bp_hash: ::prost::alloc::vec::Vec, + #[prost(bytes = "vec", tag = "9")] + pub block_merkle_root: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for BlockHeaderInnerLiteView { + const NAME: &'static str = "BlockHeaderInnerLiteView"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ClientState { + #[prost(string, tag = "1")] + pub chain_id: ::prost::alloc::string::String, + #[prost(uint64, tag = "2")] + pub latest_height: u64, + #[prost(string, tag = "3")] + pub account_id: ::prost::alloc::string::String, + #[prost(message, repeated, tag = "4")] + pub iniitial_block_producers: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for ClientState { + const NAME: &'static str = "ClientState"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConsensusState { + #[prost(message, optional, tag = "1")] + pub state: ::core::option::Option, + #[prost(bytes = "vec", tag = "2")] + pub chunk_prev_state_root: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for ConsensusState { + const NAME: &'static str = "ConsensusState"; + const PACKAGE: &'static str = "union.ibc.lightclients.near.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("union.ibc.lightclients.near.v1.{}", Self::NAME) + } +} +// @@protoc_insertion_point(module) diff --git a/lib/block-message/src/chain.rs b/lib/block-message/src/chain.rs index 72e437e309..7e2ab83fbd 100644 --- a/lib/block-message/src/chain.rs +++ b/lib/block-message/src/chain.rs @@ -114,6 +114,7 @@ pub mod arbitrum; pub mod berachain; pub mod cosmos; pub mod ethereum; +pub mod near; pub mod scroll; pub mod union; diff --git a/lib/block-message/src/chain/near.rs b/lib/block-message/src/chain/near.rs new file mode 100644 index 0000000000..2ac282ac3b --- /dev/null +++ b/lib/block-message/src/chain/near.rs @@ -0,0 +1,32 @@ +use chain_utils::near::Near; +use queue_msg::Op; +use unionlabs::never::Never; + +use crate::{ + fetch::{AnyFetch, DoFetchBlockRange, Fetch, FetchBlockRange}, + AnyChainIdentified, BlockMessage, ChainExt, Identified, +}; + +impl ChainExt for Near { + type Data = Never; + + type Fetch = Never; + + type Aggregate = Never; +} + +impl DoFetchBlockRange for Near +where + AnyChainIdentified: From>>, +{ + fn fetch_block_range(c: &Near, range: FetchBlockRange) -> Op { + // fetch(id( + // c.chain_id(), + // Fetch::::specific(FetchEvents { + // from_height: range.from_height, + // to_height: range.to_height, + // }), + // )) + todo!() + } +} diff --git a/lib/block-message/src/fetch.rs b/lib/block-message/src/fetch.rs index 94f2a37325..30dd0147d6 100644 --- a/lib/block-message/src/fetch.rs +++ b/lib/block-message/src/fetch.rs @@ -7,7 +7,7 @@ use queue_msg::{ aggregate, conc, fetch, queue_msg, wait, HandleFetch, Op, QueueError, QueueMessage, }; use tracing::instrument; -use unionlabs::ibc::core::client::height::IsHeight; +use unionlabs::{ibc::core::client::height::IsHeight, never::Never}; use crate::{ aggregate::{Aggregate, AggregateFetchBlockRange, AnyAggregate}, @@ -94,6 +94,12 @@ pub trait DoFetch: Sized + Debug + Clone + PartialEq { fn do_fetch(c: &C, _: Self) -> impl Future>; } +impl DoFetch for Never { + fn do_fetch(_: &C, this: Self) -> impl Future> { + async move { match this {} } + } +} + pub trait DoFetchBlockRange: ChainExt { fn fetch_block_range(c: &C, range: FetchBlockRange) -> Op; } diff --git a/lib/block-message/src/lib.rs b/lib/block-message/src/lib.rs index 315f0c32a4..bc88ff5bb1 100644 --- a/lib/block-message/src/lib.rs +++ b/lib/block-message/src/lib.rs @@ -3,8 +3,8 @@ use std::{collections::VecDeque, fmt::Debug}; use chain_utils::{ - arbitrum::Arbitrum, berachain::Berachain, cosmos::Cosmos, ethereum::Ethereum, scroll::Scroll, - union::Union, Chains, + arbitrum::Arbitrum, berachain::Berachain, cosmos::Cosmos, ethereum::Ethereum, near::Near, + scroll::Scroll, union::Union, Chains, }; use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use queue_msg::{Op, OpT, QueueMessage}; @@ -66,6 +66,7 @@ pub enum AnyChainIdentified { Scroll(Identified>), Arbitrum(Identified>), Berachain(Identified>), + Near(Identified>), } impl AnyChainIdentified { @@ -220,6 +221,12 @@ pub trait DoAggregate: Sized + Debug + Clone + PartialEq { fn do_aggregate(_: Self, _: VecDeque>) -> Op; } +impl DoAggregate for Identified { + fn do_aggregate(this: Self, _: VecDeque>) -> Op { + match this.t {} + } +} + macro_rules! any_chain { (|$msg:ident| $expr:expr) => { match $msg { @@ -230,6 +237,7 @@ macro_rules! any_chain { AnyChainIdentified::Scroll($msg) => $expr, AnyChainIdentified::Arbitrum($msg) => $expr, AnyChainIdentified::Berachain($msg) => $expr, + AnyChainIdentified::Near($msg) => $expr, } }; } diff --git a/lib/chain-utils/Cargo.toml b/lib/chain-utils/Cargo.toml index 8557af1ca3..773f96272c 100644 --- a/lib/chain-utils/Cargo.toml +++ b/lib/chain-utils/Cargo.toml @@ -12,7 +12,7 @@ beacon-api = { workspace = true } contracts = { workspace = true, features = ["providers"] } protos = { workspace = true, features = ["default", "client"] } serde-utils = { workspace = true } -unionlabs = { workspace = true, features = ["default"] } +unionlabs = { workspace = true, features = ["default", "near"] } arbitrary = { workspace = true, optional = true, features = ["derive"] } bip32 = { workspace = true, features = ["secp256k1"] } @@ -26,6 +26,9 @@ frame-support-procedural = { workspace = true } futures = { workspace = true } hex = { workspace = true } ics23 = { workspace = true } +near-jsonrpc-client = "0.10.1" +near-primitives = "0.23.0" +near-primitives-core = "0.23.0" num-rational = "0.4.2" num_enum = "0.7.0" prost = { workspace = true } diff --git a/lib/chain-utils/src/lib.rs b/lib/chain-utils/src/lib.rs index 2abde00def..1c6ef01108 100644 --- a/lib/chain-utils/src/lib.rs +++ b/lib/chain-utils/src/lib.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use enumorph::Enumorph; +use near::{Near, NearInitError}; use serde::{Deserialize, Serialize}; use tracing::warn; use unionlabs::{ @@ -29,6 +30,7 @@ pub mod arbitrum; pub mod berachain; pub mod cosmos; pub mod ethereum; +pub mod near; pub mod scroll; pub mod union; @@ -158,6 +160,15 @@ impl GetChain> for Chains { } } +impl GetChain for Chains { + fn get_chain(&self, chain_id: &ChainIdOf) -> Option { + self.chains + .get(&chain_id.to_string()) + .cloned() + .map(|chain| chain.try_into().expect("chain is correct type")) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "chain_type")] pub enum ChainConfigType { @@ -167,6 +178,7 @@ pub enum ChainConfigType { Scroll(scroll::Config), Arbitrum(arbitrum::Config), Berachain(berachain::Config), + Near(near::Config), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -199,6 +211,7 @@ pub enum AnyChain { Scroll(Scroll), Arbitrum(Arbitrum), Berachain(Berachain), + Near(Near), } impl AnyChain { @@ -268,6 +281,11 @@ macro_rules! any_chain { type Hc = $crate::berachain::Berachain; $expr } + AnyChain::Near($c) => { + #[allow(dead_code)] + type Hc = $crate::near::Near; + $expr + } } }; } @@ -286,6 +304,8 @@ pub enum AnyChainTryFromConfigError { Arbitrum(#[from] ArbitrumInitError), #[error("error initializing a berachain chain")] Berachain(#[from] BerachainInitError), + #[error("error initializing a near chain")] + Near(#[from] NearInitError), } impl AnyChain { @@ -318,6 +338,7 @@ impl AnyChain { ChainConfigType::Berachain(berachain) => { Self::Berachain(Berachain::new(berachain).await?) } + ChainConfigType::Near(near) => Self::Near(Near::new(near).await?), }) } } @@ -374,3 +395,11 @@ impl LightClientType> for Wasm { impl LightClientType> for Wasm { const TYPE: ClientType = ClientType::Wasm(WasmClientType::EthereumMinimal); } + +impl LightClientType for Wasm { + const TYPE: ClientType = ClientType::Wasm(WasmClientType::Near); +} + +impl LightClientType> for Near { + const TYPE: ClientType = ClientType::Cometbls; +} diff --git a/lib/chain-utils/src/near.rs b/lib/chain-utils/src/near.rs new file mode 100644 index 0000000000..7350b6e0a9 --- /dev/null +++ b/lib/chain-utils/src/near.rs @@ -0,0 +1,224 @@ +use near_jsonrpc_client::methods::{self, status}; +use near_primitives::types::{AccountId, BlockId, BlockReference, Finality}; +use serde::{Deserialize, Serialize}; +use unionlabs::{ + encoding::Borsh, + ibc::{core::client::height::Height, lightclients::near}, + id::ClientId, + near::raw_state_proof::RawStateProof, + traits::{Chain, ChainIdOf, FromStrExact}, +}; + +use crate::keyring::{ChainKeyring, ConcurrentKeyring}; + +pub const NEAR_REVISION_NUMBER: u64 = 0; + +#[derive(Debug, Clone)] +pub struct Near { + // ??? + rpc: near_jsonrpc_client::JsonRpcClient, + chain_id: String, + ibc_account_id: AccountId, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + rpc_url: String, + ibc_account_id: AccountId, +} + +impl ChainKeyring for Near { + type Address = String; + + // TODO(aeryz): temporary hack for near + type Signer = unionlabs::signer::CosmosSigner; + + fn keyring(&self) -> &ConcurrentKeyring { + unimplemented!() + } + + async fn balances(&self) -> Vec> { + unimplemented!() + } +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum NearInitError { + // TODO(aeryz): add error context? + #[error("rpc error")] + RpcError, +} + +impl Near { + pub async fn new(config: Config) -> Result { + let rpc = near_jsonrpc_client::JsonRpcClient::connect(config.rpc_url); + let chain_id = rpc + .call(status::RpcStatusRequest) + .await + .map_err(|_| NearInitError::RpcError)? + .chain_id; + + Ok(Self { + rpc, + chain_id, + ibc_account_id: config.ibc_account_id, + }) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct NearChainType; + +impl FromStrExact for NearChainType { + const EXPECTING: &'static str = "near"; +} + +impl Chain for Near { + type ChainType = NearChainType; + + type SelfClientState = near::client_state::ClientState; + type SelfConsensusState = near::consensus_state::ConsensusState; + type Header = near::header::Header; + + type StoredClientState = Tr::SelfClientState; + type StoredConsensusState = Tr::SelfConsensusState; + + type Height = Height; + type ClientId = ClientId; + type IbcStateEncoding = Borsh; + type StateProof = RawStateProof; + type ClientType = String; + + type Error = Box; + + fn chain_id(&self) -> ChainIdOf { + self.chain_id.clone() + } + + async fn query_latest_height(&self) -> Result { + self.rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(Finality::Final), + }) + .await + .map(|x| Height { + revision_number: NEAR_REVISION_NUMBER, + revision_height: x.header.height, + }) + .map_err(|x| Box::new(x) as _) + } + + async fn query_latest_height_as_destination(&self) -> Result { + self.query_latest_height().await + } + + async fn query_latest_timestamp(&self) -> Result { + self.rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(Finality::Final), + }) + .await + .map(|x| x.header.timestamp_nanosec.try_into().expect("idk bro")) + .map_err(|x| Box::new(x) as _) + } + + async fn self_client_state(&self, height: Self::Height) -> Self::SelfClientState { + let block = self + .rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::BlockId(BlockId::Height(height.revision_height)), + }) + .await + .unwrap(); + + let validators = self + .rpc + .call( + methods::EXPERIMENTAL_validators_ordered::RpcValidatorsOrderedRequest { + block_id: Some(BlockId::Height(block.header.height)), + }, + ) + .await + .unwrap(); + + near::client_state::ClientState { + chain_id: self.chain_id.clone(), + latest_height: block.header.height - 1, + ibc_account_id: self.ibc_account_id.clone(), + initial_block_producers: convert_block_producers(validators), + frozen_height: 0, + } + } + + async fn self_consensus_state(&self, height: Self::Height) -> Self::SelfConsensusState { + let block = self + .rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::BlockId(BlockId::Height(height.revision_height)), + }) + .await + .unwrap(); + + let chunk_prev_state_root = block.header.prev_state_root; + let timestamp = block.header.timestamp_nanosec; + + near::consensus_state::ConsensusState { + state: block_header_to_inner_lite(block.header), + chunk_prev_state_root, + timestamp, + } + } +} + +pub fn convert_block_producers( + bps: Vec, +) -> Vec { + bps.into_iter() + .map(|stake| { + let near_primitives::views::validator_stake_view::ValidatorStakeView::V1(stake) = stake; + let stake = near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: unionlabs::near::types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ); + stake + }) + .collect() +} + +// pub fn convert_block_header_inner( +// block_view: near_primitives::views::BlockHeaderInnerLiteView, +// ) -> near::block_header_inner::BlockHeaderInnerLiteView { +// near::block_header_inner::BlockHeaderInnerLiteView { +// height: block_view.height, +// epoch_id: near_primitives_core::CryptoHash(block_view.epoch_id.0), +// next_epoch_id: near_primitives_core::CryptoHash(block_view.next_epoch_id.0), +// prev_state_root: near_primitives_core::CryptoHash(block_view.prev_state_root.0), +// outcome_root: near_primitives_core::CryptoHash(block_view.outcome_root.0), +// timestamp: block_view.timestamp, +// timestamp_nanosec: block_view.timestamp_nanosec, +// next_bp_hash: block_view.next_bp_hash.0.into(), +// block_merkle_root: near_primitives_core::CryptoHash(block_view.block_merkle_root.0), +// } +// } + +pub fn block_header_to_inner_lite( + header: near_primitives::views::BlockHeaderView, +) -> near::block_header_inner::BlockHeaderInnerLiteView { + use near_primitives_core::hash::CryptoHash; + near::block_header_inner::BlockHeaderInnerLiteView { + height: header.height, + epoch_id: CryptoHash(header.epoch_id.0), + next_epoch_id: CryptoHash(header.next_epoch_id.0), + prev_state_root: CryptoHash(header.prev_state_root.0), + outcome_root: CryptoHash(header.outcome_root.0), + timestamp: header.timestamp, + timestamp_nanosec: header.timestamp_nanosec, + next_bp_hash: CryptoHash(header.next_bp_hash.0), + block_merkle_root: CryptoHash(header.block_merkle_root.0), + } +} diff --git a/lib/cometbls-groth16-verifier/src/lib.rs b/lib/cometbls-groth16-verifier/src/lib.rs index 63c2118375..e3e416b054 100644 --- a/lib/cometbls-groth16-verifier/src/lib.rs +++ b/lib/cometbls-groth16-verifier/src/lib.rs @@ -282,6 +282,7 @@ fn verify_generic_zkp_2( .chain_update(trusted_validators_hash) .finalize(), ); + // println!("commitment: {:?}", commitment_hash.to_be_bytes()); // drop the most significant byte to fit in bn254 F_r inputs_hash[0] = 0; let public_inputs: [substrate_bn::Fr; NB_PUBLIC_INPUTS] = [ @@ -460,4 +461,31 @@ mod tests { Err(Error::InvalidProof) ); } + + #[test] + fn print_consts() { + let mut buffer = [0u8; 64]; + GAMMA_ABC_G1[2] + .x() + .to_big_endian(&mut buffer[0..32]) + .unwrap(); + GAMMA_ABC_G1[2] + .y() + .to_big_endian(&mut buffer[32..64]) + .unwrap(); + buffer.reverse(); + println!( + "{}", + format!( + r#" + G1Affine {{ + x: Fq({:?}), + y: Fq({:?}), + }} + "#, + &buffer[32..64], + &buffer[0..32], + ) + ); + } } diff --git a/lib/ibc-vm-rs/src/lib.rs b/lib/ibc-vm-rs/src/lib.rs index c91c36d7cb..c7e95f29d4 100644 --- a/lib/ibc-vm-rs/src/lib.rs +++ b/lib/ibc-vm-rs/src/lib.rs @@ -44,8 +44,8 @@ pub enum IbcError { UnexpectedAction, // TODO(aeryz): this needs context - #[error("client message verification failed")] - ClientMessageVerificationFailed, + #[error("client message verification failed: {0}")] + ClientMessageVerificationFailed(String), #[error("connection ({0}) not found")] ConnectionNotFound(String), @@ -193,7 +193,7 @@ pub enum IbcResponse { valid: bool, }, VerifyClientMessage { - valid: bool, + error: Option, }, CheckForMisbehaviour { misbehaviour_found: bool, diff --git a/lib/ibc-vm-rs/src/states/client_state.rs b/lib/ibc-vm-rs/src/states/client_state.rs index a8239c46f5..57db8772c5 100644 --- a/lib/ibc-vm-rs/src/states/client_state.rs +++ b/lib/ibc-vm-rs/src/states/client_state.rs @@ -67,14 +67,16 @@ impl Runnable for UpdateClient { client_id, client_msg, }, - &[IbcResponse::Status { status }, IbcResponse::VerifyClientMessage { valid }, IbcResponse::CheckForMisbehaviour { misbehaviour_found }], + &[IbcResponse::Status { status }, IbcResponse::VerifyClientMessage { error }, IbcResponse::CheckForMisbehaviour { misbehaviour_found }], ) => { if *status != Status::Active { return Err(IbcError::NotActive(client_id, *status).into()); } - if !valid { - return Err(IbcError::ClientMessageVerificationFailed.into()); + + if let Some(error) = error { + return Err(IbcError::ClientMessageVerificationFailed(error.clone()).into()); } + if *misbehaviour_found { Either::Left(( Self::UpdatedStateOnMisbehaviour { diff --git a/lib/macros/src/lib.rs b/lib/macros/src/lib.rs index 12d688aa97..386a8aae8d 100644 --- a/lib/macros/src/lib.rs +++ b/lib/macros/src/lib.rs @@ -655,6 +655,7 @@ pub fn model(meta: TokenStream, ts: TokenStream) -> TokenStream { proto, ethabi, no_serde, + borsh, } = syn::parse_macro_input!(meta as Model); let output = match &item { @@ -697,6 +698,12 @@ pub fn model(meta: TokenStream, ts: TokenStream) -> TokenStream { } }); + let borsh = borsh.then(|| { + quote! { + #[derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize)] + } + }); + quote! { #[derive( #debug_derive_crate::Debug, @@ -705,6 +712,7 @@ pub fn model(meta: TokenStream, ts: TokenStream) -> TokenStream { )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #serde + #borsh } } Item::Struct(ItemStruct { fields, attrs, .. }) => { @@ -726,6 +734,12 @@ pub fn model(meta: TokenStream, ts: TokenStream) -> TokenStream { } }); + let borsh = borsh.then(|| { + quote! { + #[derive(::borsh::BorshSerialize, ::borsh::BorshDeserialize)] + } + }); + quote! { #[derive( #debug_derive_crate::Debug, @@ -734,6 +748,7 @@ pub fn model(meta: TokenStream, ts: TokenStream) -> TokenStream { )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #serde + #borsh } } _ => panic!(), @@ -752,18 +767,20 @@ struct Model { proto: Option, ethabi: Option, no_serde: bool, + borsh: bool, } impl Parse for Model { fn parse(input: syn::parse::ParseStream) -> syn::Result { const INVALID_ATTR_MSG: &str = - "invalid attribute, valid attributes are `no_serde`, `proto(...)` and `ethabi(...)`"; + "invalid attribute, valid attributes are `borsh`, `no_serde`, `proto(...)` and `ethabi(...)`"; let meta = >::parse_terminated(input)?; let mut proto = None; let mut ethabi = None; let mut no_serde = false; + let mut borsh = false; for meta in meta { match meta { @@ -805,6 +822,16 @@ impl Parse for Model { no_serde = true; } } + "borsh" => { + if borsh { + return Err(syn::Error::new_spanned( + path, + "duplicate `borsh` attribute", + )); + } else { + borsh = true; + } + } _ => return Err(syn::Error::new_spanned(path, INVALID_ATTR_MSG)), }, _ => return Err(syn::Error::new_spanned(meta, INVALID_ATTR_MSG)), @@ -815,6 +842,7 @@ impl Parse for Model { proto, ethabi, no_serde, + borsh, }) } } diff --git a/lib/near-verifier/Cargo.toml b/lib/near-verifier/Cargo.toml new file mode 100644 index 0000000000..9ea7ecb1b8 --- /dev/null +++ b/lib/near-verifier/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors = ["Union Labs"] +edition.workspace = true +license-file.workspace = true +name = "near-verifier" +repository.workspace = true +version = "0.1.0" + +[dependencies] +borsh = { workspace = true } +near-account-id = { workspace = true } +near-primitives-core = { version = "0.23" } +serde = { workspace = true } +serde_json.workspace = true +sha2 = { workspace = true } +thiserror = { workspace = true } +unionlabs = { workspace = true, features = ["near"] } + +[lints] +workspace = true diff --git a/lib/near-verifier/src/error.rs b/lib/near-verifier/src/error.rs new file mode 100644 index 0000000000..f277ff42b5 --- /dev/null +++ b/lib/near-verifier/src/error.rs @@ -0,0 +1,31 @@ +use near_account_id::AccountId; +use near_primitives_core::hash::CryptoHash; + +#[derive(thiserror::Error, Debug, PartialEq, Eq, Clone)] +pub enum Error { + #[error("epoch block producers not found for ({0:?})")] + EpochBlockProducersNotFound(CryptoHash), + #[error("public key must be of type `ed25519`")] + UnsupportedPublicKey, + #[error("signature type must be `ed25519`")] + UnsupportedSignature, + #[error("signature verification failed for pubkey {0:?} signature {1:?} message {2:?}")] + VerificationFailure(Vec, Vec, Vec), + // TODO(aeryz): add context + #[error("merkle verification failed")] + MerkleVerificationFailure, + #[error( + "state proof verification failed. Params: {0:?}\n\n{1:?}\n\n{2:?}\n\n{3:?}\n\n{4:?}\n\n" + )] + StateVerificationFailure(Vec, Vec, AccountId, Vec, Vec), + #[error("update height ({0}) must be greater than the current height ({1})")] + UpdateHeightMustBeGreater(u64, u64), + #[error("can only be updated within the same epoch or the next epoch, but got ({0:?})")] + InvalidEpochId(CryptoHash), + #[error("when updating to the next epoch, `next_bps` must be provided")] + MustHaveNextEpochId, + #[error("approved stake ({0}) is below the threshold ({1})")] + ApprovedStakeBelowThreshold(u128, u128), + #[error("next bp hash mismatch ({0} != {1})")] + NextBpsHashMismatch(CryptoHash, CryptoHash), +} diff --git a/lib/near-verifier/src/lib.rs b/lib/near-verifier/src/lib.rs new file mode 100644 index 0000000000..3a1f838f8a --- /dev/null +++ b/lib/near-verifier/src/lib.rs @@ -0,0 +1,217 @@ +use error::Error; +use near_account_id::AccountId; +use near_primitives_core::{ + borsh::{self, BorshSerialize}, + hash::CryptoHash, + types::MerkleHash, +}; +use sha2::{Digest, Sha256}; +use state_proof::StateProof; +use unionlabs::{ + ibc::lightclients::near::{ + approval::ApprovalInner, + block_header_inner::{BlockHeaderInnerLite, BlockHeaderInnerLiteView}, + light_client_block::LightClientBlockView, + validator_stake_view::ValidatorStakeView, + }, + near::{ + raw_state_proof::RawStateProof, + types::{Direction, MerklePath, PublicKey, Signature}, + }, +}; + +pub mod error; +mod nibble_slice; +pub mod state_proof; + +pub trait NearVerifierCtx { + fn get_epoch_block_producers(&self, epoch_id: CryptoHash) -> Option>; + + fn ed25519_verify( + &self, + public_key: &[u8], + signature: &[u8], + message: &[u8], + ) -> Result<(), Error>; +} + +pub fn verify_header( + ctx: &Ctx, + head: BlockHeaderInnerLiteView, + block_view: LightClientBlockView, +) -> Result<(), Error> { + let (_current_block_hash, _next_block_hash, approval_message) = + reconstruct_light_client_block_view_fields(block_view.clone()); + + if block_view.inner_lite.height <= head.height { + return Err(Error::UpdateHeightMustBeGreater( + block_view.inner_lite.height, + head.height, + )); + } + + if ![&head.epoch_id, &head.next_epoch_id].contains(&&block_view.inner_lite.epoch_id) { + return Err(Error::InvalidEpochId(block_view.inner_lite.epoch_id)); + } + + if block_view.inner_lite.epoch_id == head.next_epoch_id && block_view.next_bps.is_none() { + return Err(Error::MustHaveNextEpochId); + } + + let mut total_stake = 0; + let mut approved_stake = 0; + + let epoch_block_producers = ctx + .get_epoch_block_producers(block_view.inner_lite.epoch_id) + .ok_or(Error::EpochBlockProducersNotFound( + block_view.inner_lite.epoch_id, + ))?; + + for (maybe_signature, block_producer) in block_view + .approvals_after_next + .iter() + .zip(epoch_block_producers.iter()) + { + let ValidatorStakeView::V1(block_producer) = block_producer.clone(); + total_stake += block_producer.stake; + + if maybe_signature.is_none() { + continue; + } + + match maybe_signature { + Some(signature) => { + approved_stake += block_producer.stake; + + let PublicKey::Ed25519(pubkey) = block_producer.public_key else { + return Err(Error::UnsupportedPublicKey); + }; + + let Signature::Ed25519(sig) = signature.as_ref() else { + return Err(Error::UnsupportedSignature); + }; + + ctx.ed25519_verify(&pubkey[..], &sig, &approval_message)?; + } + None => continue, + } + } + + let threshold = total_stake.checked_mul(2).unwrap().checked_div(3).unwrap(); + if approved_stake <= threshold { + return Err(Error::ApprovedStakeBelowThreshold( + approved_stake, + threshold, + )); + } + + if let Some(next_bps) = &block_view.next_bps { + let next_bp_hash = hash_borsh(next_bps); + if next_bp_hash != block_view.inner_lite.next_bp_hash { + return Err(Error::NextBpsHashMismatch( + next_bp_hash, + block_view.inner_lite.next_bp_hash, + )); + } + } + + Ok(()) +} + +pub fn verify_state( + raw_state_proof: RawStateProof, + state_root: &CryptoHash, + account_id: &AccountId, + key: &[u8], + value: Option<&[u8]>, +) -> Result<(), Error> { + let state_proof = StateProof::parse(raw_state_proof.clone()); + + if !state_proof.verify(state_root, account_id, key, value) { + return Err(Error::StateVerificationFailure( + serde_json::to_vec(&raw_state_proof).unwrap(), + state_root.0.to_vec(), + account_id.clone(), + key.to_vec(), + value.unwrap().to_vec(), + )); + } + + Ok(()) +} + +fn reconstruct_light_client_block_view_fields( + block_view: LightClientBlockView, +) -> (CryptoHash, CryptoHash, Vec) { + let current_block_hash = combine_hash( + &combine_hash( + &CryptoHash( + Sha256::new() + .chain_update( + &borsh::to_vec(&Into::::into( + block_view.inner_lite.clone(), + )) + .unwrap(), + ) + .finalize() + .try_into() + .unwrap(), + ), + &block_view.inner_rest_hash, + ), + &block_view.prev_block_hash, + ); + + let next_block_hash = combine_hash(&block_view.next_block_inner_hash, ¤t_block_hash); + + let mut approval_message = borsh::to_vec(&ApprovalInner::Endorsement(next_block_hash)).unwrap(); + approval_message.extend_from_slice(&(block_view.inner_lite.height + 2).to_le_bytes()); + + (current_block_hash, next_block_hash, approval_message) +} + +pub fn combine_hash(hash1: &MerkleHash, hash2: &MerkleHash) -> MerkleHash { + hash_borsh((hash1, hash2)) +} + +pub fn hash_borsh(value: T) -> CryptoHash { + CryptoHash( + Sha256::new() + .chain_update(&borsh::to_vec(&value).expect("serialize will work")) + .finalize() + .try_into() + .expect("sha256 output size is always 32 bytes"), + ) +} + +/// Verify merkle path for given item and corresponding path. +pub fn verify_path( + root: MerkleHash, + path: &MerklePath, + item: T, +) -> Result<(), Error> { + if !verify_hash(root, path, CryptoHash::hash_borsh(item)) { + return Err(Error::MerkleVerificationFailure); + } + + Ok(()) +} + +pub fn verify_hash(root: MerkleHash, path: &MerklePath, item_hash: MerkleHash) -> bool { + compute_root_from_path(path, item_hash) == root +} + +pub fn compute_root_from_path(path: &MerklePath, item_hash: MerkleHash) -> MerkleHash { + let mut res = item_hash; + for item in path { + match item.direction { + Direction::Left => { + res = combine_hash(&item.hash, &res); + } + Direction::Right => { + res = combine_hash(&res, &item.hash); + } + } + } + res +} diff --git a/lib/near-verifier/src/nibble_slice.rs b/lib/near-verifier/src/nibble_slice.rs new file mode 100644 index 0000000000..b28adbe94f --- /dev/null +++ b/lib/near-verifier/src/nibble_slice.rs @@ -0,0 +1,170 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Nibble-orientated view onto byte-slice, allowing nibble-precision offsets. + +use std::{cmp::*, fmt}; + +/// Nibble-orientated view onto byte-slice, allowing nibble-precision offsets. +/// +/// This is an immutable struct. No operations actually change it. +/// +/// # Example +/// ```snippet +/// use patricia_trie::nibbleslice::NibbleSlice; +/// { +/// let d1 = &[0x01u8, 0x23, 0x45]; +/// let d2 = &[0x34u8, 0x50, 0x12]; +/// let d3 = &[0x00u8, 0x12]; +/// let n1 = NibbleSlice::new(d1); // 0,1,2,3,4,5 +/// let n2 = NibbleSlice::new(d2); // 3,4,5,0,1,2 +/// let n3 = NibbleSlice::new_offset(d3, 1); // 0,1,2 +/// assert!(n1 > n3); // 0,1,2,... > 0,1,2 +/// assert!(n1 < n2); // 0,... < 3,... +/// assert!(n2.mid(3) == n3); // 0,1,2 == 0,1,2 +/// assert!(n1.starts_with(&n3)); +/// assert_eq!(n1.common_prefix(&n3), 3); +/// assert_eq!(n2.mid(3).common_prefix(&n1), 3); +/// } +/// ``` +#[derive(Copy, Clone, Eq)] +pub struct NibbleSlice<'a> { + data: &'a [u8], + offset: usize, +} + +/// Iterator type for a nibble slice. +pub struct NibbleSliceIterator<'a> { + p: &'a NibbleSlice<'a>, + i: usize, +} + +impl Iterator for NibbleSliceIterator<'_> { + type Item = u8; + + fn next(&mut self) -> Option { + self.i += 1; + if self.i <= self.p.len() { + Some(self.p.at(self.i - 1)) + } else { + None + } + } +} + +impl<'a> NibbleSlice<'a> { + /// Create a new nibble slice with the given byte-slice. + pub fn new(data: &'a [u8]) -> Self { + NibbleSlice::new_offset(data, 0) + } + + /// Create a new nibble slice with the given byte-slice with a nibble offset. + pub fn new_offset(data: &'a [u8], offset: usize) -> Self { + NibbleSlice { data, offset } + } + + /// Create a new nibble slice from the given HPE encoded data (e.g. output of `encoded()`). + pub fn from_encoded(data: &'a [u8]) -> (Self, bool) { + ( + Self::new_offset(data, if data[0] & 16 == 16 { 1 } else { 2 }), + data[0] & 32 == 32, + ) + } + + /// Is this an empty slice? + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get the length (in nibbles, naturally) of this slice. + #[inline] + pub fn len(&self) -> usize { + self.data.len() * 2 - self.offset + } + + /// Get the nibble at position `i`. + #[inline] + pub fn at(&self, i: usize) -> u8 { + if (self.offset + i) & 1 == 1 { + self.data[(self.offset + i) / 2] & 15u8 + } else { + self.data[(self.offset + i) / 2] >> 4 + } + } + + /// Return object which represents a view on to this slice (further) offset by `i` nibbles. + pub fn mid(&self, i: usize) -> Self { + NibbleSlice { + data: self.data, + offset: self.offset + i, + } + } + + /// Do we start with the same nibbles as the whole of `them`? + pub fn starts_with(&self, them: &Self) -> bool { + self.common_prefix(them) == them.len() + } + + /// How many of the same nibbles at the beginning do we match with `them`? + pub fn common_prefix(&self, them: &Self) -> usize { + let s = min(self.len(), them.len()); + for i in 0..s { + if self.at(i) != them.at(i) { + return i; + } + } + s + } +} + +impl PartialEq for NibbleSlice<'_> { + fn eq(&self, them: &Self) -> bool { + self.len() == them.len() && self.starts_with(them) + } +} + +impl Ord for NibbleSlice<'_> { + fn cmp(&self, them: &Self) -> Ordering { + let s = min(self.len(), them.len()); + for i in 0..s { + match self.at(i).cmp(&them.at(i)) { + Ordering::Less => return Ordering::Less, + Ordering::Greater => return Ordering::Greater, + Ordering::Equal => {} + } + } + self.len().cmp(&them.len()) + } +} + +impl PartialOrd for NibbleSlice<'_> { + fn partial_cmp(&self, them: &Self) -> Option { + Some(self.cmp(them)) + } +} + +impl fmt::Debug for NibbleSlice<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_empty() { + return Ok(()); + } + write!(f, "{:01x}", self.at(0))?; + for i in 1..self.len() { + write!(f, "'{:01x}", self.at(i))?; + } + Ok(()) + } +} diff --git a/near/near-light-client/src/state_proof.rs b/lib/near-verifier/src/state_proof.rs similarity index 94% rename from near/near-light-client/src/state_proof.rs rename to lib/near-verifier/src/state_proof.rs index 2559e7935d..c152a1c6d4 100644 --- a/near/near-light-client/src/state_proof.rs +++ b/lib/near-verifier/src/state_proof.rs @@ -1,18 +1,18 @@ use std::collections::HashMap; +use borsh::{BorshDeserialize, BorshSerialize}; use near_primitives_core::{hash::CryptoHash, types::AccountId}; -use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use unionlabs::near::raw_state_proof::RawStateProof; use crate::nibble_slice::NibbleSlice; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct RawStateProof { - pub state_proof: Vec>, +pub struct StateProof { + pub state_proof_nodes: HashMap, } -impl RawStateProof { - pub fn parse(self) -> StateProof { - let state_proof_nodes = self +impl StateProof { + pub fn parse(proof: RawStateProof) -> Self { + let state_proof_nodes = proof .state_proof .into_iter() .map(|bytes| { @@ -24,13 +24,7 @@ impl RawStateProof { StateProof { state_proof_nodes } } -} - -pub struct StateProof { - pub state_proof_nodes: HashMap, -} -impl StateProof { pub fn verify( &self, state_root: &CryptoHash, @@ -96,7 +90,7 @@ impl StateProof { } } -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, PartialEq, Eq)] +#[derive(BorshSerialize, borsh::BorshDeserialize, Clone, Debug, PartialEq, Eq)] pub struct RawTrieNodeWithSize { pub node: RawTrieNode, pub memory_usage: u64, diff --git a/lib/relay-message/src/lib.rs b/lib/relay-message/src/lib.rs index 43cb5878ce..341fcb3663 100644 --- a/lib/relay-message/src/lib.rs +++ b/lib/relay-message/src/lib.rs @@ -250,6 +250,8 @@ pub enum AnyLightClientIdentified { /// The 08-wasm client tracking the state of Cosmos. CosmosOnCosmos(lc!(Cosmos => Cosmos)), + // NearOnUnion(lc!(Near => Wasm)), + // UnionOnNear(lc!(Wasm => Near)), } impl AnyLightClientIdentified { diff --git a/lib/unionlabs/Cargo.toml b/lib/unionlabs/Cargo.toml index eb283272ea..a294c3b52c 100644 --- a/lib/unionlabs/Cargo.toml +++ b/lib/unionlabs/Cargo.toml @@ -53,10 +53,10 @@ typenum = { workspace = true, features = ["const-generics", "no uint = "*" wasmparser = { version = "0.113" } -borsh = { workspace = true, features = ["borsh-derive"], optional = true } +borsh = { workspace = true, features = ["derive"] } bs58 = "0.4" -near-primitives-core = { version = "0.21", optional = true } -near-sdk = { workspace = true, optional = true } +near-account-id = { workspace = true } +near-primitives-core = { version = "0.23" } schemars = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] @@ -69,7 +69,7 @@ default = ["ethabi", "std", "stargate"] cosmwasm = [] ethabi = ["contracts", "ethers-core", "ethers-contract-derive", "ethers"] grpc = ["protos/client"] -near = ["borsh", "near-sdk", "near-primitives-core"] +near = [] std = ["ethers-core/std"] arbitrary = ["dep:arbitrary", "primitive-types/arbitrary"] diff --git a/lib/unionlabs/src/encoding.rs b/lib/unionlabs/src/encoding.rs index 3fc07acbc2..3f06b4535f 100644 --- a/lib/unionlabs/src/encoding.rs +++ b/lib/unionlabs/src/encoding.rs @@ -14,6 +14,10 @@ impl Encoding for Ssz {} pub enum Json {} impl Encoding for Json {} +// TODO: feature gate this +pub enum Borsh {} +impl Encoding for Borsh {} + impl Encode for T where T: serde::Serialize, diff --git a/lib/unionlabs/src/ibc/core/client/height.rs b/lib/unionlabs/src/ibc/core/client/height.rs index 8f488cd990..32559264da 100644 --- a/lib/unionlabs/src/ibc/core/client/height.rs +++ b/lib/unionlabs/src/ibc/core/client/height.rs @@ -8,8 +8,8 @@ use macros::model; use serde::{Deserialize, Serialize}; use ssz::Ssz; -#[derive(Ssz, Default, Copy)] -#[model(proto(raw(protos::ibc::core::client::v1::Height), into, from))] +#[derive(Ssz, Default, Copy, Eq)] +#[model(borsh, proto(raw(protos::ibc::core::client::v1::Height), into, from))] #[debug("Height({self})")] #[cfg_attr(feature = "schemars", derive(::schemars::JsonSchema))] pub struct Height { @@ -89,11 +89,17 @@ impl From for protos::ibc::core::client::v1::Height { impl PartialOrd for Height { fn partial_cmp(&self, other: &Self) -> Option { - Some(match self.revision_number.cmp(&other.revision_number) { + Some(self.cmp(other)) + } +} + +impl Ord for Height { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.revision_number.cmp(&other.revision_number) { core::cmp::Ordering::Less => core::cmp::Ordering::Less, core::cmp::Ordering::Equal => self.revision_height.cmp(&other.revision_height), core::cmp::Ordering::Greater => core::cmp::Ordering::Greater, - }) + } } } diff --git a/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs b/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs index a5d1fa42e8..ccd6996256 100644 --- a/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs +++ b/lib/unionlabs/src/ibc/core/commitment/merkle_root.rs @@ -11,7 +11,10 @@ use crate::{errors::InvalidLength, hash::H256}; // ethers_contract_derive::EthAbiCodec // ) // )] -#[model(proto(raw(protos::ibc::core::commitment::v1::MerkleRoot), into, from))] +#[model( + borsh, + proto(raw(protos::ibc::core::commitment::v1::MerkleRoot), into, from) +)] pub struct MerkleRoot { pub hash: H256, } diff --git a/lib/unionlabs/src/ibc/lightclients.rs b/lib/unionlabs/src/ibc/lightclients.rs index 1817834008..ad86f3bc3e 100644 --- a/lib/unionlabs/src/ibc/lightclients.rs +++ b/lib/unionlabs/src/ibc/lightclients.rs @@ -3,6 +3,7 @@ pub mod berachain; pub mod cometbls; pub mod ethereum; pub mod linea; +pub mod near; pub mod scroll; pub mod tendermint; pub mod wasm; diff --git a/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs b/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs index 25d01c4720..9c492b1fdc 100644 --- a/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/cometbls/client_state.rs @@ -6,6 +6,7 @@ use crate::{ }; #[model( + borsh, proto( raw(protos::union::ibc::lightclients::cometbls::v1::ClientState), into, @@ -17,6 +18,7 @@ use crate::{ from ) )] +#[derive(Default)] pub struct ClientState { pub chain_id: String, pub trusting_period: u64, diff --git a/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs index 94f2d2bd7e..4d43891b39 100644 --- a/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs +++ b/lib/unionlabs/src/ibc/lightclients/cometbls/consensus_state.rs @@ -7,6 +7,7 @@ use crate::{ }; #[model( + borsh, proto( raw(protos::union::ibc::lightclients::cometbls::v1::ConsensusState), into, diff --git a/lib/unionlabs/src/ibc/lightclients/near.rs b/lib/unionlabs/src/ibc/lightclients/near.rs new file mode 100644 index 0000000000..4c4a30dfac --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near.rs @@ -0,0 +1,7 @@ +pub mod approval; +pub mod block_header_inner; +pub mod client_state; +pub mod consensus_state; +pub mod header; +pub mod light_client_block; +pub mod validator_stake_view; diff --git a/lib/unionlabs/src/ibc/lightclients/near/approval.rs b/lib/unionlabs/src/ibc/lightclients/near/approval.rs new file mode 100644 index 0000000000..03c3f955ae --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/approval.rs @@ -0,0 +1,9 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use near_primitives_core::{hash::CryptoHash, types::BlockHeight}; + +/// The part of the block approval that is different for endorsements and skips +#[derive(BorshSerialize, BorshDeserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Hash)] +pub enum ApprovalInner { + Endorsement(CryptoHash), + Skip(BlockHeight), +} diff --git a/lib/unionlabs/src/ibc/lightclients/near/block_header_inner.rs b/lib/unionlabs/src/ibc/lightclients/near/block_header_inner.rs new file mode 100644 index 0000000000..f785c339b3 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/block_header_inner.rs @@ -0,0 +1,149 @@ +// TODO: Use h256 here for all hash types? + +use borsh::{BorshDeserialize, BorshSerialize}; +use macros::model; +use near_primitives_core::{ + hash::CryptoHash, + types::{BlockHeight, MerkleHash}, +}; + +use crate::{errors::MissingField, near::types::EpochId}; + +#[model(proto( + raw(protos::union::ibc::lightclients::near::v1::BlockHeaderInnerLiteView), + into, + from +))] +#[derive(Eq, BorshDeserialize, BorshSerialize)] +pub struct BlockHeaderInnerLiteView { + pub height: BlockHeight, + pub epoch_id: CryptoHash, + pub next_epoch_id: CryptoHash, + pub prev_state_root: CryptoHash, + pub outcome_root: CryptoHash, + // TODO(aeryz): put deprecated? + /// Legacy json number. Should not be used. + // #[deprecated] + pub timestamp: u64, + // TODO(aeryz): #[serde(with = "dec_format")] + pub timestamp_nanosec: u64, + pub next_bp_hash: CryptoHash, + pub block_merkle_root: CryptoHash, +} + +impl From + for protos::union::ibc::lightclients::near::v1::BlockHeaderInnerLiteView +{ + fn from(value: BlockHeaderInnerLiteView) -> Self { + Self { + height: value.height, + epoch_id: value.epoch_id.into(), + next_epoch_id: value.next_epoch_id.into(), + prev_state_root: value.prev_state_root.into(), + outcome_root: value.outcome_root.into(), + timestamp: value.timestamp, + timestamp_nanosec: value.timestamp_nanosec, + next_bp_hash: value.next_bp_hash.into(), + block_merkle_root: value.block_merkle_root.into(), + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromBlockHeaderInnerLiteViewError { + #[error(transparent)] + MissingField(#[from] MissingField), + #[error("invalid epoch id")] + EpochId, + #[error("invalid next epoch id")] + NextEpochId, + #[error("invalid prev state root")] + PrevStateRoot, + #[error("invalid outcome root")] + OutcomeRoot, + #[error("next bp hash")] + NextBpHash, + #[error("block merkle root")] + BlockMerkleRoot, +} + +impl TryFrom + for BlockHeaderInnerLiteView +{ + type Error = TryFromBlockHeaderInnerLiteViewError; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::BlockHeaderInnerLiteView, + ) -> Result { + Ok(Self { + height: value.height, + epoch_id: value + .epoch_id + .as_slice() + .try_into() + .map_err(|_| TryFromBlockHeaderInnerLiteViewError::EpochId)?, + next_epoch_id: value + .next_epoch_id + .as_slice() + .try_into() + .map_err(|_| TryFromBlockHeaderInnerLiteViewError::NextEpochId)?, + prev_state_root: value + .prev_state_root + .as_slice() + .try_into() + .map_err(|_| TryFromBlockHeaderInnerLiteViewError::PrevStateRoot)?, + outcome_root: value + .outcome_root + .as_slice() + .try_into() + .map_err(|_| TryFromBlockHeaderInnerLiteViewError::OutcomeRoot)?, + timestamp: value.timestamp, + timestamp_nanosec: value.timestamp_nanosec, + next_bp_hash: value + .next_bp_hash + .as_slice() + .try_into() + .map_err(|_| TryFromBlockHeaderInnerLiteViewError::NextBpHash)?, + block_merkle_root: value + .block_merkle_root + .as_slice() + .try_into() + .map_err(|_| TryFromBlockHeaderInnerLiteViewError::BlockMerkleRoot)?, + }) + } +} + +#[derive(BorshSerialize, BorshDeserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +pub struct BlockHeaderInnerLite { + /// Height of this block. + pub height: BlockHeight, + /// Epoch start hash of this block's epoch. + /// Used for retrieving validator information + pub epoch_id: EpochId, + pub next_epoch_id: EpochId, + /// Root hash of the state at the previous block. + pub prev_state_root: MerkleHash, + /// Root of the outcomes of transactions and receipts from the previous chunks. + pub prev_outcome_root: MerkleHash, + /// Timestamp at which the block was built (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC). + pub timestamp: u64, + /// Hash of the next epoch block producers set + pub next_bp_hash: CryptoHash, + /// Merkle root of block hashes up to the current block. + pub block_merkle_root: CryptoHash, +} + +impl From for BlockHeaderInnerLite { + fn from(value: BlockHeaderInnerLiteView) -> Self { + Self { + height: value.height, + epoch_id: EpochId(value.epoch_id), + next_epoch_id: EpochId(value.next_epoch_id), + prev_state_root: value.prev_state_root, + prev_outcome_root: value.outcome_root, + timestamp: value.timestamp, + next_bp_hash: value.next_bp_hash, + block_merkle_root: value.block_merkle_root, + } + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/near/client_state.rs b/lib/unionlabs/src/ibc/lightclients/near/client_state.rs new file mode 100644 index 0000000000..8e4e56fde1 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/client_state.rs @@ -0,0 +1,68 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use macros::model; +use near_account_id::AccountId; +use near_primitives_core::account::id::ParseAccountError; + +use super::validator_stake_view::{TryFromValidatorStakeView, ValidatorStakeView}; + +#[model(proto( + raw(protos::union::ibc::lightclients::near::v1::ClientState), + into, + from +))] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct ClientState { + pub chain_id: String, + pub latest_height: u64, + pub ibc_account_id: AccountId, + pub initial_block_producers: Vec, + // TODO: Make option + pub frozen_height: u64, +} + +impl From for protos::union::ibc::lightclients::near::v1::ClientState { + fn from(value: ClientState) -> Self { + Self { + chain_id: value.chain_id, + latest_height: value.latest_height, + account_id: value.ibc_account_id.into(), + iniitial_block_producers: value + .initial_block_producers + .into_iter() + .map(Into::into) + .collect(), + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromClientStateError { + #[error(transparent)] + AccountId(#[from] ParseAccountError), + #[error(transparent)] + InitialBlockProducers(#[from] TryFromValidatorStakeView), +} + +impl TryFrom for ClientState { + type Error = TryFromClientStateError; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::ClientState, + ) -> Result { + Ok(Self { + chain_id: value.chain_id, + latest_height: value.latest_height, + ibc_account_id: value + .account_id + .try_into() + .map_err(TryFromClientStateError::AccountId)?, + initial_block_producers: value + .iniitial_block_producers + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .map_err(TryFromClientStateError::InitialBlockProducers)?, + frozen_height: 0, + }) + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/near/consensus_state.rs b/lib/unionlabs/src/ibc/lightclients/near/consensus_state.rs new file mode 100644 index 0000000000..ee2a731460 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/consensus_state.rs @@ -0,0 +1,57 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use macros::model; +use near_primitives_core::hash::CryptoHash; + +use super::block_header_inner::{BlockHeaderInnerLiteView, TryFromBlockHeaderInnerLiteViewError}; +use crate::errors::{required, MissingField}; + +#[model(proto( + raw(protos::union::ibc::lightclients::near::v1::ConsensusState), + into, + from +))] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct ConsensusState { + pub state: BlockHeaderInnerLiteView, + pub chunk_prev_state_root: CryptoHash, + pub timestamp: u64, +} + +impl From for protos::union::ibc::lightclients::near::v1::ConsensusState { + fn from(value: ConsensusState) -> Self { + Self { + state: Some(value.state.into()), + chunk_prev_state_root: value.chunk_prev_state_root.into(), + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromConsensusStateError { + #[error(transparent)] + MissingField(#[from] MissingField), + #[error(transparent)] + State(#[from] TryFromBlockHeaderInnerLiteViewError), + #[error("invalid chunk prev state root")] + ChunkPrevStateRoot, +} + +impl TryFrom for ConsensusState { + type Error = TryFromConsensusStateError; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::ConsensusState, + ) -> Result { + Ok(Self { + state: required!(value.state)? + .try_into() + .map_err(TryFromConsensusStateError::State)?, + chunk_prev_state_root: value + .chunk_prev_state_root + .as_slice() + .try_into() + .map_err(|_| TryFromConsensusStateError::ChunkPrevStateRoot)?, + timestamp: 0, + }) + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/near/header.rs b/lib/unionlabs/src/ibc/lightclients/near/header.rs new file mode 100644 index 0000000000..9aaa3269f6 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/header.rs @@ -0,0 +1,71 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use macros::model; +use near_primitives_core::{hash::CryptoHash, types::BlockHeight}; + +use super::light_client_block::{LightClientBlockView, TryFromLightClientBlockView}; +use crate::{ + errors::{required, MissingField}, + near::types::{MerklePath, TryFromMerklePathItemError}, +}; + +#[model(proto(raw(protos::union::ibc::lightclients::near::v1::Header), into, from))] +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Header { + pub new_state: LightClientBlockView, + pub trusted_height: BlockHeight, + pub prev_state_root_proof: MerklePath, + pub prev_state_root: CryptoHash, +} + +impl From
for protos::union::ibc::lightclients::near::v1::Header { + fn from(value: Header) -> Self { + Self { + new_state: Some(value.new_state.into()), + trusted_height: value.trusted_height, + prev_state_root_proof: value + .prev_state_root_proof + .into_iter() + .map(Into::into) + .collect(), + prev_state_root: value.prev_state_root.into(), + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromHeaderError { + #[error(transparent)] + MissingField(#[from] MissingField), + #[error(transparent)] + NewState(#[from] TryFromLightClientBlockView), + #[error(transparent)] + PrevStateRootProof(#[from] TryFromMerklePathItemError), + #[error("invalid prev state root")] + PrevStateRoot, +} + +impl TryFrom for Header { + type Error = TryFromHeaderError; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::Header, + ) -> Result { + Ok(Self { + new_state: required!(value.new_state)? + .try_into() + .map_err(TryFromHeaderError::NewState)?, + trusted_height: value.trusted_height, + prev_state_root_proof: value + .prev_state_root_proof + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .map_err(TryFromHeaderError::PrevStateRootProof)?, + prev_state_root: value + .prev_state_root + .as_slice() + .try_into() + .map_err(|_| TryFromHeaderError::PrevStateRoot)?, + }) + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/near/light_client_block.rs b/lib/unionlabs/src/ibc/lightclients/near/light_client_block.rs new file mode 100644 index 0000000000..8d72e1aad2 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/light_client_block.rs @@ -0,0 +1,117 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use macros::model; +use near_primitives_core::hash::CryptoHash; + +use super::{ + block_header_inner::{BlockHeaderInnerLiteView, TryFromBlockHeaderInnerLiteViewError}, + validator_stake_view::{TryFromValidatorStakeView, ValidatorStakeView}, +}; +use crate::{ + errors::{required, MissingField}, + near::types::Signature, +}; + +#[model(proto( + raw(protos::union::ibc::lightclients::near::v1::LightClientBlockView), + into, + from +))] +#[derive(Eq, BorshDeserialize, BorshSerialize)] +pub struct LightClientBlockView { + pub prev_block_hash: CryptoHash, + pub next_block_inner_hash: CryptoHash, + pub inner_lite: BlockHeaderInnerLiteView, + pub inner_rest_hash: CryptoHash, + pub next_bps: Option>, + pub approvals_after_next: Vec>>, +} + +impl From + for protos::union::ibc::lightclients::near::v1::LightClientBlockView +{ + fn from(value: LightClientBlockView) -> Self { + Self { + prev_block_hash: value.prev_block_hash.into(), + next_block_inner_hash: value.next_block_inner_hash.into(), + inner_lite: Some(value.inner_lite.into()), + inner_rest_hash: value.inner_rest_hash.into(), + next_bps: match value.next_bps { + Some(next_bps) => next_bps.into_iter().map(Into::into).collect(), + None => vec![], + }, + approvals_after_next: value + .approvals_after_next + .into_iter() + .map( + |sig| protos::union::ibc::lightclients::near::v1::Signature { + signature: sig.map(|sig| { + Into::< + protos::union::ibc::lightclients::near::v1::signature::Signature, + >::into(*sig) + }), + }, + ) + .collect(), + } + } +} + +#[derive(Debug, PartialEq, thiserror::Error)] +pub enum TryFromLightClientBlockView { + #[error(transparent)] + MissingField(#[from] MissingField), + #[error("invalid prev block hash")] + PrevBlockHash, + #[error("invalid next block inner hash")] + NextBlockInnerHash, + #[error(transparent)] + InnerLite(#[from] TryFromBlockHeaderInnerLiteViewError), + #[error("invalid inner rest hash")] + InnerRestHash, + #[error(transparent)] + NextBps(#[from] TryFromValidatorStakeView), +} + +impl TryFrom + for LightClientBlockView +{ + type Error = TryFromLightClientBlockView; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::LightClientBlockView, + ) -> Result { + Ok(Self { + prev_block_hash: value + .prev_block_hash + .as_slice() + .try_into() + .map_err(|_| TryFromLightClientBlockView::PrevBlockHash)?, + next_block_inner_hash: value + .next_block_inner_hash + .as_slice() + .try_into() + .map_err(|_| TryFromLightClientBlockView::NextBlockInnerHash)?, + inner_lite: required!(value.inner_lite)? + .try_into() + .map_err(TryFromLightClientBlockView::InnerLite)?, + inner_rest_hash: value + .inner_rest_hash + .as_slice() + .try_into() + .map_err(|_| TryFromLightClientBlockView::InnerRestHash)?, + next_bps: Some( + value + .next_bps + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .map_err(TryFromLightClientBlockView::NextBps)?, + ), + approvals_after_next: value + .approvals_after_next + .into_iter() + .map(|item| item.signature.map(|sig| Box::new(sig.into()))) + .collect(), + }) + } +} diff --git a/lib/unionlabs/src/ibc/lightclients/near/validator_stake_view.rs b/lib/unionlabs/src/ibc/lightclients/near/validator_stake_view.rs new file mode 100644 index 0000000000..9339a0e5c8 --- /dev/null +++ b/lib/unionlabs/src/ibc/lightclients/near/validator_stake_view.rs @@ -0,0 +1,85 @@ +use core::str::FromStr; + +use borsh::{BorshDeserialize, BorshSerialize}; +use macros::model; +use near_account_id::AccountId; +use near_primitives_core::{account::id::ParseAccountError, types::Balance}; + +use crate::{ + errors::{required, MissingField}, + near::types::{PublicKey, TryFromPublicKeyError}, +}; + +#[model(proto( + raw(protos::union::ibc::lightclients::near::v1::ValidatorStakeView), + into, + from +))] +#[derive(BorshSerialize, BorshDeserialize, Eq)] +pub enum ValidatorStakeView { + V1(ValidatorStakeViewV1), +} + +#[derive( + BorshSerialize, + BorshDeserialize, + Debug, + Clone, + Eq, + PartialEq, + serde::Serialize, + serde::Deserialize, +)] +pub struct ValidatorStakeViewV1 { + pub account_id: AccountId, + pub public_key: PublicKey, + // TODO(aeryz): #[serde(with = "dec_format")] + pub stake: Balance, +} + +impl From for protos::union::ibc::lightclients::near::v1::ValidatorStakeView { + fn from(value: ValidatorStakeView) -> Self { + let ValidatorStakeView::V1(value) = value; + Self { + account_id: value.account_id.to_string(), + public_key: Some(value.public_key.into()), + balance: value.stake.to_le_bytes().into(), + } + } +} + +#[derive(Debug, PartialEq, Clone, thiserror::Error)] +pub enum TryFromValidatorStakeView { + #[error(transparent)] + MissingField(#[from] MissingField), + #[error(transparent)] + AccountId(#[from] ParseAccountError), + #[error(transparent)] + PublicKey(#[from] TryFromPublicKeyError), + #[error("invalid balance size {0:?}")] + Balance(Vec), +} + +impl TryFrom + for ValidatorStakeView +{ + type Error = TryFromValidatorStakeView; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::ValidatorStakeView, + ) -> Result { + Ok(Self::V1(ValidatorStakeViewV1 { + account_id: AccountId::from_str(&value.account_id) + .map_err(TryFromValidatorStakeView::AccountId)?, + public_key: required!(value.public_key)? + .try_into() + .map_err(TryFromValidatorStakeView::PublicKey)?, + stake: u128::from_le_bytes( + value + .balance + .try_into() + .map_err(TryFromValidatorStakeView::Balance)?, + ), + })) + } +} diff --git a/lib/unionlabs/src/lib.rs b/lib/unionlabs/src/lib.rs index a98e706355..1ad49d4adc 100644 --- a/lib/unionlabs/src/lib.rs +++ b/lib/unionlabs/src/lib.rs @@ -34,7 +34,6 @@ pub mod google; pub mod cosmwasm; -#[cfg(feature = "near")] pub mod near; /// Defines types that wrap the IBC specification, matching the proto module structure. This also includes `union` extensions to ibc (i.e. types defined in `union.ibc`). @@ -156,6 +155,7 @@ pub enum WasmClientType { Arbitrum, Linea, Berachain, + Near, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -204,6 +204,7 @@ impl FromStr for WasmClientType { "Arbitrum" => Ok(WasmClientType::Arbitrum), "Linea" => Ok(WasmClientType::Linea), "Berachain" => Ok(WasmClientType::Berachain), + "Near" => Ok(WasmClientType::Near), _ => Err(WasmClientTypeParseError::UnknownType(s.to_string())), } } @@ -220,6 +221,7 @@ impl Display for WasmClientType { Self::Arbitrum => write!(f, "Arbitrum"), Self::Linea => write!(f, "Linea"), Self::Berachain => write!(f, "Berachain"), + Self::Near => write!(f, "Near"), } } } diff --git a/lib/unionlabs/src/macros.rs b/lib/unionlabs/src/macros.rs index 7ca08ccc79..de3298d419 100644 --- a/lib/unionlabs/src/macros.rs +++ b/lib/unionlabs/src/macros.rs @@ -17,6 +17,8 @@ macro_rules! hex_string_array_wrapper { ::ssz::Ssz, ::serde::Serialize, ::serde::Deserialize, + ::borsh::BorshSerialize, + ::borsh::BorshDeserialize, Hash )] #[ssz(transparent)] diff --git a/lib/unionlabs/src/near/mod.rs b/lib/unionlabs/src/near/mod.rs index cd408564ea..efa195607f 100644 --- a/lib/unionlabs/src/near/mod.rs +++ b/lib/unionlabs/src/near/mod.rs @@ -1 +1,4 @@ +pub mod raw_state_proof; pub mod types; + +pub const NEAR_REVISION_NUMBER: u64 = 0; diff --git a/lib/unionlabs/src/near/raw_state_proof.rs b/lib/unionlabs/src/near/raw_state_proof.rs new file mode 100644 index 0000000000..b64528cb2b --- /dev/null +++ b/lib/unionlabs/src/near/raw_state_proof.rs @@ -0,0 +1,6 @@ +use macros::model; + +#[model] +pub struct RawStateProof { + pub state_proof: Vec>, +} diff --git a/lib/unionlabs/src/near/types.rs b/lib/unionlabs/src/near/types.rs index c07c46b9c2..e290290c7f 100644 --- a/lib/unionlabs/src/near/types.rs +++ b/lib/unionlabs/src/near/types.rs @@ -1,11 +1,7 @@ use core::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; -use near_primitives_core::{ - hash::CryptoHash, - types::{Balance, BlockHeight, MerkleHash}, -}; -use near_sdk::AccountId; +use near_primitives_core::{hash::CryptoHash, types::MerkleHash}; #[derive( Debug, @@ -22,6 +18,44 @@ pub struct MerklePathItem { pub direction: Direction, } +impl From for protos::union::ibc::lightclients::near::v1::MerklePathItem { + fn from(value: MerklePathItem) -> Self { + Self { + hash: value.hash.into(), + direction: value.direction as u64, + } + } +} + +#[derive(Debug, PartialEq, Clone, thiserror::Error)] +pub enum TryFromMerklePathItemError { + #[error("invalid hash")] + Hash, + #[error("invalid direction ({0})")] + Direction(u64), +} + +impl TryFrom for MerklePathItem { + type Error = TryFromMerklePathItemError; + + fn try_from( + value: protos::union::ibc::lightclients::near::v1::MerklePathItem, + ) -> Result { + Ok(Self { + hash: value + .hash + .as_slice() + .try_into() + .map_err(|_| TryFromMerklePathItemError::Hash)?, + direction: match value.direction { + 1 => Direction::Left, + 2 => Direction::Right, + v => return Err(TryFromMerklePathItemError::Direction(v)), + }, + }) + } +} + pub type MerklePath = Vec; #[derive( @@ -39,30 +73,6 @@ pub enum Direction { Right, } -#[derive( - PartialEq, - Eq, - Debug, - Clone, - BorshDeserialize, - BorshSerialize, - serde::Serialize, - serde::Deserialize, -)] -pub struct BlockHeaderInnerLiteView { - pub height: BlockHeight, - pub epoch_id: CryptoHash, - pub next_epoch_id: CryptoHash, - pub prev_state_root: CryptoHash, - pub outcome_root: CryptoHash, - /// Legacy json number. Should not be used. - pub timestamp: u64, - // TODO(aeryz): #[serde(with = "dec_format")] - pub timestamp_nanosec: u64, - pub next_bp_hash: CryptoHash, - pub block_merkle_root: CryptoHash, -} - /// Epoch identifier -- wrapped hash, to make it easier to distinguish. /// `EpochId` of epoch T is the hash of last block in T-2 /// `EpochId` of first two epochs is 0 @@ -93,105 +103,42 @@ impl core::str::FromStr for EpochId { } } -#[derive(BorshSerialize, BorshDeserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -pub struct BlockHeaderInnerLite { - /// Height of this block. - pub height: BlockHeight, - /// Epoch start hash of this block's epoch. - /// Used for retrieving validator information - pub epoch_id: EpochId, - pub next_epoch_id: EpochId, - /// Root hash of the state at the previous block. - pub prev_state_root: MerkleHash, - /// Root of the outcomes of transactions and receipts from the previous chunks. - pub prev_outcome_root: MerkleHash, - /// Timestamp at which the block was built (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC). - pub timestamp: u64, - /// Hash of the next epoch block producers set - pub next_bp_hash: CryptoHash, - /// Merkle root of block hashes up to the current block. - pub block_merkle_root: CryptoHash, -} - -impl From for BlockHeaderInnerLite { - fn from(value: BlockHeaderInnerLiteView) -> Self { - Self { - height: value.height, - epoch_id: EpochId(value.epoch_id), - next_epoch_id: EpochId(value.next_epoch_id), - prev_state_root: value.prev_state_root, - prev_outcome_root: value.outcome_root, - timestamp: value.timestamp, - next_bp_hash: value.next_bp_hash, - block_merkle_root: value.block_merkle_root, - } - } -} - -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] -pub struct HeaderUpdate { - pub new_state: LightClientBlockView, - pub trusted_height: BlockHeight, - pub prev_state_root_proof: MerklePath, - pub prev_state_root: CryptoHash, +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum PublicKey { + Ed25519([u8; 32]), + Secp256k1([u8; 64]), } -#[derive( - PartialEq, - Eq, - Debug, - Clone, - BorshDeserialize, - BorshSerialize, - serde::Serialize, - serde::Deserialize, -)] -pub struct LightClientBlockView { - pub prev_block_hash: CryptoHash, - pub next_block_inner_hash: CryptoHash, - pub inner_lite: BlockHeaderInnerLiteView, - pub inner_rest_hash: CryptoHash, - pub next_bps: Option>, - pub approvals_after_next: Vec>>, +impl From + for protos::union::ibc::lightclients::near::v1::validator_stake_view::PublicKey +{ + fn from(value: PublicKey) -> Self { + match value { + PublicKey::Ed25519(val) => Self::Ed25519(val.to_vec()), + PublicKey::Secp256k1(val) => Self::Secp256k1(val.to_vec()), + } + } } -#[derive( - BorshSerialize, - BorshDeserialize, - serde::Serialize, - serde::Deserialize, - Debug, - Clone, - Eq, - PartialEq, -)] -#[serde(tag = "validator_stake_struct_version")] -pub enum ValidatorStakeView { - V1(ValidatorStakeViewV1), +#[derive(Debug, PartialEq, Clone, thiserror::Error)] +pub enum TryFromPublicKeyError { + #[error("invalid length")] + InvalidLength, } -#[derive( - BorshSerialize, - BorshDeserialize, - Debug, - Clone, - Eq, - PartialEq, - serde::Serialize, - serde::Deserialize, -)] -pub struct ValidatorStakeViewV1 { - pub account_id: AccountId, - // TODO(aeryz): implement the public key type and also impl BorshSerialize for it - pub public_key: PublicKey, - // TODO(aeryz): #[serde(with = "dec_format")] - pub stake: Balance, -} +impl TryFrom + for PublicKey +{ + type Error = TryFromPublicKeyError; -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum PublicKey { - Ed25519([u8; 32]), - Secp256k1([u8; 64]), + fn try_from( + value: protos::union::ibc::lightclients::near::v1::validator_stake_view::PublicKey, + ) -> Result { + Ok(match value { + protos::union::ibc::lightclients::near::v1::validator_stake_view::PublicKey::Ed25519(val) => Self::Ed25519(val.try_into().map_err(|_| TryFromPublicKeyError::InvalidLength)?), + protos::union::ibc::lightclients::near::v1::validator_stake_view::PublicKey::Secp256k1(val) => Self::Secp256k1(val.try_into().map_err(|_| TryFromPublicKeyError::InvalidLength)?), + }) + } } impl FromStr for KeyType { @@ -425,11 +372,35 @@ pub enum Signature { Secp256k1(Vec), } -/// The part of the block approval that is different for endorsements and skips -#[derive(BorshSerialize, BorshDeserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Hash)] -pub enum ApprovalInner { - Endorsement(CryptoHash), - Skip(BlockHeight), +impl Signature { + pub fn inner(&self) -> &[u8] { + match self { + Signature::Ed25519(val) => val.as_slice(), + Signature::Secp256k1(val) => val.as_slice(), + } + } +} + +impl From for protos::union::ibc::lightclients::near::v1::signature::Signature { + fn from(value: Signature) -> Self { + match value { + Signature::Ed25519(val) => Self::Ed25519(val), + Signature::Secp256k1(val) => Self::Secp256k1(val), + } + } +} + +impl From for Signature { + fn from(value: protos::union::ibc::lightclients::near::v1::signature::Signature) -> Self { + match value { + protos::union::ibc::lightclients::near::v1::signature::Signature::Ed25519(val) => { + Self::Ed25519(val) + } + protos::union::ibc::lightclients::near::v1::signature::Signature::Secp256k1(val) => { + Self::Secp256k1(val) + } + } + } } #[derive( diff --git a/lib/unionlabs/src/traits.rs b/lib/unionlabs/src/traits.rs index c13745ce47..da60460efb 100644 --- a/lib/unionlabs/src/traits.rs +++ b/lib/unionlabs/src/traits.rs @@ -14,9 +14,10 @@ use crate::{ google::protobuf::any::Any, ibc::{ core::client::height::{Height, IsHeight}, - lightclients::{arbitrum, cometbls, ethereum, scroll, tendermint, wasm}, + lightclients::{arbitrum, cometbls, ethereum, near, scroll, tendermint, wasm}, }, id::ClientId, + near::NEAR_REVISION_NUMBER, uint::U256, MaybeArbitrary, TypeUrl, }; @@ -153,6 +154,23 @@ pub trait ClientState { fn chain_id(&self) -> Self::ChainId; } +impl ClientState for near::client_state::ClientState { + type ChainId = String; + + type Height = Height; + + fn height(&self) -> Self::Height { + Height { + revision_number: NEAR_REVISION_NUMBER, + revision_height: self.latest_height, + } + } + + fn chain_id(&self) -> Self::ChainId { + self.chain_id.clone() + } +} + impl ClientState for ethereum::client_state::ClientState { type ChainId = U256; type Height = Height; @@ -299,10 +317,25 @@ impl Header for tendermint::header::Header { } } +impl Header for near::header::Header { + fn trusted_height(&self) -> Height { + Height { + revision_number: NEAR_REVISION_NUMBER, + revision_height: self.trusted_height, + } + } +} + pub trait ConsensusState { fn timestamp(&self) -> u64; } +impl ConsensusState for near::consensus_state::ConsensusState { + fn timestamp(&self) -> u64 { + self.timestamp + } +} + impl ConsensusState for ethereum::consensus_state::ConsensusState { fn timestamp(&self) -> u64 { self.timestamp diff --git a/light-clients/cometbls-light-client/cometbls-light-client.nix b/light-clients/cometbls/cometbls-light-client.nix similarity index 66% rename from light-clients/cometbls-light-client/cometbls-light-client.nix rename to light-clients/cometbls/cometbls-light-client.nix index 92216b9a96..8671b5d0d7 100644 --- a/light-clients/cometbls-light-client/cometbls-light-client.nix +++ b/light-clients/cometbls/cometbls-light-client.nix @@ -1,8 +1,8 @@ { ... }: { perSystem = { crane, lib, ensure-wasm-client-type, ... }: let - workspace = (crane.buildWasmContract { - crateDirFromRoot = "light-clients/cometbls-light-client"; + ics08 = (crane.buildWasmContract { + crateDirFromRoot = "light-clients/cometbls/ics08"; checks = [ (file_path: '' ${ensure-wasm-client-type { @@ -14,6 +14,6 @@ }); in { - inherit (workspace) packages checks; + inherit (ics08) packages checks; }; } diff --git a/light-clients/cometbls-light-client/Cargo.toml b/light-clients/cometbls/ics08/Cargo.toml similarity index 93% rename from light-clients/cometbls-light-client/Cargo.toml rename to light-clients/cometbls/ics08/Cargo.toml index ba4208b31f..eb79d8d867 100644 --- a/light-clients/cometbls-light-client/Cargo.toml +++ b/light-clients/cometbls/ics08/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Union Labs"] edition = "2021" license = "BSL-1.1" -name = "cometbls-light-client" +name = "cometbls-ics08" publish = false version = "0.1.0" @@ -10,7 +10,7 @@ version = "0.1.0" workspace = true [package.metadata.crane] -test-include = ["light-clients/cometbls-light-client/src/test"] +test-include = ["light-clients/cometbls/ics08/src/test"] [lib] crate-type = ["cdylib", "rlib"] diff --git a/light-clients/cometbls-light-client/src/client.rs b/light-clients/cometbls/ics08/src/client.rs similarity index 100% rename from light-clients/cometbls-light-client/src/client.rs rename to light-clients/cometbls/ics08/src/client.rs diff --git a/light-clients/cometbls-light-client/src/contract.rs b/light-clients/cometbls/ics08/src/contract.rs similarity index 100% rename from light-clients/cometbls-light-client/src/contract.rs rename to light-clients/cometbls/ics08/src/contract.rs diff --git a/light-clients/cometbls-light-client/src/errors.rs b/light-clients/cometbls/ics08/src/errors.rs similarity index 100% rename from light-clients/cometbls-light-client/src/errors.rs rename to light-clients/cometbls/ics08/src/errors.rs diff --git a/light-clients/cometbls-light-client/src/lib.rs b/light-clients/cometbls/ics08/src/lib.rs similarity index 100% rename from light-clients/cometbls-light-client/src/lib.rs rename to light-clients/cometbls/ics08/src/lib.rs diff --git a/light-clients/cometbls-light-client/src/storage.rs b/light-clients/cometbls/ics08/src/storage.rs similarity index 100% rename from light-clients/cometbls-light-client/src/storage.rs rename to light-clients/cometbls/ics08/src/storage.rs diff --git a/light-clients/cometbls-light-client/src/test/client_state.json b/light-clients/cometbls/ics08/src/test/client_state.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/client_state.json rename to light-clients/cometbls/ics08/src/test/client_state.json diff --git a/light-clients/cometbls-light-client/src/test/consensus_state.json b/light-clients/cometbls/ics08/src/test/consensus_state.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/consensus_state.json rename to light-clients/cometbls/ics08/src/test/consensus_state.json diff --git a/light-clients/cometbls-light-client/src/test/substitute_client_state.json b/light-clients/cometbls/ics08/src/test/substitute_client_state.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/substitute_client_state.json rename to light-clients/cometbls/ics08/src/test/substitute_client_state.json diff --git a/light-clients/cometbls-light-client/src/test/substitute_consensus_state.json b/light-clients/cometbls/ics08/src/test/substitute_consensus_state.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/substitute_consensus_state.json rename to light-clients/cometbls/ics08/src/test/substitute_consensus_state.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1124.json b/light-clients/cometbls/ics08/src/test/updates/1124.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1124.json rename to light-clients/cometbls/ics08/src/test/updates/1124.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1131.json b/light-clients/cometbls/ics08/src/test/updates/1131.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1131.json rename to light-clients/cometbls/ics08/src/test/updates/1131.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1138.json b/light-clients/cometbls/ics08/src/test/updates/1138.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1138.json rename to light-clients/cometbls/ics08/src/test/updates/1138.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1146.json b/light-clients/cometbls/ics08/src/test/updates/1146.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1146.json rename to light-clients/cometbls/ics08/src/test/updates/1146.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1153.json b/light-clients/cometbls/ics08/src/test/updates/1153.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1153.json rename to light-clients/cometbls/ics08/src/test/updates/1153.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1158.json b/light-clients/cometbls/ics08/src/test/updates/1158.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1158.json rename to light-clients/cometbls/ics08/src/test/updates/1158.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1166.json b/light-clients/cometbls/ics08/src/test/updates/1166.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1166.json rename to light-clients/cometbls/ics08/src/test/updates/1166.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1171.json b/light-clients/cometbls/ics08/src/test/updates/1171.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1171.json rename to light-clients/cometbls/ics08/src/test/updates/1171.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1180.json b/light-clients/cometbls/ics08/src/test/updates/1180.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1180.json rename to light-clients/cometbls/ics08/src/test/updates/1180.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1185.json b/light-clients/cometbls/ics08/src/test/updates/1185.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1185.json rename to light-clients/cometbls/ics08/src/test/updates/1185.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1193.json b/light-clients/cometbls/ics08/src/test/updates/1193.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1193.json rename to light-clients/cometbls/ics08/src/test/updates/1193.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1200.json b/light-clients/cometbls/ics08/src/test/updates/1200.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1200.json rename to light-clients/cometbls/ics08/src/test/updates/1200.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1208.json b/light-clients/cometbls/ics08/src/test/updates/1208.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1208.json rename to light-clients/cometbls/ics08/src/test/updates/1208.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1214.json b/light-clients/cometbls/ics08/src/test/updates/1214.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1214.json rename to light-clients/cometbls/ics08/src/test/updates/1214.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1221.json b/light-clients/cometbls/ics08/src/test/updates/1221.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1221.json rename to light-clients/cometbls/ics08/src/test/updates/1221.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1228.json b/light-clients/cometbls/ics08/src/test/updates/1228.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1228.json rename to light-clients/cometbls/ics08/src/test/updates/1228.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1235.json b/light-clients/cometbls/ics08/src/test/updates/1235.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1235.json rename to light-clients/cometbls/ics08/src/test/updates/1235.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1242.json b/light-clients/cometbls/ics08/src/test/updates/1242.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1242.json rename to light-clients/cometbls/ics08/src/test/updates/1242.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1249.json b/light-clients/cometbls/ics08/src/test/updates/1249.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1249.json rename to light-clients/cometbls/ics08/src/test/updates/1249.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1256.json b/light-clients/cometbls/ics08/src/test/updates/1256.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1256.json rename to light-clients/cometbls/ics08/src/test/updates/1256.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1262.json b/light-clients/cometbls/ics08/src/test/updates/1262.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1262.json rename to light-clients/cometbls/ics08/src/test/updates/1262.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1271.json b/light-clients/cometbls/ics08/src/test/updates/1271.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1271.json rename to light-clients/cometbls/ics08/src/test/updates/1271.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1277.json b/light-clients/cometbls/ics08/src/test/updates/1277.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1277.json rename to light-clients/cometbls/ics08/src/test/updates/1277.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1285.json b/light-clients/cometbls/ics08/src/test/updates/1285.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1285.json rename to light-clients/cometbls/ics08/src/test/updates/1285.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1290.json b/light-clients/cometbls/ics08/src/test/updates/1290.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1290.json rename to light-clients/cometbls/ics08/src/test/updates/1290.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1298.json b/light-clients/cometbls/ics08/src/test/updates/1298.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1298.json rename to light-clients/cometbls/ics08/src/test/updates/1298.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1304.json b/light-clients/cometbls/ics08/src/test/updates/1304.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1304.json rename to light-clients/cometbls/ics08/src/test/updates/1304.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1312.json b/light-clients/cometbls/ics08/src/test/updates/1312.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1312.json rename to light-clients/cometbls/ics08/src/test/updates/1312.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1318.json b/light-clients/cometbls/ics08/src/test/updates/1318.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1318.json rename to light-clients/cometbls/ics08/src/test/updates/1318.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1326.json b/light-clients/cometbls/ics08/src/test/updates/1326.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1326.json rename to light-clients/cometbls/ics08/src/test/updates/1326.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1331.json b/light-clients/cometbls/ics08/src/test/updates/1331.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1331.json rename to light-clients/cometbls/ics08/src/test/updates/1331.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1339.json b/light-clients/cometbls/ics08/src/test/updates/1339.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1339.json rename to light-clients/cometbls/ics08/src/test/updates/1339.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1344.json b/light-clients/cometbls/ics08/src/test/updates/1344.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1344.json rename to light-clients/cometbls/ics08/src/test/updates/1344.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1350.json b/light-clients/cometbls/ics08/src/test/updates/1350.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1350.json rename to light-clients/cometbls/ics08/src/test/updates/1350.json diff --git a/light-clients/cometbls-light-client/src/test/updates/1357.json b/light-clients/cometbls/ics08/src/test/updates/1357.json similarity index 100% rename from light-clients/cometbls-light-client/src/test/updates/1357.json rename to light-clients/cometbls/ics08/src/test/updates/1357.json diff --git a/light-clients/cometbls-light-client/src/zkp_verifier.rs b/light-clients/cometbls/ics08/src/zkp_verifier.rs similarity index 100% rename from light-clients/cometbls-light-client/src/zkp_verifier.rs rename to light-clients/cometbls/ics08/src/zkp_verifier.rs diff --git a/light-clients/cometbls/near/Cargo.toml b/light-clients/cometbls/near/Cargo.toml new file mode 100644 index 0000000000..2a0cf2a894 --- /dev/null +++ b/light-clients/cometbls/near/Cargo.toml @@ -0,0 +1,25 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "cometbls-near" +repository.workspace = true +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +borsh = { workspace = true, features = ["derive"] } +hex-literal = { workspace = true } +hex.workspace = true +ibc-vm-rs = { workspace = true } +ics23 = { workspace = true } +near-contract-standards = { workspace = true } +near-primitives-core = { version = "0.21" } +near-sdk = { workspace = true, features = ["wee_alloc"] } +near-sdk-contract-tools = { workspace = true } +thiserror = { workspace = true } +unionlabs = { workspace = true, features = ["near"] } + +[lints] +workspace = true diff --git a/light-clients/cometbls/near/src/contract.rs b/light-clients/cometbls/near/src/contract.rs new file mode 100644 index 0000000000..2058020b4c --- /dev/null +++ b/light-clients/cometbls/near/src/contract.rs @@ -0,0 +1,310 @@ +use ibc_vm_rs::{IbcQuery, IbcResponse, Status}; +use ics23::ibc_api::SDK_SPECS; +use near_sdk::{ + borsh::{BorshDeserialize, BorshSerialize}, + env, near_bindgen, + store::LookupMap, +}; +use unionlabs::{ + encoding::{DecodeAs, EncodeAs as _, Proto}, + ibc::{ + core::{ + client::height::Height, + commitment::{ + merkle_path::MerklePath, merkle_proof::MerkleProof, merkle_root::MerkleRoot, + }, + }, + lightclients::cometbls::{ + client_state::ClientState, consensus_state::ConsensusState, header::Header, + }, + }, + id::ClientId, +}; + +use crate::{error::Error, verifier::verify_zkp}; + +#[near_bindgen] +#[derive(BorshDeserialize, BorshSerialize)] +pub struct Contract { + consensus_states: LookupMap>, + client_states: LookupMap, +} + +impl Default for Contract { + fn default() -> Self { + Self { + consensus_states: LookupMap::new(0), + client_states: LookupMap::new(1), + } + } +} + +#[near_bindgen] +impl Contract { + pub fn new( + &mut self, + #[allow(unused)] client_id: ClientId, + client_state: Vec, + consensus_state: Vec, + ) { + let client_state = ClientState::decode_as::(&client_state).unwrap(); + let consensus_state = ConsensusState::decode_as::(&consensus_state).unwrap(); + + if self.client_states.contains_key(&client_id.to_string()) { + env::panic_str("already have the client"); + } + + // TODO(aeryz): we might wanna store a cursor for this to reduce storage + let mut inner_consensus_states: LookupMap = + LookupMap::new(client_id.to_string().as_bytes()); + inner_consensus_states.insert(client_state.latest_height, consensus_state); + + self.consensus_states + .insert(client_id.to_string(), inner_consensus_states); + + self.client_states + .insert(client_id.to_string(), client_state); + } + + pub fn query(&self, client_id: ClientId, query: Vec) -> Vec { + query + .into_iter() + .map(|q| match q { + IbcQuery::Status => IbcResponse::Status { + status: self.status(), + }, + IbcQuery::LatestHeight => IbcResponse::LatestHeight { + height: self.latest_height(&client_id), + }, + IbcQuery::VerifyMembership { + height, + delay_time_period, + delay_block_period, + proof, + path, + value, + } => IbcResponse::VerifyMembership { + valid: self.verify_membership( + &client_id, + height, + delay_time_period, + delay_block_period, + proof, + path, + value, + ), + }, + IbcQuery::VerifyClientMessage(msg) => IbcResponse::VerifyClientMessage { + error: self + .verify_client_message(&client_id, msg) + .map_err(|e| e.to_string()) + .err(), + }, + IbcQuery::CheckForMisbehaviour(msg) => IbcResponse::CheckForMisbehaviour { + misbehaviour_found: self.check_for_misbehaviour(msg), + }, + IbcQuery::TimestampAtHeight(_) => IbcResponse::TimestampAtHeight { timestamp: 100 }, + }) + .collect() + } + + fn status(&self) -> Status { + Status::Active + } + + fn latest_height(&self, client_id: &str) -> Height { + self.client_states.get(client_id).unwrap().latest_height + } + + #[allow(unused)] + fn verify_membership( + &self, + client_id: &str, + height: Height, + // TODO(aeryz): delay times might not be relevant for other chains we could make it optional + delay_time_period: u64, + delay_block_period: u64, + proof: Vec, + path: MerklePath, + value: Vec, + // TODO(aeryz): make this a proper error type this is stupid + ) -> Result<(), Error> { + let Ok(consensus_state) = self + .consensus_states + .get(client_id) + .unwrap() + .get(&height) + .ok_or(Error::ConsensusStateNotFound(height)) + else { + return false; + }; + + let Ok(merkle_proof) = + MerkleProof::decode_as::(proof.as_ref()).map_err(Error::MerkleProofDecode) + else { + return false; + }; + + ics23::ibc_api::verify_membership( + &merkle_proof, + &SDK_SPECS, + &consensus_state.app_hash, + &path + .key_path + .into_iter() + .map(|x| x.into_bytes()) + .collect::>(), + value, + ) + .is_ok() + } + + // TODO(aeryz): client_msg can be Misbehaviour or Header + fn verify_client_message(&self, client_id: &str, client_msg: Vec) -> Result<(), Error> { + let header = Header::decode_as::(&client_msg)?; + let client_state = self + .client_states + .get(client_id) + .ok_or(Error::ClientNotFound(client_id.to_string()))?; + let consensus_states = self + .consensus_states + .get(client_id) + .ok_or(Error::ClientNotFound(client_id.to_string()))?; + + let consensus_state = consensus_states + .get(&header.trusted_height) + .ok_or(Error::ConsensusStateNotFound(header.trusted_height))?; + + // SAFETY: height is bound to be 0..i64::MAX which makes it within the bounds of u64 + let untrusted_height_number = header.signed_header.height.inner() as u64; + let trusted_height_number = header.trusted_height.revision_height; + + if untrusted_height_number <= trusted_height_number { + return Err(Error::SignedHeaderHeightMustBeMoreRecent { + signed_height: untrusted_height_number, + trusted_height: trusted_height_number, + }); + } + + let trusted_timestamp = consensus_state.timestamp; + // Normalized to nanoseconds to follow tendermint convention + let untrusted_timestamp = header.signed_header.time.as_unix_nanos(); + + if untrusted_timestamp <= trusted_timestamp { + return Err(Error::SignedHeaderTimestampMustBeMoreRecent { + signed_timestamp: untrusted_timestamp, + trusted_timestamp, + }); + } + + if is_client_expired( + untrusted_timestamp, + client_state.trusting_period, + env::block_timestamp(), + ) { + return Err(Error::HeaderExpired(consensus_state.timestamp)); + } + + let max_clock_drift = env::block_timestamp() + .checked_add(client_state.max_clock_drift) + .ok_or(Error::MathOverflow)?; + + if untrusted_timestamp >= max_clock_drift { + return Err(Error::SignedHeaderCannotExceedMaxClockDrift { + signed_timestamp: untrusted_timestamp, + max_clock_drift, + }); + } + + let trusted_validators_hash = consensus_state.next_validators_hash; + + if untrusted_height_number == trusted_height_number + 1 + && header.signed_header.validators_hash != trusted_validators_hash + { + return Err(Error::InvalidValidatorsHash { + expected: trusted_validators_hash, + actual: header.signed_header.validators_hash, + }); + } + + verify_zkp( + &client_state.chain_id, + trusted_validators_hash, + &header.signed_header, + header.zero_knowledge_proof, + ) + } + + #[allow(unused)] + fn check_for_misbehaviour(&self, client_msg: Vec) -> bool { + false + } + + pub fn test_circuit( + &self, + chain_id: String, + trusted_validators_hash: unionlabs::hash::H256, + header: unionlabs::ibc::lightclients::cometbls::light_header::LightHeader, + zkp: Vec, + ) { + verify_zkp(&chain_id, trusted_validators_hash, &header, zkp).unwrap() + } + + pub fn update_client( + &mut self, + client_id: String, + client_msg: Vec, + ) -> (Vec, Vec<(Height, Vec)>) { + let header = Header::decode_as::(&client_msg).unwrap(); + + let untrusted_height = Height { + revision_number: header.trusted_height.revision_number, + revision_height: header.signed_header.height.inner() as u64, + }; + + let client_state = self.client_states.get_mut(&client_id).unwrap(); + + if untrusted_height > client_state.latest_height { + client_state.latest_height = untrusted_height; + } + + let consensus_state = ConsensusState { + timestamp: header.signed_header.time.as_unix_nanos(), + app_hash: MerkleRoot { + hash: header.signed_header.app_hash, + }, + next_validators_hash: header.signed_header.next_validators_hash, + }; + + // TODO(aeryz): handle metadata + // save_consensus_state_metadata( + // deps.branch(), + // consensus_state.data.timestamp, + // untrusted_height, + // ); + self.consensus_states + .get_mut(&client_id) + .unwrap() + .insert(untrusted_height, consensus_state.clone()); + + ( + client_state.clone().encode_as::(), + vec![(untrusted_height, consensus_state.encode_as::())], + ) + } + + #[allow(unused)] + pub fn update_client_on_misbehaviour(&mut self, client_msg: Vec) {} +} + +fn is_client_expired( + consensus_state_timestamp: u64, + trusting_period: u64, + current_block_time: u64, +) -> bool { + if let Some(sum) = consensus_state_timestamp.checked_add(trusting_period) { + sum < current_block_time + } else { + true + } +} diff --git a/light-clients/cometbls/near/src/error.rs b/light-clients/cometbls/near/src/error.rs new file mode 100644 index 0000000000..7b43e072ee --- /dev/null +++ b/light-clients/cometbls/near/src/error.rs @@ -0,0 +1,83 @@ +use unionlabs::{ + encoding::{DecodeErrorOf, Proto}, + hash::H256, + ibc::{ + core::{client::height::Height, commitment::merkle_proof::MerkleProof}, + lightclients::cometbls::{self, header::TryFromHeaderError}, + }, + TryFromProtoBytesError, +}; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum Error { + #[error("math operation with overflow")] + MathOverflow, + + #[error("unimplemented feature")] + Unimplemented, + + #[error("unable to decode merkle proof")] + MerkleProofDecode(#[source] DecodeErrorOf), + + #[error("unable to decode client state")] + ClientStateDecode(#[source] DecodeErrorOf), + + #[error("client not found")] + ClientNotFound(String), + + #[error("Client state not found")] + ClientStateNotFound, + + #[error("Consensus state not found for {0}")] + ConsensusStateNotFound(Height), + + #[error("verify membership error: {0}")] + VerifyMembership(#[from] ics23::ibc_api::VerifyMembershipError), + + #[error("substitute client is frozen")] + SubstituteClientFrozen, + + #[error("forbidden fields have been changed during state migration")] + MigrateFieldsChanged, + + #[error("the chain id cannot be more than 31 bytes long to fit in the bn254 scalar field")] + InvalidChainId, + + #[error("invalid zkp length")] + InvalidZKPLength, + + #[error("invalid height")] + InvalidHeight, + + #[error("invalid timestamp")] + InvalidTimestamp, + + #[error( + "trusted validators hash ({expected:?}) does not match the given header's hash ({actual})" + )] + InvalidValidatorsHash { expected: H256, actual: H256 }, + + #[error("signed header ({signed_timestamp}) exceeds the max clock drift ({max_clock_drift})")] + SignedHeaderCannotExceedMaxClockDrift { + signed_timestamp: u64, + max_clock_drift: u64, + }, + + #[error("header with timestamp ({0}) is expired")] + HeaderExpired(u64), + + #[error("signed header with timestamp ({signed_timestamp}) must be more recent than the trusted one ({trusted_timestamp})")] + SignedHeaderTimestampMustBeMoreRecent { + signed_timestamp: u64, + trusted_timestamp: u64, + }, + + #[error("signed header with height ({signed_height}) must be more recent than the trusted one ({trusted_height})")] + SignedHeaderHeightMustBeMoreRecent { + signed_height: u64, + trusted_height: u64, + }, + + #[error("header decode failed {0}")] + HeaderDecode(#[from] TryFromProtoBytesError), +} diff --git a/light-clients/cometbls/near/src/lib.rs b/light-clients/cometbls/near/src/lib.rs new file mode 100644 index 0000000000..0a0ced5d53 --- /dev/null +++ b/light-clients/cometbls/near/src/lib.rs @@ -0,0 +1,3 @@ +pub mod contract; +pub mod error; +pub mod verifier; diff --git a/light-clients/cometbls/near/src/verifier.rs b/light-clients/cometbls/near/src/verifier.rs new file mode 100644 index 0000000000..2a5153810f --- /dev/null +++ b/light-clients/cometbls/near/src/verifier.rs @@ -0,0 +1,400 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use hex_literal::hex; +use near_sdk::env; +use unionlabs::{ + hash::H256, ibc::lightclients::cometbls::light_header::LightHeader, uint::U256, ByteArrayExt, +}; + +use crate::error::Error; + +#[derive(Copy, Debug, Default, Clone, BorshSerialize, BorshDeserialize)] +pub struct Fq(pub [u8; 32]); + +impl From for Fq { + fn from(value: u64) -> Self { + let mut buffer = [0u8; 32]; + buffer[0..8].copy_from_slice(&value.to_le_bytes()); + Fq(buffer) + } +} + +#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)] +pub struct Fq2(pub Fq, pub Fq); + +pub const FQ_SIZE: usize = 32; +pub const G1_SIZE: usize = 2 * FQ_SIZE; +pub const G2_SIZE: usize = 2 * G1_SIZE; +pub const EXPECTED_PROOF_SIZE: usize = G1_SIZE + G2_SIZE + G1_SIZE + G1_SIZE + G1_SIZE; + +pub const HMAC_O: &[u8] = &hex!("1F333139281E100F5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C5C"); +pub const HMAC_I: &[u8] = &hex!("75595B5342747A653636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636"); +pub const PRIME_R_MINUS_ONE: U256 = U256::from_limbs([ + 4891460686036598784, + 2896914383306846353, + 13281191951274694749, + 3486998266802970665, +]); + +pub const ALPHA_G1: G1Affine = G1Affine { + x: Fq([ + 153, 168, 24, 193, 103, 1, 111, 127, 109, 2, 216, 64, 5, 165, 237, 31, 124, 108, 25, 196, + 221, 241, 87, 51, 182, 122, 204, 1, 41, 7, 103, 9, + ]), + y: Fq([ + 255, 129, 13, 157, 51, 116, 128, 128, 105, 193, 234, 30, 93, 38, 58, 144, 207, 129, 129, + 185, 139, 65, 88, 5, 121, 113, 118, 53, 122, 206, 199, 8, + ]), +}; +pub const BETA_G2: G2Affine = G2Affine { + x: Fq2( + Fq([ + 116, 40, 132, 234, 24, 160, 14, 243, 24, 116, 213, 252, 85, 17, 177, 143, 169, 57, 29, + 198, 155, 151, 27, 137, 138, 45, 191, 198, 68, 3, 63, 21, + ]), + Fq([ + 101, 109, 201, 47, 31, 148, 220, 23, 0, 38, 205, 128, 33, 46, 81, 96, 210, 83, 158, + 126, 139, 64, 136, 93, 29, 96, 183, 112, 210, 95, 53, 25, + ]), + ), + y: Fq2( + Fq([ + 161, 102, 168, 244, 129, 112, 104, 183, 220, 39, 2, 48, 133, 154, 161, 86, 245, 174, + 18, 32, 215, 225, 172, 145, 34, 57, 191, 2, 102, 143, 210, 39, + ]), + Fq([ + 118, 162, 9, 196, 98, 30, 188, 69, 94, 17, 6, 29, 53, 168, 161, 189, 86, 130, 108, 217, + 134, 186, 224, 104, 244, 98, 252, 218, 60, 228, 213, 34, + ]), + ), +}; +pub const GAMMA_G2: G2Affine = G2Affine { + x: Fq2( + Fq([ + 25, 182, 113, 158, 66, 196, 46, 209, 223, 70, 250, 8, 200, 112, 197, 36, 26, 82, 145, + 59, 101, 217, 180, 54, 121, 224, 137, 194, 224, 187, 22, 34, + ]), + Fq([ + 207, 58, 72, 156, 167, 146, 127, 79, 129, 64, 10, 46, 189, 115, 154, 147, 91, 206, 179, + 34, 66, 100, 239, 248, 226, 72, 49, 26, 233, 107, 231, 32, + ]), + ), + y: Fq2( + Fq([ + 58, 175, 246, 108, 37, 153, 187, 92, 145, 127, 238, 195, 150, 148, 55, 234, 53, 232, + 223, 200, 25, 159, 60, 223, 127, 58, 237, 187, 117, 107, 46, 39, + ]), + Fq([ + 113, 5, 38, 216, 83, 140, 105, 78, 143, 175, 204, 198, 146, 97, 207, 62, 194, 205, 84, + 90, 225, 218, 199, 37, 240, 196, 96, 232, 96, 77, 239, 47, + ]), + ), +}; +pub const DELTA_G2: G2Affine = G2Affine { + x: Fq2( + Fq([ + 235, 4, 77, 219, 149, 30, 155, 40, 237, 167, 218, 147, 171, 163, 65, 239, 44, 150, 164, + 214, 24, 44, 167, 133, 163, 32, 24, 201, 200, 3, 212, 5, + ]), + Fq([ + 252, 185, 240, 74, 49, 201, 136, 162, 245, 166, 71, 16, 255, 175, 225, 1, 131, 29, 97, + 71, 37, 155, 84, 228, 93, 71, 224, 209, 24, 76, 94, 41, + ]), + ), + y: Fq2( + Fq([ + 7, 27, 211, 147, 97, 116, 151, 45, 70, 133, 59, 213, 58, 234, 80, 223, 14, 241, 63, + 128, 113, 63, 238, 42, 105, 160, 139, 179, 163, 166, 218, 5, + ]), + Fq([ + 66, 178, 169, 55, 218, 119, 194, 159, 243, 241, 194, 245, 213, 52, 254, 226, 230, 16, + 152, 197, 183, 237, 181, 35, 116, 35, 185, 83, 114, 104, 79, 21, + ]), + ), +}; +pub const PEDERSEN_G: G2Affine = G2Affine { + x: Fq2( + Fq([ + 90, 229, 109, 192, 20, 168, 19, 119, 18, 244, 88, 70, 88, 186, 111, 126, 57, 12, 195, + 152, 146, 249, 126, 86, 202, 133, 152, 135, 216, 216, 240, 19, + ]), + Fq([ + 135, 25, 189, 159, 250, 43, 186, 150, 57, 81, 218, 46, 8, 186, 146, 255, 193, 4, 155, + 162, 241, 253, 125, 127, 3, 176, 44, 19, 248, 246, 125, 37, + ]), + ), + y: Fq2( + Fq([ + 52, 127, 186, 120, 64, 214, 31, 105, 58, 120, 83, 131, 39, 49, 242, 98, 233, 101, 60, + 206, 146, 127, 168, 224, 219, 180, 141, 197, 66, 6, 232, 21, + ]), + Fq([ + 138, 121, 13, 200, 103, 254, 116, 205, 74, 105, 57, 58, 93, 104, 19, 238, 40, 245, 147, + 89, 234, 252, 14, 86, 172, 163, 199, 96, 204, 235, 96, 22, + ]), + ), +}; + +pub const PEDERSEN_G_ROOT_SIGMA_NEG: G2Affine = G2Affine { + x: Fq2( + Fq([ + 175, 91, 78, 48, 18, 58, 52, 67, 57, 50, 29, 214, 33, 181, 253, 249, 205, 152, 112, 98, + 89, 40, 250, 7, 35, 95, 1, 28, 223, 4, 161, 2, + ]), + Fq([ + 104, 99, 202, 226, 242, 176, 192, 206, 69, 126, 129, 173, 37, 160, 104, 251, 28, 184, + 96, 38, 9, 107, 232, 227, 247, 92, 85, 167, 65, 225, 191, 47, + ]), + ), + y: Fq2( + Fq([ + 141, 219, 123, 186, 89, 3, 145, 235, 166, 53, 241, 135, 102, 127, 128, 151, 187, 80, + 34, 126, 222, 87, 219, 103, 207, 229, 185, 28, 85, 56, 8, 44, + ]), + Fq([ + 34, 89, 106, 0, 43, 110, 130, 192, 130, 159, 109, 155, 83, 14, 12, 35, 108, 42, 22, + 226, 74, 53, 163, 179, 208, 191, 243, 236, 147, 63, 218, 39, + ]), + ), +}; +pub const GAMMA_ABC_G1: [G1Affine; 3] = [ + G1Affine { + x: Fq([ + 129, 146, 83, 48, 148, 29, 83, 216, 206, 193, 196, 66, 16, 246, 200, 130, 254, 232, 44, + 74, 233, 124, 182, 75, 79, 134, 67, 39, 229, 67, 24, 39, + ]), + y: Fq([ + 6, 36, 203, 115, 37, 168, 159, 234, 122, 210, 203, 222, 71, 138, 123, 163, 142, 202, + 24, 187, 161, 240, 36, 246, 114, 177, 248, 156, 198, 66, 51, 37, + ]), + }, + G1Affine { + x: Fq([ + 202, 75, 18, 93, 94, 26, 46, 192, 226, 38, 114, 67, 79, 190, 156, 160, 227, 202, 21, + 176, 194, 14, 22, 233, 2, 14, 214, 244, 113, 190, 13, 11, + ]), + y: Fq([ + 12, 224, 112, 182, 168, 185, 95, 104, 112, 20, 216, 61, 224, 159, 158, 254, 51, 202, + 175, 22, 170, 146, 229, 236, 136, 131, 118, 211, 235, 154, 11, 19, + ]), + }, + G1Affine { + x: Fq([ + 199, 144, 196, 161, 145, 138, 177, 46, 126, 60, 54, 0, 91, 47, 92, 188, 245, 64, 140, + 237, 152, 3, 53, 113, 118, 12, 124, 244, 213, 147, 158, 2, + ]), + y: Fq([ + 217, 241, 238, 106, 156, 19, 182, 235, 190, 46, 17, 218, 178, 63, 86, 0, 4, 15, 203, + 131, 59, 181, 121, 143, 174, 207, 157, 69, 16, 5, 241, 44, + ]), + }, +]; + +#[derive(Copy, Debug, Default, Clone, BorshSerialize, BorshDeserialize)] +pub struct G1Affine { + pub x: Fq, + pub y: Fq, +} + +impl G1Affine { + pub const fn zero() -> Self { + Self { + x: Fq([0; 32]), + y: Fq([0; 32]), + } + } +} + +#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)] +pub struct G2Affine { + pub x: Fq2, + pub y: Fq2, +} + +impl G2Affine { + pub const fn zero() -> Self { + Self { + x: Fq2(Fq([0; 32]), Fq([0; 32])), + y: Fq2(Fq([0; 32]), Fq([0; 32])), + } + } +} + +pub struct Proof { + pub a: G1Affine, + pub b: G2Affine, + pub c: G1Affine, +} + +pub struct ZKP { + pub proof: Proof, + pub proof_commitment: G1Affine, + pub proof_commitment_pok: G1Affine, +} + +pub type RawZKP = [u8; EXPECTED_PROOF_SIZE]; + +impl TryFrom<&[u8]> for ZKP { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let value = RawZKP::try_from(value).map_err(|_| Error::InvalidZKPLength)?; + + Ok(Self { + proof: Proof { + a: G1Affine::from(value.array_slice::<0, G1_SIZE>()), + b: G2Affine::from(value.array_slice::()), + c: G1Affine::from(value.array_slice::<{ G1_SIZE + G2_SIZE }, G1_SIZE>()), + }, + proof_commitment: G1Affine::from( + value.array_slice::<{ G1_SIZE + G2_SIZE + G1_SIZE }, G1_SIZE>(), + ), + proof_commitment_pok: G1Affine::from( + value.array_slice::<{ G1_SIZE + G2_SIZE + G1_SIZE + G1_SIZE }, G1_SIZE>(), + ), + }) + } +} + +impl From<[u8; G1_SIZE]> for G1Affine { + fn from(value: [u8; G1_SIZE]) -> Self { + let mut x = value.array_slice::<0, FQ_SIZE>(); + x.reverse(); + let mut y = value.array_slice::(); + y.reverse(); + G1Affine { x: Fq(x), y: Fq(y) } + } +} + +impl From<[u8; G2_SIZE]> for G2Affine { + fn from(value: [u8; G2_SIZE]) -> Self { + let mut x_0 = value.array_slice::(); + x_0.reverse(); + let mut x_1 = value.array_slice::<0, FQ_SIZE>(); + x_1.reverse(); + let mut y_0 = value.array_slice::<{ G1_SIZE + FQ_SIZE }, FQ_SIZE>(); + y_0.reverse(); + let mut y_1 = value.array_slice::(); + y_1.reverse(); + G2Affine { + x: Fq2(Fq(x_0), Fq(x_1)), + y: Fq2(Fq(y_0), Fq(y_1)), + } + } +} + +pub fn verify_zkp( + chain_id: &str, + trusted_validators_hash: H256, + header: &LightHeader, + zkp: impl Into>, +) -> Result<(), Error> { + let zkp = ZKP::try_from(zkp.into().as_ref())?; + + let commitment_hash = hash_commitment(&zkp.proof_commitment)?; + + let mut inputs_hash = env::sha256_array( + &vec![0u8; 32 - chain_id.len()] + .into_iter() + .chain(chain_id.bytes()) + .chain( + U256::from( + u64::try_from(i64::from(header.height)).map_err(|_| Error::InvalidHeight)?, + ) + .to_be_bytes(), + ) + .chain( + U256::from( + u64::try_from(i64::from(header.time.seconds)) + .map_err(|_| Error::InvalidTimestamp)?, + ) + .to_be_bytes(), + ) + .chain( + U256::from( + u64::try_from(i32::from(header.time.nanos)) + .map_err(|_| Error::InvalidTimestamp)?, + ) + .to_be_bytes(), + ) + .chain(header.validators_hash) + .chain(header.next_validators_hash) + .chain(header.app_hash) + .chain(trusted_validators_hash) + .collect::>(), + ); + + // drop the most significant byte to fit in bn254 F_r + inputs_hash[0] = 0; + inputs_hash.reverse(); + + let public_inputs = [Fq(inputs_hash), commitment_hash]; + + let initial_point: G1Affine = borsh::from_slice(&env::alt_bn128_g1_sum( + &borsh::to_vec(&[ + (false, GAMMA_ABC_G1[0].clone()), + (false, zkp.proof_commitment.clone()), + ]) + .unwrap(), + )) + .unwrap(); + + let public_inputs_msm = public_inputs + .into_iter() + .zip(GAMMA_ABC_G1.into_iter().skip(1)) + .fold(initial_point, |s, (w_i, gamma_l_i)| { + let mul: G1Affine = borsh::from_slice(&env::alt_bn128_g1_multiexp( + &borsh::to_vec(&[(gamma_l_i, w_i)]).unwrap(), + )) + .unwrap(); + + borsh::from_slice::(&env::alt_bn128_g1_sum( + &borsh::to_vec(&[(false, s), (false, mul)]).unwrap(), + )) + .unwrap() + }); + + // TODO(aeryz): either split the pairing or apply fiat-shamir transformation + if env::alt_bn128_pairing_check( + &borsh::to_vec(&[ + (zkp.proof.a, zkp.proof.b), + (public_inputs_msm, GAMMA_G2), + (zkp.proof.c, DELTA_G2), + (ALPHA_G1, BETA_G2), + (zkp.proof_commitment, PEDERSEN_G), + (zkp.proof_commitment_pok, PEDERSEN_G_ROOT_SIGMA_NEG), + ]) + .unwrap(), + ) { + Ok(()) + } else { + env::panic_str("pairing check failed"); + } +} + +fn hmac_keccak(message: &[u8]) -> [u8; 32] { + env::keccak256_array( + &HMAC_O + .iter() + .copied() + .chain(env::keccak256( + &HMAC_I + .iter() + .copied() + .chain(message.iter().copied()) + .collect::>(), + )) + .collect::>(), + ) +} + +fn hash_commitment(proof_commitment: &G1Affine) -> Result { + let mut buffer = [0u8; 64]; + buffer[0..32].copy_from_slice(&proof_commitment.x.0); + buffer[0..32].reverse(); + buffer[32..64].copy_from_slice(&proof_commitment.y.0); + buffer[32..64].reverse(); + let hmac = hmac_keccak(&buffer); + let mut out = ((U256::from_be_bytes(hmac) % PRIME_R_MINUS_ONE) + U256::from(1)).to_be_bytes(); + out.reverse(); + Ok(Fq(out)) +} diff --git a/light-clients/near/ics08-near/Cargo.toml b/light-clients/near/ics08-near/Cargo.toml new file mode 100644 index 0000000000..1c707c9891 --- /dev/null +++ b/light-clients/near/ics08-near/Cargo.toml @@ -0,0 +1,45 @@ +[package] +authors = ["Union Labs"] +edition.workspace = true +license-file.workspace = true +name = "near-ics08" +repository.workspace = true +version = "0.1.0" + +[lints] +workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +bytes = { workspace = true } +cosmwasm-std = { workspace = true, features = ["abort", "iterator"] } +cw-storage-plus = { workspace = true } +dlmalloc = { workspace = true, features = ["global"] } +hex = { workspace = true } +prost = { workspace = true } +protos = { workspace = true, features = ["proto_full", "std"] } +ripemd = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde-json-wasm = { workspace = true } +serde-utils = { workspace = true } +sha2 = { workspace = true } +sha3 = { workspace = true } +thiserror = { workspace = true } + +borsh = { workspace = true } +ics008-wasm-client = { workspace = true } +near-primitives-core = { version = "0.23" } +near-verifier = { workspace = true } +unionlabs = { workspace = true, features = ["near"] } + +base64 = { workspace = true } +[dev-dependencies] +lazy_static = "1.4.0" +near-crypto = "0.23" +near-jsonrpc-client = "0.10.1" +near-primitives = "0.23.0" +serde_json = { workspace = true } +tokio = { workspace = true, features = ["full"] } diff --git a/light-clients/near/ics08-near/src/client.rs b/light-clients/near/ics08-near/src/client.rs new file mode 100644 index 0000000000..64505330f1 --- /dev/null +++ b/light-clients/near/ics08-near/src/client.rs @@ -0,0 +1,864 @@ +use cosmwasm_std::{Deps, Empty}; +use ics008_wasm_client::{ + storage_utils::{ + read_client_state, read_consensus_state, save_client_state, save_consensus_state, + }, + IbcClient, Status, +}; +use near_primitives_core::hash::CryptoHash; +use unionlabs::{ + encoding::{DecodeAs, EncodeAs, Proto}, + google::protobuf::any::Any, + ibc::{ + core::client::height::Height, + lightclients::{ + cometbls, + near::{ + client_state::ClientState, consensus_state::ConsensusState, header::Header, + validator_stake_view::ValidatorStakeView, + }, + wasm, + }, + }, + ics24::Path, + id::ClientId, + near::raw_state_proof::RawStateProof, +}; + +use crate::{errors::Error, state::EPOCH_BLOCK_PRODUCERS_MAP}; + +pub type WasmClientState = wasm::client_state::ClientState; +pub type WasmConsensusState = wasm::consensus_state::ConsensusState; + +pub struct NearLightClient; + +impl IbcClient for NearLightClient { + type Error = Error; + + type CustomQuery = Empty; + + type Header = Header; + + type Misbehaviour = Header; + + type ClientState = ClientState; + + type ConsensusState = ConsensusState; + + type Encoding = Proto; + + fn verify_membership( + deps: Deps, + mut height: Height, + _delay_time_period: u64, + _delay_block_period: u64, + proof: Vec, + path: unionlabs::ibc::core::commitment::merkle_path::MerklePath, + value: ics008_wasm_client::StorageState, + ) -> Result<(), ics008_wasm_client::IbcClientError> { + let proof: RawStateProof = serde_json_wasm::from_slice(&proof).unwrap(); + height.revision_height += 1; + let consensus_state: WasmConsensusState = read_consensus_state(deps, &height)? + .ok_or(Error::ConsensusStateNotFound(height.revision_height))?; + let client_state: WasmClientState = read_client_state(deps)?; + let key = key_from_path(path.key_path.last().ok_or(Error::EmptyPath)?); + + match value { + ics008_wasm_client::StorageState::Occupied(value) => { + let path = path.key_path.last().ok_or(Error::EmptyPath)?; + let path = path + .parse::>() + .map_err(|_| Error::UnknownIbcPath(path.clone()))?; + + let value = match path { + Path::ClientState(_) => { + Any::::decode_as::( + value.as_ref(), + ) + .map_err(|_| Error::ForeignStateDecode(value))? + .0 + .encode_as::() + } + Path::ClientConsensusState(_) => { + Any::< + wasm::consensus_state::ConsensusState< + cometbls::consensus_state::ConsensusState, + >, + >::decode_as::(value.as_ref()) + .map_err(|_| Error::ForeignStateDecode(value))? + .0 + .data + .encode_as::() + } + _ => value, + }; + near_verifier::verify_state( + proof, + &consensus_state.data.chunk_prev_state_root, + &client_state.data.ibc_account_id, + &key, + Some(&borsh::to_vec(&value).unwrap()), + ) + } + ics008_wasm_client::StorageState::Empty => near_verifier::verify_state( + proof, + &consensus_state.data.chunk_prev_state_root, + &client_state.data.ibc_account_id, + &key, + None, + ), + } + .map_err(Into::::into)?; + + Ok(()) + } + + fn verify_header( + deps: Deps, + _env: cosmwasm_std::Env, + header: Self::Header, + ) -> Result<(), ics008_wasm_client::IbcClientError> { + let wasm_consensus_state = read_consensus_state(deps, &height(header.trusted_height))? + .ok_or(Error::ConsensusStateNotFound(header.trusted_height))?; + + near_verifier::verify_header( + &NearVerifierCtx { deps }, + wasm_consensus_state.data.state, + header.new_state.clone(), + ) + .map_err(Into::::into)?; + + // verify the `prev_state_root` of the chunk that contains the light client against the merkle root of the `prev_state_root`s of all chunks + near_verifier::verify_path( + header.new_state.inner_lite.prev_state_root, + &header.prev_state_root_proof, + header.prev_state_root, + ) + .map_err(Into::::into)?; + + Ok(()) + } + + fn verify_misbehaviour( + _deps: Deps, + _env: cosmwasm_std::Env, + _misbehaviour: Self::Misbehaviour, + ) -> Result<(), ics008_wasm_client::IbcClientError> { + unimplemented!() + } + + fn update_state( + mut deps: cosmwasm_std::DepsMut, + _env: cosmwasm_std::Env, + header: Self::Header, + ) -> Result, ics008_wasm_client::IbcClientError> { + let update_height = header.new_state.inner_lite.height; + + let new_consensus_state = ConsensusState { + state: header.new_state.inner_lite.clone(), + chunk_prev_state_root: header.prev_state_root, + timestamp: header.new_state.inner_lite.timestamp_nanosec, + }; + + save_consensus_state::( + deps.branch(), + WasmConsensusState { + data: new_consensus_state, + }, + &height(update_height), + ); + + let mut client_state: WasmClientState = read_client_state(deps.as_ref())?; + + if update_height > client_state.data.latest_height { + client_state.data.latest_height = update_height; + client_state.latest_height.revision_height = update_height; + save_client_state::(deps.branch(), client_state); + } + + if let Some(next_bps) = header.new_state.next_bps { + EPOCH_BLOCK_PRODUCERS_MAP.save( + deps.storage, + header.new_state.inner_lite.next_epoch_id.0, + &next_bps, + )?; + } + + Ok(vec![height(update_height)]) + } + + fn update_state_on_misbehaviour( + _deps: cosmwasm_std::DepsMut, + _env: cosmwasm_std::Env, + _client_message: Vec, + ) -> Result<(), ics008_wasm_client::IbcClientError> { + unimplemented!() + } + + fn check_for_misbehaviour_on_header( + _deps: Deps, + _header: Self::Header, + ) -> Result> { + Ok(false) + } + + fn check_for_misbehaviour_on_misbehaviour( + _deps: Deps, + _misbehaviour: Self::Misbehaviour, + ) -> Result> { + unimplemented!() + } + + fn verify_upgrade_and_update_state( + _deps: cosmwasm_std::DepsMut, + _upgrade_client_state: Self::ClientState, + _upgrade_consensus_state: Self::ConsensusState, + _proof_upgrade_client: Vec, + _proof_upgrade_consensus_state: Vec, + ) -> Result<(), ics008_wasm_client::IbcClientError> { + todo!() + } + + fn migrate_client_store( + _deps: cosmwasm_std::DepsMut, + ) -> Result<(), ics008_wasm_client::IbcClientError> { + todo!() + } + + fn status( + deps: Deps, + _env: &cosmwasm_std::Env, + ) -> Result> { + let client_state: WasmClientState = read_client_state(deps)?; + + if client_state.data.frozen_height != 0 { + return Ok(Status::Frozen); + } + + Ok(Status::Active) + } + + fn export_metadata( + _deps: Deps, + _env: &cosmwasm_std::Env, + ) -> Result< + Vec, + ics008_wasm_client::IbcClientError, + > { + unimplemented!() + } + + fn timestamp_at_height( + deps: Deps, + height: Height, + ) -> Result> { + Ok(read_consensus_state::(deps, &height)? + .ok_or(Error::ConsensusStateNotFound(height.revision_height))? + .data + .timestamp) + } +} + +pub struct NearVerifierCtx<'a> { + deps: Deps<'a>, +} + +impl<'a> near_verifier::NearVerifierCtx for NearVerifierCtx<'a> { + fn get_epoch_block_producers(&self, epoch_id: CryptoHash) -> Option> { + match EPOCH_BLOCK_PRODUCERS_MAP.load(self.deps.storage, epoch_id.0) { + Ok(bp) => Some(bp), + Err(_) => None, + } + } + + fn ed25519_verify( + &self, + public_key: &[u8], + signature: &[u8], + message: &[u8], + ) -> Result<(), near_verifier::error::Error> { + match self.deps.api.ed25519_verify(message, signature, public_key) { + Ok(true) => Ok(()), + _ => Err(near_verifier::error::Error::VerificationFailure( + public_key.into(), + signature.into(), + message.into(), + )), + } + } +} + +fn height(height: u64) -> Height { + Height { + revision_number: 0, + revision_height: height, + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use ics008_wasm_client::InstantiateMsg; + use near_jsonrpc_client::{methods, JsonRpcClient}; + use near_primitives::{ + types::{BlockHeight, BlockId, BlockReference}, + views::{BlockHeaderInnerLiteView, LightClientBlockView}, + }; + use unionlabs::{ + encoding::{DecodeAs, EncodeAs}, + ibc::{core::connection::connection_end::ConnectionEnd, lightclients::near}, + near::types::{self, MerklePathItem}, + }; + + use super::*; + use crate::contract::instantiate; + + async fn initialize() -> ( + near_jsonrpc_client::JsonRpcClient, + cosmwasm_std::OwnedDeps< + cosmwasm_std::MemoryStorage, + cosmwasm_std::testing::MockApi, + cosmwasm_std::testing::MockQuerier, + >, + cosmwasm_std::Env, + cosmwasm_std::MessageInfo, + ClientState, + ConsensusState, + ) { + let rpc = near_jsonrpc_client::JsonRpcClient::connect("http://localhost:3030"); + let chain_id = rpc + .call(methods::status::RpcStatusRequest) + .await + .unwrap() + .chain_id; + + let block = rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(near_primitives::types::Finality::Final), + }) + .await + .unwrap(); + + let validators = rpc + .call( + methods::EXPERIMENTAL_validators_ordered::RpcValidatorsOrderedRequest { + block_id: Some(BlockId::Height(block.header.height)), + }, + ) + .await + .unwrap(); + + println!("chain id {:?}", chain_id); + + let chunk_prev_state_root = block.header.prev_state_root; + let timestamp = block.header.timestamp_nanosec; + + ( + rpc, + mock_dependencies(), + mock_env(), + mock_info("thisaddr", &[]), + ClientState { + chain_id: "hello".to_string(), + latest_height: block.header.height - 1, + ibc_account_id: "acc.near".to_string().try_into().unwrap(), + // TODO(aeryz): this is only valid in this sandboxed environment where the validator set is not changing. For a real environment, + // the relayer must read the block producers using another endpoint. + initial_block_producers: convert_block_producers(validators), + frozen_height: 0, + }, + ConsensusState { + state: block_header_to_inner_lite(block.header), + chunk_prev_state_root, + timestamp, + }, + ) + } + + #[test] + fn test_verify_membership() { + let proof = [ + 123, 34, 115, 116, 97, 116, 101, 95, 112, 114, 111, 111, 102, 34, 58, 91, 91, 51, 44, + 49, 44, 48, 44, 48, 44, 48, 44, 49, 54, 44, 49, 49, 44, 57, 51, 44, 49, 48, 50, 44, 54, + 49, 44, 49, 48, 56, 44, 49, 52, 57, 44, 49, 54, 50, 44, 50, 50, 55, 44, 51, 55, 44, 49, + 54, 57, 44, 49, 49, 53, 44, 49, 49, 49, 44, 49, 48, 44, 56, 52, 44, 51, 52, 44, 55, 54, + 44, 51, 50, 44, 57, 52, 44, 49, 48, 49, 44, 52, 44, 49, 53, 54, 44, 49, 53, 51, 44, 57, + 56, 44, 49, 54, 56, 44, 50, 50, 52, 44, 55, 48, 44, 50, 49, 52, 44, 49, 57, 55, 44, 55, + 44, 57, 49, 44, 49, 53, 50, 44, 50, 48, 48, 44, 53, 55, 44, 49, 48, 53, 44, 49, 49, 44, + 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 49, 44, 55, 44, 50, 44, 50, 50, 53, 44, + 50, 51, 56, 44, 55, 48, 44, 49, 51, 52, 44, 49, 44, 57, 55, 44, 49, 56, 53, 44, 57, 52, + 44, 49, 57, 50, 44, 49, 54, 49, 44, 51, 49, 44, 50, 44, 50, 48, 44, 49, 51, 55, 44, 52, + 53, 44, 50, 54, 44, 54, 48, 44, 56, 48, 44, 49, 50, 52, 44, 49, 55, 54, 44, 49, 57, 44, + 49, 51, 44, 49, 52, 55, 44, 54, 44, 49, 48, 54, 44, 49, 50, 54, 44, 52, 52, 44, 50, 54, + 44, 49, 50, 49, 44, 57, 55, 44, 52, 50, 44, 49, 56, 53, 44, 49, 51, 56, 44, 54, 48, 44, + 50, 48, 48, 44, 53, 52, 44, 50, 50, 53, 44, 49, 56, 49, 44, 49, 54, 56, 44, 52, 51, 44, + 49, 49, 56, 44, 53, 49, 44, 54, 44, 50, 52, 54, 44, 49, 52, 56, 44, 50, 51, 52, 44, 57, + 55, 44, 50, 50, 51, 44, 49, 56, 49, 44, 57, 54, 44, 49, 56, 49, 44, 53, 57, 44, 53, 52, + 44, 49, 48, 55, 44, 49, 51, 49, 44, 55, 51, 44, 50, 49, 54, 44, 49, 50, 48, 44, 49, 55, + 49, 44, 50, 50, 44, 49, 50, 57, 44, 49, 49, 54, 44, 49, 48, 49, 44, 55, 55, 44, 51, 57, + 44, 50, 48, 48, 44, 49, 53, 57, 44, 49, 49, 57, 44, 49, 54, 49, 44, 50, 53, 53, 44, 49, + 56, 51, 44, 50, 54, 44, 50, 51, 50, 44, 49, 44, 55, 51, 44, 57, 57, 44, 50, 49, 55, 44, + 50, 51, 50, 44, 52, 57, 44, 50, 48, 55, 44, 50, 50, 52, 44, 50, 50, 57, 44, 49, 57, 48, + 44, 50, 52, 57, 44, 50, 51, 57, 44, 49, 53, 56, 44, 49, 50, 54, 44, 54, 54, 44, 49, 53, + 54, 44, 50, 48, 51, 44, 49, 53, 44, 50, 51, 56, 44, 50, 52, 53, 44, 49, 54, 54, 44, 50, + 48, 52, 44, 50, 53, 51, 44, 49, 48, 53, 44, 56, 50, 44, 51, 51, 44, 50, 48, 57, 44, 49, + 50, 48, 44, 49, 56, 44, 49, 56, 53, 44, 49, 48, 50, 44, 50, 52, 54, 44, 51, 52, 44, 49, + 50, 53, 44, 56, 52, 44, 57, 44, 50, 52, 51, 44, 50, 53, 44, 55, 57, 44, 49, 56, 53, 44, + 49, 50, 54, 44, 50, 50, 49, 44, 49, 52, 51, 44, 49, 52, 56, 44, 49, 52, 48, 44, 49, 54, + 56, 44, 50, 49, 53, 44, 49, 48, 52, 44, 55, 51, 44, 50, 52, 44, 50, 52, 52, 44, 57, 51, + 44, 49, 53, 48, 44, 49, 57, 49, 44, 49, 54, 44, 53, 44, 49, 48, 53, 44, 49, 49, 44, 48, + 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 50, + 50, 44, 51, 52, 44, 50, 49, 54, 44, 49, 56, 50, 44, 50, 49, 49, 44, 49, 52, 52, 44, 53, + 56, 44, 54, 50, 44, 56, 49, 44, 57, 52, 44, 50, 51, 50, 44, 49, 55, 55, 44, 50, 49, 48, + 44, 53, 49, 44, 52, 49, 44, 50, 50, 50, 44, 50, 50, 49, 44, 49, 53, 55, 44, 50, 49, 52, + 44, 49, 48, 51, 44, 50, 49, 48, 44, 49, 51, 52, 44, 50, 56, 44, 57, 56, 44, 50, 49, 50, + 44, 49, 50, 57, 44, 50, 48, 51, 44, 53, 54, 44, 49, 53, 50, 44, 56, 44, 57, 56, 44, 50, + 48, 50, 44, 53, 56, 44, 49, 49, 57, 44, 49, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, + 44, 48, 93, 44, 91, 49, 44, 56, 44, 50, 44, 57, 44, 50, 52, 49, 44, 51, 52, 44, 56, 55, + 44, 49, 56, 44, 49, 48, 49, 44, 49, 50, 49, 44, 49, 49, 56, 44, 49, 56, 51, 44, 56, 48, + 44, 50, 55, 44, 49, 53, 44, 50, 48, 52, 44, 49, 50, 51, 44, 50, 51, 52, 44, 50, 51, 49, + 44, 52, 52, 44, 49, 48, 57, 44, 57, 51, 44, 49, 57, 52, 44, 50, 44, 49, 51, 54, 44, 49, + 53, 51, 44, 51, 53, 44, 54, 44, 49, 57, 53, 44, 49, 53, 53, 44, 52, 49, 44, 50, 48, 53, + 44, 50, 49, 49, 44, 50, 52, 44, 54, 57, 44, 52, 54, 44, 57, 51, 44, 49, 57, 49, 44, 49, + 56, 56, 44, 55, 54, 44, 52, 55, 44, 49, 56, 54, 44, 49, 48, 48, 44, 50, 50, 44, 50, 54, + 44, 53, 54, 44, 49, 52, 44, 49, 55, 44, 49, 48, 55, 44, 49, 50, 49, 44, 50, 51, 53, 44, + 50, 50, 48, 44, 49, 52, 54, 44, 49, 52, 56, 44, 50, 49, 57, 44, 56, 56, 44, 51, 49, 44, + 49, 49, 57, 44, 51, 55, 44, 50, 53, 44, 49, 51, 53, 44, 49, 53, 48, 44, 49, 53, 48, 44, + 57, 56, 44, 57, 57, 44, 49, 53, 56, 44, 49, 48, 51, 44, 54, 55, 44, 49, 51, 44, 48, 44, + 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 51, 44, 49, 54, 44, 48, 44, 48, 44, 48, + 44, 48, 44, 57, 56, 44, 57, 57, 44, 52, 53, 44, 49, 49, 55, 44, 49, 49, 48, 44, 49, 48, + 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 52, 54, 44, 49, 49, 48, 44, 49, 49, 49, 44, 49, + 48, 48, 44, 49, 48, 49, 44, 52, 56, 44, 52, 52, 44, 53, 50, 44, 51, 53, 44, 49, 54, 53, + 44, 49, 55, 44, 49, 54, 52, 44, 49, 57, 52, 44, 50, 52, 53, 44, 49, 49, 52, 44, 49, 55, + 51, 44, 49, 53, 49, 44, 51, 56, 44, 50, 48, 55, 44, 50, 53, 48, 44, 52, 53, 44, 57, 44, + 57, 55, 44, 50, 53, 49, 44, 49, 48, 44, 52, 48, 44, 49, 52, 44, 51, 44, 54, 48, 44, 50, + 53, 50, 44, 49, 55, 49, 44, 54, 52, 44, 57, 52, 44, 50, 50, 54, 44, 50, 53, 44, 57, 55, + 44, 57, 56, 44, 49, 53, 52, 44, 50, 48, 54, 44, 50, 51, 52, 44, 49, 48, 44, 48, 44, 48, + 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 49, 44, 57, 54, 44, 48, 44, 49, 53, 48, 44, + 49, 52, 54, 44, 49, 51, 53, 44, 49, 49, 54, 44, 49, 48, 50, 44, 49, 50, 44, 51, 55, 44, + 50, 54, 44, 50, 50, 49, 44, 49, 48, 55, 44, 50, 51, 54, 44, 57, 57, 44, 50, 51, 50, 44, + 50, 51, 50, 44, 49, 51, 55, 44, 50, 49, 56, 44, 51, 56, 44, 50, 48, 48, 44, 49, 49, 48, + 44, 50, 52, 55, 44, 49, 44, 50, 53, 52, 44, 50, 52, 50, 44, 49, 49, 48, 44, 51, 55, 44, + 55, 55, 44, 50, 50, 44, 50, 50, 54, 44, 49, 50, 50, 44, 50, 48, 51, 44, 52, 49, 44, 57, + 57, 44, 50, 52, 49, 44, 50, 51, 52, 44, 50, 48, 53, 44, 55, 50, 44, 49, 50, 53, 44, 57, + 57, 44, 57, 56, 44, 53, 55, 44, 49, 55, 49, 44, 50, 49, 49, 44, 50, 51, 57, 44, 49, 48, + 51, 44, 49, 49, 55, 44, 49, 57, 44, 49, 51, 55, 44, 50, 52, 53, 44, 55, 54, 44, 49, 56, + 53, 44, 49, 57, 48, 44, 49, 48, 55, 44, 50, 50, 57, 44, 51, 48, 44, 50, 52, 54, 44, 50, + 49, 48, 44, 49, 50, 51, 44, 50, 48, 54, 44, 49, 57, 52, 44, 50, 48, 57, 44, 54, 53, 44, + 50, 53, 52, 44, 49, 54, 50, 44, 50, 50, 57, 44, 49, 53, 50, 44, 49, 48, 44, 48, 44, 48, + 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 49, 44, 49, 48, 44, 48, 44, 57, 44, 50, 49, + 55, 44, 49, 50, 44, 49, 54, 49, 44, 49, 53, 56, 44, 49, 48, 57, 44, 49, 55, 54, 44, 54, + 44, 50, 50, 53, 44, 49, 52, 49, 44, 52, 51, 44, 49, 48, 49, 44, 50, 50, 44, 49, 56, 50, + 44, 50, 50, 56, 44, 49, 57, 51, 44, 49, 54, 54, 44, 50, 48, 57, 44, 57, 50, 44, 54, 53, + 44, 49, 56, 49, 44, 49, 50, 54, 44, 49, 48, 53, 44, 49, 54, 53, 44, 50, 52, 48, 44, 50, + 48, 48, 44, 49, 56, 48, 44, 50, 51, 54, 44, 49, 49, 51, 44, 49, 50, 50, 44, 49, 56, 49, + 44, 50, 51, 48, 44, 49, 54, 52, 44, 49, 54, 57, 44, 57, 52, 44, 49, 51, 50, 44, 49, 52, + 52, 44, 56, 51, 44, 54, 48, 44, 50, 52, 57, 44, 50, 51, 57, 44, 50, 52, 56, 44, 49, 55, + 49, 44, 49, 56, 48, 44, 50, 48, 53, 44, 49, 53, 54, 44, 49, 49, 49, 44, 49, 55, 55, 44, + 57, 54, 44, 49, 56, 53, 44, 49, 55, 44, 49, 57, 55, 44, 57, 56, 44, 53, 55, 44, 50, 50, + 50, 44, 50, 52, 56, 44, 49, 48, 49, 44, 50, 50, 49, 44, 54, 53, 44, 55, 48, 44, 49, 55, + 44, 50, 49, 52, 44, 49, 50, 50, 44, 49, 48, 56, 44, 49, 56, 51, 44, 57, 44, 48, 44, 48, + 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 50, + 50, 44, 53, 51, 44, 54, 54, 44, 50, 52, 48, 44, 49, 56, 49, 44, 49, 53, 50, 44, 57, 49, + 44, 49, 54, 55, 44, 49, 51, 57, 44, 50, 49, 48, 44, 49, 48, 44, 54, 44, 49, 55, 44, 51, + 57, 44, 49, 54, 54, 44, 49, 51, 48, 44, 49, 54, 50, 44, 49, 51, 54, 44, 50, 50, 56, 44, + 49, 52, 56, 44, 49, 54, 57, 44, 50, 49, 53, 44, 50, 44, 49, 55, 51, 44, 49, 57, 54, 44, + 50, 49, 54, 44, 49, 53, 50, 44, 50, 50, 56, 44, 50, 51, 51, 44, 53, 57, 44, 51, 44, 57, + 44, 50, 53, 52, 44, 50, 49, 50, 44, 56, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, + 93, 44, 91, 49, 44, 48, 44, 49, 52, 52, 44, 51, 51, 44, 50, 53, 53, 44, 49, 49, 51, 44, + 49, 57, 52, 44, 51, 48, 44, 49, 50, 56, 44, 49, 50, 49, 44, 49, 56, 44, 50, 49, 57, 44, + 49, 51, 48, 44, 49, 53, 48, 44, 49, 55, 53, 44, 49, 49, 52, 44, 50, 48, 51, 44, 55, 56, + 44, 52, 44, 50, 50, 49, 44, 49, 52, 44, 50, 49, 52, 44, 49, 56, 48, 44, 49, 50, 44, 57, + 53, 44, 49, 54, 53, 44, 50, 50, 48, 44, 56, 53, 44, 54, 44, 49, 50, 52, 44, 49, 49, 54, + 44, 50, 50, 52, 44, 54, 49, 44, 50, 50, 50, 44, 49, 55, 57, 44, 49, 50, 51, 44, 53, 57, + 44, 50, 50, 44, 49, 49, 57, 44, 52, 55, 44, 49, 50, 51, 44, 52, 51, 44, 49, 49, 53, 44, + 49, 53, 56, 44, 50, 49, 49, 44, 49, 55, 54, 44, 50, 44, 50, 51, 54, 44, 50, 53, 50, 44, + 49, 48, 49, 44, 49, 50, 52, 44, 50, 52, 48, 44, 49, 55, 53, 44, 50, 48, 52, 44, 50, 49, + 56, 44, 52, 49, 44, 49, 49, 53, 44, 49, 57, 56, 44, 54, 48, 44, 52, 57, 44, 50, 49, 55, + 44, 49, 49, 51, 44, 49, 51, 53, 44, 50, 52, 57, 44, 50, 50, 55, 44, 52, 55, 44, 49, 52, + 52, 44, 49, 54, 48, 44, 56, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, + 51, 44, 49, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 48, 57, 44, 49, 48, 57, 44, 49, + 48, 53, 44, 49, 49, 54, 44, 49, 48, 57, 44, 49, 48, 49, 44, 49, 49, 48, 44, 49, 49, 54, + 44, 49, 49, 53, 44, 50, 51, 52, 44, 49, 50, 44, 49, 57, 48, 44, 52, 56, 44, 52, 56, 44, + 49, 51, 50, 44, 49, 55, 44, 50, 48, 54, 44, 49, 53, 55, 44, 50, 50, 54, 44, 49, 53, 49, + 44, 49, 57, 54, 44, 49, 54, 57, 44, 50, 50, 44, 49, 52, 52, 44, 50, 49, 49, 44, 49, 57, + 51, 44, 49, 56, 49, 44, 52, 48, 44, 49, 50, 54, 44, 51, 44, 49, 55, 49, 44, 52, 55, 44, + 53, 55, 44, 53, 57, 44, 49, 44, 49, 50, 54, 44, 50, 51, 54, 44, 57, 44, 48, 44, 50, 53, + 52, 44, 49, 51, 53, 44, 50, 49, 56, 44, 54, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, + 48, 93, 44, 91, 49, 44, 54, 44, 48, 44, 49, 52, 44, 49, 55, 50, 44, 49, 57, 50, 44, 49, + 48, 49, 44, 49, 51, 52, 44, 49, 49, 53, 44, 49, 54, 54, 44, 50, 51, 49, 44, 49, 52, 44, + 50, 53, 53, 44, 53, 44, 49, 50, 56, 44, 53, 48, 44, 52, 53, 44, 55, 56, 44, 49, 48, 53, + 44, 50, 49, 51, 44, 56, 56, 44, 49, 48, 54, 44, 49, 56, 55, 44, 50, 48, 56, 44, 50, 52, + 49, 44, 49, 52, 52, 44, 55, 56, 44, 49, 53, 56, 44, 48, 44, 49, 54, 48, 44, 49, 51, 55, + 44, 49, 57, 55, 44, 49, 50, 50, 44, 49, 56, 54, 44, 49, 53, 50, 44, 50, 49, 48, 44, 49, + 49, 51, 44, 50, 51, 55, 44, 50, 48, 55, 44, 55, 49, 44, 53, 53, 44, 49, 57, 52, 44, 56, + 56, 44, 49, 56, 50, 44, 55, 51, 44, 49, 57, 55, 44, 49, 51, 51, 44, 52, 50, 44, 49, 52, + 50, 44, 49, 54, 49, 44, 49, 52, 51, 44, 49, 57, 50, 44, 56, 57, 44, 50, 48, 54, 44, 49, + 57, 56, 44, 51, 57, 44, 49, 53, 50, 44, 49, 54, 44, 49, 53, 53, 44, 49, 53, 53, 44, 50, + 51, 54, 44, 49, 50, 53, 44, 57, 44, 49, 48, 54, 44, 49, 50, 44, 49, 55, 55, 44, 49, 51, + 52, 44, 49, 52, 56, 44, 54, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, + 49, 44, 48, 44, 54, 53, 44, 49, 56, 49, 44, 54, 55, 44, 50, 50, 53, 44, 50, 52, 50, 44, + 49, 54, 54, 44, 50, 48, 53, 44, 56, 52, 44, 50, 51, 51, 44, 53, 49, 44, 52, 49, 44, 50, + 52, 55, 44, 51, 55, 44, 49, 57, 54, 44, 49, 55, 53, 44, 50, 57, 44, 49, 54, 52, 44, 52, + 57, 44, 51, 57, 44, 49, 55, 48, 44, 49, 48, 50, 44, 49, 57, 49, 44, 49, 48, 51, 44, 53, + 52, 44, 53, 51, 44, 49, 48, 48, 44, 51, 50, 44, 53, 44, 55, 50, 44, 50, 48, 54, 44, 52, + 53, 44, 50, 52, 51, 44, 48, 44, 52, 55, 44, 49, 54, 51, 44, 49, 52, 49, 44, 57, 54, 44, + 51, 55, 44, 56, 53, 44, 49, 53, 53, 44, 49, 55, 51, 44, 50, 48, 52, 44, 51, 50, 44, 49, + 54, 53, 44, 50, 51, 52, 44, 49, 49, 51, 44, 51, 50, 44, 50, 51, 51, 44, 50, 49, 55, 44, + 49, 51, 55, 44, 56, 49, 44, 57, 49, 44, 49, 48, 49, 44, 52, 56, 44, 49, 50, 54, 44, 49, + 56, 51, 44, 50, 49, 52, 44, 53, 48, 44, 49, 52, 49, 44, 51, 55, 44, 49, 49, 54, 44, 49, + 56, 57, 44, 50, 53, 50, 44, 50, 52, 57, 44, 55, 56, 44, 54, 44, 52, 44, 48, 44, 48, 44, + 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 51, 44, 50, 49, 44, 48, 44, 48, 44, 48, 44, 49, + 54, 44, 48, 44, 48, 44, 54, 44, 53, 52, 44, 49, 57, 56, 44, 49, 53, 48, 44, 56, 54, 44, + 50, 51, 49, 44, 55, 49, 44, 53, 48, 44, 50, 52, 54, 44, 53, 52, 44, 50, 52, 54, 44, 50, + 49, 52, 44, 56, 55, 44, 55, 48, 44, 51, 56, 44, 49, 57, 57, 44, 53, 48, 44, 50, 49, 49, + 44, 50, 50, 56, 44, 55, 50, 44, 50, 50, 50, 44, 49, 52, 50, 44, 49, 53, 44, 54, 54, 44, + 49, 51, 55, 44, 50, 53, 53, 44, 56, 51, 44, 56, 49, 44, 49, 56, 44, 54, 52, 44, 49, 50, + 56, 44, 49, 49, 55, 44, 50, 49, 48, 44, 57, 48, 44, 51, 53, 44, 49, 48, 50, 44, 50, 48, + 50, 44, 55, 54, 44, 49, 48, 50, 44, 50, 51, 54, 44, 49, 53, 49, 44, 55, 50, 44, 50, 49, + 54, 44, 49, 52, 52, 44, 49, 53, 53, 44, 53, 48, 44, 55, 52, 44, 50, 49, 51, 44, 50, 48, + 56, 44, 49, 56, 44, 50, 52, 54, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, + 93, 44, 91, 49, 44, 54, 44, 48, 44, 56, 44, 50, 49, 56, 44, 49, 56, 55, 44, 49, 57, 53, + 44, 50, 52, 44, 49, 48, 49, 44, 50, 52, 53, 44, 50, 50, 44, 51, 57, 44, 50, 53, 49, 44, + 49, 56, 55, 44, 55, 44, 57, 54, 44, 49, 52, 54, 44, 51, 52, 44, 56, 44, 49, 53, 55, 44, + 51, 54, 44, 50, 49, 54, 44, 50, 48, 50, 44, 50, 57, 44, 49, 57, 53, 44, 54, 55, 44, 49, + 52, 48, 44, 50, 52, 50, 44, 53, 49, 44, 53, 55, 44, 49, 57, 55, 44, 50, 50, 51, 44, 49, + 50, 48, 44, 50, 48, 53, 44, 49, 48, 57, 44, 49, 50, 48, 44, 50, 50, 53, 44, 56, 50, 44, + 49, 49, 55, 44, 49, 55, 57, 44, 49, 55, 57, 44, 49, 49, 57, 44, 51, 56, 44, 50, 50, 56, + 44, 53, 50, 44, 50, 50, 53, 44, 50, 50, 52, 44, 49, 51, 55, 44, 49, 49, 53, 44, 50, 50, + 54, 44, 56, 54, 44, 50, 51, 44, 49, 50, 53, 44, 50, 50, 56, 44, 50, 52, 51, 44, 49, 50, + 44, 57, 49, 44, 56, 48, 44, 49, 57, 55, 44, 49, 57, 53, 44, 51, 52, 44, 51, 48, 44, 51, + 54, 44, 49, 56, 49, 44, 56, 52, 44, 52, 48, 44, 49, 56, 53, 44, 49, 53, 52, 44, 49, 44, + 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 91, 48, 44, 49, 51, 44, 48, 44, 48, + 44, 48, 44, 51, 50, 44, 52, 55, 44, 57, 57, 44, 49, 48, 56, 44, 49, 48, 53, 44, 49, 48, + 49, 44, 49, 49, 48, 44, 49, 49, 54, 44, 56, 51, 44, 49, 49, 54, 44, 57, 55, 44, 49, 49, + 54, 44, 49, 48, 49, 44, 53, 52, 44, 48, 44, 48, 44, 48, 44, 55, 44, 49, 51, 54, 44, 50, + 50, 44, 50, 49, 53, 44, 50, 48, 49, 44, 50, 51, 48, 44, 50, 49, 55, 44, 50, 52, 53, 44, + 57, 44, 50, 50, 54, 44, 49, 48, 50, 44, 49, 52, 57, 44, 50, 48, 50, 44, 49, 49, 48, 44, + 56, 50, 44, 49, 49, 54, 44, 50, 50, 54, 44, 49, 52, 52, 44, 49, 54, 53, 44, 50, 49, 54, + 44, 52, 56, 44, 57, 56, 44, 49, 57, 56, 44, 49, 51, 54, 44, 50, 48, 56, 44, 50, 52, 50, + 44, 51, 53, 44, 49, 48, 48, 44, 56, 55, 44, 50, 51, 49, 44, 50, 53, 44, 49, 57, 53, 44, + 49, 56, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 93, 93, 125, + ]; + let mut deps = mock_dependencies(); + + save_client_state::( + deps.as_mut(), + WasmClientState { + data: ClientState { + chain_id: "".to_string(), + latest_height: 8, + ibc_account_id: "ibc-union.node0".parse().unwrap(), + initial_block_producers: vec![], + frozen_height: 0, + }, + checksum: [0; 32].into(), + latest_height: height(8), + }, + ); + + save_consensus_state::( + deps.as_mut(), + WasmConsensusState { + data: ConsensusState { + state: serde_json::from_str( + r#"{"height":3375,"epoch_id":"4hfNTi6XHVo34VY7k4h7GJBWE8xbEJAk92c2ttf8xoVf","next_epoch_id":"2acN456fNCK8hy5V6T6qodE4AGYmYyWd17Jy3FYeGomk","prev_state_root":"J33gHY214xtdYYopfvohsu5JeRvdpUytxvrkCw3Ues4s","outcome_root":"7tkzFg8RHBmMw1ncRJZCCZAizgq4rwCftTKYLce8RU8t","timestamp":1721755287056479127,"timestamp_nanosec":1721755287056479127,"next_bp_hash":"N674zAVEovLgQJsLWqavBj9sCaTWh6XRcEaL1fcSv9F","block_merkle_root":"4xTs6nVJtYeovyszCDbM2B6unLX9HB6a8Dc955gcPdPY"}"# + ).unwrap(), + chunk_prev_state_root: CryptoHash([193, 114, 116, 236, 32, 52, 48, 3, 63, 141, 251, 243, 43, 183, 171, 164, 249, 121, 112, 90, 162, 153, 194, 14, 105, 139, 16, 121, 64, 69, 102, 34]) + , + timestamp: 10000, + }, + }, + &height(8), + ); + + let value: Vec = [ + 10, 10, 99, 111, 109, 101, 116, 98, 108, 115, 45, 50, 18, 20, 10, 1, 49, 18, 15, 79, + 82, 68, 69, 82, 95, 85, 78, 79, 82, 68, 69, 82, 69, 68, 24, 1, 34, 18, 10, 9, 48, 56, + 45, 119, 97, 115, 109, 45, 49, 26, 5, 10, 3, 105, 98, 99, + ] + .to_vec(); + + let mut conn = ConnectionEnd::::decode_as::(&value).unwrap(); + conn.client_id = "cometbls-2".to_string(); + conn.counterparty.client_id = "08-wasm-5".to_string(); + + // println!( + // "DECODED: {:?}", + // ConnectionEnd::::decode_as::(&value).unwrap() + // ); + + NearLightClient::verify_membership( + deps.as_ref(), + Height { + revision_number: 0, + revision_height: 7, + }, + 0, + 0, + proof.to_vec(), + unionlabs::ibc::core::commitment::merkle_path::MerklePath { + key_path: vec!["ibc".to_string(), "connections/connection-2".to_string()], + }, + ics008_wasm_client::StorageState::Occupied(conn.encode_as::()), + ) + .unwrap(); + } + + #[tokio::test] + async fn create_client() { + let (rpc, mut deps, env, info, client_state, consensus_state) = initialize().await; + + instantiate( + deps.as_mut(), + env, + info, + InstantiateMsg { + client_state: client_state.encode_as::().into(), + consensus_state: consensus_state.encode_as::().into(), + checksum: [0; 32].into(), + }, + ) + .unwrap(); + + let acc = rpc + .call(methods::query::RpcQueryRequest { + block_reference: BlockReference::Finality(near_primitives::types::Finality::Final), + request: near_primitives::views::QueryRequest::ViewAccount { + account_id: String::from("dev-1721650593739.node0").try_into().unwrap(), + }, + }) + .await + .unwrap(); + + println!("Account: {acc:?}"); + } + + #[tokio::test] + async fn update_client() { + let (rpc, mut deps, env, info, client_state, consensus_state) = initialize().await; + + instantiate( + deps.as_mut(), + env.clone(), + info.clone(), + InstantiateMsg { + client_state: client_state.clone().encode_as::().into(), + consensus_state: consensus_state.clone().encode_as::().into(), + checksum: [0; 32].into(), + }, + ) + .unwrap(); + + for _ in 0..30 { + let wasm_client_state: WasmClientState = + read_client_state::(deps.as_ref()).unwrap(); + let wasm_consensus_state: WasmConsensusState = read_consensus_state::( + deps.as_ref(), + &Height { + revision_number: 0, + revision_height: wasm_client_state.data.latest_height, + }, + ) + .unwrap() + .unwrap(); + println!( + "HEIGHT: {}, EPOCH: {}", + wasm_client_state.data.latest_height, wasm_consensus_state.data.state.epoch_id + ); + + let block = rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::BlockId(BlockId::Height( + wasm_client_state.data.latest_height, + )), + }) + .await + .unwrap(); + + let lc_block = loop { + tokio::time::sleep(Duration::from_millis(100)).await; + let lc_block = rpc + .call( + methods::next_light_client_block::RpcLightClientNextBlockRequest { + last_block_hash: block.header.hash, + }, + ) + .await; + + if let Ok(Some(lc_block)) = lc_block { + if lc_block.inner_lite.height > wasm_client_state.data.latest_height + 1 { + println!( + "current height: {}, lc block height: {}", + wasm_client_state.data.latest_height, lc_block.inner_lite.height + ); + break lc_block; + } + } + }; + + let current_height = lc_block.inner_lite.height; + + let (prev_state_root, prev_state_root_proof) = chunk_proof(&rpc, current_height).await; + + let header = Header { + new_state: convert_light_client_block_view(lc_block), + trusted_height: wasm_client_state.data.latest_height, + prev_state_root_proof, + prev_state_root, + }; + + NearLightClient::verify_header(deps.as_ref(), env.clone(), header.clone()).unwrap(); + + NearLightClient::update_state(deps.as_mut(), env.clone(), header).unwrap(); + } + } + + pub fn convert_block_header_inner( + block_view: BlockHeaderInnerLiteView, + ) -> near::block_header_inner::BlockHeaderInnerLiteView { + near::block_header_inner::BlockHeaderInnerLiteView { + height: block_view.height, + epoch_id: CryptoHash(block_view.epoch_id.0), + next_epoch_id: CryptoHash(block_view.next_epoch_id.0), + prev_state_root: CryptoHash(block_view.prev_state_root.0), + outcome_root: CryptoHash(block_view.outcome_root.0), + timestamp: block_view.timestamp, + timestamp_nanosec: block_view.timestamp_nanosec, + next_bp_hash: CryptoHash(block_view.next_bp_hash.0), + block_merkle_root: CryptoHash(block_view.block_merkle_root.0), + } + } + + pub fn convert_light_client_block_view( + light_client_block: LightClientBlockView, + ) -> near::light_client_block::LightClientBlockView { + near::light_client_block::LightClientBlockView { + inner_lite: convert_block_header_inner(light_client_block.inner_lite), + prev_block_hash: near_primitives_core::hash::CryptoHash( + light_client_block.prev_block_hash.0, + ), + next_block_inner_hash: near_primitives_core::hash::CryptoHash( + light_client_block.next_block_inner_hash.0, + ), + inner_rest_hash: near_primitives_core::hash::CryptoHash( + light_client_block.inner_rest_hash.0, + ), + next_bps: light_client_block.next_bps.map(|bps| { + bps.into_iter() + .map(|stake| { + let near_primitives::views::validator_stake_view::ValidatorStakeView::V1( + stake, + ) = stake; + near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ) + }) + .collect() + }), + approvals_after_next: light_client_block + .approvals_after_next + .into_iter() + .map(|sig| { + sig.map(|s| match s.as_ref() { + near_crypto::Signature::ED25519(sig) => { + Box::new(types::Signature::Ed25519(sig.to_bytes().to_vec())) + } + near_crypto::Signature::SECP256K1(_) => { + Box::new(types::Signature::Secp256k1(Vec::new())) + } + }) + }) + .collect(), + } + } + + pub async fn chunk_proof( + rpc: &JsonRpcClient, + block_height: BlockHeight, + ) -> (CryptoHash, types::MerklePath) { + let chunks = rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::BlockId(BlockId::Height(block_height)), + }) + .await + .unwrap() + .chunks; + + let prev_state_root = CryptoHash(chunks[0].prev_state_root.0); + + let (_, merkle_path) = near_primitives::merkle::merklize( + &chunks + .into_iter() + .map(|chunk| CryptoHash(chunk.prev_state_root.0)) + .collect::>(), + ); + + let prev_state_root_proof = merkle_path[0] + .clone() + .into_iter() + .map(|item| MerklePathItem { + hash: near_primitives_core::hash::CryptoHash(item.hash.0), + direction: match item.direction { + near_primitives::merkle::Direction::Left => types::Direction::Left, + near_primitives::merkle::Direction::Right => types::Direction::Right, + }, + }) + .collect(); + + (prev_state_root, prev_state_root_proof) + } + + pub fn convert_block_producers( + bps: Vec, + ) -> Vec { + bps.into_iter() + .map(|stake| { + let near_primitives::views::validator_stake_view::ValidatorStakeView::V1(stake) = + stake; + let stake = near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: unionlabs::near::types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ); + stake + }) + .collect() + } + + pub fn block_header_to_inner_lite( + header: near_primitives::views::BlockHeaderView, + ) -> near::block_header_inner::BlockHeaderInnerLiteView { + use near_primitives_core::hash::CryptoHash; + near::block_header_inner::BlockHeaderInnerLiteView { + height: header.height, + epoch_id: CryptoHash(header.epoch_id.0), + next_epoch_id: CryptoHash(header.next_epoch_id.0), + prev_state_root: CryptoHash(header.prev_state_root.0), + outcome_root: CryptoHash(header.outcome_root.0), + timestamp: header.timestamp, + timestamp_nanosec: header.timestamp_nanosec, + next_bp_hash: CryptoHash(header.next_bp_hash.0), + block_merkle_root: CryptoHash(header.block_merkle_root.0), + } + } +} + +fn key_from_path(path: &str) -> Vec { + let mut commitments: Vec = Vec::new(); + commitments.extend(b"commitments"); + commitments.extend(borsh::to_vec(path).unwrap()); + commitments +} diff --git a/light-clients/near/ics08-near/src/contract.rs b/light-clients/near/ics08-near/src/contract.rs new file mode 100644 index 0000000000..262bd77463 --- /dev/null +++ b/light-clients/near/ics08-near/src/contract.rs @@ -0,0 +1,68 @@ +use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; +use ics008_wasm_client::{ + define_cosmwasm_light_client_contract, + storage_utils::{save_proto_client_state, save_proto_consensus_state}, + CustomQueryOf, InstantiateMsg, +}; +use protos::ibc::lightclients::wasm::v1::{ + ClientState as ProtoClientState, ConsensusState as ProtoConsensusState, +}; +use unionlabs::{ + encoding::{DecodeAs, Proto}, + ibc::{ + core::client::height::Height, + lightclients::near::{client_state::ClientState, consensus_state::ConsensusState}, + }, +}; + +use crate::{client::NearLightClient, errors::Error, state::EPOCH_BLOCK_PRODUCERS_MAP}; + +#[entry_point] +pub fn instantiate( + mut deps: DepsMut>, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let client_state = + ClientState::decode_as::(&msg.client_state).map_err(Error::ClientStateDecode)?; + + let consensus_state = ConsensusState::decode_as::(&msg.consensus_state) + .map_err(Error::ConsensusStateDecode)?; + + EPOCH_BLOCK_PRODUCERS_MAP + .save( + deps.storage, + consensus_state.state.epoch_id.0, + &client_state.initial_block_producers, + ) + .unwrap(); + + save_proto_consensus_state::( + deps.branch(), + ProtoConsensusState { + data: msg.consensus_state.into(), + }, + &Height { + revision_number: 0, + revision_height: client_state.latest_height, + }, + ); + save_proto_client_state::( + deps, + ProtoClientState { + data: msg.client_state.into(), + checksum: msg.checksum.into(), + latest_height: Some( + Height { + revision_number: 0, + revision_height: client_state.latest_height, + } + .into(), + ), + }, + ); + Ok(Response::default()) +} + +define_cosmwasm_light_client_contract!(NearLightClient, Near); diff --git a/light-clients/near/ics08-near/src/errors.rs b/light-clients/near/ics08-near/src/errors.rs new file mode 100644 index 0000000000..d4a8a58d5d --- /dev/null +++ b/light-clients/near/ics08-near/src/errors.rs @@ -0,0 +1,34 @@ +use ics008_wasm_client::IbcClientError; +use near_primitives_core::hash::CryptoHash; +use unionlabs::{ + encoding::{DecodeErrorOf, Proto}, + ibc::lightclients::near::{client_state::ClientState, consensus_state::ConsensusState}, +}; + +use crate::client::NearLightClient; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum Error { + #[error("consensus state not found at height {0}")] + ConsensusStateNotFound(u64), + #[error("epoch block producer not found for epoch {0}")] + EpochBlockProducerNotFound(CryptoHash), + #[error(transparent)] + Verifier(#[from] near_verifier::error::Error), + #[error("unable to decode client state")] + ClientStateDecode(#[source] DecodeErrorOf), + #[error("unable to decode consensus state")] + ConsensusStateDecode(#[source] DecodeErrorOf), + #[error("the proof path {0} is unknown")] + UnknownIbcPath(String), + #[error("empty path")] + EmptyPath, + #[error("unable to decode cometbls state {0:?}")] + ForeignStateDecode(Vec), +} + +impl From for IbcClientError { + fn from(value: Error) -> Self { + IbcClientError::ClientSpecific(value) + } +} diff --git a/light-clients/near/ics08-near/src/lib.rs b/light-clients/near/ics08-near/src/lib.rs new file mode 100644 index 0000000000..382a752037 --- /dev/null +++ b/light-clients/near/ics08-near/src/lib.rs @@ -0,0 +1,5 @@ +pub mod client; +pub mod contract; +pub mod errors; +pub mod state; +pub mod storage; diff --git a/light-clients/near/ics08-near/src/state.rs b/light-clients/near/ics08-near/src/state.rs new file mode 100644 index 0000000000..34e1dcfb6a --- /dev/null +++ b/light-clients/near/ics08-near/src/state.rs @@ -0,0 +1,5 @@ +use cw_storage_plus::Map; +use unionlabs::ibc::lightclients::near::validator_stake_view::ValidatorStakeView; + +pub const EPOCH_BLOCK_PRODUCERS_MAP: Map<[u8; 32], Vec> = + Map::new("epoch_block_producers_map"); diff --git a/light-clients/near/ics08-near/src/storage.rs b/light-clients/near/ics08-near/src/storage.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/light-clients/near/ics08-near/src/storage.rs @@ -0,0 +1 @@ + diff --git a/near/near-light-client/Cargo.toml b/light-clients/near/near/Cargo.toml similarity index 95% rename from near/near-light-client/Cargo.toml rename to light-clients/near/near/Cargo.toml index 6fe48b27e1..c547c12128 100644 --- a/near/near-light-client/Cargo.toml +++ b/light-clients/near/near/Cargo.toml @@ -10,6 +10,7 @@ near-contract-standards = { workspace = true } near-primitives-core = { version = "0.21" } near-sdk = { workspace = true, features = ["wee_alloc"] } near-sdk-contract-tools = { workspace = true } +near-verifier = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } unionlabs = { workspace = true, features = ["near"] } diff --git a/near/near-light-client/near-light-client.nix b/light-clients/near/near/near-light-client.nix similarity index 87% rename from near/near-light-client/near-light-client.nix rename to light-clients/near/near/near-light-client.nix index e21e8f3bfa..cfac4cb15b 100644 --- a/near/near-light-client/near-light-client.nix +++ b/light-clients/near/near/near-light-client.nix @@ -2,7 +2,7 @@ perSystem = { self', lib, unstablePkgs, pkgs, system, config, rust, crane, stdenv, dbg, ... }: let near-light-client = (crane.buildWasmContract { - crateDirFromRoot = "near/near-light-client"; + crateDirFromRoot = "light-clients/near/near"; extraBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ]; extraNativeBuildInputs = [ pkgs.clang ]; }); diff --git a/light-clients/near/near/src/contract.rs b/light-clients/near/near/src/contract.rs new file mode 100644 index 0000000000..57d9227f5f --- /dev/null +++ b/light-clients/near/near/src/contract.rs @@ -0,0 +1,234 @@ +use ibc_vm_rs::{IbcQuery, IbcResponse, Status}; +use near_primitives_core::hash::CryptoHash; +use near_sdk::{ + borsh::{self, BorshDeserialize, BorshSerialize}, + env, near_bindgen, + store::LookupMap, + PanicOnDefault, +}; +#[allow(unused)] +use near_sdk_contract_tools::{owner::OwnerExternal, Owner}; +use near_verifier::{state_proof::RawStateProof, NearVerifierCtx}; +use unionlabs::{ + ibc::{ + core::{client::height::Height, commitment::merkle_path::MerklePath}, + lightclients::near::{ + client_state::ClientState, consensus_state::ConsensusState, header::Header, + validator_stake_view::ValidatorStakeView, + }, + }, + id::ClientId, +}; + +#[near_bindgen] +#[derive(PanicOnDefault, BorshDeserialize, BorshSerialize)] +pub struct Contract { + consensus_states: LookupMap, + client_state: ClientState, + epoch_block_producers_map: LookupMap>, +} + +#[near_bindgen] +impl Contract { + #[init] + pub fn initialize( + #[allow(unused)] client_id: ClientId, + client_state: Vec, + consensus_state: Vec, + ) -> Self { + let client_state: ClientState = borsh::from_slice(&client_state).unwrap(); + let consensus_state: ConsensusState = borsh::from_slice(&consensus_state).unwrap(); + let mut block_producers = LookupMap::new(b"epoch_block_producers".as_slice()); + block_producers.insert( + consensus_state.state.epoch_id, + client_state.initial_block_producers.clone(), + ); + let mut consensus_states: LookupMap = LookupMap::new(b"c"); + consensus_states.insert(client_state.latest_height, consensus_state); + Self { + client_state, + consensus_states, + epoch_block_producers_map: block_producers, + } + } + + pub fn query(&self, query: Vec) -> Vec { + query + .into_iter() + .map(|q| match q { + IbcQuery::Status => IbcResponse::Status { + status: self.status(), + }, + IbcQuery::LatestHeight => IbcResponse::LatestHeight { + height: self.latest_height(), + }, + IbcQuery::VerifyMembership { + height, + delay_time_period, + delay_block_period, + proof, + path, + value, + } => IbcResponse::VerifyMembership { + valid: self.verify_membership( + height, + delay_time_period, + delay_block_period, + proof, + path, + value, + ), + }, + IbcQuery::VerifyClientMessage(msg) => IbcResponse::VerifyClientMessage { + error: self.verify_client_message(msg), + }, + IbcQuery::CheckForMisbehaviour(msg) => IbcResponse::CheckForMisbehaviour { + misbehaviour_found: self.check_for_misbehaviour(msg), + }, + IbcQuery::TimestampAtHeight(_) => IbcResponse::TimestampAtHeight { timestamp: 100 }, + }) + .collect() + } + + pub fn status(&self) -> Status { + Status::Active + } + + pub fn latest_height(&self) -> Height { + Height { + revision_number: 0, + revision_height: self.client_state.latest_height, + } + } + + #[allow(unused)] + pub fn verify_membership( + &self, + height: Height, + // TODO(aeryz): delay times might not be relevant for other chains we could make it optional + delay_time_period: u64, + delay_block_period: u64, + proof: Vec, + path: MerklePath, + value: Vec, + ) -> bool { + let proof: RawStateProof = serde_json::from_slice(&proof).unwrap(); + let consensus_state = self + .consensus_states + .get(&(height.revision_height + 1)) + .unwrap(); + + let key = key_from_path(&path.key_path[1]); + + near_verifier::verify_state( + proof, + &consensus_state.chunk_prev_state_root, + &self.client_state.ibc_account_id, + &key, + Some(&borsh::to_vec(&value).unwrap()), + ) + .unwrap(); + + true + } + + // TODO(aeryz): client_msg can be Misbehaviour or Header + pub fn verify_client_message(&self, client_msg: Vec) -> bool { + let header: Header = borsh::from_slice(&client_msg).unwrap(); + + let consensus_state = self.consensus_states.get(&header.trusted_height).unwrap(); + + near_verifier::verify_header( + self, + consensus_state.state.clone(), + header.new_state.clone(), + ) + .unwrap(); + + near_verifier::verify_path( + header.new_state.inner_lite.prev_state_root, + &header.prev_state_root_proof, + header.prev_state_root, + ) + .unwrap(); + + true + } + + #[allow(unused)] + pub fn check_for_misbehaviour(&self, client_msg: Vec) -> bool { + false + } + + pub fn update_client(&mut self, client_msg: Vec) -> (Vec, Vec<(Height, Vec)>) { + let header: Header = borsh::from_slice(&client_msg).unwrap(); + let new_consensus_state = ConsensusState { + state: header.new_state.inner_lite.clone(), + chunk_prev_state_root: header.prev_state_root, + timestamp: header.new_state.inner_lite.timestamp_nanosec, + }; + self.consensus_states.insert( + header.new_state.inner_lite.height, + new_consensus_state.clone(), + ); + self.client_state.latest_height = header.new_state.inner_lite.height; + if let Some(next_bps) = &header.new_state.next_bps { + self.epoch_block_producers_map + .insert(header.new_state.inner_lite.next_epoch_id, next_bps.clone()); + } + + ( + borsh::to_vec(&self.client_state).unwrap(), + vec![( + Height { + revision_number: 0, + revision_height: header.new_state.inner_lite.height, + }, + borsh::to_vec(&new_consensus_state).unwrap(), + )], + ) + } + + #[allow(unused)] + pub fn update_client_on_misbehaviour(&mut self, client_msg: Vec) {} +} + +impl NearVerifierCtx for Contract { + fn get_epoch_block_producers( + &self, + epoch_id: CryptoHash, + ) -> Option> + { + self.epoch_block_producers_map + .get(&epoch_id) + .map(|bps| bps.clone()) + } + + fn ed25519_verify( + &self, + public_key: &[u8], + signature: &[u8], + message: &[u8], + ) -> Result<(), near_verifier::error::Error> { + if env::ed25519_verify( + signature.try_into().unwrap(), + message, + public_key.try_into().unwrap(), + ) { + Ok(()) + } else { + Err(near_verifier::error::Error::VerificationFailure( + public_key.into(), + signature.into(), + message.into(), + )) + } + } +} + +fn key_from_path(path: &str) -> Vec { + let mut commitments: Vec = Vec::new(); + commitments.extend(b"commitments"); + commitments.extend(borsh::to_vec(path).unwrap()); + commitments +} diff --git a/light-clients/near/near/src/lib.rs b/light-clients/near/near/src/lib.rs new file mode 100644 index 0000000000..1c8ef8b5e7 --- /dev/null +++ b/light-clients/near/near/src/lib.rs @@ -0,0 +1,3 @@ +mod contract; + +pub use contract::*; diff --git a/near/near-light-client/src/nibble_slice.rs b/light-clients/near/near/src/nibble_slice.rs similarity index 100% rename from near/near-light-client/src/nibble_slice.rs rename to light-clients/near/near/src/nibble_slice.rs diff --git a/near/near-ibc-tests/src/main.rs b/near/near-ibc-tests/src/main.rs index a0c5d42d76..a5e52f61b8 100644 --- a/near/near-ibc-tests/src/main.rs +++ b/near/near-ibc-tests/src/main.rs @@ -8,26 +8,30 @@ use msgs::{ChannelOpenTry, RecvPacket}; use near_primitives_core::hash::CryptoHash; use near_workspaces::{ network::Sandbox, - sandbox, + sandbox, testnet, types::{Gas, KeyType, NearToken, SecretKey}, Account, AccountId, Contract, Worker, }; use unionlabs::{ - ibc::core::{ - channel::{self, packet::Packet}, - client::height::Height, - commitment::merkle_prefix::MerklePrefix, + ibc::{ + core::{ + channel::{self, packet::Packet}, + client::height::Height, + commitment::merkle_prefix::MerklePrefix, + }, + lightclients::near::{ + client_state::ClientState, consensus_state::ConsensusState, header::Header, + }, }, - near::types::HeaderUpdate, validated::ValidateT, }; use utils::convert_block_producers; use crate::{ msgs::{ - AcknowledgePacket, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, ClientState, - ConnectionOpenAck, ConnectionOpenConfirm, ConnectionOpenInit, ConnectionOpenTry, - ConsensusState, CreateClient, RegisterClient, UpdateClient, + AcknowledgePacket, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, ConnectionOpenAck, + ConnectionOpenConfirm, ConnectionOpenInit, ConnectionOpenTry, CreateClient, RegisterClient, + UpdateClient, }, utils::{ chunk_proof, convert_block_header_inner, convert_light_client_block_view, state_proof, @@ -61,11 +65,18 @@ pub async fn deploy_contract( .unwrap() } +async fn my_main() { + let testnet = testnet().await.unwrap(); +} + #[tokio::main] async fn main() { env_logger::init(); - let sandbox = sandbox().await.unwrap(); + my_main().await; + panic!(); + + let sandbox = sandbox().await.unwrap(); let ibc_contract = deploy_contract(&sandbox, "ibc.test.near", IBC_WASM_PATH_ENV).await; let alice_lc = deploy_contract(&sandbox, "light-client.test.near", NEAR_LC_WASM_PATH_ENV).await; let bob_lc = deploy_contract( @@ -323,7 +334,7 @@ async fn update_client( let update = UpdateClient { client_id: client_id.to_string(), - client_msg: borsh::to_vec(&HeaderUpdate { + client_msg: borsh::to_vec(&Header { new_state: convert_light_client_block_view(light_client_block), trusted_height: latest_height.revision_height, prev_state_root_proof, @@ -653,15 +664,17 @@ async fn create_client( let create = CreateClient { client_type: client_type.clone(), client_state: borsh::to_vec(&ClientState { + chain_id: "hello".to_string(), latest_height: height - 1, ibc_account_id: ibc_contract.id().clone(), // TODO(aeryz): this is only valid in this sandboxed environment where the validator set is not changing. For a real environment, // the relayer must read the block producers using another endpoint. initial_block_producers: lc_block.next_bps.map(convert_block_producers), + frozen_height: 0, }) .unwrap(), consensus_state: borsh::to_vec(&ConsensusState { - state: convert_block_header_inner(lc_block.inner_lite), + state: convert_block_header_inner(lc_block.inner_lite.clone()), chunk_prev_state_root: CryptoHash( sandbox .view_block() @@ -672,6 +685,7 @@ async fn create_client( .prev_state_root .0, ), + timestamp: lc_block.inner_lite.timestamp_nanosec, }) .unwrap(), }; diff --git a/near/near-ibc-tests/src/msgs.rs b/near/near-ibc-tests/src/msgs.rs index 485535b9b2..effedb22f2 100644 --- a/near/near-ibc-tests/src/msgs.rs +++ b/near/near-ibc-tests/src/msgs.rs @@ -1,7 +1,4 @@ -use borsh::{BorshDeserialize, BorshSerialize}; use ibc_vm_rs::states::connection_handshake; -use near_primitives_core::hash::CryptoHash; -use near_sdk::AccountId; use unionlabs::{ ibc::core::{ channel::{self, packet::Packet}, @@ -9,7 +6,6 @@ use unionlabs::{ connection::version::Version, }, id::{ChannelId, ConnectionId, PortId}, - near::types::{self, BlockHeaderInnerLiteView}, }; #[derive(serde::Serialize)] @@ -107,19 +103,6 @@ pub struct GetAccountId { pub client_type: String, } -#[derive(BorshSerialize, BorshDeserialize)] -pub struct ConsensusState { - pub state: BlockHeaderInnerLiteView, - pub chunk_prev_state_root: CryptoHash, -} - -#[derive(BorshSerialize, BorshDeserialize)] -pub struct ClientState { - pub latest_height: u64, - pub ibc_account_id: AccountId, - pub initial_block_producers: Option>, -} - #[derive(serde::Serialize)] pub struct RecvPacket { pub packet: Packet, diff --git a/near/near-ibc-tests/src/utils.rs b/near/near-ibc-tests/src/utils.rs index 6b6166a9cd..89f8ee072a 100644 --- a/near/near-ibc-tests/src/utils.rs +++ b/near/near-ibc-tests/src/utils.rs @@ -8,7 +8,10 @@ use near_primitives::{ use near_primitives_core::hash::CryptoHash; use near_sdk::AccountId; use near_workspaces::{network::Sandbox, Worker}; -use unionlabs::near::types::{self, MerklePathItem}; +use unionlabs::{ + ibc::lightclients::near, + near::types::{self, MerklePathItem}, +}; pub async fn state_proof( sandbox: &Worker, @@ -76,17 +79,21 @@ pub async fn chunk_proof( (prev_state_root, prev_state_root_proof) } -pub fn convert_block_producers(bps: Vec) -> Vec { +pub fn convert_block_producers( + bps: Vec, +) -> Vec { bps.into_iter() .map(|stake| { let ValidatorStakeView::V1(stake) = stake; - let stake = types::ValidatorStakeView::V1(types::ValidatorStakeViewV1 { - account_id: stake.account_id, - public_key: types::PublicKey::Ed25519( - stake.public_key.key_data().try_into().unwrap(), - ), - stake: stake.stake, - }); + let stake = near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ); stake }) .collect() @@ -94,8 +101,8 @@ pub fn convert_block_producers(bps: Vec) -> Vec types::BlockHeaderInnerLiteView { - types::BlockHeaderInnerLiteView { +) -> near::block_header_inner::BlockHeaderInnerLiteView { + near::block_header_inner::BlockHeaderInnerLiteView { height: block_view.height, epoch_id: CryptoHash(block_view.epoch_id.0), next_epoch_id: CryptoHash(block_view.next_epoch_id.0), @@ -110,8 +117,8 @@ pub fn convert_block_header_inner( pub fn convert_light_client_block_view( light_client_block: LightClientBlockView, -) -> types::LightClientBlockView { - types::LightClientBlockView { +) -> near::light_client_block::LightClientBlockView { + near::light_client_block::LightClientBlockView { inner_lite: convert_block_header_inner(light_client_block.inner_lite), prev_block_hash: near_primitives_core::hash::CryptoHash( light_client_block.prev_block_hash.0, @@ -126,13 +133,15 @@ pub fn convert_light_client_block_view( bps.into_iter() .map(|stake| { let ValidatorStakeView::V1(stake) = stake; - types::ValidatorStakeView::V1(types::ValidatorStakeViewV1 { - account_id: stake.account_id, - public_key: types::PublicKey::Ed25519( - stake.public_key.key_data().try_into().unwrap(), - ), - stake: stake.stake, - }) + near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ) }) .collect() }), diff --git a/near/near-ibc/src/contract.rs b/near/near-ibc/src/contract.rs index a42dba6be6..848e974678 100644 --- a/near/near-ibc/src/contract.rs +++ b/near/near-ibc/src/contract.rs @@ -535,7 +535,7 @@ impl Contract { let account_id = self.clients.get(&client_id.to_string()).unwrap(); PromiseOrValue::Promise( ext_light_client::ext(account_id.clone()) - .query(ibc_queries) + .query(client_id, ibc_queries) .then(Contract::ext(env::current_account_id()).callback_query(runnable)), ) } @@ -549,7 +549,7 @@ impl Contract { let account_id = self.account_ids.get(&client_type).unwrap(); PromiseOrValue::Promise( ext_light_client::ext(account_id.clone()) - .initialize(client_id, client_state, consensus_state) + .new(client_id, client_state, consensus_state) .then( Contract::ext(env::current_account_id()) .callback_initialize(runnable), @@ -577,7 +577,7 @@ impl Contract { let account_id = self.clients.get(&client_id.to_string()).unwrap(); PromiseOrValue::Promise( ext_light_client::ext(account_id.clone()) - .update_client(client_msg) + .update_client(client_id.to_string(), client_msg) .then( Contract::ext(env::current_account_id()) .callback_update_client(runnable), @@ -712,30 +712,15 @@ pub enum LightClientQuery { #[ext_contract(ext_light_client)] pub trait LightClient { - fn initialize(client_id: ClientId, client_state: Vec, consensus_state: Vec); + fn new(&mut self, client_id: ClientId, client_state: Vec, consensus_state: Vec); - fn query(&self, query: Vec) -> Vec; + fn query(&self, client_id: ClientId, query: Vec) -> Vec; - fn status(&self) -> Status; - - fn latest_height(&self) -> Height; - - fn verify_membership( - &self, - height: Height, - // TODO(aeryz): delay times might not be relevant for other chains we could make it optional - delay_time_period: u64, - delay_block_period: u64, - proof: Vec, - path: MerklePath, - value: Vec, - ) -> bool; - - fn verify_client_message(&self, client_msg: Vec) -> bool; - - fn check_for_misbehaviour(&self, client_msg: Vec) -> bool; - - fn update_client(&mut self, client_msg: Vec) -> (Vec, Vec<(Height, Vec)>); + fn update_client( + &mut self, + client_id: String, + client_msg: Vec, + ) -> (Vec, Vec<(Height, Vec)>); fn update_client_on_misbehaviour(&mut self, client_msg: Vec); } diff --git a/near/near-light-client/src/contract.rs b/near/near-light-client/src/contract.rs deleted file mode 100644 index 2fe1d9b48d..0000000000 --- a/near/near-light-client/src/contract.rs +++ /dev/null @@ -1,310 +0,0 @@ -use ibc_vm_rs::{IbcQuery, IbcResponse, Status}; -use near_primitives_core::hash::CryptoHash; -use near_sdk::{ - borsh::{self, BorshDeserialize, BorshSerialize}, - env, near_bindgen, - store::LookupMap, - PanicOnDefault, -}; -#[allow(unused)] -use near_sdk_contract_tools::{owner::OwnerExternal, Owner}; -use unionlabs::{ - ibc::core::{client::height::Height, commitment::merkle_path::MerklePath}, - id::ClientId, - near::types::{ - ApprovalInner, BlockHeaderInnerLite, BlockHeaderInnerLiteView, HeaderUpdate, - LightClientBlockView, PublicKey, Signature, ValidatorStakeView, - }, -}; - -use crate::{ - merkle::{self, combine_hash, hash_borsh}, - state_proof::RawStateProof, - ClientState, ConsensusState, -}; - -#[near_bindgen] -#[derive(PanicOnDefault, BorshDeserialize, BorshSerialize)] -pub struct Contract { - consensus_states: LookupMap, - client_state: ClientState, - epoch_block_producers_map: LookupMap>, -} - -#[near_bindgen] -impl Contract { - #[init] - pub fn initialize( - #[allow(unused)] client_id: ClientId, - client_state: Vec, - consensus_state: Vec, - ) -> Self { - let client_state: ClientState = borsh::from_slice(&client_state).unwrap(); - let consensus_state: ConsensusState = borsh::from_slice(&consensus_state).unwrap(); - let mut block_producers = LookupMap::new(b"epoch_block_producers".as_slice()); - block_producers.insert( - consensus_state.state.epoch_id, - client_state.initial_block_producers.clone().unwrap(), - ); - let mut consensus_states: LookupMap = LookupMap::new(b"c"); - consensus_states.insert(client_state.latest_height, consensus_state); - Self { - client_state, - consensus_states, - epoch_block_producers_map: block_producers, - } - } - - pub fn query(&self, query: Vec) -> Vec { - query - .into_iter() - .map(|q| match q { - IbcQuery::Status => IbcResponse::Status { - status: self.status(), - }, - IbcQuery::LatestHeight => IbcResponse::LatestHeight { - height: self.latest_height(), - }, - IbcQuery::VerifyMembership { - height, - delay_time_period, - delay_block_period, - proof, - path, - value, - } => IbcResponse::VerifyMembership { - valid: self.verify_membership( - height, - delay_time_period, - delay_block_period, - proof, - path, - value, - ), - }, - IbcQuery::VerifyClientMessage(msg) => IbcResponse::VerifyClientMessage { - valid: self.verify_client_message(msg), - }, - IbcQuery::CheckForMisbehaviour(msg) => IbcResponse::CheckForMisbehaviour { - misbehaviour_found: self.check_for_misbehaviour(msg), - }, - IbcQuery::TimestampAtHeight(_) => IbcResponse::TimestampAtHeight { timestamp: 100 }, - }) - .collect() - } - - pub fn status(&self) -> Status { - Status::Active - } - - pub fn latest_height(&self) -> Height { - Height { - revision_number: 0, - revision_height: self.client_state.latest_height, - } - } - - #[allow(unused)] - pub fn verify_membership( - &self, - height: Height, - // TODO(aeryz): delay times might not be relevant for other chains we could make it optional - delay_time_period: u64, - delay_block_period: u64, - proof: Vec, - path: MerklePath, - value: Vec, - ) -> bool { - let raw_state_proof: RawStateProof = serde_json::from_slice(&proof).unwrap(); - let state_proof = raw_state_proof.parse(); - let consensus_state = self - .consensus_states - .get(&(height.revision_height + 1)) - .unwrap(); - - let key = key_from_path(&path.key_path[1]); - - if !state_proof.verify( - &consensus_state.chunk_prev_state_root, - &self.client_state.ibc_account_id, - &key, - Some(&borsh::to_vec(&value).unwrap()), - ) { - panic!("commitment verification failed"); - } - - true - } - - // TODO(aeryz): client_msg can be Misbehaviour or Header - pub fn verify_client_message(&self, client_msg: Vec) -> bool { - let header_update: HeaderUpdate = borsh::from_slice(&client_msg).unwrap(); - - let consensus_state = self - .consensus_states - .get(&header_update.trusted_height) - .unwrap(); - - validate_head( - consensus_state.state.clone(), - header_update.new_state.clone(), - &self.epoch_block_producers_map, - ); - - merkle::verify_path( - header_update.new_state.inner_lite.prev_state_root, - &header_update.prev_state_root_proof, - header_update.prev_state_root, - ) - } - - #[allow(unused)] - pub fn check_for_misbehaviour(&self, client_msg: Vec) -> bool { - false - } - - pub fn update_client(&mut self, client_msg: Vec) -> (Vec, Vec<(Height, Vec)>) { - let header_update: HeaderUpdate = borsh::from_slice(&client_msg).unwrap(); - let new_consensus_state = ConsensusState { - state: header_update.new_state.inner_lite.clone(), - chunk_prev_state_root: header_update.prev_state_root, - }; - self.consensus_states.insert( - header_update.new_state.inner_lite.height, - new_consensus_state.clone(), - ); - self.client_state.latest_height = header_update.new_state.inner_lite.height; - if let Some(next_bps) = &header_update.new_state.next_bps { - self.epoch_block_producers_map.insert( - header_update.new_state.inner_lite.next_epoch_id, - next_bps.clone(), - ); - } - - ( - borsh::to_vec(&self.client_state).unwrap(), - vec![( - Height { - revision_number: 0, - revision_height: header_update.new_state.inner_lite.height, - }, - borsh::to_vec(&new_consensus_state).unwrap(), - )], - ) - } - - #[allow(unused)] - pub fn update_client_on_misbehaviour(&mut self, client_msg: Vec) {} -} - -fn key_from_path(path: &str) -> Vec { - let mut commitments: Vec = Vec::new(); - commitments.extend(b"commitments"); - commitments.extend(borsh::to_vec(path).unwrap()); - commitments -} - -fn reconstruct_light_client_block_view_fields( - block_view: LightClientBlockView, -) -> (CryptoHash, CryptoHash, Vec) { - let current_block_hash = combine_hash( - &combine_hash( - &CryptoHash( - env::sha256( - &borsh::to_vec(&Into::::into( - block_view.inner_lite.clone(), - )) - .unwrap(), - ) - .try_into() - .unwrap(), - ), - &block_view.inner_rest_hash, - ), - &block_view.prev_block_hash, - ); - - let next_block_hash = combine_hash(&block_view.next_block_inner_hash, ¤t_block_hash); - - let mut approval_message = borsh::to_vec(&ApprovalInner::Endorsement(next_block_hash)).unwrap(); - approval_message.extend_from_slice(&(block_view.inner_lite.height + 2).to_le_bytes()); - - (current_block_hash, next_block_hash, approval_message) -} - -fn validate_head( - head: BlockHeaderInnerLiteView, - block_view: LightClientBlockView, - epoch_block_producers_map: &LookupMap>, -) { - let (_current_block_hash, _next_block_hash, approval_message) = - reconstruct_light_client_block_view_fields(block_view.clone()); - - assert!(block_view.inner_lite.height > head.height, "false"); - - assert!( - [&head.epoch_id, &head.next_epoch_id].contains(&&block_view.inner_lite.epoch_id), - "false" - ); - - assert!( - !(block_view.inner_lite.epoch_id == head.next_epoch_id && block_view.next_bps.is_none()), - "false" - ); - - let mut total_stake = 0; - let mut approved_stake = 0; - - let epoch_block_producers = epoch_block_producers_map - .get(&block_view.inner_lite.epoch_id) - .expect("noo"); - for (maybe_signature, block_producer) in block_view - .approvals_after_next - .iter() - .zip(epoch_block_producers.iter()) - { - let ValidatorStakeView::V1(block_producer) = block_producer.clone(); - total_stake += block_producer.stake; - - if maybe_signature.is_none() { - continue; - } - - match maybe_signature { - Some(signature) => { - approved_stake += block_producer.stake; - - let PublicKey::Ed25519(pubkey) = block_producer.public_key else { - panic!("pubkey type is not supported"); - }; - - assert!( - verify_signature(&pubkey[..], signature, &approval_message), - "invalid signature" - ); - } - None => continue, - } - } - - let threshold = total_stake.checked_mul(2).unwrap().checked_div(3).unwrap(); - assert!(approved_stake > threshold, "approved_stake <= threshold"); - - if let Some(next_bps) = &block_view.next_bps { - assert!( - hash_borsh(next_bps) == block_view.inner_lite.next_bp_hash, - "next bps hash mismatch" - ); - } -} - -fn verify_signature(public_key: &[u8], signature: &Signature, message: &[u8]) -> bool { - let &Signature::Ed25519(sig) = &signature else { - panic!("signature must be ed25519"); - }; - env::ed25519_verify( - sig.as_slice().try_into().unwrap(), - message, - public_key.try_into().unwrap(), - ) -} diff --git a/near/near-light-client/src/lib.rs b/near/near-light-client/src/lib.rs deleted file mode 100644 index aa173df8c9..0000000000 --- a/near/near-light-client/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -mod contract; -mod merkle; -mod nibble_slice; -mod state_proof; -pub mod types; - -pub use contract::*; -use near_primitives_core::{hash::CryptoHash, types::AccountId}; -use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; -use unionlabs::near::types::{BlockHeaderInnerLiteView, ValidatorStakeView}; - -#[derive(BorshSerialize, BorshDeserialize)] -pub struct ClientState { - latest_height: u64, - ibc_account_id: AccountId, - initial_block_producers: Option>, -} - -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] -pub struct ConsensusState { - pub state: BlockHeaderInnerLiteView, - pub chunk_prev_state_root: CryptoHash, -} diff --git a/near/near-light-client/src/merkle.rs b/near/near-light-client/src/merkle.rs deleted file mode 100644 index 0d024a5992..0000000000 --- a/near/near-light-client/src/merkle.rs +++ /dev/null @@ -1,40 +0,0 @@ -use borsh::BorshSerialize; -use near_primitives_core::{hash::CryptoHash, types::MerkleHash}; -use near_sdk::env; -use unionlabs::near::types::{Direction, MerklePath}; - -pub fn hash_borsh(value: T) -> CryptoHash { - CryptoHash( - env::sha256(&borsh::to_vec(&value).expect("serialize will work")) - .try_into() - .expect("sha256 output size is always 32 bytes"), - ) -} - -pub fn combine_hash(hash1: &MerkleHash, hash2: &MerkleHash) -> MerkleHash { - hash_borsh((hash1, hash2)) -} - -/// Verify merkle path for given item and corresponding path. -pub fn verify_path(root: MerkleHash, path: &MerklePath, item: T) -> bool { - verify_hash(root, path, CryptoHash::hash_borsh(item)) -} - -pub fn verify_hash(root: MerkleHash, path: &MerklePath, item_hash: MerkleHash) -> bool { - compute_root_from_path(path, item_hash) == root -} - -pub fn compute_root_from_path(path: &MerklePath, item_hash: MerkleHash) -> MerkleHash { - let mut res = item_hash; - for item in path { - match item.direction { - Direction::Left => { - res = combine_hash(&item.hash, &res); - } - Direction::Right => { - res = combine_hash(&res, &item.hash); - } - } - } - res -} diff --git a/near/near-light-client/src/types.rs b/near/near-light-client/src/types.rs deleted file mode 100644 index 4e7729a0d0..0000000000 --- a/near/near-light-client/src/types.rs +++ /dev/null @@ -1,118 +0,0 @@ -use borsh::{BorshDeserialize, BorshSerialize}; -use near_primitives_core::{ - hash::CryptoHash, - types::{Balance, BlockHeight}, -}; -use near_sdk::AccountId; -// use unionlabs::tendermint::crypto::public_key::PublicKey; - -#[derive( - PartialEq, - Eq, - Debug, - Clone, - BorshDeserialize, - BorshSerialize, - serde::Serialize, - serde::Deserialize, -)] -pub struct BlockHeaderInnerLiteView { - pub height: BlockHeight, - pub epoch_id: CryptoHash, - pub next_epoch_id: CryptoHash, - pub prev_state_root: CryptoHash, - pub outcome_root: CryptoHash, - /// Legacy json number. Should not be used. - pub timestamp: u64, - // TODO(aeryz): #[serde(with = "dec_format")] - pub timestamp_nanosec: u64, - pub next_bp_hash: CryptoHash, - pub block_merkle_root: CryptoHash, -} - -#[derive( - PartialEq, - Eq, - Debug, - Clone, - BorshDeserialize, - BorshSerialize, - serde::Serialize, - serde::Deserialize, -)] -pub struct LightClientBlockView { - pub prev_block_hash: CryptoHash, - pub next_block_inner_hash: CryptoHash, - pub inner_lite: BlockHeaderInnerLiteView, - pub inner_rest_hash: CryptoHash, - pub next_bps: Option>, - pub approvals_after_next: Vec>>, -} - -#[derive( - BorshSerialize, - BorshDeserialize, - serde::Serialize, - serde::Deserialize, - Debug, - Clone, - Eq, - PartialEq, -)] -#[serde(tag = "validator_stake_struct_version")] -pub enum ValidatorStakeView { - V1(ValidatorStakeViewV1), -} - -#[derive( - BorshSerialize, - BorshDeserialize, - Debug, - Clone, - Eq, - PartialEq, - serde::Serialize, - serde::Deserialize, -)] -pub struct ValidatorStakeViewV1 { - pub account_id: AccountId, - // TODO(aeryz): make a near specific publickey type or use a common one? - pub public_key: Vec, - // TODO(aeryz): #[serde(with = "dec_format")] - pub stake: Balance, -} - -// TODO(aeryz): make this the proper type -#[derive( - Debug, - Clone, - PartialEq, - Eq, - serde::Serialize, - serde::Deserialize, - BorshSerialize, - BorshDeserialize, -)] -pub enum Signature { - Ed25519(Vec), - Secp256k1(Vec), -} - -/// The part of the block approval that is different for endorsements and skips -#[derive(BorshSerialize, BorshDeserialize, serde::Serialize, Debug, Clone, PartialEq, Eq, Hash)] -pub enum ApprovalInner { - Endorsement(CryptoHash), - Skip(BlockHeight), -} - -#[derive( - PartialEq, - Eq, - Debug, - Clone, - borsh::BorshDeserialize, - borsh::BorshSerialize, - serde::Serialize, - serde::Deserialize, -)] -pub struct UpdateState {} diff --git a/near/near.nix b/near/near.nix index e7d6df6db0..60b1953d99 100644 --- a/near/near.nix +++ b/near/near.nix @@ -1,5 +1,5 @@ { ... }: { - perSystem = { self', lib, unstablePkgs, pkgs, system, config, rust, crane, stdenv, dbg, ... }: + perSystem = { self', lib, unstablePkgs, pkgs, system, config, rust, crane, stdenv, dbg, python, ... }: let near-ibc-tests = pkgs.stdenv.mkDerivation { @@ -28,6 +28,31 @@ meta.mainProgram = "near-ibc-tests"; }; + + test-circuit = pkgs.stdenv.mkDerivation { + name = "test-circuit"; + buildInputs = [ pkgs.makeWrapper ]; + src = + (crane.buildWorkspaceMember { + crateDirFromRoot = "near/test-circuit"; + extraEnv = { + PROTOC = "${pkgs.protobuf}/bin/protoc"; + LIBCLANG_PATH = "${pkgs.llvmPackages_14.libclang.lib}/lib"; + }; + extraBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ]; + extraNativeBuildInputs = [ pkgs.clang ]; + extraEnv = { }; + }).packages.test-circuit; + installPhase = '' + mkdir -p $out/bin + cp -r $src/bin/test-circuit $out/bin/test-circuit + wrapProgram $out/bin/test-circuit \ + --set NEAR_SANDBOX_BIN_PATH "${near-sandbox}/bin/neard" \ + --set VERIFIER "${self'.packages.cometbls-near}/lib/cometbls_near.wasm" + ''; + meta.mainProgram = "test-circuit"; + }; + cargo-near = craneLib.buildPackage rec { pname = "cargo-near"; version = "v0.6.2"; @@ -66,6 +91,30 @@ craneLib = crane.lib.overrideToolchain rustToolchain; + nearcore = craneLib.buildPackage rec { + pname = "neard"; + version = "177c8657acd79a9a33f4e9f2ecadfabad792eae1"; + + buildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ] ++ ( + lib.optionals pkgs.stdenv.isDarwin [ pkgs.darwin.apple_sdk.frameworks.Security ] + ); + + nativeBuildInputs = [ + pkgs.clang + ]; + + LIBCLANG_PATH = "${pkgs.llvmPackages_14.libclang.lib}/lib"; + + cargoExtraArgs = " --verbose --verbose -p neard"; + + src = pkgs.fetchFromGitHub { + owner = "aeryz"; + repo = "nearcore"; + rev = version; + hash = "sha256-2Iii+prFl5W4OS9VLwbce+QssKe8dLH/P+bVG8AWJ2c="; + }; + }; + near-sandbox = craneLib.buildPackage rec { pname = "neard"; version = "326c6098c652c0fe3419067ad0ff839804658b7d"; @@ -94,7 +143,13 @@ }; near-light-client = (crane.buildWasmContract { - crateDirFromRoot = "near/near-light-client"; + crateDirFromRoot = "light-clients/near/near"; + extraBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ]; + extraNativeBuildInputs = [ pkgs.clang ]; + }); + + near-ics08 = (crane.buildWasmContract { + crateDirFromRoot = "light-clients/near/ics08-near"; extraBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ]; extraNativeBuildInputs = [ pkgs.clang ]; }); @@ -110,10 +165,70 @@ extraBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ]; extraNativeBuildInputs = [ pkgs.clang ]; }); + + cometbls-near = (crane.buildWasmContract { + crateDirFromRoot = "light-clients/cometbls/near"; + extraBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.perl pkgs.gnumake ]; + extraNativeBuildInputs = [ pkgs.clang ]; + }); + + near-localnet = pkgs.writeShellApplication { + name = "near-localnet"; + # runtimeInputs = [ nearup ]; + runtimeInputs = [ + (python.withPackages (py-pkgs: [ + py-pkgs.nearup + ])) + ] ++ [ pkgs.strace pkgs.iproute pkgs.busybox unstablePkgs.nodePackages_latest.near-cli ]; + text = '' + mkdir /tmp + export TMPDIR=/tmp + export TEMP=/tmp + + nearup run --override --binary-path ${nearcore}/bin localnet + sleep 3 + echo Deploying ibc.. + ls -la ~/.near + mkdir neardev + + echo N | near create-account ibc-union.node0 \ + --networkId asd \ + --masterAccount node0 \ + --keyPath ~/.near/localnet/node0/validator_key.json \ + --nodeUrl http://localhost:3030 + + near deploy \ + --networkId asd \ + --wasmFile ${self'.packages.near-ibc}/lib/near_ibc.wasm \ + --masterAccount node0 \ + --keyPath ~/.near/localnet/node0/validator_key.json \ + --nodeUrl http://localhost:3030 \ + --accountId ibc-union.node0 + echo "Deployed near-ibc" + + near create-account cometbls-light-client.node0 \ + --networkId asd \ + --masterAccount node0 \ + --keyPath ~/.near/localnet/node0/validator_key.json \ + --nodeUrl http://localhost:3030 + + near deploy \ + --networkId asd \ + --wasmFile ${self'.packages.cometbls-near}/lib/cometbls_near.wasm \ + --masterAccount node0 \ + --keyPath ~/.near/localnet/node0/validator_key.json \ + --nodeUrl http://localhost:3030 \ + --accountId cometbls-light-client.node0 + echo "Deployed cometbls-near" + cat ~/.near/localnet/node0/validator_key.json + + tail -f /.nearup/logs/localnet/node0.log + ''; + }; in { - packages = near-light-client.packages // dummy-ibc-app.packages // near-ibc.packages // { - inherit near-ibc-tests near-sandbox cargo-near; + packages = near-light-client.packages // dummy-ibc-app.packages // near-ibc.packages // cometbls-near.packages // near-ics08.packages // { + inherit near-ibc-tests near-sandbox cargo-near nearcore near-localnet test-circuit; }; checks = near-light-client.checks // near-ibc.checks; diff --git a/near/test-circuit/Cargo.toml b/near/test-circuit/Cargo.toml new file mode 100644 index 0000000000..325bb246e0 --- /dev/null +++ b/near/test-circuit/Cargo.toml @@ -0,0 +1,35 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "test-circuit" +repository.workspace = true +version = "0.1.0" + +[lints] +workspace = true + +[dependencies] +borsh = { workspace = true, features = ["borsh-derive"] } +ibc-vm-rs.workspace = true +near-contract-standards.workspace = true +near-sdk = { workspace = true } +near-sdk-contract-tools = { workspace = true } +near-units = "0.2.0" +near-workspaces.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio = { workspace = true, features = ["full"] } +unionlabs = { workspace = true, features = ["near"] } +# near-primitives = { git = "https://github.com/near/nearcore" } +base64 = { workspace = true } +env_logger = "0.9" +hex-literal.workspace = true +hex.workspace = true +near-crypto = "0.20" +near-jsonrpc-client = "0.8" +near-jsonrpc-primitives = "0.20" +near-primitives = "0.20" +near-primitives-core = { version = "0.21" } +near-store = { git = "https://github.com/near/nearcore" } +sha2 = { workspace = true } +time = { workspace = true, features = ["parsing", "serde"] } diff --git a/near/test-circuit/src/main.rs b/near/test-circuit/src/main.rs new file mode 100644 index 0000000000..ee01c70a45 --- /dev/null +++ b/near/test-circuit/src/main.rs @@ -0,0 +1,112 @@ +use std::{env, str::FromStr, thread::sleep, time::Duration}; + +use hex_literal::hex; +use near_primitives_core::hash::CryptoHash; +use near_workspaces::{ + network::Sandbox, + sandbox, testnet, + types::{Gas, KeyType, NearToken, SecretKey}, + Account, AccountId, Contract, Worker, +}; +use unionlabs::{ + encoding::{EncodeAs, Proto}, + google::protobuf::timestamp::Timestamp, + hash::H256, + ibc::lightclients::cometbls::{client_state::ClientState, light_header::LightHeader}, +}; + +const VERIFIER_ENV: &str = "VERIFIER"; + +mod alice { + pub const CLIENT_TYPE: &str = "near-alice"; +} +mod bob { + pub const CLIENT_TYPE: &str = "near-bob"; +} + +#[derive(serde::Serialize)] +pub struct CreateClient { + pub client_id: String, + pub client_state: Vec, + pub consensus_state: Vec, +} + +#[derive(serde::Serialize)] +pub struct TestCircuit { + chain_id: String, + trusted_validators_hash: H256, + header: LightHeader, + zkp: Vec, +} + +pub async fn deploy_contract( + sandbox: &Worker, + account_id: &str, + env_key: &'static str, +) -> Contract { + let wasm_path = env::var(env_key).unwrap(); + let wasm_blob = std::fs::read(wasm_path).unwrap(); + let account_id = account_id.to_string().try_into().unwrap(); + let secret_key = SecretKey::from_seed(KeyType::ED25519, "testificate"); + sandbox + .create_tla_and_deploy(account_id, secret_key, &wasm_blob) + .await + .unwrap() + .unwrap() +} + +#[tokio::main] +async fn main() { + let sandbox = sandbox().await.unwrap(); + let verifier_contract = deploy_contract(&sandbox, "verifier.test.near", VERIFIER_ENV).await; + let owner = sandbox.root_account().unwrap(); + let user = owner + .create_subaccount("user") + .initial_balance(NearToken::from_near(30)) + .transact() + .await + .unwrap() + .into_result() + .unwrap(); + let res = user + .call(verifier_contract.id(), "initialize") + .args_json(CreateClient { + client_id: String::from("08-wasm-0"), + client_state: ClientState::default().encode_as::(), + consensus_state: Vec::new(), + }) + .transact() + .await + .unwrap(); + println!("res1: {res:?}"); + + let res = user + .call(verifier_contract.id(), "test_circuit") + .gas(Gas::from_gas(300000000000000)) + .args_json(TestCircuit { + chain_id: "union-testnet-8".into(), + trusted_validators_hash: hex!( + "1deda64b1cc1319718f168b5aa8ed904b7d5b0ab932acdf6deae0ad9bd565a53" + ) + .into(), + header: LightHeader { + height: 969001.try_into().unwrap(), + time: Timestamp::from_str("2024-06-18T13:20:56.784169335Z").unwrap(), + validators_hash: hex!( + "1deda64b1cc1319718f168b5aa8ed904b7d5b0ab932acdf6deae0ad9bd565a53" + ) + .into(), + next_validators_hash: hex!( + "01a84dca649aa2df8de2f65a84c9092bbd5296b4bc54d818f844b28573d8e0be" + ) + .into(), + app_hash: hex!("1818da4a8b1c430557a3018adc2bf9a06e56c3b530e5cce7709232e0f03bd9ab") + .into(), + }, + zkp: hex!("086541c22b53d509d8369492d32683188f0b379950ea3c5da84aca2b331d911c163bc6e30c7610b6903832184d284399d140b316134202cfa53b695ed17db64e271a8ab10b015cc4562730180cc7af7d7509b64de00b5864ccef3ab6b5c187da1511c4af3392d5e4465cebeb3c92cad546ab6b5b7de08923ae756d4a49d972920ed4f1b33bde26016e753fe00e9ee8b37873e4df4696cce84baa34e444d6f9dc0021b25644dc22fd9414197dd9e094180eac33a5e6fc6d2e04e12df5baaae92815173080dedcafeb2789245e75f1c38ddaa4611273fa5eed1cb77f75aabace770186385a3a373190a9091147de95b3f11050152bc4376573ed454cfd703f1e7106edb33921b12717708fe03861534c812a5ea6c7e0ec428c02292f1e7dafb45901e8b29e0b18ba7cbfad2a7aef7db558f3eb49a943a379a03b1b976df912a0c329b66224da89f94e29c49b3c5070b86b23d9d23424246235088ea858a21340cc2d1120ac3dc25febd188abf16774ea49564f34bc769b6abd9295128c391dad18").to_vec(), + }) + .transact() + .await + .unwrap(); + println!("res: {res:?}"); +} diff --git a/networks/devnet.nix b/networks/devnet.nix index e61f64abde..c2d9198203 100644 --- a/networks/devnet.nix +++ b/networks/devnet.nix @@ -1,5 +1,5 @@ { inputs, ... }: { - perSystem = { devnetConfig, pkgs, lib, self', nix-filter, inputs', system, get-flake, mkCi, mkNodeId, dbg, ensureAtRepositoryRoot, ... }: + perSystem = { devnetConfig, pkgs, lib, self', nix-filter, inputs', system, get-flake, mkCi, mkNodeId, dbg, ensureAtRepositoryRoot, nearup, ... }: let arion = inputs'.arion.packages.default; @@ -62,6 +62,7 @@ # self'.packages.scroll-light-client self'.packages.arbitrum-light-client # self'.packages.berachain-light-client + self'.packages.near-ics08 ]; cosmwasmContracts = [ { @@ -113,7 +114,7 @@ keyType = "ed25519"; validatorCount = 4; lightClients = [ - self'.packages.cometbls-light-client + self'.packages.cometbls-ics08 ]; inherit cosmwasmContracts; portIncrease = 100; @@ -127,7 +128,7 @@ keyType = "ed25519"; validatorCount = 4; lightClients = [ - self'.packages.cometbls-light-client + self'.packages.cometbls-ics08 ]; inherit cosmwasmContracts; portIncrease = 200; @@ -154,7 +155,7 @@ keyType = "ed25519"; validatorCount = 4; lightClients = [ - self'.packages.cometbls-light-client + self'.packages.cometbls-ics08 ]; inherit cosmwasmContracts; portIncrease = 300; @@ -222,6 +223,14 @@ devnet-union-minimal = devnet-union-minimal.services; + devnet-near = { + devnet-near = import ./services/near.nix { + inherit pkgs; + inherit nearup; + near-localnet = self'.packages.near-localnet; + }; + }; + devnet-eth = { geth = import ./services/geth.nix { inherit pkgs; @@ -304,6 +313,7 @@ // mkNamedModule "devnet-osmosis" // mkNamedModule "devnet-simd" // mkNamedModule "devnet-union-minimal" + // mkNamedModule "devnet-near" // mkNamedModule "devnet-union"; mkNamedSpec = name: { @@ -323,6 +333,7 @@ // mkNamedSpec "devnet-osmosis" // mkNamedSpec "devnet-simd" // mkNamedSpec "devnet-union-minimal" + // mkNamedSpec "devnet-near" // mkNamedSpec "devnet-union"; mkNamedBuild = name: { @@ -336,7 +347,8 @@ // mkNamedBuild "devnet-osmosis" // mkNamedBuild "devnet-simd" // mkNamedBuild "devnet-union-minimal" - // mkNamedBuild "devnet-union"; + // mkNamedBuild "devnet-union" + // mkNamedBuild "devnet-near"; mkArionBuild = name: ciCondition: { ${name} = mkCi ciCondition (pkgs.writeShellApplication { @@ -403,6 +415,7 @@ // (mkArionBuild "devnet-osmosis" (system == "x86_64-linux")) // (mkArionBuild "devnet-eth" (system == "x86_64-linux")) // (mkArionBuild "devnet-union-minimal" (system == "x86_64-linux")) + // (mkArionBuild "devnet-near" (system == "x86_64-linux")) // (builtins.foldl' (acc: elem: elem.scripts or { } // acc) { } allCosmosDevnets); _module.args.networks.modules = modules; diff --git a/networks/services/near.nix b/networks/services/near.nix new file mode 100644 index 0000000000..f3db3862e3 --- /dev/null +++ b/networks/services/near.nix @@ -0,0 +1,18 @@ +{ pkgs, near-localnet, nearup }: +{ + image = { + enableRecommendedContents = true; + contents = [ + pkgs.coreutils + near-localnet + ]; + }; + service = { + tty = true; + stop_signal = "SIGINT"; + command = [ "${near-localnet}/bin/near-localnet" ]; + ports = [ + "3030:3030" + ]; + }; +} diff --git a/poc-relayer/Cargo.toml b/poc-relayer/Cargo.toml new file mode 100644 index 0000000000..cd090b9dc8 --- /dev/null +++ b/poc-relayer/Cargo.toml @@ -0,0 +1,33 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "poc-relayer" +repository.workspace = true +version = "0.1.0" + +[dependencies] +base64 = { workspace = true } +borsh = { workspace = true, features = ["borsh-derive"] } +chain-utils = { workspace = true } +cometbft-rpc = { workspace = true } +hex.workspace = true +ibc-vm-rs.workspace = true +near-crypto = "0.23" +near-jsonrpc-client = "0.10.1" +near-jsonrpc-primitives = "0.23" +near-primitives = "0.23.0" +near-primitives-core = { version = "0.23" } +near-verifier = { workspace = true } +num-bigint = { workspace = true } +prost = { workspace = true } +protos.workspace = true +serde.workspace = true +serde_json = { workspace = true } +tendermint = { workspace = true } +tendermint-proto = { workspace = true } +tendermint-rpc = { workspace = true, features = ["http-client", "websocket-client", "default"] } +tokio = { workspace = true, features = ["full"] } +unionlabs = { workspace = true, features = ["near"] } + +[lints] +workspace = true diff --git a/poc-relayer/src/main.rs b/poc-relayer/src/main.rs new file mode 100644 index 0000000000..cfbfafec5e --- /dev/null +++ b/poc-relayer/src/main.rs @@ -0,0 +1,1231 @@ +use std::{collections::HashMap, str::FromStr, time::Duration}; + +use chain_utils::{ + cosmos_sdk::{CosmosSdkChainExt, CosmosSdkChainRpcs as _, GasConfig}, + keyring::{ChainKeyring, KeyringConfig}, +}; +use ibc_vm_rs::{states::connection_handshake, DEFAULT_IBC_VERSION}; +use near_jsonrpc_client::{methods, JsonRpcClient}; +use near_jsonrpc_primitives::types::query::QueryResponseKind; +use near_primitives::{ + action::{Action, FunctionCallAction}, + hash::CryptoHash, + transaction::Transaction, + types::{AccountId, BlockId, BlockReference, Finality}, + views::{BlockHeaderInnerLiteView, LightClientBlockView, QueryRequest}, +}; +use num_bigint::BigUint; +use prost::Message; +use protos::union::galois::api::v3::union_prover_api_client; +use serde::{Deserialize, Serialize}; +use tendermint_rpc::{Client as _, WebSocketClientUrl}; +use tokio::time::sleep; +use unionlabs::{ + bounded::BoundedI64, + cometbls::types::canonical_vote::CanonicalVote, + encoding::{DecodeAs, Encode, Proto}, + google::protobuf::any::{mk_any, IntoAny}, + ibc::{ + core::{ + client::height::{Height, IsHeight as _}, + commitment::merkle_prefix::MerklePrefix, + connection::version::Version, + }, + lightclients::{ + cometbls::{self, client_state::ClientState}, + near, wasm, + }, + }, + ics24::ClientConsensusStatePath, + near::{ + raw_state_proof::RawStateProof, + types::{self, MerklePathItem}, + }, + tendermint::{ + crypto::public_key::PublicKey, + types::{ + commit_sig::CommitSig, signed_header::SignedHeader, simple_validator::SimpleValidator, + validator::Validator, + }, + }, + traits::Chain, + union::galois::{ + prove_request::ProveRequest, prove_response::ProveResponse, + validator_set_commit::ValidatorSetCommit, + }, + validated::ValidateT as _, +}; + +#[derive(serde::Serialize)] +pub struct CreateClient { + pub client_type: String, + pub client_state: Vec, + pub consensus_state: Vec, +} + +#[derive(serde::Serialize)] +pub struct RegisterClient { + pub client_type: String, + pub account: String, +} + +#[derive(serde::Serialize)] +pub struct ConnectionOpenInit { + pub client_id: String, + pub counterparty: connection_handshake::Counterparty, + pub version: Version, + pub delay_period: u64, +} + +#[derive(Clone, serde::Serialize)] +pub struct UpdateClient { + pub client_id: String, + pub client_msg: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde( + bound(serialize = "", deserialize = ""), + deny_unknown_fields, + rename_all = "snake_case" +)] +pub enum AbciQueryType { + State, + Proof, +} + +struct Near { + rpc: JsonRpcClient, + light_client_id: AccountId, + ibc_id: AccountId, + signer: near_crypto::InMemorySigner, +} + +struct Union { + union: chain_utils::union::Union, +} + +impl Union { + async fn new() -> Self { + Union { + union: chain_utils::union::Union::new(chain_utils::union::Config { + keyring: KeyringConfig { + name: "alice".to_string(), + keys: vec![ + serde_json::from_str( + r#"{ "raw": "0xaa820fa947beb242032a41b6dc9a8b9c37d8f5fbcda0966b1ec80335b10a7d6f" }"# + ).unwrap() + ], + }, + ws_url: WebSocketClientUrl::from_str("ws://localhost:26657/websocket").unwrap(), + grpc_url: "http://localhost:9090".to_string(), + gas_config: GasConfig { + gas_price: 1.0, + gas_denom: "muno".to_string(), + gas_multiplier: 1.1, + max_gas: 400000, + }, + prover_endpoints: vec!["http://localhost:9999".to_string()], + }) + .await + .unwrap(), + } + } + + async fn fetch_validators(&self, height: Height) -> Vec { + self.union + .tm_client() + .validators( + TryInto::<::tendermint::block::Height>::try_into(height.revision_height()).unwrap(), + tendermint_rpc::Paging::All, + ) + .await + .unwrap() + .validators + .into_iter() + .map(tendermint_helpers::tendermint_validator_info_to_validator) + .collect() + } + + async fn fetch_commit(&self, height: Height) -> SignedHeader { + let commit = self + .union + .tm_client() + .commit( + TryInto::<::tendermint::block::Height>::try_into(height.revision_height()).unwrap(), + ) + .await + .unwrap(); + + tendermint_helpers::tendermint_commit_to_signed_header(commit) + } + + async fn fetch_prove_request(&self, request: ProveRequest) -> ProveResponse { + let response = union_prover_api_client::UnionProverApiClient::connect( + self.union.prover_endpoints[0].clone(), + ) + .await + .unwrap() + .prove(protos::union::galois::api::v3::ProveRequest::from(request)) + .await + .unwrap(); + // .poll(protos::union::galois::api::v3::PollRequest::from( + // PollRequest { + // request: request.clone(), + // }, + // )) + // .await + // .map(|x| x.into_inner().try_into().unwrap()); + + // match response { + // Err(err) => panic!("prove request failed: {:?}", err), + // Ok(PollResponse::Failed(ProveRequestFailed { message })) => { + // panic!("panic with: {message}"); + // } + // Ok(PollResponse::Done(ProveRequestDone { response })) => response, + // _ => panic!("other"), + // } + response.into_inner().try_into().unwrap() + } + + async fn next_light_header( + &self, + trusted_height: Height, + new_height: Height, + ) -> cometbls::header::Header { + let signed_header = self.fetch_commit(new_height).await; + let untrusted_validators = self.fetch_validators(new_height).await; + let trusted_validators = self.fetch_validators(trusted_height).await; + + let make_validators_commit = + |mut validators: Vec| { + // Validators must be sorted to match the root, by token then address + validators.sort_by(|a, b| { + // TODO: Double check how these comparisons are supposed to work + #[allow(clippy::collapsible_else_if)] + if a.voting_power == b.voting_power { + if a.address < b.address { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } else { + if a.voting_power > b.voting_power { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } + }); + + // The bitmap is a public input of the circuit, it must fit in Fr (scalar field) bn254 + let mut bitmap = BigUint::default(); + // REVIEW: This will over-allocate for the trusted validators; should be benchmarked + let mut signatures = Vec::>::with_capacity(validators.len()); + + let validators_map = validators + .iter() + .enumerate() + .map(|(i, v)| (v.address, i)) + .collect::>(); + + // For each validator signature, we search for the actual validator + // in the set and set it's signed bit to 1. We then push the + // signature only if the validator signed. It's possible that we + // don't find a validator for a given signature as the validator set + // may have drifted (trusted validator set). + for sig in signed_header.commit.signatures.iter() { + match sig { + CommitSig::Absent => {} + CommitSig::Commit { + validator_address, + timestamp: _, + signature, + } => { + if let Some(validator_index) = validators_map.get(validator_address) { + bitmap.set_bit(*validator_index as u64, true); + signatures.push(signature.clone()); + } else { + } + } + CommitSig::Nil { .. } => {} + } + } + + let simple_validators = validators + .iter() + .map(|v| { + let PublicKey::Bn254(ref key) = v.pub_key else { + panic!("must be bn254") + }; + SimpleValidator { + pub_key: PublicKey::Bn254(key.to_vec()), + voting_power: v.voting_power.into(), + } + }) + .collect::>(); + + ValidatorSetCommit { + validators: simple_validators, + signatures, + bitmap: bitmap.to_bytes_be(), + } + }; + + let trusted_validators_commit = make_validators_commit(trusted_validators); + let untrusted_validators_commit = make_validators_commit(untrusted_validators); + let proof = self.fetch_prove_request(ProveRequest { + vote: CanonicalVote { + // REVIEW: Should this be hardcoded to precommit? + ty: unionlabs::tendermint::types::signed_msg_type::SignedMsgType::Precommit, + height: signed_header.commit.height, + round: BoundedI64::new(signed_header.commit.round.inner().into()) + .expect("0..=i32::MAX can be converted to 0..=i64::MAX safely"), + block_id: unionlabs::tendermint::types::canonical_block_id::CanonicalBlockId { + hash: signed_header.commit.block_id.hash.unwrap_or_default(), + part_set_header: unionlabs::tendermint::types::canonical_block_header::CanonicalPartSetHeader { + total: signed_header.commit.block_id.part_set_header.total, + hash: signed_header + .commit + .block_id + .part_set_header + .hash + .unwrap_or_default(), + }, + }, + chain_id: signed_header.header.chain_id.clone(), + }, + untrusted_header: signed_header.header.clone(), + trusted_commit: trusted_validators_commit, + untrusted_commit: untrusted_validators_commit, + }) + .await; + + cometbls::header::Header { + signed_header: signed_header.into(), + trusted_height, + zero_knowledge_proof: proof.proof.evm_proof, + } + } + + async fn create_client(&self, client_state: A, consensus_state: B) { + self.union + .keyring() + .with(|signer| async { + let msg = protos::ibc::core::client::v1::MsgCreateClient { + client_state: Some(client_state.into_any().into()), + consensus_state: Some(consensus_state.into_any().into()), + signer: signer.to_string(), + }; + + let (tx_hash, _) = self + .union + .broadcast_tx_commit(signer, [mk_any(&msg)], "".to_string()) + .await + .unwrap(); + + println!("[ + ] Near client created: {tx_hash}"); + }) + .await; + } + + async fn fetch_abci_query(&self, height: u64, ty: AbciQueryType, path: &str) -> Vec { + const IBC_STORE_PATH: &str = "store/ibc/key"; + + let mut client = + protos::cosmos::base::tendermint::v1beta1::service_client::ServiceClient::connect( + self.union.grpc_url().clone(), + ) + .await + .unwrap(); + + let query_result = client + .abci_query( + protos::cosmos::base::tendermint::v1beta1::AbciQueryRequest { + data: path.as_bytes().to_vec(), + path: IBC_STORE_PATH.to_string(), + height: i64::try_from(height).unwrap() - 1_i64, + prove: matches!(ty, AbciQueryType::Proof), + }, + ) + .await + .unwrap() + .into_inner(); + + match ty { + AbciQueryType::State => query_result.value, + AbciQueryType::Proof => { + let proof = protos::ibc::core::commitment::v1::MerkleProof { + proofs: query_result + .proof_ops + .unwrap() + .ops + .into_iter() + .map(|op| { + ::decode( + op.data.as_slice(), + ) + .unwrap() + }) + .collect::>(), + }; + + proof.encode_to_vec() + } + } + } + + async fn connection_open_try>( + &self, + client_id: &str, + client_state: S, + counterparty_client_id: &str, + counterparty_connection_id: &str, + proof_height: u64, + proof_init: Vec, + proof_client: Vec, + proof_consensus: Vec, + consensus_height: Height, + ) { + self.union + .keyring() + .with(|signer| async { + let msg = protos::ibc::core::connection::v1::MsgConnectionOpenTry { + client_id: client_id.to_string(), + client_state: Some(client_state.into()), + counterparty: Some( + connection_handshake::Counterparty { + client_id: counterparty_client_id.to_string().validate().unwrap(), + connection_id: counterparty_connection_id.to_string(), + prefix: MerklePrefix { + key_prefix: b"ibc".into(), + }, + } + .into(), + ), + delay_period: 0, + counterparty_versions: DEFAULT_IBC_VERSION + .clone() + .into_iter() + .map(Into::into) + .collect(), + proof_height: Some( + Height { + revision_number: 0, + revision_height: proof_height, + } + .into(), + ), + proof_init, + proof_client, + proof_consensus, + consensus_height: Some(consensus_height.into()), + signer: signer.to_string(), + host_consensus_state_proof: vec![], + previous_connection_id: "".to_string(), + }; + + let (tx_hash, _) = self + .union + .broadcast_tx_commit(signer, [mk_any(&msg)], "".to_string()) + .await + .unwrap(); + + println!("[ + ] ConnectionOpenTry on Union: {tx_hash}"); + }) + .await; + } + + async fn connection_open_confirm( + &self, + connection_id: &str, + proof_ack: Vec, + proof_height: u64, + ) { + self.union + .keyring() + .with(|signer| async { + let msg = protos::ibc::core::connection::v1::MsgConnectionOpenConfirm { + connection_id: connection_id.to_string(), + proof_ack, + proof_height: Some( + Height { + revision_number: 0, + revision_height: proof_height, + } + .into(), + ), + signer: signer.to_string(), + }; + + let (tx_hash, _) = self + .union + .broadcast_tx_commit(signer, [mk_any(&msg)], "".to_string()) + .await + .unwrap(); + + println!("[ + ] ConnectionOpenConfirm on Union: {tx_hash}"); + }) + .await; + } + + async fn update_client>(&self, client_id: &str, header: T) { + self.union + .keyring() + .with(|signer| async { + let msg = protos::ibc::lightclients::wasm::v1::ClientMessage { + data: header.encode(), + }; + + let msg = protos::ibc::core::client::v1::MsgUpdateClient { + client_id: client_id.to_string(), + client_message: Some(mk_any(&msg)), + signer: signer.to_string(), + }; + + let (tx_hash, _) = self + .union + .broadcast_tx_commit(signer, [mk_any(&msg)], "".to_string()) + .await + .unwrap(); + + println!("[ + ] Near client updated: {tx_hash}"); + }) + .await; + } +} + +impl Near { + fn new() -> Self { + let rpc = JsonRpcClient::connect("http://localhost:3030"); + let light_client_id: AccountId = "cometbls-light-client.node0".parse().unwrap(); + let ibc_id: AccountId = "ibc-union.node0".parse().unwrap(); + let secret_key = "ed25519:3D4YudUQRE39Lc4JHghuB5WM8kbgDDa34mnrEP5DdTApVH81af7e2dWgNPEaiQfdJnZq1CNPp5im4Rg5b733oiMP".parse().unwrap(); + let account_id = "node0".parse().unwrap(); + let signer = near_crypto::InMemorySigner::from_secret_key(account_id, secret_key); + + Near { + rpc, + light_client_id, + ibc_id, + signer, + } + } + + async fn state_proof(&self, block_height: u64, commitment_key: &str) -> (Vec, Vec) { + let mut proof_key = b"commitments".to_vec(); + proof_key.extend(borsh::to_vec(commitment_key).unwrap()); + + match self + .rpc + .call(methods::query::RpcQueryRequest { + block_reference: BlockReference::BlockId(BlockId::Height(block_height)), + request: QueryRequest::ViewState { + account_id: self.ibc_id.clone(), + prefix: proof_key.into(), + include_proof: true, + }, + }) + .await + .unwrap() + .kind + { + QueryResponseKind::ViewState(res) => { + let value = res.values[0].value.to_vec(); + let state_proof = res + .proof + .clone() + .into_iter() + .map(|item| item.to_vec()) + .collect(); + ( + serde_json::to_vec(&RawStateProof { state_proof }).unwrap(), + borsh::from_slice::>(&value).unwrap(), + ) + } + _ => panic!("invalid response"), + } + } + + async fn update_client>(&self, client_id: &str, header: T) { + let update = UpdateClient { + client_id: client_id.to_string(), + client_msg: header.encode(), + }; + + self.send_tx("update_client", update).await; + } + + async fn connection_open_init(&self, client_id: &str, counterparty_client_id: &str) { + let init = ConnectionOpenInit { + client_id: client_id.to_string(), + counterparty: connection_handshake::Counterparty { + client_id: counterparty_client_id.to_string().validate().unwrap(), + connection_id: "".into(), + prefix: MerklePrefix { + key_prefix: b"ibc".into(), + }, + }, + version: DEFAULT_IBC_VERSION[0].clone(), + delay_period: 0, + }; + + self.send_tx("connection_open_init", init).await; + } + + async fn connection_open_ack( + &self, + connection_id: &str, + counterparty_connection_id: &str, + connection_end_proof: Vec, + proof_height: Height, + ) { + #[derive(serde::Serialize)] + pub struct ConnectionOpenAck { + pub connection_id: String, + pub version: Version, + pub counterparty_connection_id: String, + pub connection_end_proof: Vec, + pub proof_height: Height, + } + + let ack = ConnectionOpenAck { + connection_id: connection_id.to_string(), + version: DEFAULT_IBC_VERSION[0].clone(), + counterparty_connection_id: counterparty_connection_id.to_string(), + connection_end_proof, + proof_height, + }; + + self.send_tx("connection_open_ack", ack).await; + } + + async fn next_light_header(&self, trusted_height: u64) -> near::header::Header { + let block = self + .rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(Finality::Final), + }) + .await + .unwrap(); + + sleep(Duration::from_secs(1)).await; + + let lc_block = self + .rpc + .call( + methods::next_light_client_block::RpcLightClientNextBlockRequest { + last_block_hash: block.header.hash, + }, + ) + .await + .unwrap() + .unwrap(); + + let (prev_state_root, prev_state_root_proof) = + self.chunk_proof(lc_block.inner_lite.height).await; + + near::header::Header { + new_state: Self::convert_light_client_block_view(lc_block), + trusted_height, + prev_state_root_proof, + prev_state_root, + } + } + + pub async fn chunk_proof(&self, block_height: u64) -> (CryptoHash, types::MerklePath) { + let chunks = self + .rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::BlockId(BlockId::Height(block_height)), + }) + .await + .unwrap() + .chunks; + + let prev_state_root = CryptoHash(chunks[0].prev_state_root.0); + + let (_, merkle_path) = near_primitives::merkle::merklize( + &chunks + .into_iter() + .map(|chunk| CryptoHash(chunk.prev_state_root.0)) + .collect::>(), + ); + + let prev_state_root_proof = merkle_path[0] + .clone() + .into_iter() + .map(|item| MerklePathItem { + hash: near_primitives_core::hash::CryptoHash(item.hash.0), + direction: match item.direction { + near_primitives::merkle::Direction::Left => types::Direction::Left, + near_primitives::merkle::Direction::Right => types::Direction::Right, + }, + }) + .collect(); + + (prev_state_root, prev_state_root_proof) + } + + pub fn convert_block_header_inner( + block_view: BlockHeaderInnerLiteView, + ) -> near::block_header_inner::BlockHeaderInnerLiteView { + near::block_header_inner::BlockHeaderInnerLiteView { + height: block_view.height, + epoch_id: CryptoHash(block_view.epoch_id.0), + next_epoch_id: CryptoHash(block_view.next_epoch_id.0), + prev_state_root: CryptoHash(block_view.prev_state_root.0), + outcome_root: CryptoHash(block_view.outcome_root.0), + timestamp: block_view.timestamp, + timestamp_nanosec: block_view.timestamp_nanosec, + next_bp_hash: CryptoHash(block_view.next_bp_hash.0), + block_merkle_root: CryptoHash(block_view.block_merkle_root.0), + } + } + + pub fn convert_light_client_block_view( + light_client_block: LightClientBlockView, + ) -> near::light_client_block::LightClientBlockView { + near::light_client_block::LightClientBlockView { + inner_lite: Self::convert_block_header_inner(light_client_block.inner_lite), + prev_block_hash: near_primitives_core::hash::CryptoHash( + light_client_block.prev_block_hash.0, + ), + next_block_inner_hash: near_primitives_core::hash::CryptoHash( + light_client_block.next_block_inner_hash.0, + ), + inner_rest_hash: near_primitives_core::hash::CryptoHash( + light_client_block.inner_rest_hash.0, + ), + next_bps: light_client_block.next_bps.map(|bps| { + bps.into_iter() + .map(|stake| { + let near_primitives::views::validator_stake_view::ValidatorStakeView::V1( + stake, + ) = stake; + near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ) + }) + .collect() + }), + approvals_after_next: light_client_block + .approvals_after_next + .into_iter() + .map(|sig| { + sig.map(|s| match s.as_ref() { + near_crypto::Signature::ED25519(sig) => { + Box::new(types::Signature::Ed25519(sig.to_bytes().to_vec())) + } + near_crypto::Signature::SECP256K1(_) => { + Box::new(types::Signature::Secp256k1(Vec::new())) + } + }) + }) + .collect(), + } + } + + async fn self_consensus_state(&self, height: u64) -> near::consensus_state::ConsensusState { + let block = self + .rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::BlockId(BlockId::Height(height)), + }) + .await + .unwrap(); + let chunk_prev_state_root = block.header.prev_state_root; + let timestamp = block.header.timestamp_nanosec; + near::consensus_state::ConsensusState { + state: block_header_to_inner_lite(block.header), + chunk_prev_state_root, + timestamp, + } + } + + async fn self_client_state(&self) -> near::client_state::ClientState { + let chain_id = self + .rpc + .call(methods::status::RpcStatusRequest) + .await + .unwrap() + .chain_id; + + let block = self + .rpc + .call(methods::block::RpcBlockRequest { + block_reference: BlockReference::Finality(near_primitives::types::Finality::Final), + }) + .await + .unwrap(); + + let validators = self + .rpc + .call( + methods::EXPERIMENTAL_validators_ordered::RpcValidatorsOrderedRequest { + block_id: Some(BlockId::Height(block.header.height)), + }, + ) + .await + .unwrap(); + near::client_state::ClientState { + chain_id, + latest_height: block.header.height - 1, + ibc_account_id: self.ibc_id.clone(), + // TODO(aeryz): this is only valid in this sandboxed environment where the validator set is not changing. For a real environment, + // the relayer must read the block producers using another endpoint. + initial_block_producers: convert_block_producers(validators), + frozen_height: 0, + } + } + + async fn send_tx(&self, method_name: &str, args: T) { + let access_key_query_response = self + .rpc + .call(methods::query::RpcQueryRequest { + block_reference: BlockReference::latest(), + request: near_primitives::views::QueryRequest::ViewAccessKey { + account_id: self.signer.account_id.clone(), + public_key: self.signer.public_key.clone(), + }, + }) + .await + .unwrap(); + + println!("access key {:?}", access_key_query_response); + + let current_nonce = match access_key_query_response.kind { + QueryResponseKind::AccessKey(access_key) => access_key.nonce, + _ => panic!("failed to extract current nonce"), + }; + + let transaction = Transaction { + signer_id: self.signer.account_id.clone(), + public_key: self.signer.public_key.clone(), + nonce: current_nonce + 1, + receiver_id: self.ibc_id.clone(), + block_hash: access_key_query_response.block_hash, + actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { + method_name: method_name.to_string(), + args: serde_json::to_string(&args).unwrap().into_bytes(), + gas: 100_000_000_000_000, // 100 TeraGas + deposit: 0, + }))], + }; + + let request = methods::send_tx::RpcSendTransactionRequest { + signed_transaction: transaction.sign(&self.signer), + wait_until: near_primitives::views::TxExecutionStatus::Final, + }; + + let response = self.rpc.call(request).await.unwrap(); + + println!("response: {:?}", response); + + for out in response + .final_execution_outcome + .unwrap() + .into_outcome() + .receipts_outcome + { + if !out.outcome.logs.is_empty() { + println!("[ i ] Logs: {:?}", out.outcome.logs); + } + } + } + + async fn register_client(&self) { + let register_client = RegisterClient { + client_type: "cometbls".to_string(), + account: self.light_client_id.to_string(), + }; + self.send_tx("register_client", register_client).await; + } + + async fn create_client, B: Encode>( + &self, + client_state: A, + consensus_state: B, + ) { + let create_client = CreateClient { + client_type: "cometbls".to_string(), + client_state: client_state.encode(), + consensus_state: consensus_state.encode(), + }; + + self.send_tx("create_client", create_client).await; + + println!("[ + ] Client created!"); + } +} + +#[tokio::main] +async fn main() { + let near_lc = "08-wasm-0"; + let cometbls_lc = "cometbls-1"; + let near_connection = "connection-1"; + let union_connection = "connection-0"; + let near = Near::new(); + let union = Union::new().await; + + near.register_client().await; + + let latest_height = union.union.query_latest_height().await.unwrap(); + let union_client_state = union.union.self_client_state(latest_height).await; + near.create_client( + union_client_state.clone(), + union.union.self_consensus_state(latest_height).await, + ) + .await; + + let cs = near.self_client_state().await; + union + .create_client( + wasm::client_state::ClientState { + data: cs.clone(), + checksum: hex::decode( + "88ec41b1142e2895fe8b1260897ed7734670412381322694e32b81348a441a94", + ) + .unwrap() + .try_into() + .unwrap(), + latest_height: Height { + revision_number: 0, + revision_height: cs.latest_height, + }, + }, + wasm::consensus_state::ConsensusState { + data: near.self_consensus_state(cs.latest_height + 1).await, + }, + ) + .await; + + near.connection_open_init(cometbls_lc, near_lc).await; + let header = near.next_light_header(cs.latest_height).await; + let current_near_lc_height = header.new_state.inner_lite.height; + union.update_client(near_lc, header.clone()).await; + + sleep(Duration::from_secs(3)).await; + + let (proof_init, _) = near + .state_proof( + header.new_state.inner_lite.height - 1, + &format!("connections/{}", near_connection), + ) + .await; + + let (proof_client, client_state) = near + .state_proof( + header.new_state.inner_lite.height - 1, + &format!("clients/{cometbls_lc}/clientState"), + ) + .await; + + let client_state = ClientState::decode_as::(&client_state) + .unwrap() + .into_any(); + let (proof_consensus, _) = near + .state_proof( + header.new_state.inner_lite.height - 1, + &ClientConsensusStatePath { + client_id: cometbls_lc.to_string(), + height: union_client_state.latest_height, + } + .to_string(), + ) + .await; + + union + .connection_open_try( + near_lc, + client_state, + cometbls_lc, + near_connection, + header.new_state.inner_lite.height - 1, + proof_init, + proof_client, + proof_consensus, + union_client_state.latest_height, + ) + .await; + + sleep(Duration::from_secs(6)).await; + + let latest_height = union.union.query_latest_height().await.unwrap(); + let light_header = union + .next_light_header(union_client_state.latest_height, latest_height) + .await; + + near.update_client(cometbls_lc, light_header.clone()).await; + + let proof_try = union + .fetch_abci_query( + light_header.signed_header.height.inner() as u64, + AbciQueryType::Proof, + &format!("connections/{union_connection}"), + ) + .await; + + near.connection_open_ack( + near_connection, + union_connection, + proof_try, + Height { + revision_number: 1, + revision_height: light_header.signed_header.height.inner() as u64, + }, + ) + .await; + + sleep(Duration::from_secs(2)).await; + let header = near.next_light_header(current_near_lc_height).await; + union.update_client(near_lc, header.clone()).await; + + let (proof_ack, _) = near + .state_proof( + header.new_state.inner_lite.height - 1, + &format!("connections/{}", near_connection), + ) + .await; + + union + .connection_open_confirm( + union_connection, + proof_ack, + header.new_state.inner_lite.height - 1, + ) + .await; +} + +pub fn convert_block_producers( + bps: Vec, +) -> Vec { + bps.into_iter() + .map(|stake| { + let near_primitives::views::validator_stake_view::ValidatorStakeView::V1(stake) = stake; + let stake = near::validator_stake_view::ValidatorStakeView::V1( + near::validator_stake_view::ValidatorStakeViewV1 { + account_id: stake.account_id, + public_key: unionlabs::near::types::PublicKey::Ed25519( + stake.public_key.key_data().try_into().unwrap(), + ), + stake: stake.stake, + }, + ); + stake + }) + .collect() +} + +pub fn block_header_to_inner_lite( + header: near_primitives::views::BlockHeaderView, +) -> near::block_header_inner::BlockHeaderInnerLiteView { + use near_primitives_core::hash::CryptoHash; + near::block_header_inner::BlockHeaderInnerLiteView { + height: header.height, + epoch_id: CryptoHash(header.epoch_id.0), + next_epoch_id: CryptoHash(header.next_epoch_id.0), + prev_state_root: CryptoHash(header.prev_state_root.0), + outcome_root: CryptoHash(header.outcome_root.0), + timestamp: header.timestamp, + timestamp_nanosec: header.timestamp_nanosec, + next_bp_hash: CryptoHash(header.next_bp_hash.0), + block_merkle_root: CryptoHash(header.block_merkle_root.0), + } +} + +fn key_from_path(path: &str) -> Vec { + let mut commitments: Vec = Vec::new(); + commitments.extend(b"commitments"); + commitments.extend(borsh::to_vec(path).unwrap()); + commitments +} + +pub mod tendermint_helpers { + use unionlabs::{ + bounded::BoundedI64, + google::protobuf::timestamp::Timestamp, + hash::H256, + tendermint::{ + crypto::public_key::PublicKey, + types::{ + block_id::BlockId, commit::Commit, commit_sig::CommitSig, + part_set_header::PartSetHeader, signed_header::SignedHeader, validator::Validator, + }, + }, + }; + + pub fn tendermint_commit_to_signed_header( + commit: tendermint_rpc::endpoint::commit::Response, + ) -> SignedHeader { + let header_timestamp = + tendermint_proto::google::protobuf::Timestamp::from(commit.signed_header.header.time); + + SignedHeader { + header: unionlabs::tendermint::types::header::Header { + version: unionlabs::tendermint::version::consensus::Consensus { + block: commit.signed_header.header.version.block, + app: commit.signed_header.header.version.app, + }, + chain_id: commit.signed_header.header.chain_id.into(), + height: tendermint_height_to_bounded_i64(commit.signed_header.header.height), + time: Timestamp { + seconds: header_timestamp.seconds.try_into().unwrap(), + nanos: header_timestamp.nanos.try_into().unwrap(), + }, + last_block_id: BlockId { + hash: Some(tendermint_hash_to_h256( + commit.signed_header.header.last_block_id.unwrap().hash, + )), + part_set_header: PartSetHeader { + total: commit + .signed_header + .header + .last_block_id + .unwrap() + .part_set_header + .total, + hash: Some(tendermint_hash_to_h256( + commit + .signed_header + .header + .last_block_id + .unwrap() + .part_set_header + .hash, + )), + }, + }, + last_commit_hash: tendermint_hash_to_h256( + commit.signed_header.header.last_commit_hash.unwrap(), + ), + data_hash: tendermint_hash_to_h256(commit.signed_header.header.data_hash.unwrap()), + validators_hash: tendermint_hash_to_h256( + commit.signed_header.header.validators_hash, + ), + next_validators_hash: tendermint_hash_to_h256( + commit.signed_header.header.next_validators_hash, + ), + consensus_hash: tendermint_hash_to_h256(commit.signed_header.header.consensus_hash), + app_hash: commit + .signed_header + .header + .app_hash + .as_bytes() + .try_into() + .unwrap(), + last_results_hash: tendermint_hash_to_h256( + commit.signed_header.header.last_results_hash.unwrap(), + ), + evidence_hash: tendermint_hash_to_h256( + commit.signed_header.header.evidence_hash.unwrap(), + ), + proposer_address: commit + .signed_header + .header + .proposer_address + .as_bytes() + .try_into() + .expect("value is a [u8; 20] internally, this should not fail; qed;"), + }, + commit: Commit { + height: tendermint_height_to_bounded_i64(commit.signed_header.commit.height), + round: i32::from(commit.signed_header.commit.round) + .try_into() + .unwrap(), + block_id: BlockId { + hash: Some(tendermint_hash_to_h256( + commit.signed_header.commit.block_id.hash, + )), + part_set_header: PartSetHeader { + total: commit.signed_header.commit.block_id.part_set_header.total, + hash: Some(tendermint_hash_to_h256( + commit.signed_header.commit.block_id.part_set_header.hash, + )), + }, + }, + signatures: commit + .signed_header + .commit + .signatures + .into_iter() + .map(tendermint_commit_sig_to_commit_sig) + .collect(), + }, + } + } + + fn tendermint_commit_sig_to_commit_sig(sig: tendermint::block::CommitSig) -> CommitSig { + match sig { + ::tendermint::block::CommitSig::BlockIdFlagAbsent => CommitSig::Absent, + ::tendermint::block::CommitSig::BlockIdFlagCommit { + validator_address, + timestamp, + signature, + } => CommitSig::Commit { + validator_address: Vec::from(validator_address).try_into().unwrap(), + timestamp: { + let ts = tendermint_proto::google::protobuf::Timestamp::from(timestamp); + + Timestamp { + seconds: ts.seconds.try_into().unwrap(), + nanos: ts.nanos.try_into().unwrap(), + } + }, + signature: signature.unwrap().into_bytes(), + }, + ::tendermint::block::CommitSig::BlockIdFlagNil { + validator_address, + timestamp, + signature, + } => CommitSig::Nil { + validator_address: Vec::from(validator_address).try_into().unwrap(), + timestamp: { + let ts = tendermint_proto::google::protobuf::Timestamp::from(timestamp); + + Timestamp { + seconds: ts.seconds.try_into().unwrap(), + nanos: ts.nanos.try_into().unwrap(), + } + }, + signature: signature.unwrap().into_bytes(), + }, + } + } + + pub fn tendermint_validator_info_to_validator(val: ::tendermint::validator::Info) -> Validator { + Validator { + address: val + .address + .as_bytes() + .try_into() + .expect("value is 20 bytes internally; should not fail; qed"), + pub_key: match val.pub_key { + ::tendermint::PublicKey::Ed25519(key) => PublicKey::Ed25519(key.as_bytes().into()), + ::tendermint::PublicKey::Bn254(key) => PublicKey::Bn254(key.to_vec()), + _ => todo!(), + }, + voting_power: BoundedI64::new(val.power.value().try_into().unwrap()).unwrap(), + proposer_priority: val.proposer_priority.value(), + } + } + + fn tendermint_hash_to_h256(hash: tendermint::Hash) -> H256 { + match hash { + tendermint::Hash::Sha256(hash) => hash.into(), + tendermint::Hash::None => panic!("empty hash???"), + } + } + + pub fn tendermint_height_to_bounded_i64( + height: ::tendermint::block::Height, + ) -> BoundedI64<0, { i64::MAX }> { + i64::from(height).try_into().unwrap() + } +} diff --git a/python-packages.nix b/python-packages.nix new file mode 100644 index 0000000000..1d0ea4cd8c --- /dev/null +++ b/python-packages.nix @@ -0,0 +1,163 @@ +# Generated by pip2nix 0.8.0.dev1 +# See https://github.com/nix-community/pip2nix + +{ pkgs, fetchurl, fetchgit, fetchhg }: + +self: super: { + "boto3" = super.buildPythonPackage rec { + pname = "boto3"; + version = "1.34.138"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/19/5d/4101b48b34f51d876f88079091ae981aed00ed1842b57a9c8880907a4be1/boto3-1.34.138-py3-none-any.whl"; + sha256 = "1jzi6y3mqyrblsn6z1zwadfllmaa9gd98p7v26a2fwddbylqllc1"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ + self."botocore" + self."jmespath" + self."s3transfer" + ]; + }; + "botocore" = super.buildPythonPackage rec { + pname = "botocore"; + version = "1.34.138"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/9d/f5/9481570f4b44244821bf3b59c55c8bc92aebf4cce3b896e0d9268365dd86/botocore-1.34.138-py3-none-any.whl"; + sha256 = "0yipwndrql30gd894304nl0sxdc2wlm5paafmsfg19ir9janmsc4"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ + self."jmespath" + self."python-dateutil" + self."urllib3" + ]; + }; + "click" = super.buildPythonPackage rec { + pname = "click"; + version = "8.1.7"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl"; + sha256 = "0a0c77rq458xjfkrkdxqinlza5447kby9w8msshpf0haqabgnx5f"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ ]; + }; + "jmespath" = super.buildPythonPackage rec { + pname = "jmespath"; + version = "1.0.1"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl"; + sha256 = "10194nk0641vz2kpy442dsgdv44ia43zksrf6f4apg5mf76f9qh2"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ ]; + }; + "nearup" = super.buildPythonPackage rec { + pname = "nearup"; + version = "1.16.0"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/ea/c6/a3248b346b25db4bc182418329f3dfcff908a02550fddfabdd1925282050/nearup-1.16.0-py3-none-any.whl"; + sha256 = "1acyax81sgsr8zz7chfhi1hj6k0vma63yp5pd0bcfviqlmjfaqji"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ + self."boto3" + self."click" + self."psutil" + ]; + }; + "psutil" = super.buildPythonPackage rec { + pname = "psutil"; + version = "6.0.0"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz"; + sha256 = "1whqm3x84m2ajf6llz6v2mmkrxr1icrla1fa4vx6kndn23ry9alg"; + }; + format = "setuptools"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ ]; + }; + "python-dateutil" = super.buildPythonPackage rec { + pname = "python-dateutil"; + version = "2.9.0.post0"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl"; + sha256 = "09q48zvsbagfa3w87zkd2c5xl54wmb9rf2hlr20j4a5fzxxvrcm8"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ + self."six" + ]; + }; + "s3transfer" = super.buildPythonPackage rec { + pname = "s3transfer"; + version = "0.10.2"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/3c/4a/b221409913760d26cf4498b7b1741d510c82d3ad38381984a3ddc135ec66/s3transfer-0.10.2-py3-none-any.whl"; + sha256 = "0sdzy5i1s8q0c6ladzg0qijz6836k634kvqab3pdlf8aww6w58gc"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ + self."botocore" + ]; + }; + "six" = super.buildPythonPackage rec { + pname = "six"; + version = "1.16.0"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl"; + sha256 = "0m02dsi8lvrjf4bi20ab6lm7rr6krz7pg6lzk3xjs2l9hqfjzfwa"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ ]; + }; + "urllib3" = super.buildPythonPackage rec { + pname = "urllib3"; + version = "1.26.19"; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/ae/6a/99eaaeae8becaa17a29aeb334a18e5d582d873b6f084c11f02581b8d7f7f/urllib3-1.26.19-py2.py3-none-any.whl"; + sha256 = "1wrhd00acgypgx729i9mfj9vdikyhdlka38bx3hgr6dib523981p"; + }; + format = "wheel"; + doCheck = false; + buildInputs = [ ]; + checkInputs = [ ]; + nativeBuildInputs = [ ]; + propagatedBuildInputs = [ ]; + }; +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..3d514a1ec4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +nearup diff --git a/site/src/content/docs/docs/integration/wasm-clients.mdx b/site/src/content/docs/docs/integration/wasm-clients.mdx new file mode 100644 index 0000000000..f82310f8c0 --- /dev/null +++ b/site/src/content/docs/docs/integration/wasm-clients.mdx @@ -0,0 +1,55 @@ +--- +title: "Configuring the Union Client" +--- + +The initial step to start communicating with Union through IBC is to have a light client. Light clients are used for verifying the consensus and storage of a counterparty +chain in a low-cost, high-performance manner. Because they require very little computation power, and storage units compared to a full node, these algorithms can be implemented +on-chain. The `CometBLS` light client is used for tracking `Union`'s consensus. Apart from the traditional light clients, it uses ZK verification to make the consensus +verification even more cost-effective. + +We have various `CometBLS` light client implementations meant to be run on different chains: + +## Cosmos SDK-based chains + +Up until the recent release of [the wasm light clients](https://www.ibcprotocol.dev/blog/wasm-client), the traditional way of having a light client on a Cosmos-SDK chain was +to implement a light client with Go and bundle it with the chain which meant modifying the light client code requires a binary upgrade to the chain. After IBC proved its capability +to be extended into other platforms, [the wasm light client module](https://www.ibcprotocol.dev/blog/wasm-client) is introduced for chains to be able to easily, deploy and modify +light clients. For this very reason, our go-to solution is to use the `CometBLS wasm light client`. + +### Integrating `08-wasm` module to your chain + +There is pretty extensive documentation on how to do this on [the official docs](https://ibc.cosmos.network/v8/ibc/light-clients/wasm/integration/). Before diving into this +documentation, note that the `08-wasm` module requires your chain to be on `IBC-go` v7 or v8. And also note that adding this module will also add the dependency to [libwasmvm](https://github.com/CosmWasm/wasmvm) +to your chain which is already there if you are already using [CosmWasm](https://github.com/cosmwasm/cosmwasm). + +### Adding CometBLS light client + +The wasm light client module is permissioned through governance. So storing a contract requires you to submit a `store-code` proposal. You can check [this part](https://ibc.cosmos.network/v8/ibc/light-clients/wasm/governance/) of the +official docs but we also summarized the steps for you to keep it easy. + +1. Download the latest `CometBLS` light client: +```bash +wget https://github.com/unionlabs/union/releases/download/v0.24.0/cometbls-ics08.wasm +``` + +2. Submit a proposal to store the `CometBLS` light client: +```bash +$NODE_BIN tx ibc-wasm store-code cometbls-ics08.wasm --title "Upload CometBLS Wasm client" --summary "Upload CometBLS Wasm client" --deposit 100000$DENOM --from $VALIDATOR --gas auto --gas-adjustment 2 +``` + +3. Deposit to the proposal: +```bash +$NODE_BIN tx gov deposit $PROPOSAL_ID 1000000000$DENOM --from $VALIDATOR --gas auto --gas-adjustment 2 +``` + +4. Make sure enough validators vote `yes`: +```bash +$NODE_BIN tx gov vote "$prop_id" yes --from $VALIDATOR -y --gas auto --gas-adjustment 2 +``` + +After the proposal passes, you will see the stored light client's checksum by using this query: + +```bash +$NODE_BIN query ibc-wasm checksums +``` + diff --git a/uniond/proto/union/ibc/lightclients/near/v1/near.proto b/uniond/proto/union/ibc/lightclients/near/v1/near.proto new file mode 100644 index 0000000000..01601c2544 --- /dev/null +++ b/uniond/proto/union/ibc/lightclients/near/v1/near.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; +package union.ibc.lightclients.near.v1; + +option go_package = "union/ibc/lightclients/near"; +import "ibc/core/client/v1/client.proto"; + +message Header { + LightClientBlockView new_state = 1; + uint64 trusted_height = 2; + repeated MerklePathItem prev_state_root_proof = 3; + bytes prev_state_root= 4; +} + +message MerklePathItem { + bytes hash = 1; + uint64 direction = 2; +} + +message LightClientBlockView { + bytes prev_block_hash = 1; + bytes next_block_inner_hash = 2; + BlockHeaderInnerLiteView inner_lite = 3; + bytes inner_rest_hash = 4; + repeated ValidatorStakeView next_bps = 5; + repeated Signature approvals_after_next = 6; +} + +message Signature { + oneof signature { + bytes ed25519 = 1; + bytes secp256k1 = 2; + } +} + +message ValidatorStakeView { + string account_id = 1; + oneof public_key { + bytes ed25519 = 2; + bytes secp256k1 = 3; + } + // TODO(aeryz): u128 + bytes balance = 4; +} + +message PublicKey { + bytes ed25519 = 1; + bytes secp256k1 = 2; +} + +message BlockHeaderInnerLiteView { + uint64 height = 1; + bytes epoch_id = 2; + bytes next_epoch_id = 3; + bytes prev_state_root = 4; + bytes outcome_root = 5; + uint64 timestamp = 6; + uint64 timestamp_nanosec = 7; + bytes next_bp_hash = 8; + bytes block_merkle_root = 9; +} + +message ClientState { + string chain_id = 1; + uint64 latest_height = 2; + string account_id = 3; + repeated ValidatorStakeView iniitial_block_producers = 4; +} + +message ConsensusState { + BlockHeaderInnerLiteView state = 1; + bytes chunk_prev_state_root = 2; +} diff --git a/voyager/src/main.rs b/voyager/src/main.rs index 27b3722c99..0e4d960f43 100644 --- a/voyager/src/main.rs +++ b/voyager/src/main.rs @@ -26,6 +26,7 @@ use chain_utils::{ cosmos::Cosmos, ethereum::{Ethereum, EthereumConsensusChain}, keyring::ChainKeyring, + near::Near, scroll::Scroll, union::Union, wasm::Wasm, @@ -368,6 +369,10 @@ async fn do_main(args: cli::AppArgs) -> Result<(), VoyagerError> { .query_latest_height() .await .map_err(|e| VoyagerError::Command(Box::new(e)))?, + AnyChain::Near(on) => on + .query_latest_height() + .await + .map_err(|e| VoyagerError::Command(e))?, }; print_json(&height);