From 064b7ef126aa69004bcdf25e400d25ba8219596d Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Tue, 2 Dec 2025 17:33:15 +0800 Subject: [PATCH 01/11] hydration indexer: compiled --- Cargo.lock | 1919 ++++++++++++++++- chain-signatures/crypto/src/kdf.rs | 8 + chain-signatures/node/Cargo.toml | 10 +- chain-signatures/node/src/backlog/mod.rs | 90 +- chain-signatures/node/src/cli.rs | 12 +- chain-signatures/node/src/indexer_common.rs | 412 ++++ chain-signatures/node/src/indexer_eth/mod.rs | 33 +- .../artifacts/hydration_metadata.scale | Bin 0 -> 487324 bytes .../node/src/indexer_hydration/mod.rs | 621 ++++++ chain-signatures/node/src/indexer_sol.rs | 310 +-- chain-signatures/node/src/lib.rs | 2 + chain-signatures/node/src/protocol/mod.rs | 3 +- .../node/src/protocol/signature.rs | 2 +- chain-signatures/node/src/rpc.rs | 239 +- .../node/src/sign_bidirectional.rs | 28 +- chain-signatures/primitives/src/lib.rs | 14 +- integration-tests/src/containers.rs | 3 + integration-tests/src/lib.rs | 3 + integration-tests/src/local.rs | 6 + 19 files changed, 3306 insertions(+), 409 deletions(-) create mode 100644 chain-signatures/node/src/indexer_common.rs create mode 100644 chain-signatures/node/src/indexer_hydration/artifacts/hydration_metadata.scale create mode 100644 chain-signatures/node/src/indexer_hydration/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 68742b764..aceafc1d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,7 +511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" dependencies = [ "alloy-rlp-derive", - "arrayvec", + "arrayvec 0.7.6", "bytes", ] @@ -796,7 +796,7 @@ checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", + "arrayvec 0.7.6", "derive_more 2.0.1", "nybbles", "serde", @@ -1107,6 +1107,29 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1172,7 +1195,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "itertools 0.13.0", "num-bigint 0.4.6", "num-integer", @@ -1180,6 +1203,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1786b2e3832f6f0f7c8d62d5d5a282f6952a1ab99981c54cd52b6ac1d8f02df5" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -1228,7 +1263,7 @@ dependencies = [ "ark-ff-macros 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", - "arrayvec", + "arrayvec 0.7.6", "digest 0.10.7", "educe", "itertools 0.13.0", @@ -1331,7 +1366,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] @@ -1393,7 +1428,7 @@ checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ "ark-serialize-derive 0.5.0", "ark-std 0.5.0", - "arrayvec", + "arrayvec 0.7.6", "digest 0.10.7", "num-bigint 0.4.6", ] @@ -1450,12 +1485,60 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "ark-transcript" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c1c928edb9d8ff24cb5dcb7651d3a98494fff3099eee95c2404cd813a9139f" +dependencies = [ + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3", +] + +[[package]] +name = "ark-vrf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9501da18569b2afe0eb934fb7afd5a247d238b94116155af4dd068f319adfe6d" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_chacha 0.3.1", + "sha2 0.10.9", + "w3f-ring-proof", + "zeroize", +] + +[[package]] +name = "array-bytes" +version = "6.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" + [[package]] name = "arrayref" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -1489,7 +1572,7 @@ dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", - "nom", + "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror 1.0.69", @@ -1795,6 +1878,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1968,6 +2057,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base64" version = "0.12.3" @@ -2031,6 +2126,17 @@ dependencies = [ "zip", ] +[[package]] +name = "binary-merkle-tree" +version = "16.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95c9f6900c9fd344d53fbdfb36e1343429079d73f4168c8ef48884bf15616dbd" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", +] + [[package]] name = "bincode" version = "1.3.3" @@ -2040,6 +2146,17 @@ dependencies = [ "serde", ] +[[package]] +name = "bip39" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" +dependencies = [ + "bitcoin_hashes 0.13.0", + "serde", + "unicode-normalization", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -2070,12 +2187,28 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin-io" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative 0.1.2", +] + [[package]] name = "bitcoin_hashes" version = "0.14.0" @@ -2083,7 +2216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.1", ] [[package]] @@ -2123,6 +2256,27 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq 0.1.5", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "constant_time_eq 0.3.1", +] + [[package]] name = "blake3" version = "1.8.2" @@ -2130,7 +2284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.6", "cc", "cfg-if 1.0.0", "constant_time_eq 0.3.1", @@ -2312,6 +2466,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bounded-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee8eddd066a8825ec5570528e6880471210fd5d88cb6abbe1cfdd51ca249c33" +dependencies = [ + "jam-codec", + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + [[package]] name = "brotli" version = "8.0.0" @@ -2767,7 +2934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -2797,6 +2964,12 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "common-path" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2902,6 +3075,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -3034,6 +3216,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -3079,6 +3270,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "ctr" version = "0.9.2" @@ -3130,6 +3336,7 @@ dependencies = [ "fiat-crypto", "rustc_version 0.4.1", "subtle", + "zeroize", ] [[package]] @@ -3359,6 +3566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -3370,7 +3578,7 @@ checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ "asn1-rs", "displaydoc", - "nom", + "nom 7.1.3", "num-bigint 0.4.6", "num-traits", "rusticata-macros", @@ -3403,6 +3611,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "derive-where" version = "1.5.0" @@ -3473,6 +3692,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ + "convert_case 0.7.1", "proc-macro2", "quote", "syn 2.0.101", @@ -3551,7 +3771,7 @@ dependencies = [ "aes", "aes-gcm", "alloy-rlp", - "arrayvec", + "arrayvec 0.7.6", "ctr", "delay_map", "enr 0.12.1", @@ -3569,7 +3789,7 @@ dependencies = [ "socket2 0.4.10", "tokio", "tracing", - "uint", + "uint 0.9.5", "zeroize", ] @@ -3607,6 +3827,33 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "docify" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a772b62b1837c8f060432ddcc10b17aae1453ef17617a99bc07789252d2a5896" +dependencies = [ + "docify_macros", +] + +[[package]] +name = "docify_macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e6be249b0a462a14784a99b19bf35a667bb5e09de611738bb7362fa4c95ff7" +dependencies = [ + "common-path", + "derive-syn-parse", + "once_cell", + "proc-macro2", + "quote", + "regex", + "syn 2.0.101", + "termcolor", + "toml 0.8.22", + "walkdir", +] + [[package]] name = "docker_credential" version = "1.3.1" @@ -3618,6 +3865,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dtoa" version = "1.0.10" @@ -3723,6 +3976,22 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "ed25519-zebra" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9" +dependencies = [ + "curve25519-dalek 4.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ed25519 2.2.3", + "hashbrown 0.15.5", + "pkcs8", + "rand_core 0.6.4", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -3944,6 +4213,12 @@ dependencies = [ "log", ] +[[package]] +name = "environmental" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" + [[package]] name = "equivalent" version = "1.0.2" @@ -3957,7 +4232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3984,7 +4259,7 @@ dependencies = [ "hmac 0.12.1", "pbkdf2 0.11.0", "rand 0.8.5", - "scrypt", + "scrypt 0.10.0", "serde", "serde_json", "sha2 0.10.9", @@ -4007,7 +4282,7 @@ dependencies = [ "serde_json", "sha3", "thiserror 1.0.69", - "uint", + "uint 0.9.5", ] [[package]] @@ -4018,9 +4293,9 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash 0.8.0", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", "tiny-keccak", ] @@ -4033,12 +4308,12 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash 0.8.0", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "primitive-types 0.12.2", "scale-info", - "uint", + "uint 0.9.5", ] [[package]] @@ -4185,7 +4460,7 @@ version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bytes", "cargo_metadata", "chrono", @@ -4378,6 +4653,21 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "expander" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2" +dependencies = [ + "blake2", + "file-guard", + "fs-err", + "prettyplease 0.2.34", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "eyre" version = "0.6.12" @@ -4409,7 +4699,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "auto_impl", "bytes", ] @@ -4420,7 +4710,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "auto_impl", "bytes", ] @@ -4462,6 +4752,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "file-guard" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "filetime" version = "0.2.25" @@ -4577,6 +4877,43 @@ dependencies = [ "percent-encoding 2.3.1", ] +[[package]] +name = "frame-decode" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c470df86cf28818dd3cd2fc4667b80dbefe2236c722c3dc1d09e7c6c82d6dfcd" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-decode", + "scale-encode", + "scale-info", + "scale-type-resolver", + "sp-crypto-hashing", + "thiserror 2.0.12", +] + +[[package]] +name = "frame-metadata" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c26fcb0454397c522c05fdad5380c4e622f8a875638af33bff5a320d1fc965" +dependencies = [ + "cfg-if 1.0.0", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + [[package]] name = "fs2" version = "0.4.3" @@ -4820,12 +5157,22 @@ dependencies = [ ] [[package]] -name = "ghash" -version = "0.5.1" +name = "getrandom_or_panic" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" dependencies = [ - "opaque-debug", + "rand 0.8.5", + "rand_core 0.6.4", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", "polyval", ] @@ -5061,6 +5408,21 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -5097,13 +5459,14 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.1.5", + "serde", ] [[package]] @@ -5222,7 +5585,7 @@ dependencies = [ "helios-common", "helios-verifiable-api-client", "hex", - "jsonrpsee", + "jsonrpsee 0.19.0", "openssl", "parking_lot 0.12.3", "reqwest 0.12.15", @@ -5402,13 +5765,19 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hex-conservative" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", ] [[package]] @@ -6038,6 +6407,26 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d15461ab0dcc56706adf266158acbc44ccf719bf7d0af30705f58b90a4b8c" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint 0.10.0", +] + [[package]] name = "impl-rlp" version = "0.3.0" @@ -6056,6 +6445,15 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.3" @@ -6091,7 +6489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "serde", ] @@ -6135,6 +6533,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "integration-tests" version = "0.1.0" @@ -6234,7 +6641,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6303,6 +6710,34 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jam-codec" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb948eace373d99de60501a02fb17125d30ac632570de20dccc74370cdd611b9" +dependencies = [ + "arrayvec 0.7.6", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "jam-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "jam-codec-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319af585c4c8a6b5552a52b7787a1ab3e4d59df7614190b1f85b9b842488789d" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "jni" version = "0.21.1" @@ -6395,17 +6830,29 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5f3783308bddc49d0218307f66a09330c106fbd792c58bac5c8dc294fdd0f98" dependencies = [ - "jsonrpsee-client-transport", - "jsonrpsee-core", + "jsonrpsee-client-transport 0.19.0", + "jsonrpsee-core 0.19.0", "jsonrpsee-http-client", "jsonrpsee-proc-macros", "jsonrpsee-server", - "jsonrpsee-types", + "jsonrpsee-types 0.19.0", "jsonrpsee-wasm-client", - "jsonrpsee-ws-client", + "jsonrpsee-ws-client 0.19.0", "tracing", ] +[[package]] +name = "jsonrpsee" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e281ae70cc3b98dac15fced3366a880949e65fc66e345ce857a5682d152f3e62" +dependencies = [ + "jsonrpsee-client-transport 0.24.10", + "jsonrpsee-core 0.24.10", + "jsonrpsee-types 0.24.10", + "jsonrpsee-ws-client 0.24.10", +] + [[package]] name = "jsonrpsee-client-transport" version = "0.19.0" @@ -6416,10 +6863,10 @@ dependencies = [ "futures-util", "gloo-net", "http 0.2.12", - "jsonrpsee-core", + "jsonrpsee-core 0.19.0", "pin-project", "rustls-native-certs 0.6.3", - "soketto", + "soketto 0.7.1", "thiserror 1.0.69", "tokio", "tokio-rustls 0.24.1", @@ -6428,6 +6875,29 @@ dependencies = [ "webpki-roots 0.24.0", ] +[[package]] +name = "jsonrpsee-client-transport" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4280b709ac3bb5e16cf3bad5056a0ec8df55fa89edfe996361219aadc2c7ea" +dependencies = [ + "base64 0.22.1", + "futures-util", + "http 1.3.1", + "jsonrpsee-core 0.24.10", + "pin-project", + "rustls 0.23.26", + "rustls-pki-types", + "rustls-platform-verifier", + "soketto 0.8.1", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.26.2", + "tokio-util", + "tracing", + "url 2.5.4", +] + [[package]] name = "jsonrpsee-core" version = "0.19.0" @@ -6442,13 +6912,13 @@ dependencies = [ "futures-util", "globset", "hyper 0.14.32", - "jsonrpsee-types", + "jsonrpsee-types 0.19.0", "parking_lot 0.12.3", "rand 0.8.5", "rustc-hash 1.1.0", "serde", "serde_json", - "soketto", + "soketto 0.7.1", "thiserror 1.0.69", "tokio", "tokio-stream", @@ -6456,6 +6926,26 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "jsonrpsee-core" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348ee569eaed52926b5e740aae20863762b16596476e943c9e415a6479021622" +dependencies = [ + "async-trait", + "futures-timer", + "futures-util", + "jsonrpsee-types 0.24.10", + "pin-project", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "jsonrpsee-http-client" version = "0.19.0" @@ -6465,8 +6955,8 @@ dependencies = [ "async-trait", "hyper 0.14.32", "hyper-rustls 0.24.2", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.19.0", + "jsonrpsee-types 0.19.0", "serde", "serde_json", "thiserror 1.0.69", @@ -6496,11 +6986,11 @@ checksum = "6e79d78cfd5abd8394da10753723093c3ff64391602941c9c4b1d80a3414fd53" dependencies = [ "futures-util", "hyper 0.14.32", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.19.0", + "jsonrpsee-types 0.19.0", "serde", "serde_json", - "soketto", + "soketto 0.7.1", "tokio", "tokio-stream", "tokio-util", @@ -6522,15 +7012,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "jsonrpsee-types" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f05e0028e55b15dbd2107163b3c744cd3bb4474f193f95d9708acbf5677e44" +dependencies = [ + "http 1.3.1", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "jsonrpsee-wasm-client" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fe953c2801356f214d3f4051f786b3d11134512a46763ee8c39a9e3fa2cc1c0" dependencies = [ - "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-client-transport 0.19.0", + "jsonrpsee-core 0.19.0", + "jsonrpsee-types 0.19.0", ] [[package]] @@ -6540,9 +7042,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c71b2597ec1c958c6d5bc94bb61b44d74eb28e69dc421731ab0035706f13882" dependencies = [ "http 0.2.12", - "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-client-transport 0.19.0", + "jsonrpsee-core 0.19.0", + "jsonrpsee-types 0.19.0", +] + +[[package]] +name = "jsonrpsee-ws-client" +version = "0.24.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78fc744f17e7926d57f478cf9ca6e1ee5d8332bf0514860b1a3cdf1742e614cc" +dependencies = [ + "http 1.3.1", + "jsonrpsee-client-transport 0.24.10", + "jsonrpsee-core 0.24.10", + "jsonrpsee-types 0.24.10", + "url 2.5.4", ] [[package]] @@ -6593,6 +7108,16 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "keccak-hash" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1b8590eb6148af2ea2d75f38e7d29f5ca970d5a4df456b3ef19b8b415d0264" +dependencies = [ + "primitive-types 0.13.1", + "tiny-keccak", +] + [[package]] name = "lalrpop" version = "0.20.2" @@ -7143,7 +7668,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] @@ -7152,7 +7677,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] @@ -7262,6 +7787,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memory-db" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e300c54e3239a86f9c61cc63ab0f03862eb40b1c6e065dc6fd6ceaeff6da93d" +dependencies = [ + "foldhash 0.1.5", + "hash-db", + "hashbrown 0.15.5", +] + [[package]] name = "memory_units" version = "0.4.0" @@ -7420,7 +7956,9 @@ dependencies = [ "ciborium", "clap", "deadpool-redis", + "ed25519-zebra", "ethabi", + "futures", "futures-util", "google-datastore1", "google-secretmanager1", @@ -7450,7 +7988,8 @@ dependencies = [ "opentelemetry-appender-tracing", "opentelemetry-otlp", "opentelemetry_sdk", - "prometheus", + "parity-scale-codec", + "prometheus 0.14.0", "rand 0.8.5", "redis", "reqwest 0.11.27", @@ -7465,6 +8004,12 @@ dependencies = [ "solana-client", "solana-sdk", "solana-transaction-status", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-trie", + "subxt", + "subxt-signer", "sysinfo", "test-log", "thiserror 1.0.69", @@ -7512,6 +8057,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "multi-stash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" + [[package]] name = "multiaddr" version = "0.17.1" @@ -8625,6 +9176,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -8641,6 +9198,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + [[package]] name = "nonzero_ext" version = "0.3.0" @@ -8761,6 +9327,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec 0.7.6", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -8852,7 +9428,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", "syn 2.0.101", @@ -8993,7 +9569,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "auto_impl", "bytes", "ethereum-types", @@ -9194,15 +9770,29 @@ dependencies = [ "group", ] +[[package]] +name = "parity-bip39" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "unicode-normalization", +] + [[package]] name = "parity-scale-codec" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bitvec", "byte-slice-cast", + "bytes", "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", @@ -9212,9 +9802,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.7.4" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", @@ -9312,6 +9902,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -9332,7 +9933,7 @@ checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", "hmac 0.12.1", - "password-hash", + "password-hash 0.4.2", "sha2 0.10.9", ] @@ -9344,6 +9945,7 @@ checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", "hmac 0.12.1", + "password-hash 0.5.0", ] [[package]] @@ -9378,6 +9980,15 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -9560,6 +10171,43 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polkavm-common" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a5794b695626ba70d29e66e3f4f4835767452a6723f3a0bc20884b07088fe8" + +[[package]] +name = "polkavm-derive" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95282a203ae1f6828a04ff334145c3f6dc718bba6d3959805d273358b45eab93" +dependencies = [ + "polkavm-derive-impl-macro", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6069dc7995cde6e612b868a02ce48b54397c6d2582bd1b97b63aabbe962cd779" +dependencies = [ + "polkavm-common", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581d34cafec741dc5ffafbb341933c205b6457f3d76257a9d99fb56687219c91" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.101", +] + [[package]] name = "polling" version = "2.8.0" @@ -9677,7 +10325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash 0.7.0", - "uint", + "uint 0.9.5", ] [[package]] @@ -9687,11 +10335,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash 0.8.0", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", + "scale-info", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec 0.7.1", + "impl-num-traits", + "impl-serde 0.5.0", "scale-info", - "uint", + "uint 0.10.0", ] [[package]] @@ -9770,9 +10432,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -9792,23 +10454,37 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.14.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", "parking_lot 0.12.3", - "protobuf", - "thiserror 2.0.12", + "thiserror 1.0.69", ] [[package]] -name = "prometheus-client" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static", + "memchr", + "parking_lot 0.12.3", + "protobuf", + "thiserror 2.0.12", +] + +[[package]] +name = "prometheus-client" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" dependencies = [ "dtoa", @@ -10013,7 +10689,7 @@ dependencies = [ "once_cell", "socket2 0.5.9", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10253,6 +10929,26 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "regex" version = "1.11.1" @@ -10608,7 +11304,7 @@ version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b57d4bd9e6b5fe469da5452a8a137bc2d030a3cd47c46908efc615bbc699da" dependencies = [ - "ark-bls12-381", + "ark-bls12-381 0.5.0", "ark-bn254 0.5.0", "ark-ec 0.5.0", "ark-ff 0.5.0", @@ -10858,7 +11554,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -10885,7 +11581,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10898,7 +11594,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11036,7 +11732,7 @@ dependencies = [ "security-framework 3.2.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11095,6 +11791,12 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" + [[package]] name = "rw-stream-sink" version = "0.3.0" @@ -11130,16 +11832,85 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scale-bits" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27243ab0d2d6235072b017839c5f0cd1a3b1ce45c0f7a715363b0c7d36c76c94" +dependencies = [ + "parity-scale-codec", + "scale-info", + "scale-type-resolver", + "serde", +] + +[[package]] +name = "scale-decode" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d6ed61699ad4d54101ab5a817169259b5b0efc08152f8632e61482d8a27ca3d" +dependencies = [ + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits", + "scale-decode-derive", + "scale-type-resolver", + "smallvec", + "thiserror 2.0.12", +] + +[[package]] +name = "scale-decode-derive" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cb245f7fdb489e7ba43a616cbd34427fe3ba6fe0edc1d0d250085e6c84f3ec" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "scale-encode" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64901733157f9d25ef86843bd783eda439fac7efb0ad5a615d12d2cf3a29464b" +dependencies = [ + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits", + "scale-encode-derive", + "scale-type-resolver", + "smallvec", + "thiserror 2.0.12", +] + +[[package]] +name = "scale-encode-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a3993a13b4eafa89350604672c8757b7ea84c7c5947d4b3691e3169c96379b" +dependencies = [ + "darling 0.20.11", + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "scale-info" version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ + "bitvec", "cfg-if 1.0.0", "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", + "serde", ] [[package]] @@ -11154,6 +11925,48 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "scale-type-resolver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" +dependencies = [ + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-typegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c61b6b706a3eaad63b506ab50a1d2319f817ae01cf753adcc3f055f9f0fcd6" +dependencies = [ + "proc-macro2", + "quote", + "scale-info", + "syn 2.0.101", + "thiserror 2.0.12", +] + +[[package]] +name = "scale-value" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884aab179aba344c67ddcd1d7dd8e3f8fee202f2e570d97ec34ec8688442a5b3" +dependencies = [ + "base58", + "blake2", + "either", + "parity-scale-codec", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-type-resolver", + "serde", + "thiserror 2.0.12", + "yap", +] + [[package]] name = "schannel" version = "0.1.27" @@ -11187,6 +12000,36 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "schnellru" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" +dependencies = [ + "ahash", + "cfg-if 1.0.0", + "hashbrown 0.13.2", +] + +[[package]] +name = "schnorrkel" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9fcb6c2e176e86ec703e22560d99d65a5ee9056ae45a08e13e84ebf796296f" +dependencies = [ + "aead", + "arrayref", + "arrayvec 0.7.6", + "curve25519-dalek 4.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom_or_panic", + "merlin", + "rand_core 0.6.4", + "serde_bytes", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -11205,6 +12048,18 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash 0.5.0", + "pbkdf2 0.12.2", + "salsa20", + "sha2 0.10.9", +] + [[package]] name = "sct" version = "0.7.1" @@ -11261,7 +12116,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.0", "rand 0.8.5", "secp256k1-sys 0.10.1", "serde", @@ -11273,7 +12128,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.0", "rand 0.9.1", "secp256k1-sys 0.11.0", ] @@ -11314,6 +12169,24 @@ dependencies = [ "cc", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -11699,6 +12572,12 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "simple-mermaid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" + [[package]] name = "simple_asn1" version = "0.6.3" @@ -11780,6 +12659,96 @@ dependencies = [ "futures-lite 2.6.0", ] +[[package]] +name = "smoldot" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16e5723359f0048bf64bfdfba64e5732a56847d42c4fd3fe56f18280c813413" +dependencies = [ + "arrayvec 0.7.6", + "async-lock 3.4.0", + "atomic-take", + "base64 0.22.1", + "bip39", + "blake2-rfc", + "bs58 0.5.1", + "chacha20", + "crossbeam-queue", + "derive_more 2.0.1", + "ed25519-zebra", + "either", + "event-listener 5.4.0", + "fnv", + "futures-lite 2.6.0", + "futures-util", + "hashbrown 0.15.5", + "hex", + "hmac 0.12.1", + "itertools 0.14.0", + "libm", + "libsecp256k1 0.7.2", + "merlin", + "nom 8.0.0", + "num-bigint 0.4.6", + "num-rational 0.4.2", + "num-traits", + "pbkdf2 0.12.2", + "pin-project", + "poly1305", + "rand 0.8.5", + "rand_chacha 0.3.1", + "ruzstd", + "schnorrkel", + "serde", + "serde_json", + "sha2 0.10.9", + "sha3", + "siphasher 1.0.1", + "slab", + "smallvec", + "soketto 0.8.1", + "twox-hash 2.1.2", + "wasmi", + "x25519-dalek 2.0.1", + "zeroize", +] + +[[package]] +name = "smoldot-light" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bba9e591716567d704a8252feeb2f1261a286e1e2cbdd4e49e9197c34a14e2" +dependencies = [ + "async-channel 2.3.1", + "async-lock 3.4.0", + "base64 0.22.1", + "blake2-rfc", + "bs58 0.5.1", + "derive_more 2.0.1", + "either", + "event-listener 5.4.0", + "fnv", + "futures-channel", + "futures-lite 2.6.0", + "futures-util", + "hashbrown 0.15.5", + "hex", + "itertools 0.14.0", + "log", + "lru 0.12.5", + "parking_lot 0.12.3", + "pin-project", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "serde_json", + "siphasher 1.0.1", + "slab", + "smol", + "smoldot", + "zeroize", +] + [[package]] name = "snap" version = "1.1.1" @@ -11839,6 +12808,21 @@ dependencies = [ "sha-1", ] +[[package]] +name = "soketto" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures", + "httparse", + "log", + "rand 0.8.5", + "sha1", +] + [[package]] name = "solana-account" version = "2.2.1" @@ -14059,7 +15043,337 @@ dependencies = [ ] [[package]] -name = "spin" +name = "sp-application-crypto" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6067f30cf3fb9270471cf24a65d73b33330f32573abab2d97196f83fc076de0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", +] + +[[package]] +name = "sp-arithmetic" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f4755af7cc57f4a2a830e134b403fc832caa5d93dacb970ffc7ac717f38c40" +dependencies = [ + "docify", + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "static_assertions", +] + +[[package]] +name = "sp-core" +version = "38.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707602208776d0e19d4269bb3f68c5306cacbdfabbb2e4d8d499af7b907bb0a3" +dependencies = [ + "ark-vrf", + "array-bytes", + "bitflags 1.3.2", + "blake2", + "bounded-collections", + "bs58 0.5.1", + "dyn-clone", + "ed25519-zebra", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.5.0", + "itertools 0.11.0", + "k256", + "libsecp256k1 0.7.2", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot 0.12.3", + "paste", + "primitive-types 0.13.1", + "rand 0.8.5", + "scale-info", + "schnorrkel", + "secp256k1 0.28.2", + "secrecy 0.8.0", + "serde", + "sha2 0.10.9", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror 1.0.69", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.9", + "sha3", + "twox-hash 1.6.3", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "sp-externalities" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cbf059dce180a8bf8b6c8b08b6290fa3d1c7f069a60f1df038ab5dd5fc0ba6" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage", +] + +[[package]] +name = "sp-io" +version = "43.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2059e3b338c0174e8dc9e144cc7e612165ca4c960c3a23c6c99c29ef34768f" +dependencies = [ + "bytes", + "docify", + "ed25519-dalek 2.1.1", + "libsecp256k1 0.7.2", + "log", + "parity-scale-codec", + "polkavm-derive", + "rustversion", + "secp256k1 0.28.2", + "sp-core", + "sp-crypto-hashing", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-tracing", + "sp-trie", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keystore" +version = "0.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5c0b829014afc22e992be2c198f2677592db43267fc218e9f3207dbbfb6fbb" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.3", + "sp-core", + "sp-externalities", +] + +[[package]] +name = "sp-panic-handler" +version = "13.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8b52e69a577cbfdea62bfaf16f59eb884422ce98f78b5cd8d9bf668776bced1" +dependencies = [ + "backtrace", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "44.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee57bb77e94c26306501426ac82aca401bb80ee2279ecdba148f68e76cf58247" +dependencies = [ + "binary-merkle-tree", + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "num-traits", + "parity-scale-codec", + "paste", + "rand 0.8.5", + "scale-info", + "serde", + "simple-mermaid", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", + "sp-trie", + "sp-weights", + "tracing", + "tuplex", +] + +[[package]] +name = "sp-runtime-interface" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdc2bc2adbfb9b4396ae07c7d94db20414d2351608e29e1f44e4f643b387c70" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04178084ae654b3924934a56943ee73e3562db4d277e948393561b08c3b5b5fe" +dependencies = [ + "Inflector", + "expander", + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "sp-state-machine" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "042677239cca40eb6a0d70e0b220f5693516f59853c2d678de471a79652cd16e" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-trie", + "thiserror 1.0.69", + "tracing", + "trie-db", +] + +[[package]] +name = "sp-std" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" + +[[package]] +name = "sp-storage" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3b70ca340e41cde9d2e069d354508a6e37a6573d66f7cc38f11549002f64ec" +dependencies = [ + "impl-serde 0.5.0", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", +] + +[[package]] +name = "sp-tracing" +version = "19.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c7372456c39cc81e15befe54d0caab8378f2b30fd34d1bcb5f0f56631c6b6e" +dependencies = [ + "parity-scale-codec", + "regex", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.20", +] + +[[package]] +name = "sp-trie" +version = "41.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2a05942903900c23aaa5fded094fa8186523e646ae8874bff3fce74985d0e5" +dependencies = [ + "ahash", + "foldhash 0.1.5", + "hash-db", + "hashbrown 0.15.5", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "scale-info", + "schnellru", + "sp-core", + "sp-externalities", + "substrate-prometheus-endpoint", + "thiserror 1.0.69", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-wasm-interface" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd177d0658f3df0492f28bd39d665133a7868db5aa66c8642c949b6265430719" +dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", +] + +[[package]] +name = "sp-weights" +version = "33.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb3f1b1373a0926b44ddabfa55a608ea78c20ee356f35575c031db2f0202545" +dependencies = [ + "bounded-collections", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-debug-derive", +] + +[[package]] +name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" @@ -14447,6 +15761,21 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ss58-registry" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + [[package]] name = "ssz_types" version = "0.11.0" @@ -14588,12 +15917,236 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "substrate-bip39" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca58ffd742f693dc13d69bdbb2e642ae239e0053f6aab3b104252892f856700a" +dependencies = [ + "hmac 0.12.1", + "pbkdf2 0.12.2", + "schnorrkel", + "sha2 0.10.9", + "zeroize", +] + +[[package]] +name = "substrate-prometheus-endpoint" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23e4bc8e910a312820d589047ab683928b761242dbe31dee081fbdb37cbe0be" +dependencies = [ + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "log", + "prometheus 0.13.4", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "subxt" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddbf938ac1d86a361a84709a71cdbae5d87f370770b563651d1ec052eed9d0b4" +dependencies = [ + "async-trait", + "derive-where", + "either", + "frame-metadata", + "futures", + "hex", + "jsonrpsee 0.24.10", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-crypto-hashing", + "subxt-core", + "subxt-lightclient", + "subxt-macro", + "subxt-metadata", + "subxt-rpcs", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "tracing", + "url 2.5.4", + "wasm-bindgen-futures", + "web-time", +] + +[[package]] +name = "subxt-codegen" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c250ad8cd102d40ae47977b03295a2ff791375f30ddc7474d399fb56efb793b" +dependencies = [ + "heck 0.5.0", + "parity-scale-codec", + "proc-macro2", + "quote", + "scale-info", + "scale-typegen", + "subxt-metadata", + "syn 2.0.101", + "thiserror 2.0.12", +] + +[[package]] +name = "subxt-core" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5705c5b420294524e41349bf23c6b11aa474ce731de7317f4153390e1927f702" +dependencies = [ + "base58", + "blake2", + "derive-where", + "frame-decode", + "frame-metadata", + "hashbrown 0.14.5", + "hex", + "impl-serde 0.5.0", + "keccak-hash", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-bits", + "scale-decode", + "scale-encode", + "scale-info", + "scale-value", + "serde", + "serde_json", + "sp-crypto-hashing", + "subxt-metadata", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "subxt-lightclient" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e02732a6c9ae46bc282c1a741b3d3e494021b3e87e7e92cfb3620116d92911" +dependencies = [ + "futures", + "futures-util", + "serde", + "serde_json", + "smoldot-light", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "subxt-macro" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501bf358698f5ab02a6199a1fcd3f1b482e2f5b6eb5d185411e6a74a175ec8e8" +dependencies = [ + "darling 0.20.11", + "parity-scale-codec", + "proc-macro-error2", + "quote", + "scale-typegen", + "subxt-codegen", + "subxt-metadata", + "subxt-utils-fetchmetadata", + "syn 2.0.101", +] + +[[package]] +name = "subxt-metadata" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fb7c0bfafad78dda7084c6a2444444744af3bbf7b2502399198b9b4c20eddf" +dependencies = [ + "frame-decode", + "frame-metadata", + "hashbrown 0.14.5", + "parity-scale-codec", + "scale-info", + "sp-crypto-hashing", + "thiserror 2.0.12", +] + +[[package]] +name = "subxt-rpcs" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab68a9c20ecedb0cb7d62d64f884e6add91bb70485783bf40aa8eac5c389c6e0" +dependencies = [ + "derive-where", + "frame-metadata", + "futures", + "hex", + "impl-serde 0.5.0", + "jsonrpsee 0.24.10", + "parity-scale-codec", + "primitive-types 0.13.1", + "serde", + "serde_json", + "subxt-core", + "subxt-lightclient", + "thiserror 2.0.12", + "tokio-util", + "tracing", + "url 2.5.4", +] + +[[package]] +name = "subxt-signer" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fb6463f7f46817043de9f20ba11f485ee474378fcdbe4150aa849274523bd1c" +dependencies = [ + "base64 0.22.1", + "bip39", + "cfg-if 1.0.0", + "crypto_secretbox", + "hex", + "hmac 0.12.1", + "parity-scale-codec", + "pbkdf2 0.12.2", + "regex", + "schnorrkel", + "scrypt 0.11.0", + "secp256k1 0.30.0", + "secrecy 0.10.3", + "serde", + "serde_json", + "sha2 0.10.9", + "sp-crypto-hashing", + "subxt-core", + "thiserror 2.0.12", + "zeroize", +] + +[[package]] +name = "subxt-utils-fetchmetadata" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f6812a653c5a3e63a079aa3b60a3f4c362722753c3222286eaa1800f9002" +dependencies = [ + "hex", + "parity-scale-codec", + "thiserror 2.0.12", +] + [[package]] name = "superstruct" version = "0.7.0" @@ -14792,7 +16345,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix 1.0.5", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -15375,6 +16928,7 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", + "time", "tracing", "tracing-core", "tracing-log", @@ -15406,6 +16960,27 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "trie-db" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c0670ab45a6b7002c7df369fee950a27cf29ae0474343fd3a15aa15f691e7a6" +dependencies = [ + "hash-db", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" +dependencies = [ + "hash-db", +] + [[package]] name = "trust-dns-proto" version = "0.22.0" @@ -15479,6 +17054,30 @@ dependencies = [ "webpki-roots 0.24.0", ] +[[package]] +name = "tuplex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "digest 0.10.7", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + [[package]] name = "typenum" version = "1.18.0" @@ -15503,6 +17102,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -15538,9 +17149,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -15728,6 +17339,74 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "w3f-bls" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6bfb937b3d12077654a9e43e32a4e9c20177dd9fea0f3aba673e7840bb54f32" +dependencies = [ + "ark-bls12-377", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-serialize-derive 0.4.2", + "arrayref", + "digest 0.10.7", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "sha2 0.10.9", + "sha3", + "zeroize", +] + +[[package]] +name = "w3f-pcs" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe7a8d5c914b69392ab3b267f679a2e546fe29afaddce47981772ac71bd02e1" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "merlin", +] + +[[package]] +name = "w3f-plonk-common" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aca389e494fe08c5c108b512e2328309036ee1c0bc7bdfdb743fef54d448c8c" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "getrandom_or_panic", + "rand_core 0.6.4", + "w3f-pcs", +] + +[[package]] +name = "w3f-ring-proof" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a639379402ad51504575dbd258740383291ac8147d3b15859bdf1ea48c677de" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "ark-transcript", + "w3f-pcs", + "w3f-plonk-common", +] + [[package]] name = "wait-timeout" version = "0.2.1" @@ -15869,6 +17548,56 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmi" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19af97fcb96045dd1d6b4d23e2b4abdbbe81723dbc5c9f016eb52145b320063" +dependencies = [ + "arrayvec 0.7.6", + "multi-stash", + "smallvec", + "spin 0.9.8", + "wasmi_collections", + "wasmi_core", + "wasmi_ir", + "wasmparser", +] + +[[package]] +name = "wasmi_collections" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e80d6b275b1c922021939d561574bf376613493ae2b61c6963b15db0e8813562" + +[[package]] +name = "wasmi_core" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8c51482cc32d31c2c7ff211cd2bedd73c5bd057ba16a2ed0110e7a96097c33" +dependencies = [ + "downcast-rs", + "libm", +] + +[[package]] +name = "wasmi_ir" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e431a14c186db59212a88516788bd68ed51f87aa1e08d1df742522867b5289a" +dependencies = [ + "wasmi_core", +] + +[[package]] +name = "wasmparser" +version = "0.221.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "wasmtimer" version = "0.2.1" @@ -16000,7 +17729,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -16481,6 +18210,8 @@ source = "git+https://github.com/dalek-cryptography/curve25519-dalek?rev=5b7082b dependencies = [ "curve25519-dalek 4.1.3 (git+https://github.com/dalek-cryptography/curve25519-dalek?rev=5b7082bbc8e0b2106ab0d956064f61fa0f393cdc)", "rand_core 0.6.4", + "serde", + "zeroize", ] [[package]] @@ -16494,7 +18225,7 @@ dependencies = [ "data-encoding", "der-parser", "lazy_static", - "nom", + "nom 7.1.3", "oid-registry", "rusticata-macros", "thiserror 1.0.69", @@ -16523,6 +18254,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yap" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe269e7b803a5e8e20cbd97860e136529cd83bf2c9c6d37b142467e7e1f051f" + [[package]] name = "yasna" version = "0.5.2" diff --git a/chain-signatures/crypto/src/kdf.rs b/chain-signatures/crypto/src/kdf.rs index 2c31ce993..0cc2da2a6 100644 --- a/chain-signatures/crypto/src/kdf.rs +++ b/chain-signatures/crypto/src/kdf.rs @@ -19,6 +19,7 @@ pub enum Chain { Ethereum, Solana, Bitcoin, + Hydration, } impl Chain { @@ -28,6 +29,7 @@ impl Chain { Chain::Ethereum => "0x1", Chain::Solana => "0x800001f5", Chain::Bitcoin => "bip122:000000000019d6689c085ae165831e93", + Chain::Hydration => "polkadot:2034", } } @@ -37,6 +39,7 @@ impl Chain { Chain::Ethereum => "eip155:1", Chain::Solana => "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", Chain::Bitcoin => "bip122:000000000019d6689c085ae165831e93", + Chain::Hydration => "polkadot:2034", } } } @@ -90,6 +93,11 @@ pub fn derive_epsilon_sol(key_version: u32, sender: &str, path: &str) -> Scalar keccak(derivation_path.as_bytes()) } +pub fn derive_epsilon_hydration(key_version: u32, sender: &str, path: &str) -> Scalar { + let derivation_path = derivation_path(key_version, Chain::Hydration, sender, path); + keccak(derivation_path.as_bytes()) +} + pub fn derive_key(public_key: PublicKey, epsilon: Scalar) -> PublicKey { (::ProjectivePoint::GENERATOR * epsilon + public_key).to_affine() } diff --git a/chain-signatures/node/Cargo.toml b/chain-signatures/node/Cargo.toml index 657585b9c..955327447 100644 --- a/chain-signatures/node/Cargo.toml +++ b/chain-signatures/node/Cargo.toml @@ -39,7 +39,15 @@ opentelemetry-appender-tracing = "0.29.1" alloy-signer-local = "1.0.38" alloy-sol-types = "1.4.1" helios = { git = "https://github.com/a16z/helios", rev = "e1f9a50f73d7af6f6ae4acf2b663d04aab6adb19" } - +ed25519-zebra = { version = "4.1", default-features = false, features = ["alloc"] } +futures = "0.3.31" +subxt = { version = "0.44", default-features = false, features = ["jsonrpsee","native","unstable-light-client"] } +sp-core = { version = "38.1", default-features = false, features = ["std"] } +sp-runtime = { version = "44.0.0", default-features = false, features = ["std"] } +sp-trie = { version = "41.1.0", default-features = false, features = ["std"] } +sp-state-machine = { version = "0.48", default-features = false, features = ["std"] } +codec = { package = "parity-scale-codec", version = "3", features = ["derive"] } +subxt-signer = "0.44" # workspace dependencies alloy.workspace = true diff --git a/chain-signatures/node/src/backlog/mod.rs b/chain-signatures/node/src/backlog/mod.rs index 12610b3ff..5eebedc88 100644 --- a/chain-signatures/node/src/backlog/mod.rs +++ b/chain-signatures/node/src/backlog/mod.rs @@ -781,20 +781,22 @@ mod tests { Chain::Ethereum, sign_id_eth, BacklogTransaction::Bidirectional(tx_eth.clone()), - SignRequestType::SignBidirectional(SignBidirectionalEvent { - sender: Default::default(), - serialized_transaction: vec![], - dest: "ethereum".to_string(), - caip2_id: "eip155:1".to_string(), - key_version: 0, - deposit: 0, - path: "".to_string(), - algo: "".to_string(), - params: "".to_string(), - program_id, - output_deserialization_schema: vec![], - respond_serialization_schema: vec![], - }), + SignRequestType::SignBidirectional( + crate::indexer_common::SignBidirectionalEvent::Solana(SignBidirectionalEvent { + sender: Default::default(), + serialized_transaction: vec![], + dest: "ethereum".to_string(), + caip2_id: "eip155:1".to_string(), + key_version: 0, + deposit: 0, + path: "".to_string(), + algo: "".to_string(), + params: "".to_string(), + program_id, + output_deserialization_schema: vec![], + respond_serialization_schema: vec![], + }), + ), ) .await; backlog @@ -802,20 +804,22 @@ mod tests { Chain::Solana, sign_id_sol, BacklogTransaction::Bidirectional(tx_sol.clone()), - SignRequestType::SignBidirectional(SignBidirectionalEvent { - sender: Default::default(), - serialized_transaction: vec![], - dest: "solana".to_string(), - caip2_id: "solana:5eykt4UsFY6PZFX8nTM1".to_string(), - key_version: 0, - deposit: 0, - path: "".to_string(), - algo: "".to_string(), - params: "".to_string(), - program_id, - output_deserialization_schema: vec![], - respond_serialization_schema: vec![], - }), + SignRequestType::SignBidirectional( + crate::indexer_common::SignBidirectionalEvent::Solana(SignBidirectionalEvent { + sender: Default::default(), + serialized_transaction: vec![], + dest: "solana".to_string(), + caip2_id: "solana:5eykt4UsFY6PZFX8nTM1".to_string(), + key_version: 0, + deposit: 0, + path: "".to_string(), + algo: "".to_string(), + params: "".to_string(), + program_id, + output_deserialization_schema: vec![], + respond_serialization_schema: vec![], + }), + ), ) .await; backlog @@ -823,20 +827,22 @@ mod tests { Chain::NEAR, sign_id_near, BacklogTransaction::Bidirectional(tx_near.clone()), - SignRequestType::SignBidirectional(SignBidirectionalEvent { - sender: Default::default(), - serialized_transaction: vec![], - dest: "near".to_string(), - caip2_id: "near:mainnet".to_string(), - key_version: 0, - deposit: 0, - path: "".to_string(), - algo: "".to_string(), - params: "".to_string(), - program_id, - output_deserialization_schema: vec![], - respond_serialization_schema: vec![], - }), + SignRequestType::SignBidirectional( + crate::indexer_common::SignBidirectionalEvent::Solana(SignBidirectionalEvent { + sender: Default::default(), + serialized_transaction: vec![], + dest: "near".to_string(), + caip2_id: "near:mainnet".to_string(), + key_version: 0, + deposit: 0, + path: "".to_string(), + algo: "".to_string(), + params: "".to_string(), + program_id, + output_deserialization_schema: vec![], + respond_serialization_schema: vec![], + }), + ), ) .await; diff --git a/chain-signatures/node/src/cli.rs b/chain-signatures/node/src/cli.rs index 79a0594ef..e14ad7825 100644 --- a/chain-signatures/node/src/cli.rs +++ b/chain-signatures/node/src/cli.rs @@ -12,7 +12,7 @@ use crate::rpc::{ContractStateWatcher, NearClient, RpcExecutor}; use crate::storage::app_data_storage; use crate::storage::checkpoint_storage::CheckpointStorage; use crate::storage::triple_storage::TriplePair; -use crate::{indexer, indexer_eth, indexer_sol, logs, mesh, storage, web}; +use crate::{indexer, indexer_eth, indexer_hydration, indexer_sol, logs, mesh, storage, web}; use clap::Parser; use deadpool_redis::Runtime; @@ -64,6 +64,9 @@ pub enum Cli { /// Solana Indexer options #[clap(flatten)] sol: indexer_sol::SolArgs, + /// Hydration Indexer options + #[clap(flatten)] + hydration: indexer_hydration::HydrationArgs, /// NEAR requests options #[clap(flatten)] indexer_options: indexer::Options, @@ -106,6 +109,7 @@ impl Cli { sign_sk, eth, sol, + hydration, indexer_options, my_address, storage_options, @@ -152,6 +156,7 @@ impl Cli { args.extend(eth.into_str_args()); args.extend(sol.into_str_args()); + args.extend(hydration.into_str_args()); args.extend(indexer_options.into_str_args()); args.extend(storage_options.into_str_args()); args.extend(log_options.into_str_args()); @@ -175,6 +180,7 @@ pub async fn run(cmd: Cli) -> anyhow::Result<()> { sign_sk, eth, sol, + hydration, indexer_options, my_address, storage_options, @@ -264,11 +270,13 @@ pub async fn run(cmd: Cli) -> anyhow::Result<()> { let eth = eth.into_config(); let sol = sol.into_config(); + let hydration = hydration.into_config(); let network = NetworkConfig { cipher_sk, sign_sk }; let near_client = NearClient::new(&near_rpc, &my_address, &network, &mpc_contract_id, signer); - let (rpc_channel, rpc) = RpcExecutor::new(&near_client, ð, &sol, backlog.clone()); + let (rpc_channel, rpc) = + RpcExecutor::new(&near_client, ð, &sol, &hydration, backlog.clone()).await; let (sync_channel, sync) = SyncTask::new( &client, diff --git a/chain-signatures/node/src/indexer_common.rs b/chain-signatures/node/src/indexer_common.rs new file mode 100644 index 000000000..fddd9ab7b --- /dev/null +++ b/chain-signatures/node/src/indexer_common.rs @@ -0,0 +1,412 @@ +use crate::backlog::Backlog; +use crate::backlog::BacklogTransaction; +use crate::backlog::SignTx; +use crate::indexer_hydration::{ + HydrationRespondBidirectionalEvent, HydrationSignBidirectionalRequestedEvent, + HydrationSignatureRespondedEvent, +}; +use crate::mesh::wait_threshold_active; +use crate::mesh::MeshState; +use crate::node_client::NodeClient; +use crate::protocol::Chain; +use crate::protocol::IndexedSignRequest; +use crate::protocol::Sign; +use crate::protocol::SignRequestType; +use crate::rpc::ContractStateWatcher; +use crate::sign_bidirectional::BidirectionalTx; +use crate::sign_bidirectional::BidirectionalTxId; +use crate::sign_bidirectional::PendingRequestStatus; +use anchor_lang::prelude::Pubkey; +use k256::Scalar; +use mpc_primitives::SignId; +use mpc_primitives::Signature; +use near_account_id::AccountId; +use std::str::FromStr; +use std::time::Duration; +use tokio::sync::mpsc; +use tokio::sync::watch; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SignBidirectionalEvent { + Solana(signet_program::SignBidirectionalEvent), + Hydration(HydrationSignBidirectionalRequestedEvent), +} + +impl SignBidirectionalEvent { + pub fn sender(&self) -> Pubkey { + match self { + SignBidirectionalEvent::Solana(event) => event.sender, + SignBidirectionalEvent::Hydration(event) => Pubkey::new_from_array(event.sender), + } + } + + pub fn path(&self) -> String { + match self { + SignBidirectionalEvent::Solana(event) => event.path.clone(), + SignBidirectionalEvent::Hydration(event) => event.path.clone(), + } + } + + pub fn dest(&self) -> String { + match self { + SignBidirectionalEvent::Solana(event) => event.dest.clone(), + SignBidirectionalEvent::Hydration(event) => event.dest.clone(), + } + } + + pub(crate) fn algo(&self) -> String { + match self { + SignBidirectionalEvent::Solana(event) => event.algo.clone(), + SignBidirectionalEvent::Hydration(event) => event.algo.clone(), + } + } + + pub fn params(&self) -> String { + match self { + SignBidirectionalEvent::Solana(event) => event.params.clone(), + SignBidirectionalEvent::Hydration(event) => event.params.clone(), + } + } + + pub fn output_deserialization_schema(&self) -> Vec { + match self { + SignBidirectionalEvent::Solana(event) => event.output_deserialization_schema.clone(), + SignBidirectionalEvent::Hydration(event) => event.output_deserialization_schema.clone(), + } + } + + pub fn respond_serialization_schema(&self) -> Vec { + match self { + SignBidirectionalEvent::Solana(event) => event.respond_serialization_schema.clone(), + SignBidirectionalEvent::Hydration(event) => event.respond_serialization_schema.clone(), + } + } + + pub fn key_version(&self) -> u32 { + match self { + SignBidirectionalEvent::Solana(event) => event.key_version, + SignBidirectionalEvent::Hydration(event) => event.key_version, + } + } + + pub(crate) fn deposit(&self) -> u64 { + match self { + SignBidirectionalEvent::Solana(event) => event.deposit, + SignBidirectionalEvent::Hydration(event) => event.deposit, + } + } + + pub fn serialized_transaction(&self) -> Vec { + match self { + SignBidirectionalEvent::Solana(event) => event.serialized_transaction.clone(), + SignBidirectionalEvent::Hydration(event) => event.serialized_transaction.clone(), + } + } + + pub fn caip2_id(&self) -> String { + match self { + SignBidirectionalEvent::Solana(event) => event.caip2_id.clone(), + SignBidirectionalEvent::Hydration(event) => event.caip2_id.clone(), + } + } + + pub fn epsilon(&self) -> Scalar { + match self { + SignBidirectionalEvent::Solana(event) => mpc_crypto::kdf::derive_epsilon_sol( + event.key_version, + &event.sender.to_string(), + &event.path, + ), + SignBidirectionalEvent::Hydration(event) => mpc_crypto::kdf::derive_epsilon_hydration( + event.key_version, + &Pubkey::new_from_array(event.sender).to_string(), + &event.path, + ), + } + } +} + +pub enum RespondBidirectionalEvent { + Solana(signet_program::RespondBidirectionalEvent), + Hydration(HydrationRespondBidirectionalEvent), +} + +impl RespondBidirectionalEvent { + pub fn request_id(&self) -> [u8; 32] { + match self { + RespondBidirectionalEvent::Solana(event) => event.request_id, + RespondBidirectionalEvent::Hydration(event) => event.request_id, + } + } + + pub fn responder(&self) -> [u8; 32] { + match self { + RespondBidirectionalEvent::Solana(event) => event.responder.to_bytes(), + RespondBidirectionalEvent::Hydration(event) => event.responder, + } + } + + pub fn serialized_output(&self) -> Vec { + match self { + RespondBidirectionalEvent::Solana(event) => event.serialized_output.clone(), + RespondBidirectionalEvent::Hydration(event) => event.serialized_output.clone(), + } + } + + pub fn signature(&self) -> Signature { + match self { + RespondBidirectionalEvent::Solana(event) => { + crate::indexer_sol::to_mpc_signature(event.signature.clone()).unwrap() + } + RespondBidirectionalEvent::Hydration(event) => event.signature.clone(), + } + } +} + +pub enum SignatureRespondedEvent { + Solana(signet_program::SignatureRespondedEvent), + Hydration(HydrationSignatureRespondedEvent), +} + +impl SignatureRespondedEvent { + pub fn request_id(&self) -> [u8; 32] { + match self { + SignatureRespondedEvent::Solana(event) => event.request_id, + SignatureRespondedEvent::Hydration(event) => event.request_id, + } + } + + pub fn signature(&self) -> Signature { + match self { + SignatureRespondedEvent::Solana(event) => { + crate::indexer_sol::to_mpc_signature(event.signature.clone()).unwrap() + } + SignatureRespondedEvent::Hydration(event) => event.signature.clone(), + } + } +} + +pub(crate) trait SignatureEventTrait { + fn generate_request_id(&self) -> [u8; 32]; + fn generate_sign_request( + &self, + entropy: [u8; 32], + total_timeout: Duration, + ) -> anyhow::Result; + fn source_chain(&self) -> Chain; +} + +pub(crate) trait SignatureEvent: SignatureEventTrait + std::fmt::Debug {} + +pub(crate) type SignatureEventBox = Box; + +pub(crate) async fn process_sign_event( + sign_event: SignatureEventBox, + entropy: [u8; 32], + sign_tx: mpsc::Sender, + node_near_account_id: AccountId, + total_timeout: Duration, + backlog: Backlog, +) -> anyhow::Result<()> { + let sign_request = sign_event.generate_sign_request(entropy, total_timeout)?; + + // Insert the transaction into the backlog when we first see the sign request + let sign_id = sign_request.id; + let sign_request_type = sign_request.sign_request_type.clone(); + + // Create the appropriate BacklogTransaction based on the sign request type + let backlog_tx = match &sign_request_type { + SignRequestType::Sign => BacklogTransaction::Sign(SignTx { + request_id: sign_id.request_id, + source_chain: sign_event.source_chain(), + key_version: sign_request.args.key_version, + status: PendingRequestStatus::AwaitingResponse, + }), + SignRequestType::SignBidirectional(_event) => { + // For bidirectional requests, start with a Sign transaction + // The protocol will advance it to Bidirectional after generating the signature + BacklogTransaction::Sign(SignTx { + request_id: sign_id.request_id, + source_chain: sign_event.source_chain(), + key_version: sign_request.args.key_version, + status: PendingRequestStatus::AwaitingResponse, + }) + } + _ => anyhow::bail!("Unexpected sign request type"), + }; + + backlog + .insert( + sign_event.source_chain(), + sign_id, + backlog_tx, + sign_request_type, + ) + .await; + + if let Err(err) = sign_tx.send(Sign::Request(sign_request)).await { + // TODO: handle error to ensure 100% success rate + tracing::error!(?err, "Failed to send Solana sign request into queue"); + } else { + crate::metrics::NUM_SIGN_REQUESTS + .with_label_values(&[ + sign_event.source_chain().as_str(), + node_near_account_id.as_str(), + ]) + .inc(); + } + + Ok(()) +} + +pub(crate) async fn recover_backlog( + backlog: &Backlog, + contract_watcher: &mut ContractStateWatcher, + mesh_state: &mut watch::Receiver, + node_client: &NodeClient, + source_chain: Chain, +) { + // Recover backlog before doing anything. + // Wait for threshold to be available + let threshold = contract_watcher.wait_threshold().await; + if threshold > 0 { + wait_threshold_active(mesh_state, threshold).await; + + let mesh_state = mesh_state.borrow().clone(); + backlog + .recover(&mesh_state, node_client, threshold, &[source_chain]) + .await; + } +} + +pub(crate) async fn process_respond_event( + respond_event: SignatureRespondedEvent, + sign_tx: mpsc::Sender, + contract_watcher: &mut ContractStateWatcher, + backlog: &Backlog, +) -> anyhow::Result<()> { + let sign_id = SignId::new(respond_event.request_id()); + + let source_chain = match respond_event { + SignatureRespondedEvent::Solana(_) => Chain::Solana, + SignatureRespondedEvent::Hydration(_) => Chain::Hydration, + }; + + let Some(sign_type) = backlog.sign_type(source_chain, &sign_id).await else { + anyhow::bail!( + "sign type not found for respond event (may have already been processed): {sign_id:?}" + ) + }; + let event = match sign_type { + SignRequestType::SignBidirectional(event) => event, + SignRequestType::Sign => { + tracing::info!(?sign_id, "sign request completed successfully"); + backlog.remove(Chain::Solana, &sign_id).await; + if let Err(err) = sign_tx.send(Sign::Completion(sign_id)).await { + anyhow::bail!("failed to send completion for respond event: {err:?}"); + } + return Ok(()); + } + SignRequestType::RespondBidirectional(_) => { + anyhow::bail!("RespondBidirectional received respond event?: {sign_id:?}") + } + }; + + tracing::info!(?sign_id, "bidirectional processing initial respond event"); + let target_chain = Chain::from_str(&event.dest()) + .map_err(|err| anyhow::anyhow!("unable to parse target chain from dest: {err:?}"))?; + + let Some(BacklogTransaction::Sign(_)) = backlog.get(source_chain, &sign_id).await else { + anyhow::bail!("bidirectional tx not found for advancement: {sign_id:?}"); + }; + + let mpc_sig = respond_event.signature(); + + // Sign and hash the transaction to get the correct tx_id and nonce + let (signed_tx_hash, nonce) = crate::sign_bidirectional::sign_and_hash_transaction( + &event.serialized_transaction(), + mpc_sig, + )?; + + let tx_id = BidirectionalTxId(signed_tx_hash.into()); + + // Get the MPC public key and derive the from_address + let root_public_key = contract_watcher.wait_public_key().await; + let epsilon = event.epsilon(); + let from_address = crate::sign_bidirectional::derive_user_address(root_public_key, epsilon); + + let bidirectional_tx = BidirectionalTx { + id: tx_id, + sender: event.sender(), + serialized_transaction: event.serialized_transaction(), + source_chain, + target_chain, + caip2_id: event.caip2_id(), + key_version: event.key_version(), + deposit: event.deposit(), + path: event.path(), + algo: event.algo(), + dest: event.dest(), + params: event.params(), + output_deserialization_schema: event.output_deserialization_schema(), + respond_serialization_schema: event.respond_serialization_schema(), + request_id: respond_event.request_id(), + from_address, + nonce, + status: PendingRequestStatus::AwaitingResponse, + }; + + tracing::info!( + ?sign_id, + ?tx_id, + nonce = ?bidirectional_tx.nonce, + from_address = ?bidirectional_tx.from_address, + "bidirectional tx details before advancement", + ); + + match backlog + .advance(source_chain, sign_id, bidirectional_tx) + .await + { + Ok(_) => { + tracing::info!( + ?sign_id, + ?tx_id, + ?target_chain, + "advance bidirectional tx to execution successful" + ); + } + Err(err) => { + tracing::error!( + ?sign_id, + ?tx_id, + ?target_chain, + ?err, + "advance bidirectional tx to execution failed" + ); + } + } + + Ok(()) +} + +pub(crate) async fn process_respond_bidirectional_event( + event: RespondBidirectionalEvent, + sign_tx: mpsc::Sender, + backlog: &Backlog, +) -> anyhow::Result<()> { + let sign_id = SignId::new(event.request_id()); + tracing::info!(?sign_id, "processing RespondBidirectionalEvent"); + if backlog.remove(Chain::Solana, &sign_id).await.is_some() { + tracing::info!(?sign_id, "bidirectional tx completed"); + } else { + tracing::warn!(?sign_id, "bidirectional tx not found on completion"); + } + + if let Err(err) = sign_tx.send(Sign::Completion(sign_id)).await { + anyhow::bail!( + "failed to send completion for respond bidirectional: {err:?} for sign id: {sign_id:?}" + ) + }; + Ok(()) +} diff --git a/chain-signatures/node/src/indexer_eth/mod.rs b/chain-signatures/node/src/indexer_eth/mod.rs index 31deb233f..48168b12f 100644 --- a/chain-signatures/node/src/indexer_eth/mod.rs +++ b/chain-signatures/node/src/indexer_eth/mod.rs @@ -2,7 +2,6 @@ pub mod indexer_eth_direct_rpc; pub mod indexer_eth_helios; use crate::backlog::Backlog; -use crate::mesh::wait_threshold_active; use crate::mesh::MeshState; use crate::node_client::NodeClient; use crate::protocol::{Chain, IndexedSignRequest, Sign, SignRequestType}; @@ -721,8 +720,8 @@ impl EthereumIndexer { pub async fn run(self) { let backlog = self.backlog; - let contract_watcher = self.contract_watcher; - let mesh_state = self.mesh_state; + let mut contract_watcher = self.contract_watcher; + let mut mesh_state = self.mesh_state; let node_client = self.node_client; let app_data_storage = self.app_data_storage; let client = self.client; @@ -730,7 +729,14 @@ impl EthereumIndexer { let sign_tx = self.sign_tx; let node_near_account_id = self.node_near_account_id; - Self::recover_backlog(&backlog, contract_watcher, mesh_state, node_client).await; + crate::indexer_common::recover_backlog( + &backlog, + &mut contract_watcher, + &mut mesh_state, + &node_client, + Chain::Ethereum, + ) + .await; let last_processed_block = Self::get_last_processed_block(&app_data_storage).await; @@ -1418,25 +1424,6 @@ impl EthereumIndexer { } } - async fn recover_backlog( - backlog: &Backlog, - mut contract_watcher: ContractStateWatcher, - mut mesh_state: watch::Receiver, - node_client: NodeClient, - ) { - // Recover backlog before doing anything. - // Wait for threshold to be available - let threshold = contract_watcher.wait_threshold().await; - if threshold > 0 { - wait_threshold_active(&mut mesh_state, threshold).await; - - let mesh_state = mesh_state.borrow().clone(); - backlog - .recover(&mesh_state, &node_client, threshold, &[Chain::Ethereum]) - .await; - } - } - async fn get_last_processed_block(app_data_storage: &AppDataStorage) -> Option { app_data_storage .last_processed_block_eth() diff --git a/chain-signatures/node/src/indexer_hydration/artifacts/hydration_metadata.scale b/chain-signatures/node/src/indexer_hydration/artifacts/hydration_metadata.scale new file mode 100644 index 0000000000000000000000000000000000000000..3635e19e3fe6499e4eef18b6535e820462b42370 GIT binary patch literal 487324 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 z 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

_= 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%0D{{0PJImqMCrM2`bzf2m1ZzMlt@}3% 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, + /// Hydration signer URI + #[clap(long, env("MPC_HYDRATION_SIGNER_URI"))] + pub signer_uri: Option, + #[clap(long, env("MPC_HYDRATION_TOTAL_TIMEOUT"), default_value = "200")] + pub total_timeout: Option, +} + +impl HydrationArgs { + pub fn into_str_args(self) -> Vec { + let mut args = Vec::with_capacity(2); + if let Some(rpc_ws_url) = self.rpc_ws_url { + args.extend(["--hydration-rpc-ws-url".to_string(), rpc_ws_url]); + } + if let Some(signer_uri) = self.signer_uri { + args.extend(["--hydration-signer-uri".to_string(), signer_uri]); + } + if let Some(total_timeout) = self.total_timeout { + args.extend(["--total-timeout".to_string(), total_timeout.to_string()]); + } + args + } + + pub fn into_config(self) -> Option { + Some(HydrationConfig { + rpc_ws_url: self.rpc_ws_url?, + signer_uri: self.signer_uri?, + total_timeout: self.total_timeout?, + }) + } + + pub fn from_config(config: Option) -> Self { + match config { + Some(config) => HydrationArgs { + rpc_ws_url: Some(config.rpc_ws_url), + signer_uri: Some(config.signer_uri), + total_timeout: Some(config.total_timeout), + }, + None => HydrationArgs { + rpc_ws_url: None, + signer_uri: None, + total_timeout: None, + }, + } + } +} + +#[derive(Clone)] +pub struct HydrationConfig { + /// Hydration RPC ws URL + pub rpc_ws_url: String, + /// Hydration signer URI + pub signer_uri: String, + /// total timeout for a sign request starting from indexed time in seconds + pub total_timeout: u64, +} + +impl fmt::Debug for HydrationConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HydrationConfig") + .field("rpc_ws_url", &self.rpc_ws_url) + .field("signer_uri", &"") + .field("total_timeout", &self.total_timeout) + .finish() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct HydrationSignatureRequestedEvent { + pub sender: [u8; 32], + pub payload: [u8; 32], + pub path: String, + pub key_version: u32, + pub deposit: u64, + pub chain_id: String, + pub algo: String, + pub dest: String, + pub params: String, +} + +impl From for HydrationSignatureRequestedEvent { + fn from(event: hydration::signet::events::SignatureRequested) -> Self { + let mut sender = [0u8; 32]; + let sender_array: &[u8; 32] = + >::as_ref(&event.sender); + sender.copy_from_slice(sender_array); + Self { + sender, + payload: event.payload, + path: String::from_utf8(event.path).unwrap(), + key_version: event.key_version, + deposit: event.deposit.try_into().unwrap(), + chain_id: String::from_utf8(event.chain_id).unwrap(), + algo: String::from_utf8(event.algo).unwrap(), + dest: String::from_utf8(event.dest).unwrap(), + params: String::from_utf8(event.params).unwrap(), + } + } +} + +impl SignatureEvent for HydrationSignatureRequestedEvent {} + +impl SignatureEventTrait for HydrationSignatureRequestedEvent { + fn generate_request_id(&self) -> [u8; 32] { + // Encode the event data in ABI format + let encoded = encode(&[ + Token::String(hex::encode(self.sender)), + Token::Bytes(self.payload.to_vec()), + Token::String(self.path.clone()), + Token::Uint(self.key_version.into()), + Token::String(self.chain_id.clone()), + Token::String(self.algo.clone()), + Token::String(self.dest.clone()), + Token::String(self.params.clone()), + ]); + // Calculate keccak256 hash + let mut hasher = Keccak256::new(); + hasher.update(&encoded); + hasher.finalize().into() + } + + fn generate_sign_request( + &self, + entropy: [u8; 32], + total_timeout: Duration, + ) -> anyhow::Result { + tracing::info!("found hydration event: {:?}", self); + if self.deposit == 0 { + tracing::warn!("deposit is 0, skipping sign request"); + anyhow::bail!("deposit is 0"); + } + + if self.key_version > LATEST_MPC_KEY_VERSION { + tracing::warn!("unsupported key version: {}", self.key_version); + anyhow::bail!("unsupported key version"); + } + + let Some(payload) = Scalar::from_bytes(self.payload) else { + tracing::warn!( + "solana `sign` did not produce payload hash correctly: {:?}", + self.payload, + ); + anyhow::bail!("failed to convert event payload hash to scalar"); + }; + + if payload > *MAX_SECP256K1_SCALAR { + tracing::warn!("payload exceeds secp256k1 curve order: {payload:?}"); + anyhow::bail!("payload exceeds secp256k1 curve order"); + } + + let epsilon = mpc_crypto::kdf::derive_epsilon_hydration( + self.key_version, + format!("0x{}", self.sender.encode_hex()).as_str(), + &self.path, + ); + + let sign_id = SignId::new(self.generate_request_id()); + tracing::info!(?sign_id, "solana signature requested"); + + Ok(IndexedSignRequest { + id: sign_id, + args: SignArgs { + entropy, + epsilon, + payload, + path: self.path.clone(), + key_version: self.key_version, + }, + chain: Chain::Hydration, + timestamp_sign_queue: Instant::now(), + unix_timestamp_indexed: crate::util::current_unix_timestamp(), + total_timeout, + sign_request_type: SignRequestType::Sign, + }) + } + + fn source_chain(&self) -> Chain { + Chain::Hydration + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct HydrationSignBidirectionalRequestedEvent { + pub sender: [u8; 32], + pub serialized_transaction: Vec, + pub caip2_id: String, + pub key_version: u32, + pub deposit: u64, + pub path: String, + pub algo: String, + pub dest: String, + pub params: String, + pub program_id: [u8; 32], + pub output_deserialization_schema: Vec, + pub respond_serialization_schema: Vec, +} + +impl From + for HydrationSignBidirectionalRequestedEvent +{ + fn from(event: hydration::signet::events::SignBidirectionalRequested) -> Self { + let mut sender = [0u8; 32]; + let sender_array: &[u8; 32] = + >::as_ref(&event.sender); + sender.copy_from_slice(sender_array); + let mut program_id = [0u8; 32]; + let program_id_array: &[u8; 32] = + >::as_ref(&event.program_id); + program_id.copy_from_slice(program_id_array); + Self { + sender, + serialized_transaction: event.serialized_transaction, + caip2_id: String::from_utf8(event.caip2_id).unwrap(), + path: String::from_utf8(event.path).unwrap(), + key_version: event.key_version, + deposit: event.deposit.try_into().unwrap(), + algo: String::from_utf8(event.algo).unwrap(), + dest: String::from_utf8(event.dest).unwrap(), + params: String::from_utf8(event.params).unwrap(), + program_id, + output_deserialization_schema: event.output_deserialization_schema, + respond_serialization_schema: event.respond_serialization_schema, + } + } +} + +impl SignatureEvent for HydrationSignBidirectionalRequestedEvent {} + +impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { + fn generate_request_id(&self) -> [u8; 32] { + // Match TypeScript implementation using ABI encoding + let encoded = ( + Pubkey::new_from_array(self.sender).to_string(), + self.serialized_transaction.clone(), + self.caip2_id.clone(), + self.key_version, + self.path.clone(), + self.algo.clone(), + self.dest.clone(), + self.params.clone(), + ) + .abi_encode_packed(); + + alloy::primitives::keccak256(encoded).into() + } + + fn generate_sign_request( + &self, + entropy: [u8; 32], + total_timeout: Duration, + ) -> anyhow::Result { + tracing::info!("found solana event: {:?}", self); + if self.deposit == 0 { + tracing::warn!("deposit is 0, skipping sign request"); + anyhow::bail!("deposit is 0"); + } + + if self.key_version > LATEST_MPC_KEY_VERSION { + tracing::warn!("unsupported key version: {}", self.key_version); + anyhow::bail!("unsupported key version"); + } + + let request_id = self.generate_request_id(); + let rlp_encoded_tx = self.serialized_transaction.clone(); + + // Call the existing derive_epsilon_sol function with the correct parameters + // to match the TypeScript implementation + let epsilon = mpc_crypto::kdf::derive_epsilon_hydration( + self.key_version, + format!("0x{}", self.sender.encode_hex()).as_str(), + &self.path, + ); + + let sign_id = SignId::new(request_id); + tracing::info!(?sign_id, "hydration signature requested"); + let unsigned_tx_hash = hash_rlp_data(rlp_encoded_tx); + let Some(payload) = Scalar::from_bytes(unsigned_tx_hash) else { + anyhow::bail!("Failed to convert unsigned_tx_hash to scalar: {unsigned_tx_hash:?}"); + }; + + if payload > *MAX_SECP256K1_SCALAR { + tracing::warn!("payload exceeds secp256k1 curve order: {payload:?}"); + anyhow::bail!("payload exceeds secp256k1 curve order"); + } + + Ok(IndexedSignRequest { + id: sign_id, + args: SignArgs { + entropy, + epsilon, + payload, + path: self.path.clone(), + key_version: self.key_version, + }, + chain: Chain::Solana, + timestamp_sign_queue: Instant::now(), + unix_timestamp_indexed: crate::util::current_unix_timestamp(), + total_timeout, + sign_request_type: SignRequestType::SignBidirectional( + crate::indexer_common::SignBidirectionalEvent::Hydration(self.clone()), + ), + }) + } + + fn source_chain(&self) -> Chain { + Chain::Hydration + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HydrationRespondBidirectionalEvent { + pub request_id: [u8; 32], + pub responder: [u8; 32], + pub serialized_output: Vec, + pub signature: Signature, +} + +impl From + for HydrationRespondBidirectionalEvent +{ + fn from(event: hydration::signet::events::RespondBidirectionalEvent) -> Self { + let signature = to_mpc_signature(event.signature).unwrap(); + let responder = account32_to_bytes(&event.responder); + Self { + request_id: event.request_id, + responder, + serialized_output: event.serialized_output, + signature, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HydrationSignatureRespondedEvent { + pub request_id: [u8; 32], + pub responder: [u8; 32], + pub signature: Signature, +} + +impl From for HydrationSignatureRespondedEvent { + fn from(event: hydration::signet::events::SignatureResponded) -> Self { + let signature = to_mpc_signature(event.signature).unwrap(); + let responder = account32_to_bytes(&event.responder); + Self { + request_id: event.request_id, + responder, + signature, + } + } +} + +fn to_mpc_signature(sig: HydrationSignature) -> anyhow::Result { + let x_bytes: FieldBytes = sig.big_r.x.into(); + let y_bytes: FieldBytes = sig.big_r.y.into(); + let enc = EncodedPoint::from_affine_coordinates(&x_bytes, &y_bytes, false); + + let big_r = AffinePoint::from_encoded_point(&enc) + .into_option() + .ok_or_else(|| anyhow::anyhow!("invalid affine point in HydrationSignature"))?; + + let s = Scalar::from_bytes(sig.s) + .ok_or_else(|| anyhow::anyhow!("invalid scalar in HydrationSignature"))?; + + Ok(Signature::new(big_r, s, sig.recovery_id)) +} + +fn account32_to_bytes(account: &subxt::utils::AccountId32) -> [u8; 32] { + let mut result = [0u8; 32]; + let account_array: &[u8; 32] = >::as_ref(account); + result.copy_from_slice(account_array); + result +} + +#[subxt::subxt(runtime_metadata_path = "src/indexer_hydration/artifacts/hydration_metadata.scale")] +pub mod hydration {} + +/// Storage key for `frame_system::Events`. +fn system_events_key() -> Vec { + let mut key = Vec::with_capacity(32); + key.extend_from_slice(&twox_128(b"System")); + key.extend_from_slice(&twox_128(b"Events")); + key +} + +/// Fetch and *verify* the SCALE‑encoded `System::Events` bytes at a given block. +/// +/// - Uses `state_get_read_proof` via `LegacyRpcMethods`. +/// - Verifies the proof against `state_root` using `read_proof_check`. +/// - Returns the proven SCALE bytes for `System::Events`. +async fn fetch_proven_system_events_bytes( + legacy_rpc: &LegacyRpcMethods, + state_root: H256, + block_hash: HashFor, +) -> Result> { + let events_key = system_events_key(); + + // 1. Get storage proof for System::Events at this block. + let read_proof = legacy_rpc + .state_get_read_proof([events_key.as_slice()], Some(block_hash)) + .await + .map_err(|e| anyhow!("state_get_read_proof failed: {e}"))?; + + // read_proof.proof is Vec; Bytes wraps Vec. + let sp_proof = StorageProof::new(read_proof.proof.into_iter().map(|bytes| bytes.0)); + + // 2. Verify the proof against the block's state_root using Blake2 trie layout. + let values_by_key = + read_proof_check::(state_root, sp_proof, vec![events_key.clone()]) + .map_err(|e| anyhow!("read_proof_check failed: {e}"))?; + + // 3. Extract the SCALE‑encoded System::Events bytes. + let events_bytes = values_by_key + .get(&events_key) + .and_then(|opt| opt.as_ref()) + .ok_or_else(|| anyhow!("System::Events missing from verified proof"))? + .to_vec(); + + Ok(events_bytes) +} + +pub async fn run( + hydration: Option, + sign_tx: mpsc::Sender, + node_near_account_id: AccountId, + backlog: Backlog, + mut contract_watcher: ContractStateWatcher, + mut mesh_state: watch::Receiver, + node_client: NodeClient, +) -> Result<()> { + let Some(hydration) = hydration else { + tracing::warn!("hydration indexer is disabled"); + return Ok(()); + }; + let total_timeout = Duration::from_secs(hydration.total_timeout); + + let ws_url = "ws://127.0.0.1:9944"; + + // High‑level Subxt client for blocks + events. + let hydration_api = OnlineClient::::from_url(ws_url).await?; + + // Low‑level RPC client for legacy methods like state_get_read_proof. + let rpc_client = RpcClient::from_url(ws_url).await?; + let legacy_rpc = LegacyRpcMethods::::new(rpc_client); + + // Wait for threshold to be available + crate::indexer_common::recover_backlog( + &backlog, + &mut contract_watcher, + &mut mesh_state, + &node_client, + Chain::Hydration, + ) + .await; + + // Subscribe to finalized Hydration blocks. + let mut blocks = hydration_api.blocks().subscribe_finalized().await?; + + while let Some(block_res) = blocks.next().await { + let block = block_res?; + let number = block.number(); + let hash = block.hash(); + let header = block.header().clone(); + + // Subxt's Substrate header uses H256 as state root (BlakeTwo256 hash). + let state_root: H256 = header.state_root; + + // Events as decoded by Subxt (unproven bytes). + let events = block.events().await?; + // Raw SCALE bytes for `System::Events` that Subxt decoded. + let events_bytes_unproven = events.bytes().to_vec(); + + // Events bytes proven via storage proof under state_root. + let events_bytes_proven = + fetch_proven_system_events_bytes(&legacy_rpc, state_root, hash).await?; + + // Sanity check: bytes that Subxt decoded must match the Merkle‑proven bytes. + if events_bytes_unproven != events_bytes_proven { + return Err(anyhow!( + "Mismatch between RPC events and Merkle‑proven System::Events \ + in block #{number} ({hash:?})" + )); + } + + // At this point: + // - Block is finalized (subscribe_finalized) + // - System::Events is Merkle‑proven under state_root + // - The bytes Subxt decoded match the proven bytes + // + // → Safe to trust individual decoded events. + + let sign_tx = sign_tx.clone(); + let node_near_account_id = node_near_account_id.clone(); + let backlog = backlog.clone(); + + for ev in events.iter() { + let ev = ev?; + + // SignatureRequested + if let Some(req) = ev.as_event::()? { + let event = HydrationSignatureRequestedEvent::from(req); + tracing::info!( + "Hydration::Signet::SignatureRequested in block #{number} ({hash:?}): {:?}", + event + ); + + let entropy: [u8; 32] = rand::random(); + + crate::indexer_common::process_sign_event( + Box::new(event), + entropy, + sign_tx.clone(), + node_near_account_id.clone(), + total_timeout, + backlog.clone(), + ) + .await?; + } + + // SignatureResponded + if let Some(resp) = ev.as_event::()? { + let event = HydrationSignatureRespondedEvent::from(resp); + tracing::info!( + "Hydration::Signet::SignatureResponded in block #{number} ({hash:?}): {:?}", + event + ); + crate::indexer_common::process_respond_event( + crate::indexer_common::SignatureRespondedEvent::Hydration(event), + sign_tx.clone(), + &mut contract_watcher, + &backlog, + ) + .await?; + } + + // Bidirectional request + if let Some(req_bi) = + ev.as_event::()? + { + let event = HydrationSignBidirectionalRequestedEvent::from(req_bi); + tracing::info!( + "Hydration::Signet::SignBidirectionalRequested in block #{number} ({hash:?}): {:?}", + event + ); + + let entropy: [u8; 32] = rand::random(); + + crate::indexer_common::process_sign_event( + Box::new(event), + entropy, + sign_tx.clone(), + node_near_account_id.clone(), + total_timeout, + backlog.clone(), + ) + .await?; + } + + // Bidirectional response + if let Some(resp_bi) = + ev.as_event::()? + { + let event = HydrationRespondBidirectionalEvent::from(resp_bi); + tracing::info!( + "Hydration::Signet::RespondBidirectionalEvent in block #{number} ({hash:?}): {:?}", + event + ); + crate::indexer_common::process_respond_bidirectional_event( + crate::indexer_common::RespondBidirectionalEvent::Hydration(event), + sign_tx.clone(), + &backlog, + ) + .await?; + } + } + } + + Ok(()) +} diff --git a/chain-signatures/node/src/indexer_sol.rs b/chain-signatures/node/src/indexer_sol.rs index 0a578ae01..14c9b94b3 100644 --- a/chain-signatures/node/src/indexer_sol.rs +++ b/chain-signatures/node/src/indexer_sol.rs @@ -1,12 +1,11 @@ -use crate::backlog::{Backlog, BacklogTransaction, SignTx}; -use crate::mesh::{wait_threshold_active, MeshState}; +use crate::backlog::Backlog; +use crate::mesh::MeshState; use crate::node_client::NodeClient; use crate::protocol::{Chain, IndexedSignRequest, Sign, SignRequestType}; use crate::rpc::ContractStateWatcher; -use crate::sign_bidirectional::{ - hash_rlp_data, BidirectionalTx, BidirectionalTxId, PendingRequestStatus, -}; +use crate::sign_bidirectional::hash_rlp_data; +use crate::indexer_common::{SignatureEvent, SignatureEventBox, SignatureEventTrait}; use alloy_sol_types::SolValue; use anchor_client::anchor_lang::AnchorDeserialize; use anchor_client::{Client, Cluster, Program}; @@ -164,19 +163,6 @@ pub struct SolSignRequest { pub key_version: u32, } -trait SignatureEventTrait { - fn generate_request_id(&self) -> [u8; 32]; - fn generate_sign_request( - &self, - tx_sig: Vec, - total_timeout: Duration, - ) -> anyhow::Result; -} - -trait SignatureEvent: SignatureEventTrait + std::fmt::Debug {} - -type SignatureEventBox = Box; - impl SignatureEvent for SignatureRequestedEvent {} impl SignatureEventTrait for SignatureRequestedEvent { @@ -200,7 +186,7 @@ impl SignatureEventTrait for SignatureRequestedEvent { fn generate_sign_request( &self, - tx_sig: Vec, + entropy: [u8; 32], total_timeout: Duration, ) -> anyhow::Result { tracing::info!("found solana event: {:?}", self); @@ -232,8 +218,8 @@ impl SignatureEventTrait for SignatureRequestedEvent { let epsilon = derive_epsilon_sol(self.key_version, &self.sender.to_string(), &self.path); // Use transaction signature as entropy - let mut entropy = [0u8; 32]; - entropy.copy_from_slice(&tx_sig[..32]); + // let mut entropy = [0u8; 32]; + // entropy.copy_from_slice(&tx_sig[..32]); let sign_id = SignId::new(self.generate_request_id()); tracing::info!(?sign_id, "solana signature requested"); @@ -254,6 +240,10 @@ impl SignatureEventTrait for SignatureRequestedEvent { sign_request_type: SignRequestType::Sign, }) } + + fn source_chain(&self) -> Chain { + Chain::Solana + } } impl SignatureEvent for SignBidirectionalEvent {} @@ -278,7 +268,7 @@ impl SignatureEventTrait for SignBidirectionalEvent { fn generate_sign_request( &self, - tx_sig: Vec, + entropy: [u8; 32], total_timeout: Duration, ) -> anyhow::Result { tracing::info!("found solana event: {:?}", self); @@ -299,10 +289,6 @@ impl SignatureEventTrait for SignBidirectionalEvent { // to match the TypeScript implementation let epsilon = derive_epsilon_sol(self.key_version, &self.sender.to_string(), &self.path); - // Use transaction signature as entropy - let mut entropy = [0u8; 32]; - entropy.copy_from_slice(&tx_sig[..32]); - let sign_id = SignId::new(request_id); tracing::info!(?sign_id, "solana signature requested"); let unsigned_tx_hash = hash_rlp_data(rlp_encoded_tx); @@ -328,9 +314,15 @@ impl SignatureEventTrait for SignBidirectionalEvent { timestamp_sign_queue: Instant::now(), unix_timestamp_indexed: crate::util::current_unix_timestamp(), total_timeout, - sign_request_type: SignRequestType::SignBidirectional(self.clone()), + sign_request_type: SignRequestType::SignBidirectional( + crate::indexer_common::SignBidirectionalEvent::Solana(self.clone()), + ), }) } + + fn source_chain(&self) -> Chain { + Chain::Solana + } } type Result = anyhow::Result; @@ -356,14 +348,15 @@ pub async fn run( }; // Wait for threshold to be available - let threshold = contract_watcher.wait_threshold().await; - if threshold > 0 { - wait_threshold_active(&mut mesh_state, threshold).await; - let mesh_state = mesh_state.borrow().clone(); - backlog - .recover(&mesh_state, &node_client, threshold, &[Chain::Solana]) - .await; - } + crate::indexer_common::recover_backlog( + &backlog, + &mut contract_watcher, + &mut mesh_state, + &node_client, + Chain::Solana, + ) + .await; + let keypair = Keypair::from_base58_string(&sol.account_sk); let cluster = Cluster::Custom(sol.rpc_http_url.clone(), sol.rpc_ws_url.clone()); let client = @@ -484,47 +477,17 @@ async fn process_anchor_sign_event( total_timeout: Duration, backlog: Backlog, ) -> anyhow::Result<()> { - let sign_request = sign_event.generate_sign_request(tx_sig, total_timeout)?; - - // Insert the transaction into the backlog when we first see the sign request - let sign_id = sign_request.id; - let sign_request_type = sign_request.sign_request_type.clone(); - - // Create the appropriate BacklogTransaction based on the sign request type - let backlog_tx = match &sign_request_type { - SignRequestType::Sign => BacklogTransaction::Sign(SignTx { - request_id: sign_id.request_id, - source_chain: Chain::Solana, - key_version: sign_request.args.key_version, - status: PendingRequestStatus::AwaitingResponse, - }), - SignRequestType::SignBidirectional(_event) => { - // For bidirectional requests, start with a Sign transaction - // The protocol will advance it to Bidirectional after generating the signature - BacklogTransaction::Sign(SignTx { - request_id: sign_id.request_id, - source_chain: Chain::Solana, - key_version: sign_request.args.key_version, - status: PendingRequestStatus::AwaitingResponse, - }) - } - _ => anyhow::bail!("Unexpected sign request type"), - }; - - backlog - .insert(Chain::Solana, sign_id, backlog_tx, sign_request_type) - .await; - - if let Err(err) = sign_tx.send(Sign::Request(sign_request)).await { - // TODO: handle error to ensure 100% success rate - tracing::error!(?err, "Failed to send Solana sign request into queue"); - } else { - crate::metrics::requests::NUM_SIGN_REQUESTS - .with_label_values(&[Chain::Solana.as_str(), node_near_account_id.as_str()]) - .inc(); - } - - Ok(()) + let mut entropy = [0u8; 32]; + entropy.copy_from_slice(&tx_sig[..32]); + crate::indexer_common::process_sign_event( + sign_event, + entropy, + sign_tx, + node_near_account_id, + total_timeout, + backlog, + ) + .await } // Reference: https://github.com/solana-foundation/anchor/blob/a5df519319ac39cff21191f2b09d54eda42c5716/client/src/lib.rs#L31 @@ -931,167 +894,56 @@ async fn subscribe_to_program_respond_events( continue; }; for ev in respond_bidirectional_events { - let sign_id = SignId::new(ev.request_id); - tracing::info!(?sign_id, "processing RespondBidirectionalEvent"); - if backlog.remove(Chain::Solana, &sign_id).await.is_some() { - tracing::info!(?sign_id, "bidirectional tx completed"); - } else { - tracing::warn!(?sign_id, "bidirectional tx not found on completion"); - } - - if let Err(err) = sign_tx.send(Sign::Completion(sign_id)).await { - tracing::error!( - ?sign_id, - ?err, - "failed to send completion for respond bidirectional" - ); + if let Err(err) = crate::indexer_common::process_respond_bidirectional_event( + crate::indexer_common::RespondBidirectionalEvent::Solana(ev), + sign_tx.clone(), + &backlog, + ) + .await + { + tracing::error!(?err, "failed to process respond bidirectional event"); } } for ev in respond_events { - let sign_id = SignId::new(ev.request_id); - - let Some(sign_type) = backlog.sign_type(Chain::Solana, &sign_id).await else { - tracing::warn!( - ?sign_id, - "sign type not found for respond event (may have already been processed)" - ); - continue; - }; - let event = match sign_type { - SignRequestType::SignBidirectional(event) => event, - SignRequestType::Sign => { - tracing::info!(?sign_id, "sign request completed successfully"); - backlog.remove(Chain::Solana, &sign_id).await; - if let Err(err) = sign_tx.send(Sign::Completion(sign_id)).await { - tracing::error!( - ?sign_id, - ?err, - "failed to send completion for respond event" - ); - } - continue; - } - SignRequestType::RespondBidirectional(_) => { - tracing::warn!(?sign_id, "RespondBidirectional received respond event?"); - continue; - } - }; - - tracing::info!(?sign_id, "bidirectional processing initial respond event"); - let Ok(target_chain) = Chain::from_str(&event.dest).inspect_err(|err| { - tracing::warn!(?sign_id, %err, "unable to parse target chain from dest"); - }) else { - continue; - }; - - let Some(BacklogTransaction::Sign(_)) = backlog.get(Chain::Solana, &sign_id).await - else { - tracing::warn!(?sign_id, "bidirectional tx not found for advancement"); - continue; - }; - - // Create a 65-byte uncompressed point representation (0x04 || x || y) - let mut big_r = [0u8; 65]; - big_r[0] = 0x04; - big_r[1..33].copy_from_slice(&ev.signature.big_r.x); - big_r[33..65].copy_from_slice(&ev.signature.big_r.y); - - let Ok(big_r) = k256::EncodedPoint::from_bytes(big_r).inspect_err(|err| { - tracing::warn!(?sign_id, %err, "unable to parse big_r for encoded point"); - }) else { - continue; - }; - let big_r_ct_opt = AffinePoint::from_encoded_point(&big_r); - let big_r = if bool::from(big_r_ct_opt.is_some()) { - big_r_ct_opt.unwrap() - } else { - tracing::warn!(?sign_id, "failed to create AffinePoint from encoded point"); - continue; - }; - - let Some(s) = Scalar::from_bytes(ev.signature.s) else { - tracing::warn!(?sign_id, "failed to create Scalar from s bytes"); - continue; - }; - - let mpc_sig = mpc_primitives::Signature { - big_r, - s, - recovery_id: ev.signature.recovery_id, - }; - - // Sign and hash the transaction to get the correct tx_id and nonce - let (signed_tx_hash, nonce) = crate::sign_bidirectional::sign_and_hash_transaction( - &event.serialized_transaction, - mpc_sig, - )?; - - let tx_id = BidirectionalTxId(signed_tx_hash.into()); - - // Get the MPC public key and derive the from_address - let root_public_key = contract_watcher.wait_public_key().await; - let epsilon = mpc_crypto::kdf::derive_epsilon_sol( - event.key_version, - &event.sender.to_string(), - &event.path, - ); - let from_address = - crate::sign_bidirectional::derive_user_address(root_public_key, epsilon); - - let bidirectional_tx = BidirectionalTx { - id: tx_id, - sender: event.sender, - serialized_transaction: event.serialized_transaction, - source_chain: Chain::Solana, - target_chain, - caip2_id: event.caip2_id, - key_version: event.key_version, - deposit: event.deposit, - path: event.path.clone(), - algo: event.algo.clone(), - dest: event.dest.clone(), - params: event.params.clone(), - output_deserialization_schema: event.output_deserialization_schema.clone(), - respond_serialization_schema: event.respond_serialization_schema.clone(), - request_id: ev.request_id, - from_address, - nonce, - status: PendingRequestStatus::AwaitingResponse, - }; - - tracing::info!( - ?sign_id, - ?tx_id, - nonce = ?bidirectional_tx.nonce, - from_address = ?bidirectional_tx.from_address, - "bidirectional tx details before advancement", - ); - - match backlog - .advance(Chain::Solana, sign_id, bidirectional_tx) - .await + if let Err(err) = crate::indexer_common::process_respond_event( + crate::indexer_common::SignatureRespondedEvent::Solana(ev), + sign_tx.clone(), + &mut contract_watcher, + &backlog, + ) + .await { - Ok(_) => { - tracing::info!( - ?sign_id, - ?tx_id, - ?target_chain, - "advance bidirectional tx to execution successful" - ); - } - Err(err) => { - tracing::error!( - ?sign_id, - ?tx_id, - ?target_chain, - ?err, - "advance bidirectional tx to execution failed" - ); - } + tracing::error!(?err, "failed to process respond event"); } } } Ok(()) } + +pub fn to_mpc_signature( + sig: signet_program::Signature, +) -> anyhow::Result { + // Create a 65-byte uncompressed point representation (0x04 || x || y) + let mut big_r = [0u8; 65]; + big_r[0] = 0x04; + big_r[1..33].copy_from_slice(&sig.big_r.x); + big_r[33..65].copy_from_slice(&sig.big_r.y); + + let big_r = k256::EncodedPoint::from_bytes(big_r) + .map_err(|err| anyhow::anyhow!("unable to parse big_r for encoded point: {err}"))?; + let big_r_ct_opt = AffinePoint::from_encoded_point(&big_r); + let big_r = big_r_ct_opt + .into_option() + .ok_or_else(|| anyhow::anyhow!("failed to create AffinePoint from encoded point"))?; + + let s = Scalar::from_bytes(sig.s) + .ok_or_else(|| anyhow::anyhow!("failed to create Scalar from s bytes"))?; + + Ok(mpc_primitives::Signature { + big_r, + s, + recovery_id: sig.recovery_id, + }) +} diff --git a/chain-signatures/node/src/lib.rs b/chain-signatures/node/src/lib.rs index 69131d95a..163630c6e 100644 --- a/chain-signatures/node/src/lib.rs +++ b/chain-signatures/node/src/lib.rs @@ -5,7 +5,9 @@ pub mod cli; pub mod config; pub mod gcp; pub mod indexer; +pub mod indexer_common; pub mod indexer_eth; +pub mod indexer_hydration; pub mod indexer_sol; pub mod kdf; pub mod logs; diff --git a/chain-signatures/node/src/protocol/mod.rs b/chain-signatures/node/src/protocol/mod.rs index 372dce3bf..58d6b361c 100644 --- a/chain-signatures/node/src/protocol/mod.rs +++ b/chain-signatures/node/src/protocol/mod.rs @@ -19,7 +19,6 @@ pub use cryptography::CryptographicError; pub use message::{Message, MessageChannel}; pub use mpc_primitives::Chain; pub use signature::{IndexedSignRequest, Sign}; -use signet_program::SignBidirectionalEvent; pub use state::{Node, NodeState}; use crate::backlog::Backlog; @@ -238,7 +237,7 @@ pub async fn spawn_system_metrics(node_account_id: &str) -> tokio::task::JoinHan #[allow(clippy::large_enum_variant)] pub enum SignRequestType { Sign, - SignBidirectional(SignBidirectionalEvent), + SignBidirectional(crate::indexer_common::SignBidirectionalEvent), RespondBidirectional(RespondBidirectionalTx), } diff --git a/chain-signatures/node/src/protocol/signature.rs b/chain-signatures/node/src/protocol/signature.rs index d08bebae9..e199b789a 100644 --- a/chain-signatures/node/src/protocol/signature.rs +++ b/chain-signatures/node/src/protocol/signature.rs @@ -930,7 +930,7 @@ impl SignGenerator { tracing::info!( ?sign_id, source_chain = ?self.indexed.chain, - target_chain = ?event.dest, + target_chain = ?event.dest(), "generated signature for bidirectional request, awaiting indexer to process" ); } diff --git a/chain-signatures/node/src/rpc.rs b/chain-signatures/node/src/rpc.rs index d5ba4acf9..f787fce06 100644 --- a/chain-signatures/node/src/rpc.rs +++ b/chain-signatures/node/src/rpc.rs @@ -41,6 +41,14 @@ use std::time::{Duration, Instant}; use tokio::sync::{mpsc, watch}; use url::Url; +use crate::indexer_hydration::HydrationConfig; +use hydration::runtime_types::bounded_collections::bounded_vec::BoundedVec as HydrationBoundedVec; +use hydration::runtime_types::pallet_signet::pallet::{ + AffinePoint as HydrationAffinePoint, Signature as HydrationSignature, +}; +use subxt::{OnlineClient, SubstrateConfig}; +use subxt_signer::{sr25519, SecretUri}; + /// The maximum amount of times to retry publishing a signature. const MAX_PUBLISH_RETRY: usize = 6; /// The maximum number of concurrent RPC requests the system can make @@ -319,19 +327,31 @@ pub struct RpcExecutor { near: NearClient, eth: Option, solana: Option, + hydration: Option, action_rx: mpsc::Receiver, backlog: Backlog, } impl RpcExecutor { - pub fn new( + pub async fn new( near: &NearClient, eth: &Option, solana: &Option, + hydration: &Option, backlog: Backlog, ) -> (RpcChannel, Self) { let eth = eth.as_ref().map(EthClient::new); let solana = solana.as_ref().map(SolanaClient::new); + let hydration = match hydration { + Some(h) => match HydrationClient::new(h).await { + Ok(client) => Some(client), + Err(e) => { + tracing::error!(%e, "failed to create hydration client"); + None + } + }, + None => None, + }; let (tx, rx) = mpsc::channel(MAX_CONCURRENT_RPC_REQUESTS); ( RpcChannel { tx }, @@ -339,6 +359,7 @@ impl RpcExecutor { near: near.clone(), eth, solana, + hydration, action_rx: rx, backlog, }, @@ -390,7 +411,7 @@ impl RpcExecutor { tokio::spawn(async move { match chain { - Chain::NEAR | Chain::Solana => { + Chain::NEAR | Chain::Solana | Chain::Hydration => { execute_publish(client, action, near_account_id, backlog).await; } Chain::Ethereum => { @@ -421,6 +442,13 @@ impl RpcExecutor { ChainClient::Err("no solana client available for node") } } + Chain::Hydration => { + if let Some(hydration) = &self.hydration { + ChainClient::Hydration(hydration.clone()) + } else { + ChainClient::Err("no hydration client available for node") + } + } } } } @@ -642,6 +670,111 @@ impl SolanaClient { } } +#[subxt::subxt(runtime_metadata_path = "src/indexer_hydration/artifacts/hydration_metadata.scale")] +pub mod hydration {} +#[derive(Clone)] +pub struct HydrationClient { + api: OnlineClient, + signer: sr25519::Keypair, +} +type TxHash = subxt::config::HashFor; + +impl HydrationClient { + /// Create a new Hydration client. + /// + /// `rpc_url`: e.g. "ws://127.0.0.1:9944" or "wss://rpc.hydration.cloud" + /// `signer_uri`: sr25519 secret URI, e.g. "//Alice" or a mnemonic. + pub async fn new(config: &HydrationConfig) -> anyhow::Result { + let api = OnlineClient::::from_url(&config.rpc_ws_url).await?; + + let uri = SecretUri::from_str(&config.signer_uri)?; + let signer = sr25519::Keypair::from_uri(&uri)?; + + Ok(Self { api, signer }) + } + + /// Helper to convert your MPC signature type into the pallet_signet Signature. + fn to_hydration_signature(sig: &Signature) -> HydrationSignature { + let enc = sig.big_r.to_encoded_point(false); + + let x: [u8; 32] = enc + .x() + .map(|x| x.as_slice()) + .map(|x| x.try_into().expect("x must be 32 bytes")) + .expect("missing x"); + let y: [u8; 32] = enc + .y() + .map(|y| y.as_slice()) + .map(|y| y.try_into().expect("y must be 32 bytes")) + .expect("missing y"); + + let s_bytes: [u8; 32] = sig.s.to_bytes().into(); + + HydrationSignature { + big_r: HydrationAffinePoint { x, y }, + s: s_bytes, + recovery_id: sig.recovery_id, + } + } + + /// Call the Signet pallet's `respond()` extrinsic for a *single* request. + /// + /// You can extend this to true batching later; for now we just send 1-element + /// vectors, which is perfectly fine for the BoundedVec arguments. + pub async fn call_respond(&self, id: &SignId, response: &Signature) -> anyhow::Result<()> { + // Signet's request_id is a [u8; 32]; you already use it for Ethereum: + // DynSolValue::FixedBytes(action.indexed.id.request_id.into(), 32) + // + // The `respond` extrinsic expects `BoundedVec` arguments, so wrap the 1-element + // `Vec` values in the generated `HydrationBoundedVec` newtype. + let request_ids = HydrationBoundedVec(vec![id.request_id]); + + let signatures = HydrationBoundedVec(vec![Self::to_hydration_signature(response)]); + + // Build the call: signet::respond(request_ids, signatures) + let tx = hydration::tx().signet().respond(request_ids, signatures); + + // Sign, submit, and wait for finalized success. + let progress = self + .api + .tx() + .sign_and_submit_then_watch_default(&tx, &self.signer) + .await?; + + let _events = progress.wait_for_finalized_success().await?; + + Ok(()) + } + + pub async fn call_respond_bidirectional( + &self, + id: &SignId, + serialized_output: Vec, + response: &Signature, + ) -> anyhow::Result { + let request_id: [u8; 32] = id.request_id; + let hyd_sig = Self::to_hydration_signature(response); + + let tx = hydration::tx().signet().respond_bidirectional( + request_id, + HydrationBoundedVec(serialized_output), + hyd_sig, + ); + + let progress = self + .api + .tx() + .sign_and_submit_then_watch_default(&tx, &self.signer) + .await?; + + let events = progress.wait_for_finalized_success().await?; + + let tx_hash = events.extrinsic_hash(); + + Ok(tx_hash) + } +} + /// Client related to a specific chain #[allow(clippy::large_enum_variant)] pub enum ChainClient { @@ -649,6 +782,7 @@ pub enum ChainClient { Near(NearClient), Ethereum(EthClient), Solana(SolanaClient), + Hydration(HydrationClient), } async fn update_contract(near: NearClient, contract: watch::Sender>) { @@ -737,6 +871,15 @@ async fn execute_publish( ) .await .map_err(|_| ()), + ChainClient::Hydration(hyd) => try_publish_hydration( + hyd, + &action, + &action.timestamp, + &signature, + &near_account_id, + ) + .await + .map_err(|_| ()), ChainClient::Err(msg) => { tracing::warn!(msg, "no client for chain"); Ok(()) @@ -1254,6 +1397,10 @@ async fn execute_batch_publish( ChainClient::Ethereum(eth) => { try_batch_publish_eth(eth, actions, &signatures, near_account_id, start).await } + ChainClient::Hydration(_) => { + tracing::error!("Hydration has no batch publish"); + Ok(()) + } ChainClient::Err(msg) => { tracing::warn!(msg, "no client for chain"); Ok(()) @@ -1400,3 +1547,91 @@ async fn try_publish_sol( .observe(timestamp.elapsed().as_secs_f64()); Ok(()) } + +async fn try_publish_hydration( + hyd: &HydrationClient, + action: &PublishAction, + timestamp: &Instant, + signature: &Signature, + near_account_id: &AccountId, // you already pass this around for metrics labels +) -> Result<(), ()> { + let chain = action.indexed.chain; + let sign_id = action.indexed.id; + let request_ids = [action.indexed.id.request_id]; + + tracing::info!( + ?sign_id, + ?chain, + elapsed = ?timestamp.elapsed(), + request_id = ?request_ids[0], + "Hydration: publishing signature" + ); + + match &action.indexed.sign_request_type { + SignRequestType::Sign | SignRequestType::SignBidirectional(_) => { + hyd.call_respond(&action.indexed.id, signature) + .await + .map_err(|e| { + tracing::error!(?sign_id, ?e, "Hydration: failed to publish signature"); + crate::metrics::SIGNATURE_PUBLISH_FAILURES + .with_label_values(&[chain.as_str(), near_account_id.as_str()]) + .inc(); + })?; + tracing::info!( + ?sign_id, + elapsed = ?timestamp.elapsed(), + "published hydration signature successfully" + ); + } + SignRequestType::RespondBidirectional(respond_bidirectional_tx) => { + let serialized_output = respond_bidirectional_tx.output.clone(); + tracing::debug!( + ?sign_id, + request_id = ?request_ids[0], + serialized_output_len = serialized_output.len(), + "try_publish_hydration: entering RespondBidirectional arm" + ); + let tx_hash = hyd + .call_respond_bidirectional(&action.indexed.id, serialized_output, signature) + .await + .map_err(|e| { + tracing::error!( + ?sign_id, + ?e, + "Hydration: failed to publish respond bidirectional signature" + ); + crate::metrics::SIGNATURE_PUBLISH_FAILURES + .with_label_values(&[chain.as_str(), near_account_id.as_str()]) + .inc(); + })?; + tracing::info!( + ?sign_id, + tx_hash = ?tx_hash, + elapsed = ?timestamp.elapsed(), + "published respond bidirectional hydration signature successfully" + ); + } + } + + crate::metrics::NUM_SIGN_SUCCESS + .with_label_values(&[chain.as_str(), near_account_id.as_str()]) + .inc(); + let sign_latency_in_secs = crate::util::duration_between_unix( + action.indexed.unix_timestamp_indexed, + crate::util::current_unix_timestamp(), + ) + .as_secs(); + crate::metrics::SIGN_TOTAL_LATENCY + .with_label_values(&[chain.as_str(), near_account_id.as_str()]) + .observe(sign_latency_in_secs as f64); + crate::metrics::SIGN_RESPOND_LATENCY + .with_label_values(&[chain.as_str(), near_account_id.as_str()]) + .observe(timestamp.elapsed().as_secs_f64()); + if sign_latency_in_secs <= 30 { + crate::metrics::NUM_SIGN_SUCCESS_30S + .with_label_values(&[chain.as_str(), near_account_id.as_str()]) + .inc(); + } + + Ok(()) +} diff --git a/chain-signatures/node/src/sign_bidirectional.rs b/chain-signatures/node/src/sign_bidirectional.rs index 72243286b..0e91f1664 100644 --- a/chain-signatures/node/src/sign_bidirectional.rs +++ b/chain-signatures/node/src/sign_bidirectional.rs @@ -74,11 +74,11 @@ impl BidirectionalTx { anyhow::bail!("sign request is not a sign bidirectional"); }; - let unsigned_rlp_data = &event.serialized_transaction; - let target_chain = Chain::from_str(&event.dest).map_err(|err| { + let unsigned_rlp_data = &event.serialized_transaction(); + let target_chain = Chain::from_str(&event.dest()).map_err(|err| { anyhow::anyhow!( "invalid target chain '{}' for bidirectional transaction: {err}", - event.dest + event.dest() ) })?; let source_chain = signature.indexed.chain; @@ -95,19 +95,19 @@ impl BidirectionalTx { Ok(Self { id: BidirectionalTxId(signed_transaction_hash.into()), - sender: event.sender, - serialized_transaction: event.serialized_transaction, + sender: event.sender(), + serialized_transaction: event.serialized_transaction(), source_chain, target_chain, - caip2_id: event.caip2_id, - key_version: event.key_version, - deposit: event.deposit, - path: event.path, - algo: event.algo, - dest: event.dest, - params: event.params, - output_deserialization_schema: event.output_deserialization_schema, - respond_serialization_schema: event.respond_serialization_schema, + caip2_id: event.caip2_id(), + key_version: event.key_version(), + deposit: event.deposit(), + path: event.path(), + algo: event.algo(), + dest: event.dest(), + params: event.params(), + output_deserialization_schema: event.output_deserialization_schema(), + respond_serialization_schema: event.respond_serialization_schema(), request_id: signature.indexed.id.request_id, from_address, nonce, diff --git a/chain-signatures/primitives/src/lib.rs b/chain-signatures/primitives/src/lib.rs index 60b84f549..0e94a0a09 100644 --- a/chain-signatures/primitives/src/lib.rs +++ b/chain-signatures/primitives/src/lib.rs @@ -125,6 +125,7 @@ pub enum Chain { NEAR, Ethereum, Solana, + Hydration, } impl Chain { @@ -133,11 +134,17 @@ impl Chain { Chain::NEAR => "NEAR", Chain::Ethereum => "Ethereum", Chain::Solana => "Solana", + Chain::Hydration => "Hydration", } } - pub const fn iter() -> [Chain; 3] { - [Chain::NEAR, Chain::Ethereum, Chain::Solana] + pub const fn iter() -> [Chain; 4] { + [ + Chain::NEAR, + Chain::Ethereum, + Chain::Solana, + Chain::Hydration, + ] } pub fn checkpoint_interval(&self) -> Option { @@ -145,6 +152,7 @@ impl Chain { Chain::NEAR => return None, Chain::Ethereum => ("CHECKPOINT_INTERVAL_ETHEREUM", 20), Chain::Solana => ("CHECKPOINT_INTERVAL_SOLANA", 120), + Chain::Hydration => ("CHECKPOINT_INTERVAL_HYDRATION", 120), }; let interval = std::env::var(key) @@ -158,6 +166,7 @@ impl Chain { vec![ ("CHECKPOINT_INTERVAL_ETHEREUM", "2"), ("CHECKPOINT_INTERVAL_SOLANA", "5"), + ("CHECKPOINT_INTERVAL_HYDRATION", "5"), ] } } @@ -176,6 +185,7 @@ impl FromStr for Chain { "near" => Ok(Chain::NEAR), "ethereum" | "eth" => Ok(Chain::Ethereum), "solana" | "sol" => Ok(Chain::Solana), + "hydration" | "hyd" => Ok(Chain::Hydration), other => Err(format!("unknown or unsupported chain {other}")), } } diff --git a/integration-tests/src/containers.rs b/integration-tests/src/containers.rs index 9566e6306..30e57f9ce 100644 --- a/integration-tests/src/containers.rs +++ b/integration-tests/src/containers.rs @@ -114,6 +114,8 @@ impl Node { }; let eth_args = EthArgs::from_config(config.cfg.eth.clone()); let sol_args = mpc_node::indexer_sol::SolArgs::from_config(config.cfg.sol.clone()); + let hydration_args = + mpc_node::indexer_hydration::HydrationArgs::from_config(config.cfg.hydration.clone()); let args = mpc_node::cli::Cli::Start { near_rpc: config.near_rpc.clone(), mpc_contract_id: ctx.mpc_contract.id().clone(), @@ -124,6 +126,7 @@ impl Node { indexer_options: indexer_options.clone(), eth: eth_args, sol: sol_args, + hydration: hydration_args, my_address: None, storage_options: ctx.storage_options.clone(), log_options: ctx.log_options.clone(), diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 13f71aad7..d0fbe1bcc 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -22,6 +22,7 @@ use mpc_contract::config::{PresignatureConfig, ProtocolConfig, TripleConfig}; use mpc_contract::primitives::CandidateInfo; use mpc_node::gcp::GcpService; use mpc_node::indexer_eth::EthConfig; +use mpc_node::indexer_hydration::HydrationConfig; use mpc_node::indexer_sol::SolConfig; use mpc_node::storage::triple_storage::{TriplePair, TripleStorage}; use mpc_node::{logs, mesh, node_client, storage}; @@ -61,6 +62,7 @@ pub struct NodeConfig { pub protocol: ProtocolConfig, pub eth: Option, pub sol: Option, + pub hydration: Option, } impl Default for NodeConfig { @@ -85,6 +87,7 @@ impl Default for NodeConfig { }, eth: None, sol: None, + hydration: None, } } } diff --git a/integration-tests/src/local.rs b/integration-tests/src/local.rs index 6a8e5d78d..b486cad24 100644 --- a/integration-tests/src/local.rs +++ b/integration-tests/src/local.rs @@ -69,6 +69,8 @@ impl Node { }; let eth = mpc_node::indexer_eth::EthArgs::from_config(cfg.eth.clone()); let sol = mpc_node::indexer_sol::SolArgs::from_config(cfg.sol.clone()); + let hydration = + mpc_node::indexer_hydration::HydrationArgs::from_config(cfg.hydration.clone()); let near_rpc = ctx.worker.rpc_addr(); let mpc_contract_id = ctx.mpc_contract.id().clone(); let cli = mpc_node::cli::Cli::Start { @@ -81,6 +83,7 @@ impl Node { sign_sk: Some(sign_sk.clone()), eth, sol, + hydration, indexer_options, my_address: None, storage_options: ctx.storage_options.clone(), @@ -167,6 +170,8 @@ impl Node { let eth = EthArgs::from_config(config.cfg.eth.clone()); let sol = mpc_node::indexer_sol::SolArgs::from_config(config.cfg.sol.clone()); + let hydration = + mpc_node::indexer_hydration::HydrationArgs::from_config(config.cfg.hydration.clone()); let cli = mpc_node::cli::Cli::Start { near_rpc: config.near_rpc.clone(), mpc_contract_id: ctx.mpc_contract.id().clone(), @@ -177,6 +182,7 @@ impl Node { sign_sk: Some(config.sign_sk.clone()), eth, sol, + hydration, indexer_options, my_address: None, storage_options: ctx.storage_options.clone(), From b92ac9843fef61f3cbf4b7f5a01493cb49b23708 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 3 Dec 2025 10:57:08 +0800 Subject: [PATCH 02/11] add hydration indexer run to cli.rs --- chain-signatures/node/src/cli.rs | 9 +++++++++ chain-signatures/node/src/indexer_hydration/mod.rs | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/chain-signatures/node/src/cli.rs b/chain-signatures/node/src/cli.rs index e14ad7825..07ec85d25 100644 --- a/chain-signatures/node/src/cli.rs +++ b/chain-signatures/node/src/cli.rs @@ -378,6 +378,15 @@ pub async fn run(cmd: Cli) -> anyhow::Result<()> { tokio::spawn(indexer_sol::run( sol, + sign_tx.clone(), + account_id.clone(), + backlog.clone(), + contract_watcher.clone(), + mesh_state.clone(), + client.clone(), + )); + tokio::spawn(indexer_hydration::run( + hydration, sign_tx, account_id, backlog, diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index 67dbd95a7..d2ddddf5b 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -476,7 +476,9 @@ pub async fn run( }; let total_timeout = Duration::from_secs(hydration.total_timeout); - let ws_url = "ws://127.0.0.1:9944"; + let ws_url = "ws://127.0.0.1:8000"; + + tracing::info!("connecting to hydration rpc at {}", ws_url); // High‑level Subxt client for blocks + events. let hydration_api = OnlineClient::::from_url(ws_url).await?; @@ -499,6 +501,7 @@ pub async fn run( let mut blocks = hydration_api.blocks().subscribe_finalized().await?; while let Some(block_res) = blocks.next().await { + tracing::info!("received block from hydration rpc"); let block = block_res?; let number = block.number(); let hash = block.hash(); From 683c65e296eac7a1da60fe189c1ce2da6f831bb4 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 3 Dec 2025 13:07:35 +0800 Subject: [PATCH 03/11] minor --- chain-signatures/node/src/indexer_hydration/mod.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index d2ddddf5b..f506bc2f4 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -59,7 +59,10 @@ impl HydrationArgs { args.extend(["--hydration-signer-uri".to_string(), signer_uri]); } if let Some(total_timeout) = self.total_timeout { - args.extend(["--total-timeout".to_string(), total_timeout.to_string()]); + args.extend([ + "--hydration-total-timeout".to_string(), + total_timeout.to_string(), + ]); } args } @@ -180,7 +183,7 @@ impl SignatureEventTrait for HydrationSignatureRequestedEvent { let Some(payload) = Scalar::from_bytes(self.payload) else { tracing::warn!( - "solana `sign` did not produce payload hash correctly: {:?}", + "hydration `sign` did not produce payload hash correctly: {:?}", self.payload, ); anyhow::bail!("failed to convert event payload hash to scalar"); @@ -198,7 +201,7 @@ impl SignatureEventTrait for HydrationSignatureRequestedEvent { ); let sign_id = SignId::new(self.generate_request_id()); - tracing::info!(?sign_id, "solana signature requested"); + tracing::info!(?sign_id, "hydration signature requested"); Ok(IndexedSignRequest { id: sign_id, @@ -292,7 +295,7 @@ impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { entropy: [u8; 32], total_timeout: Duration, ) -> anyhow::Result { - tracing::info!("found solana event: {:?}", self); + tracing::info!("found hydration event: {:?}", self); if self.deposit == 0 { tracing::warn!("deposit is 0, skipping sign request"); anyhow::bail!("deposit is 0"); @@ -335,7 +338,7 @@ impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { path: self.path.clone(), key_version: self.key_version, }, - chain: Chain::Solana, + chain: Chain::Hydration, timestamp_sign_queue: Instant::now(), unix_timestamp_indexed: crate::util::current_unix_timestamp(), total_timeout, From 1c3ef3518564e9ce8a7945645ab926c80bc08ae5 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 3 Dec 2025 13:37:19 +0800 Subject: [PATCH 04/11] make run() return nothing --- .../node/src/indexer_hydration/mod.rs | 120 +++++++++++++----- 1 file changed, 89 insertions(+), 31 deletions(-) diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index f506bc2f4..3f1e3b2d4 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -40,12 +40,16 @@ use tokio::sync::watch; #[group(id = "indexer_hydration_options")] pub struct HydrationArgs { /// Hydration RPC ws URL - #[clap(long, env("MPC_HYDRATION_RPC_WS_URL"))] + #[clap(long = "hydration-rpc-ws-url", env("MPC_HYDRATION_RPC_WS_URL"))] pub rpc_ws_url: Option, /// Hydration signer URI - #[clap(long, env("MPC_HYDRATION_SIGNER_URI"))] + #[clap(long = "hydration-signer-uri", env("MPC_HYDRATION_SIGNER_URI"))] pub signer_uri: Option, - #[clap(long, env("MPC_HYDRATION_TOTAL_TIMEOUT"), default_value = "200")] + #[clap( + long = "hydration-total-timeout", + env("MPC_HYDRATION_TOTAL_TIMEOUT"), + default_value = "200" + )] pub total_timeout: Option, } @@ -472,10 +476,10 @@ pub async fn run( mut contract_watcher: ContractStateWatcher, mut mesh_state: watch::Receiver, node_client: NodeClient, -) -> Result<()> { +) { let Some(hydration) = hydration else { tracing::warn!("hydration indexer is disabled"); - return Ok(()); + return; }; let total_timeout = Duration::from_secs(hydration.total_timeout); @@ -484,10 +488,23 @@ pub async fn run( tracing::info!("connecting to hydration rpc at {}", ws_url); // High‑level Subxt client for blocks + events. - let hydration_api = OnlineClient::::from_url(ws_url).await?; - + let hydration_api = OnlineClient::::from_url(ws_url).await; + let hydration_api = match hydration_api { + Ok(api) => api, + Err(e) => { + tracing::error!("failed to connect to hydration rpc: {e}"); + return; + } + }; // Low‑level RPC client for legacy methods like state_get_read_proof. - let rpc_client = RpcClient::from_url(ws_url).await?; + let rpc_client = RpcClient::from_url(ws_url).await; + let rpc_client = match rpc_client { + Ok(client) => client, + Err(e) => { + tracing::error!("failed to connect to hydration rpc: {e}"); + return; + } + }; let legacy_rpc = LegacyRpcMethods::::new(rpc_client); // Wait for threshold to be available @@ -501,11 +518,23 @@ pub async fn run( .await; // Subscribe to finalized Hydration blocks. - let mut blocks = hydration_api.blocks().subscribe_finalized().await?; + let mut blocks = match hydration_api.blocks().subscribe_finalized().await { + Ok(blocks) => blocks, + Err(e) => { + tracing::error!("failed to subscribe to finalized blocks: {e}"); + return; + } + }; while let Some(block_res) = blocks.next().await { tracing::info!("received block from hydration rpc"); - let block = block_res?; + let block = match block_res { + Ok(block) => block, + Err(e) => { + tracing::error!("failed to get block: {e}"); + continue; + } + }; let number = block.number(); let hash = block.hash(); let header = block.header().clone(); @@ -514,20 +543,33 @@ pub async fn run( let state_root: H256 = header.state_root; // Events as decoded by Subxt (unproven bytes). - let events = block.events().await?; + let events = match block.events().await { + Ok(events) => events, + Err(e) => { + tracing::error!("failed to get events: {e}"); + continue; + } + }; // Raw SCALE bytes for `System::Events` that Subxt decoded. let events_bytes_unproven = events.bytes().to_vec(); // Events bytes proven via storage proof under state_root. let events_bytes_proven = - fetch_proven_system_events_bytes(&legacy_rpc, state_root, hash).await?; + match fetch_proven_system_events_bytes(&legacy_rpc, state_root, hash).await { + Ok(events_bytes_proven) => events_bytes_proven, + Err(e) => { + tracing::error!("failed to fetch proven system events bytes: {e}"); + continue; + } + }; // Sanity check: bytes that Subxt decoded must match the Merkle‑proven bytes. if events_bytes_unproven != events_bytes_proven { - return Err(anyhow!( + tracing::error!( "Mismatch between RPC events and Merkle‑proven System::Events \ in block #{number} ({hash:?})" - )); + ); + continue; } // At this point: @@ -542,10 +584,16 @@ pub async fn run( let backlog = backlog.clone(); for ev in events.iter() { - let ev = ev?; + let ev = match ev { + Ok(ev) => ev, + Err(e) => { + tracing::error!("failed to get event: {e}"); + continue; + } + }; // SignatureRequested - if let Some(req) = ev.as_event::()? { + if let Ok(Some(req)) = ev.as_event::() { let event = HydrationSignatureRequestedEvent::from(req); tracing::info!( "Hydration::Signet::SignatureRequested in block #{number} ({hash:?}): {:?}", @@ -554,7 +602,7 @@ pub async fn run( let entropy: [u8; 32] = rand::random(); - crate::indexer_common::process_sign_event( + if let Err(e) = crate::indexer_common::process_sign_event( Box::new(event), entropy, sign_tx.clone(), @@ -562,28 +610,34 @@ pub async fn run( total_timeout, backlog.clone(), ) - .await?; + .await + { + tracing::error!("failed to process sign event: {e}"); + } } // SignatureResponded - if let Some(resp) = ev.as_event::()? { + if let Ok(Some(resp)) = ev.as_event::() { let event = HydrationSignatureRespondedEvent::from(resp); tracing::info!( "Hydration::Signet::SignatureResponded in block #{number} ({hash:?}): {:?}", event ); - crate::indexer_common::process_respond_event( + if let Err(e) = crate::indexer_common::process_respond_event( crate::indexer_common::SignatureRespondedEvent::Hydration(event), sign_tx.clone(), &mut contract_watcher, &backlog, ) - .await?; + .await + { + tracing::error!("failed to process respond event: {e}"); + } } // Bidirectional request - if let Some(req_bi) = - ev.as_event::()? + if let Ok(Some(req_bi)) = + ev.as_event::() { let event = HydrationSignBidirectionalRequestedEvent::from(req_bi); tracing::info!( @@ -593,7 +647,7 @@ pub async fn run( let entropy: [u8; 32] = rand::random(); - crate::indexer_common::process_sign_event( + if let Err(e) = crate::indexer_common::process_sign_event( Box::new(event), entropy, sign_tx.clone(), @@ -601,27 +655,31 @@ pub async fn run( total_timeout, backlog.clone(), ) - .await?; + .await + { + tracing::error!("failed to process sign event: {e}"); + } } // Bidirectional response - if let Some(resp_bi) = - ev.as_event::()? + if let Ok(Some(resp_bi)) = + ev.as_event::() { let event = HydrationRespondBidirectionalEvent::from(resp_bi); tracing::info!( "Hydration::Signet::RespondBidirectionalEvent in block #{number} ({hash:?}): {:?}", event ); - crate::indexer_common::process_respond_bidirectional_event( + if let Err(e) = crate::indexer_common::process_respond_bidirectional_event( crate::indexer_common::RespondBidirectionalEvent::Hydration(event), sign_tx.clone(), &backlog, ) - .await?; + .await + { + tracing::error!("failed to process respond bidirectional event: {e}"); + } } } } - - Ok(()) } From 326f7159f8f05579b997ffa5f3c10a9e7a7b899f Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Wed, 10 Dec 2025 17:50:41 +0800 Subject: [PATCH 05/11] use same entropy --- chain-signatures/node/src/indexer_hydration/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index 3f1e3b2d4..41f48e180 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -600,7 +600,7 @@ pub async fn run( event ); - let entropy: [u8; 32] = rand::random(); + let entropy: [u8; 32] = ev.bytes().to_vec()[..32].try_into().unwrap(); if let Err(e) = crate::indexer_common::process_sign_event( Box::new(event), From 0fff87fca5683a23df3fdb6c1897ef5a2625f96f Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Thu, 11 Dec 2025 19:18:32 +0800 Subject: [PATCH 06/11] use same entropy for sign bidirectional as well --- chain-signatures/node/src/indexer_hydration/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index 41f48e180..c2483e4f9 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -645,7 +645,7 @@ pub async fn run( event ); - let entropy: [u8; 32] = rand::random(); + let entropy: [u8; 32] = ev.bytes().to_vec()[..32].try_into().unwrap(); if let Err(e) = crate::indexer_common::process_sign_event( Box::new(event), From 4aa13582419c2f167ffc677a5fdea89fabf0587f Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Sat, 13 Dec 2025 15:55:27 +0800 Subject: [PATCH 07/11] fix all errors except read respond addr match --- chain-signatures/node/src/backlog/mod.rs | 2 +- chain-signatures/node/src/indexer_common.rs | 47 ++++++++++++------ chain-signatures/node/src/indexer_eth/mod.rs | 10 +++- .../artifacts/hydration_metadata.scale | Bin 487324 -> 490290 bytes .../node/src/indexer_hydration/mod.rs | 21 +++++--- .../node/src/respond_bidirectional.rs | 18 +++---- chain-signatures/node/src/rpc.rs | 24 +++++++-- .../node/src/sign_bidirectional.rs | 28 ++++++++++- 8 files changed, 113 insertions(+), 37 deletions(-) diff --git a/chain-signatures/node/src/backlog/mod.rs b/chain-signatures/node/src/backlog/mod.rs index 5eebedc88..16663e658 100644 --- a/chain-signatures/node/src/backlog/mod.rs +++ b/chain-signatures/node/src/backlog/mod.rs @@ -741,7 +741,7 @@ mod tests { fn create_test_tx(id: u8, status: PendingRequestStatus) -> BidirectionalTx { BidirectionalTx { id: BidirectionalTxId(B256::from([id; 32])), - sender: Pubkey::new_unique(), + sender: [0u8; 32], serialized_transaction: vec![1, 2, 3], source_chain: Chain::Solana, target_chain: Chain::Ethereum, diff --git a/chain-signatures/node/src/indexer_common.rs b/chain-signatures/node/src/indexer_common.rs index fddd9ab7b..4dd1c04e7 100644 --- a/chain-signatures/node/src/indexer_common.rs +++ b/chain-signatures/node/src/indexer_common.rs @@ -16,7 +16,6 @@ use crate::rpc::ContractStateWatcher; use crate::sign_bidirectional::BidirectionalTx; use crate::sign_bidirectional::BidirectionalTxId; use crate::sign_bidirectional::PendingRequestStatus; -use anchor_lang::prelude::Pubkey; use k256::Scalar; use mpc_primitives::SignId; use mpc_primitives::Signature; @@ -33,10 +32,19 @@ pub enum SignBidirectionalEvent { } impl SignBidirectionalEvent { - pub fn sender(&self) -> Pubkey { + pub fn sender(&self) -> [u8; 32] { match self { - SignBidirectionalEvent::Solana(event) => event.sender, - SignBidirectionalEvent::Hydration(event) => Pubkey::new_from_array(event.sender), + SignBidirectionalEvent::Solana(event) => event.sender.to_bytes(), + SignBidirectionalEvent::Hydration(event) => event.sender, + } + } + + pub fn sender_string(&self) -> String { + match self { + SignBidirectionalEvent::Solana(event) => event.sender.to_string(), + SignBidirectionalEvent::Hydration(event) => { + crate::indexer_hydration::ss58_address_from_account32(event.sender) + } } } @@ -112,15 +120,15 @@ impl SignBidirectionalEvent { pub fn epsilon(&self) -> Scalar { match self { - SignBidirectionalEvent::Solana(event) => mpc_crypto::kdf::derive_epsilon_sol( - event.key_version, - &event.sender.to_string(), - &event.path, + SignBidirectionalEvent::Solana(_) => mpc_crypto::kdf::derive_epsilon_sol( + self.key_version(), + &self.sender_string(), + &self.path(), ), - SignBidirectionalEvent::Hydration(event) => mpc_crypto::kdf::derive_epsilon_hydration( - event.key_version, - &Pubkey::new_from_array(event.sender).to_string(), - &event.path, + SignBidirectionalEvent::Hydration(_) => mpc_crypto::kdf::derive_epsilon_hydration( + self.key_version(), + &self.sender_string(), + &self.path(), ), } } @@ -169,6 +177,13 @@ pub enum SignatureRespondedEvent { } impl SignatureRespondedEvent { + pub fn source_chain(&self) -> Chain { + match self { + SignatureRespondedEvent::Solana(_) => Chain::Solana, + SignatureRespondedEvent::Hydration(_) => Chain::Hydration, + } + } + pub fn request_id(&self) -> [u8; 32] { match self { SignatureRespondedEvent::Solana(event) => event.request_id, @@ -301,7 +316,7 @@ pub(crate) async fn process_respond_event( SignRequestType::SignBidirectional(event) => event, SignRequestType::Sign => { tracing::info!(?sign_id, "sign request completed successfully"); - backlog.remove(Chain::Solana, &sign_id).await; + backlog.remove(respond_event.source_chain(), &sign_id).await; if let Err(err) = sign_tx.send(Sign::Completion(sign_id)).await { anyhow::bail!("failed to send completion for respond event: {err:?}"); } @@ -314,7 +329,11 @@ pub(crate) async fn process_respond_event( tracing::info!(?sign_id, "bidirectional processing initial respond event"); let target_chain = Chain::from_str(&event.dest()) - .map_err(|err| anyhow::anyhow!("unable to parse target chain from dest: {err:?}"))?; + .map_err(|err| anyhow::anyhow!("unable to parse target chain from dest: {err:?}")); + let target_chain = match target_chain { + Ok(chain) => chain, + Err(_) => Chain::Ethereum, + }; let Some(BacklogTransaction::Sign(_)) = backlog.get(source_chain, &sign_id).await else { anyhow::bail!("bidirectional tx not found for advancement: {sign_id:?}"); diff --git a/chain-signatures/node/src/indexer_eth/mod.rs b/chain-signatures/node/src/indexer_eth/mod.rs index 48168b12f..b81b97d57 100644 --- a/chain-signatures/node/src/indexer_eth/mod.rs +++ b/chain-signatures/node/src/indexer_eth/mod.rs @@ -1096,7 +1096,15 @@ impl EthereumIndexer { serialized_output, total_timeout, ) { - Ok(sign_request) => respond_requests.push(sign_request), + Ok(sign_request) => { + tracing::info!( + ?tx_id, + ?sign_id, + ?sign_request, + "sign_request from serialized output" + ); + respond_requests.push(sign_request); + } Err(err) => tracing::warn!( ?tx_id, ?sign_id, diff --git a/chain-signatures/node/src/indexer_hydration/artifacts/hydration_metadata.scale b/chain-signatures/node/src/indexer_hydration/artifacts/hydration_metadata.scale index 3635e19e3fe6499e4eef18b6535e820462b42370..68331d3206359666d7edddedb21d5dbd7bd62c23 100644 GIT binary patch delta 22495 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 K?A4+}mHz|UT?qpK delta 20821 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 diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index c2483e4f9..21f0a6263 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -8,9 +8,7 @@ use crate::node_client::NodeClient; use crate::protocol::{Chain, IndexedSignRequest, Sign, SignRequestType}; use crate::rpc::ContractStateWatcher; use crate::sign_bidirectional::hash_rlp_data; -use alloy::primitives::hex::ToHexExt; use alloy_sol_types::SolValue; -use anchor_lang::prelude::Pubkey; use anyhow::{anyhow, Result}; use ethabi::{encode, Token}; use hydration::runtime_types::pallet_signet::pallet::Signature as HydrationSignature; @@ -21,6 +19,7 @@ use mpc_primitives::Signature; use mpc_primitives::{SignArgs, SignId, LATEST_MPC_KEY_VERSION}; use near_account_id::AccountId; use sha3::{Digest, Keccak256}; +use sp_core::crypto::{AccountId32 as SpAccountId32, Ss58AddressFormatRegistry, Ss58Codec}; use sp_core::{twox_128, H256}; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::read_proof_check; @@ -152,9 +151,10 @@ impl SignatureEvent for HydrationSignatureRequestedEvent {} impl SignatureEventTrait for HydrationSignatureRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { + let sender_str = ss58_address_from_account32(self.sender); // Encode the event data in ABI format let encoded = encode(&[ - Token::String(hex::encode(self.sender)), + Token::String(sender_str), Token::Bytes(self.payload.to_vec()), Token::String(self.path.clone()), Token::Uint(self.key_version.into()), @@ -198,9 +198,10 @@ impl SignatureEventTrait for HydrationSignatureRequestedEvent { anyhow::bail!("payload exceeds secp256k1 curve order"); } + let sender_str = ss58_address_from_account32(self.sender); let epsilon = mpc_crypto::kdf::derive_epsilon_hydration( self.key_version, - format!("0x{}", self.sender.encode_hex()).as_str(), + sender_str.as_str(), &self.path, ); @@ -278,9 +279,10 @@ impl SignatureEvent for HydrationSignBidirectionalRequestedEvent {} impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { + let sender_str = ss58_address_from_account32(self.sender); // Match TypeScript implementation using ABI encoding let encoded = ( - Pubkey::new_from_array(self.sender).to_string(), + sender_str, self.serialized_transaction.clone(), self.caip2_id.clone(), self.key_version, @@ -313,11 +315,13 @@ impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { let request_id = self.generate_request_id(); let rlp_encoded_tx = self.serialized_transaction.clone(); + let sender_str = ss58_address_from_account32(self.sender); + // Call the existing derive_epsilon_sol function with the correct parameters // to match the TypeScript implementation let epsilon = mpc_crypto::kdf::derive_epsilon_hydration( self.key_version, - format!("0x{}", self.sender.encode_hex()).as_str(), + sender_str.as_str(), &self.path, ); @@ -468,6 +472,11 @@ async fn fetch_proven_system_events_bytes( Ok(events_bytes) } +pub fn ss58_address_from_account32(sender: [u8; 32]) -> String { + let acc = SpAccountId32::from(sender); + acc.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into()) +} + pub async fn run( hydration: Option, sign_tx: mpsc::Sender, diff --git a/chain-signatures/node/src/respond_bidirectional.rs b/chain-signatures/node/src/respond_bidirectional.rs index d9f1b97ce..cd638bab3 100644 --- a/chain-signatures/node/src/respond_bidirectional.rs +++ b/chain-signatures/node/src/respond_bidirectional.rs @@ -13,6 +13,7 @@ use tokio::time::Duration; const MAGIC_ERROR_PREFIX: [u8; 4] = [0xde, 0xad, 0xbe, 0xef]; const SOLANA_RESPOND_BIDIRECTIONAL_PATH: &str = "solana response key"; +const HYDRATION_RESPOND_BIDIRECTIONAL_PATH: &str = "hydration response key"; // Use Borsh as this is what we are using for solana pub(crate) const RESPOND_SERIALIZATION_FORMAT: SerDeserFormat = SerDeserFormat::Borsh; // Use Abi as this is what we are using for ethereum @@ -117,16 +118,13 @@ impl CompletedTx { let Some(payload) = Scalar::from_bytes(message) else { anyhow::bail!("Failed to convert respond bidirectional message to scalar: {message:?}"); }; - let path = SOLANA_RESPOND_BIDIRECTIONAL_PATH.to_string(); - tracing::info!( - "requester to derive epsilon: {:?}", - self.tx.sender.to_string() - ); - let epsilon = mpc_crypto::kdf::derive_epsilon_sol( - self.tx.key_version, - &self.tx.sender.to_string(), - &path, - ); + let path = match chain { + Chain::Solana => SOLANA_RESPOND_BIDIRECTIONAL_PATH.to_string(), + Chain::Hydration => HYDRATION_RESPOND_BIDIRECTIONAL_PATH.to_string(), + _ => anyhow::bail!("Unsupported chain: {}", chain), + }; + tracing::info!("requester to derive epsilon: {:?}", self.tx.sender); + let epsilon = self.tx.epsilon(&path)?; let entropy = self.tx.id.0; Ok(IndexedSignRequest { id: SignId::new(request_id_bytes), diff --git a/chain-signatures/node/src/rpc.rs b/chain-signatures/node/src/rpc.rs index f787fce06..aba2afdff 100644 --- a/chain-signatures/node/src/rpc.rs +++ b/chain-signatures/node/src/rpc.rs @@ -46,7 +46,11 @@ use hydration::runtime_types::bounded_collections::bounded_vec::BoundedVec as Hy use hydration::runtime_types::pallet_signet::pallet::{ AffinePoint as HydrationAffinePoint, Signature as HydrationSignature, }; -use subxt::{OnlineClient, SubstrateConfig}; +use subxt::config::substrate::{ + BlakeTwo256, SubstrateConfig, SubstrateExtrinsicParams, SubstrateHeader, +}; +use subxt::Config as SubxtConfig; +use subxt::OnlineClient; use subxt_signer::{sr25519, SecretUri}; /// The maximum amount of times to retry publishing a signature. @@ -670,14 +674,26 @@ impl SolanaClient { } } +pub enum HydradxConfig {} + +impl SubxtConfig for HydradxConfig { + type AccountId = ::AccountId; + type Address = ::AccountId; + type Signature = ::Signature; + type Hasher = BlakeTwo256; + type Header = SubstrateHeader; + type ExtrinsicParams = SubstrateExtrinsicParams; + type AssetId = ::AssetId; +} + #[subxt::subxt(runtime_metadata_path = "src/indexer_hydration/artifacts/hydration_metadata.scale")] pub mod hydration {} #[derive(Clone)] pub struct HydrationClient { - api: OnlineClient, + api: OnlineClient, signer: sr25519::Keypair, } -type TxHash = subxt::config::HashFor; +type TxHash = subxt::config::HashFor; impl HydrationClient { /// Create a new Hydration client. @@ -685,7 +701,7 @@ impl HydrationClient { /// `rpc_url`: e.g. "ws://127.0.0.1:9944" or "wss://rpc.hydration.cloud" /// `signer_uri`: sr25519 secret URI, e.g. "//Alice" or a mnemonic. pub async fn new(config: &HydrationConfig) -> anyhow::Result { - let api = OnlineClient::::from_url(&config.rpc_ws_url).await?; + let api = OnlineClient::::from_url(&config.rpc_ws_url).await?; let uri = SecretUri::from_str(&config.signer_uri)?; let signer = sr25519::Keypair::from_uri(&uri)?; diff --git a/chain-signatures/node/src/sign_bidirectional.rs b/chain-signatures/node/src/sign_bidirectional.rs index 0e91f1664..36898c733 100644 --- a/chain-signatures/node/src/sign_bidirectional.rs +++ b/chain-signatures/node/src/sign_bidirectional.rs @@ -48,7 +48,7 @@ pub enum PendingRequestStatus { #[derive(Debug, Clone, Hash, serde::Serialize, serde::Deserialize)] pub struct BidirectionalTx { pub id: BidirectionalTxId, - pub sender: Pubkey, + pub sender: [u8; 32], pub serialized_transaction: Vec, pub source_chain: Chain, pub target_chain: Chain, @@ -114,6 +114,32 @@ impl BidirectionalTx { status: PendingRequestStatus::AwaitingResponse, }) } + + pub fn sender_string(&self) -> anyhow::Result { + match self.source_chain { + Chain::Solana => Ok(Pubkey::new_from_array(self.sender).to_string()), + Chain::Hydration => Ok(crate::indexer_hydration::ss58_address_from_account32( + self.sender, + )), + _ => anyhow::bail!("Unsupported chain: {}", self.source_chain), + } + } + + pub fn epsilon(&self, path: &str) -> anyhow::Result { + match self.source_chain { + Chain::Solana => Ok(mpc_crypto::kdf::derive_epsilon_sol( + self.key_version, + &self.sender_string()?, + path, + )), + Chain::Hydration => Ok(mpc_crypto::kdf::derive_epsilon_hydration( + self.key_version, + &self.sender_string()?, + path, + )), + _ => anyhow::bail!("Unsupported chain: {}", self.source_chain), + } + } } #[derive(Debug, Clone, Default)] From 37324260417ba01ae5c44f13a8708e30c62674aa Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Mon, 15 Dec 2025 10:41:40 +0800 Subject: [PATCH 08/11] more refactoring and script to generate hydration signer_uri --- chain-signatures/node/src/indexer_common.rs | 40 +- .../node/src/indexer_hydration/mod.rs | 25 +- chain-signatures/node/src/indexer_sol.rs | 12 +- .../node/src/sign_bidirectional.rs | 63 +- infra/scripts/generate_keys/Cargo.lock | 1375 +++++++++++++++-- infra/scripts/generate_keys/Cargo.toml | 3 +- infra/scripts/generate_keys/src/main.rs | 11 + 7 files changed, 1328 insertions(+), 201 deletions(-) diff --git a/chain-signatures/node/src/indexer_common.rs b/chain-signatures/node/src/indexer_common.rs index 4dd1c04e7..29a32c14a 100644 --- a/chain-signatures/node/src/indexer_common.rs +++ b/chain-signatures/node/src/indexer_common.rs @@ -16,6 +16,7 @@ use crate::rpc::ContractStateWatcher; use crate::sign_bidirectional::BidirectionalTx; use crate::sign_bidirectional::BidirectionalTxId; use crate::sign_bidirectional::PendingRequestStatus; +use anchor_lang::prelude::Pubkey; use k256::Scalar; use mpc_primitives::SignId; use mpc_primitives::Signature; @@ -39,12 +40,14 @@ impl SignBidirectionalEvent { } } - pub fn sender_string(&self) -> String { + pub(crate) fn sender_string(&self) -> anyhow::Result { + crate::indexer_common::sender_string(self.sender(), self.source_chain()) + } + + pub(crate) fn source_chain(&self) -> Chain { match self { - SignBidirectionalEvent::Solana(event) => event.sender.to_string(), - SignBidirectionalEvent::Hydration(event) => { - crate::indexer_hydration::ss58_address_from_account32(event.sender) - } + SignBidirectionalEvent::Solana(_) => Chain::Solana, + SignBidirectionalEvent::Hydration(_) => Chain::Hydration, } } @@ -118,18 +121,18 @@ impl SignBidirectionalEvent { } } - pub fn epsilon(&self) -> Scalar { + pub fn epsilon(&self) -> anyhow::Result { match self { - SignBidirectionalEvent::Solana(_) => mpc_crypto::kdf::derive_epsilon_sol( + SignBidirectionalEvent::Solana(_) => Ok(mpc_crypto::kdf::derive_epsilon_sol( self.key_version(), - &self.sender_string(), + &self.sender_string()?, &self.path(), - ), - SignBidirectionalEvent::Hydration(_) => mpc_crypto::kdf::derive_epsilon_hydration( + )), + SignBidirectionalEvent::Hydration(_) => Ok(mpc_crypto::kdf::derive_epsilon_hydration( self.key_version(), - &self.sender_string(), + &self.sender_string()?, &self.path(), - ), + )), } } } @@ -209,6 +212,7 @@ pub(crate) trait SignatureEventTrait { total_timeout: Duration, ) -> anyhow::Result; fn source_chain(&self) -> Chain; + fn sender_string(&self) -> String; } pub(crate) trait SignatureEvent: SignatureEventTrait + std::fmt::Debug {} @@ -351,7 +355,7 @@ pub(crate) async fn process_respond_event( // Get the MPC public key and derive the from_address let root_public_key = contract_watcher.wait_public_key().await; - let epsilon = event.epsilon(); + let epsilon = event.epsilon()?; let from_address = crate::sign_bidirectional::derive_user_address(root_public_key, epsilon); let bidirectional_tx = BidirectionalTx { @@ -429,3 +433,13 @@ pub(crate) async fn process_respond_bidirectional_event( }; Ok(()) } + +pub(crate) fn sender_string(sender: [u8; 32], source_chain: Chain) -> anyhow::Result { + match source_chain { + Chain::Solana => Ok(Pubkey::new_from_array(sender).to_string()), + Chain::Hydration => Ok(crate::indexer_hydration::ss58_address_from_account32( + sender, + )), + _ => anyhow::bail!("Unsupported chain: {source_chain}"), + } +} diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index 21f0a6263..f3e88b7af 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -151,10 +151,9 @@ impl SignatureEvent for HydrationSignatureRequestedEvent {} impl SignatureEventTrait for HydrationSignatureRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { - let sender_str = ss58_address_from_account32(self.sender); // Encode the event data in ABI format let encoded = encode(&[ - Token::String(sender_str), + Token::String(self.sender_string()), Token::Bytes(self.payload.to_vec()), Token::String(self.path.clone()), Token::Uint(self.key_version.into()), @@ -198,10 +197,9 @@ impl SignatureEventTrait for HydrationSignatureRequestedEvent { anyhow::bail!("payload exceeds secp256k1 curve order"); } - let sender_str = ss58_address_from_account32(self.sender); let epsilon = mpc_crypto::kdf::derive_epsilon_hydration( self.key_version, - sender_str.as_str(), + &self.sender_string(), &self.path, ); @@ -228,6 +226,10 @@ impl SignatureEventTrait for HydrationSignatureRequestedEvent { fn source_chain(&self) -> Chain { Chain::Hydration } + + fn sender_string(&self) -> String { + ss58_address_from_account32(self.sender) + } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -279,10 +281,9 @@ impl SignatureEvent for HydrationSignBidirectionalRequestedEvent {} impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { - let sender_str = ss58_address_from_account32(self.sender); // Match TypeScript implementation using ABI encoding let encoded = ( - sender_str, + self.sender_string(), self.serialized_transaction.clone(), self.caip2_id.clone(), self.key_version, @@ -315,13 +316,11 @@ impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { let request_id = self.generate_request_id(); let rlp_encoded_tx = self.serialized_transaction.clone(); - let sender_str = ss58_address_from_account32(self.sender); - // Call the existing derive_epsilon_sol function with the correct parameters // to match the TypeScript implementation let epsilon = mpc_crypto::kdf::derive_epsilon_hydration( self.key_version, - sender_str.as_str(), + &self.sender_string(), &self.path, ); @@ -359,6 +358,10 @@ impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { fn source_chain(&self) -> Chain { Chain::Hydration } + + fn sender_string(&self) -> String { + ss58_address_from_account32(self.sender) + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -472,7 +475,7 @@ async fn fetch_proven_system_events_bytes( Ok(events_bytes) } -pub fn ss58_address_from_account32(sender: [u8; 32]) -> String { +pub(crate) fn ss58_address_from_account32(sender: [u8; 32]) -> String { let acc = SpAccountId32::from(sender); acc.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into()) } @@ -492,7 +495,7 @@ pub async fn run( }; let total_timeout = Duration::from_secs(hydration.total_timeout); - let ws_url = "ws://127.0.0.1:8000"; + let ws_url: &str = hydration.rpc_ws_url.as_str(); tracing::info!("connecting to hydration rpc at {}", ws_url); diff --git a/chain-signatures/node/src/indexer_sol.rs b/chain-signatures/node/src/indexer_sol.rs index 14c9b94b3..d9319e0ee 100644 --- a/chain-signatures/node/src/indexer_sol.rs +++ b/chain-signatures/node/src/indexer_sol.rs @@ -215,7 +215,7 @@ impl SignatureEventTrait for SignatureRequestedEvent { // Call the existing derive_epsilon_sol function with the correct parameters // to match the TypeScript implementation - let epsilon = derive_epsilon_sol(self.key_version, &self.sender.to_string(), &self.path); + let epsilon = derive_epsilon_sol(self.key_version, &self.sender_string(), &self.path); // Use transaction signature as entropy // let mut entropy = [0u8; 32]; @@ -244,6 +244,10 @@ impl SignatureEventTrait for SignatureRequestedEvent { fn source_chain(&self) -> Chain { Chain::Solana } + + fn sender_string(&self) -> String { + self.sender.to_string() + } } impl SignatureEvent for SignBidirectionalEvent {} @@ -287,7 +291,7 @@ impl SignatureEventTrait for SignBidirectionalEvent { // Call the existing derive_epsilon_sol function with the correct parameters // to match the TypeScript implementation - let epsilon = derive_epsilon_sol(self.key_version, &self.sender.to_string(), &self.path); + let epsilon = derive_epsilon_sol(self.key_version, &self.sender_string(), &self.path); let sign_id = SignId::new(request_id); tracing::info!(?sign_id, "solana signature requested"); @@ -323,6 +327,10 @@ impl SignatureEventTrait for SignBidirectionalEvent { fn source_chain(&self) -> Chain { Chain::Solana } + + fn sender_string(&self) -> String { + self.sender.to_string() + } } type Result = anyhow::Result; diff --git a/chain-signatures/node/src/sign_bidirectional.rs b/chain-signatures/node/src/sign_bidirectional.rs index 36898c733..c79b16b6c 100644 --- a/chain-signatures/node/src/sign_bidirectional.rs +++ b/chain-signatures/node/src/sign_bidirectional.rs @@ -1,8 +1,7 @@ -use crate::protocol::{Chain, IndexedSignRequest, SignRequestType}; +use crate::protocol::{Chain, IndexedSignRequest}; use crate::respond_bidirectional::SerDeserFormat; use alloy::primitives::{keccak256, Address, Bytes, B256, I256, U256}; use alloy_dyn_abi::{DynSolType, DynSolValue}; -use anchor_lang::prelude::Pubkey; use borsh::BorshSerialize; use k256::elliptic_curve::point::AffineCoordinates; use k256::{AffinePoint, Scalar}; @@ -13,7 +12,6 @@ use serde_json::Value; use sha3::{Digest, Keccak256}; use std::collections::HashMap; use std::io::Write; -use std::str::FromStr; #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Copy)] pub struct BidirectionalTxId(pub B256); @@ -68,64 +66,11 @@ pub struct BidirectionalTx { } impl BidirectionalTx { - pub fn new(signature: SignBidirectionalSignature) -> anyhow::Result { - let SignRequestType::SignBidirectional(event) = signature.indexed.sign_request_type.clone() - else { - anyhow::bail!("sign request is not a sign bidirectional"); - }; - - let unsigned_rlp_data = &event.serialized_transaction(); - let target_chain = Chain::from_str(&event.dest()).map_err(|err| { - anyhow::anyhow!( - "invalid target chain '{}' for bidirectional transaction: {err}", - event.dest() - ) - })?; - let source_chain = signature.indexed.chain; - - let (signed_transaction_hash, nonce) = - sign_and_hash_transaction(unsigned_rlp_data, signature.signature)?; - - tracing::info!(signed_transaction_hash = ?signed_transaction_hash, "signed_transaction_hash"); - - let from_address = - derive_user_address(signature.public_key, signature.indexed.args.epsilon); - - tracing::info!(from_address = ?from_address, "from_address"); - - Ok(Self { - id: BidirectionalTxId(signed_transaction_hash.into()), - sender: event.sender(), - serialized_transaction: event.serialized_transaction(), - source_chain, - target_chain, - caip2_id: event.caip2_id(), - key_version: event.key_version(), - deposit: event.deposit(), - path: event.path(), - algo: event.algo(), - dest: event.dest(), - params: event.params(), - output_deserialization_schema: event.output_deserialization_schema(), - respond_serialization_schema: event.respond_serialization_schema(), - request_id: signature.indexed.id.request_id, - from_address, - nonce, - status: PendingRequestStatus::AwaitingResponse, - }) - } - - pub fn sender_string(&self) -> anyhow::Result { - match self.source_chain { - Chain::Solana => Ok(Pubkey::new_from_array(self.sender).to_string()), - Chain::Hydration => Ok(crate::indexer_hydration::ss58_address_from_account32( - self.sender, - )), - _ => anyhow::bail!("Unsupported chain: {}", self.source_chain), - } + pub(crate) fn sender_string(&self) -> anyhow::Result { + crate::indexer_common::sender_string(self.sender, self.source_chain) } - pub fn epsilon(&self, path: &str) -> anyhow::Result { + pub(crate) fn epsilon(&self, path: &str) -> anyhow::Result { match self.source_chain { Chain::Solana => Ok(mpc_crypto::kdf::derive_epsilon_sol( self.key_version, diff --git a/infra/scripts/generate_keys/Cargo.lock b/infra/scripts/generate_keys/Cargo.lock index 804bb754d..347ddcc06 100644 --- a/infra/scripts/generate_keys/Cargo.lock +++ b/infra/scripts/generate_keys/Cargo.lock @@ -62,6 +62,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -71,12 +83,329 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1786b2e3832f6f0f7c8d62d5d5a282f6952a1ab99981c54cd52b6ac1d8f02df5" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.98", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "ark-transcript" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c1c928edb9d8ff24cb5dcb7651d3a98494fff3099eee95c2404cd813a9139f" +dependencies = [ + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_core", + "sha3", +] + +[[package]] +name = "ark-vrf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d63e9780640021b74d02b32895d8cec1b4abe8e5547b560a6bda6b14b78c6da" +dependencies = [ + "ark-bls12-381 0.5.0", + "ark-ec 0.5.0", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "digest 0.10.7", + "rand_chacha", + "sha2 0.10.8", + "w3f-ring-proof", + "zeroize", +] + +[[package]] +name = "array-bytes" +version = "6.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -164,6 +493,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -191,6 +526,22 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -221,7 +572,27 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", ] [[package]] @@ -256,6 +627,19 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "bounded-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee8eddd066a8825ec5570528e6880471210fd5d88cb6abbe1cfdd51ca249c33" +dependencies = [ + "jam-codec", + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + [[package]] name = "bs58" version = "0.4.0" @@ -268,7 +652,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2", + "sha2 0.10.8", "tinyvec", ] @@ -280,9 +664,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "byteorder" @@ -349,7 +733,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -427,12 +811,12 @@ checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" dependencies = [ "bs58 0.5.1", "coins-core", - "digest", + "digest 0.10.7", "hmac", "k256", "serde", - "sha2", - "thiserror 1.0.61", + "sha2 0.10.8", + "thiserror 1.0.69", ] [[package]] @@ -447,8 +831,8 @@ dependencies = [ "once_cell", "pbkdf2 0.12.2", "rand", - "sha2", - "thiserror 1.0.61", + "sha2 0.10.8", + "thiserror 1.0.69", ] [[package]] @@ -460,15 +844,15 @@ dependencies = [ "base64 0.21.7", "bech32", "bs58 0.5.1", - "digest", + "digest 0.10.7", "generic-array", "hex", "ripemd", "serde", "serde_derive", - "sha2", + "sha2 0.10.8", "sha3", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -490,12 +874,38 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" @@ -608,7 +1018,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -639,6 +1049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -651,6 +1062,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -684,13 +1106,22 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -755,6 +1186,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -762,9 +1199,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", + "serdect", "signature", "spki", ] @@ -775,6 +1213,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "signature", ] @@ -787,10 +1226,38 @@ dependencies = [ "curve25519-dalek", "ed25519", "rand_core", - "sha2", + "sha2 0.10.8", "subtle", ] +[[package]] +name = "ed25519-zebra" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0017d969298eec91e3db7a2985a8cab4df6341d86e6f3a6f5878b13fb7846bc9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.15.5", + "pkcs8", + "rand_core", + "sha2 0.10.8", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "either" version = "1.13.0" @@ -805,7 +1272,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -813,6 +1280,7 @@ dependencies = [ "pkcs8", "rand_core", "sec1", + "serdect", "subtle", "zeroize", ] @@ -853,6 +1321,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "environmental" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" + [[package]] name = "equivalent" version = "1.0.1" @@ -877,7 +1371,7 @@ checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ "aes", "ctr", - "digest", + "digest 0.10.7", "hex", "hmac", "pbkdf2 0.11.0", @@ -885,9 +1379,9 @@ dependencies = [ "scrypt", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "sha3", - "thiserror 1.0.61", + "thiserror 1.0.69", "uuid", ] @@ -904,8 +1398,8 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror 1.0.61", - "uint", + "thiserror 1.0.69", + "uint 0.9.5", ] [[package]] @@ -916,9 +1410,9 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash 0.8.0", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", "tiny-keccak", ] @@ -931,12 +1425,12 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash 0.8.0", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "primitive-types 0.12.2", "scale-info", - "uint", + "uint 0.9.5", ] [[package]] @@ -983,7 +1477,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -1051,7 +1545,7 @@ dependencies = [ "strum", "syn 2.0.98", "tempfile", - "thiserror 1.0.61", + "thiserror 1.0.69", "tiny-keccak", "unicode-xid", ] @@ -1068,7 +1562,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", "tracing", ] @@ -1092,7 +1586,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", "tokio", "tracing", "tracing-futures", @@ -1124,7 +1618,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "tracing", @@ -1150,8 +1644,8 @@ dependencies = [ "eth-keystore", "ethers-core", "rand", - "sha2", - "thiserror 1.0.61", + "sha2 0.10.8", + "thiserror 1.0.69", "tracing", ] @@ -1179,7 +1673,7 @@ dependencies = [ "serde_json", "solang-parser", "svm-rs", - "thiserror 1.0.61", + "thiserror 1.0.69", "tiny-keccak", "tokio", "tracing", @@ -1262,6 +1756,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1327,6 +1827,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -1414,6 +1915,7 @@ dependencies = [ "mpc-keys", "near-crypto", "rand", + "sp-core", ] [[package]] @@ -1451,6 +1953,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand", + "rand_core", +] + [[package]] name = "ghash" version = "0.5.1" @@ -1515,11 +2027,46 @@ dependencies = [ "tracing", ] +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hashers" @@ -1551,6 +2098,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hkdf" version = "0.12.4" @@ -1566,7 +2119,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1588,14 +2141,14 @@ dependencies = [ "aes-gcm", "byteorder", "chacha20poly1305", - "digest", + "digest 0.10.7", "generic-array", "hkdf", "hmac", "p256", "rand_core", "serde", - "sha2", + "sha2 0.10.8", "subtle", "x25519-dalek", "zeroize", @@ -1821,6 +2374,26 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-num-traits" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d15461ab0dcc56706adf266158acbc44ccf719bf7d0af30705f58b90a4b8c" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint 0.10.0", +] + [[package]] name = "impl-rlp" version = "0.3.0" @@ -1839,15 +2412,24 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] @@ -1858,12 +2440,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -1884,12 +2466,30 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.11.0" @@ -1899,12 +2499,49 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jam-codec" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb948eace373d99de60501a02fb17125d30ac632570de20dccc74370cdd611b9" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "jam-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "jam-codec-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "319af585c4c8a6b5552a52b7787a1ab3e4d59df7614190b1f85b9b842488789d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -1954,7 +2591,8 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", + "serdect", + "sha2 0.10.8", "signature", ] @@ -1976,7 +2614,7 @@ dependencies = [ "ascii-canvas", "bit-set", "ena", - "itertools", + "itertools 0.11.0", "lalrpop-util", "petgraph", "regex", @@ -2019,6 +2657,52 @@ dependencies = [ "libc", ] +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64 0.22.1", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -2054,7 +2738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -2063,6 +2747,18 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + [[package]] name = "mime" version = "0.3.17" @@ -2118,7 +2814,7 @@ checksum = "4e7b41110a20f1d82bb06f06e4800068c5ade6d8ff844787f8753bc2ce7b16f7" dependencies = [ "anyhow", "json_comments", - "thiserror 1.0.61", + "thiserror 1.0.69", "tracing", ] @@ -2141,11 +2837,11 @@ dependencies = [ "near-stdx", "primitive-types 0.10.1", "rand", - "secp256k1", + "secp256k1 0.27.0", "serde", "serde_json", "subtle", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -2198,6 +2894,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2309,30 +3015,45 @@ dependencies = [ "primeorder", ] +[[package]] +name = "parity-bip39" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +dependencies = [ + "bitcoin_hashes", + "rand", + "rand_core", + "serde", + "unicode-normalization", +] + [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ "arrayvec", "bitvec", "byte-slice-cast", + "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] @@ -2360,15 +3081,32 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.4.2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core", "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "path-slash" version = "0.2.1" @@ -2381,10 +3119,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest", + "digest 0.10.7", "hmac", - "password-hash", - "sha2", + "password-hash 0.4.2", + "sha2 0.10.8", ] [[package]] @@ -2393,8 +3131,9 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", + "password-hash 0.5.0", ] [[package]] @@ -2406,6 +3145,15 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2589,7 +3337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" dependencies = [ "fixed-hash 0.7.0", - "uint", + "uint 0.9.5", ] [[package]] @@ -2599,27 +3347,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash 0.8.0", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", + "scale-info", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec 0.7.1", + "impl-num-traits", + "impl-serde 0.5.0", "scale-info", - "uint", + "uint 0.10.0", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.23.9", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -2642,9 +3404,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2731,7 +3493,27 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.12", "libredox", - "thiserror 1.0.61", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -2850,7 +3632,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -2976,10 +3758,12 @@ version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ + "bitvec", "cfg-if", "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", + "serde", ] [[package]] @@ -2994,6 +3778,25 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "schnorrkel" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9fcb6c2e176e86ec703e22560d99d65a5ee9056ae45a08e13e84ebf796296f" +dependencies = [ + "aead", + "arrayref", + "arrayvec", + "curve25519-dalek", + "getrandom_or_panic", + "merlin", + "rand_core", + "serde_bytes", + "sha2 0.10.8", + "subtle", + "zeroize", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3009,7 +3812,7 @@ dependencies = [ "hmac", "pbkdf2 0.11.0", "salsa20", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -3032,6 +3835,7 @@ dependencies = [ "der", "generic-array", "pkcs8", + "serdect", "subtle", "zeroize", ] @@ -3043,7 +3847,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ "rand", - "secp256k1-sys", + "secp256k1-sys 0.8.1", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys 0.9.2", ] [[package]] @@ -3055,6 +3868,24 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "semver" version = "1.0.22" @@ -3078,18 +3909,38 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -3129,6 +3980,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3137,7 +3998,20 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", ] [[package]] @@ -3148,7 +4022,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -3157,7 +4031,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -3173,7 +4047,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core", ] @@ -3226,14 +4100,117 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" dependencies = [ - "itertools", + "itertools 0.11.0", "lalrpop", "lalrpop-util", "phf", - "thiserror 1.0.61", + "thiserror 1.0.69", "unicode-xid", ] +[[package]] +name = "sp-core" +version = "38.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707602208776d0e19d4269bb3f68c5306cacbdfabbb2e4d8d499af7b907bb0a3" +dependencies = [ + "ark-vrf", + "array-bytes", + "bitflags 1.3.2", + "blake2", + "bounded-collections", + "bs58 0.5.1", + "dyn-clone", + "ed25519-zebra", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde 0.5.0", + "itertools 0.11.0", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot", + "paste", + "primitive-types 0.13.1", + "rand", + "scale-info", + "schnorrkel", + "secp256k1 0.28.2", + "secrecy", + "serde", + "sha2 0.10.8", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror 1.0.69", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.8", + "sha3", + "twox-hash", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "sp-externalities" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cbf059dce180a8bf8b6c8b08b6290fa3d1c7f069a60f1df038ab5dd5fc0ba6" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage", +] + +[[package]] +name = "sp-std" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" + +[[package]] +name = "sp-storage" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3b70ca340e41cde9d2e069d354508a6e37a6573d66f7cc38f11549002f64ec" +dependencies = [ + "impl-serde 0.5.0", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", +] + [[package]] name = "spin" version = "0.5.2" @@ -3256,6 +4233,21 @@ dependencies = [ "der", ] +[[package]] +name = "ss58-registry" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19409f13998e55816d1c728395af0b52ec066206341d939e22e7766df9b494b8" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3302,11 +4294,24 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "substrate-bip39" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca58ffd742f693dc13d69bdbb2e642ae239e0053f6aab3b104252892f856700a" +dependencies = [ + "hmac", + "pbkdf2 0.12.2", + "schnorrkel", + "sha2 0.10.8", + "zeroize", +] + [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svm-rs" @@ -3322,8 +4327,8 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", - "thiserror 1.0.61", + "sha2 0.10.8", + "thiserror 1.0.69", "url", "zip", ] @@ -3421,11 +4426,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.61", + "thiserror-impl 1.0.69", ] [[package]] @@ -3439,9 +4444,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3585,7 +4590,7 @@ checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.6", "toml_edit 0.22.16", ] @@ -3599,14 +4604,12 @@ dependencies = [ ] [[package]] -name = "toml_edit" -version = "0.21.1" +name = "toml_datetime" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", + "serde_core", ] [[package]] @@ -3618,10 +4621,31 @@ dependencies = [ "indexmap", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.6", "winnow 0.6.26", ] +[[package]] +name = "toml_edit" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +dependencies = [ + "indexmap", + "toml_datetime 0.7.3", + "toml_parser", + "winnow 0.7.14", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow 0.7.14", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -3690,11 +4714,23 @@ dependencies = [ "rand", "rustls", "sha1", - "thiserror 1.0.61", + "thiserror 1.0.69", "url", "utf-8", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest 0.10.7", + "rand", + "static_assertions", +] + [[package]] name = "typenum" version = "1.17.0" @@ -3713,6 +4749,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -3725,6 +4773,15 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.2.6" @@ -3798,6 +4855,74 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "w3f-bls" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6bfb937b3d12077654a9e43e32a4e9c20177dd9fea0f3aba673e7840bb54f32" +dependencies = [ + "ark-bls12-377", + "ark-bls12-381 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-serialize-derive 0.4.2", + "arrayref", + "digest 0.10.7", + "rand", + "rand_chacha", + "rand_core", + "sha2 0.10.8", + "sha3", + "zeroize", +] + +[[package]] +name = "w3f-pcs" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbe7a8d5c914b69392ab3b267f679a2e546fe29afaddce47981772ac71bd02e1" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "merlin", +] + +[[package]] +name = "w3f-plonk-common" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aca389e494fe08c5c108b512e2328309036ee1c0bc7bdfdb743fef54d448c8c" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "getrandom_or_panic", + "rand_core", + "w3f-pcs", +] + +[[package]] +name = "w3f-ring-proof" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a639379402ad51504575dbd258740383291ac8147d3b15859bdf1ea48c677de" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "ark-transcript", + "w3f-pcs", + "w3f-plonk-common", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4100,18 +5225,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" dependencies = [ "memchr", ] [[package]] name = "winnow" -version = "0.6.26" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4160,7 +5285,7 @@ dependencies = [ "pharos", "rustc_version", "send_wrapper 0.6.0", - "thiserror 1.0.61", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4215,6 +5340,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "zerofrom" version = "0.1.5" @@ -4238,9 +5383,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -4287,7 +5432,7 @@ dependencies = [ "aes", "byteorder", "bzip2", - "constant_time_eq", + "constant_time_eq 0.1.5", "crc32fast", "crossbeam-utils", "flate2", diff --git a/infra/scripts/generate_keys/Cargo.toml b/infra/scripts/generate_keys/Cargo.toml index ebfc95a09..dde70d1d6 100644 --- a/infra/scripts/generate_keys/Cargo.toml +++ b/infra/scripts/generate_keys/Cargo.toml @@ -10,4 +10,5 @@ ethers = "2.0" hex = "0.4.3" near-crypto = "0.27" mpc-keys = { path = "../../../chain-signatures/keys" } -rand = "0.8" \ No newline at end of file +rand = "0.8" +sp-core = { version = "38.1", default-features = false, features = ["std"] } \ No newline at end of file diff --git a/infra/scripts/generate_keys/src/main.rs b/infra/scripts/generate_keys/src/main.rs index f3e8176a5..e0c67a66d 100644 --- a/infra/scripts/generate_keys/src/main.rs +++ b/infra/scripts/generate_keys/src/main.rs @@ -1,11 +1,22 @@ use ethers::signers::LocalWallet; use ethers::signers::Signer; use mpc_keys::hpke; +use sp_core::crypto::{Ss58AddressFormatRegistry, Ss58Codec}; +use sp_core::{sr25519, Pair}; use std::env; fn main() { let args: Vec = env::args().collect(); + let (hydration_pair, hydration_phrase, _seed) = sr25519::Pair::generate_with_phrase(None); + + let hydration_account_id = hydration_pair + .public() + .to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into()); + + println!("Hydrationsigner_uri (secret phrase): {hydration_phrase}"); + println!("Hydration ss58 address: {hydration_account_id}"); + let solana_sk = near_crypto::SecretKey::from_random(near_crypto::KeyType::ED25519); let solana_pk = solana_sk.public_key(); println!( From f2142843bded624429bbcd755c713a50e2c82e34 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Mon, 15 Dec 2025 11:11:16 +0800 Subject: [PATCH 09/11] remove unwraps --- chain-signatures/node/src/indexer_common.rs | 24 +++- .../node/src/indexer_hydration/mod.rs | 126 ++++++++++++------ 2 files changed, 103 insertions(+), 47 deletions(-) diff --git a/chain-signatures/node/src/indexer_common.rs b/chain-signatures/node/src/indexer_common.rs index 29a32c14a..7c034875f 100644 --- a/chain-signatures/node/src/indexer_common.rs +++ b/chain-signatures/node/src/indexer_common.rs @@ -172,6 +172,13 @@ impl RespondBidirectionalEvent { RespondBidirectionalEvent::Hydration(event) => event.signature.clone(), } } + + pub fn source_chain(&self) -> Chain { + match self { + RespondBidirectionalEvent::Solana(_) => Chain::Solana, + RespondBidirectionalEvent::Hydration(_) => Chain::Hydration, + } + } } pub enum SignatureRespondedEvent { @@ -265,7 +272,8 @@ pub(crate) async fn process_sign_event( if let Err(err) = sign_tx.send(Sign::Request(sign_request)).await { // TODO: handle error to ensure 100% success rate - tracing::error!(?err, "Failed to send Solana sign request into queue"); + let chain = sign_event.source_chain(); + tracing::error!(?err, chain = %chain, "Failed to send {} sign request into queue", chain.as_str()); } else { crate::metrics::NUM_SIGN_REQUESTS .with_label_values(&[ @@ -306,21 +314,19 @@ pub(crate) async fn process_respond_event( ) -> anyhow::Result<()> { let sign_id = SignId::new(respond_event.request_id()); - let source_chain = match respond_event { - SignatureRespondedEvent::Solana(_) => Chain::Solana, - SignatureRespondedEvent::Hydration(_) => Chain::Hydration, - }; + let source_chain = respond_event.source_chain(); let Some(sign_type) = backlog.sign_type(source_chain, &sign_id).await else { anyhow::bail!( "sign type not found for respond event (may have already been processed): {sign_id:?}" ) }; + let event = match sign_type { SignRequestType::SignBidirectional(event) => event, SignRequestType::Sign => { tracing::info!(?sign_id, "sign request completed successfully"); - backlog.remove(respond_event.source_chain(), &sign_id).await; + backlog.remove(source_chain, &sign_id).await; if let Err(err) = sign_tx.send(Sign::Completion(sign_id)).await { anyhow::bail!("failed to send completion for respond event: {err:?}"); } @@ -420,7 +426,11 @@ pub(crate) async fn process_respond_bidirectional_event( ) -> anyhow::Result<()> { let sign_id = SignId::new(event.request_id()); tracing::info!(?sign_id, "processing RespondBidirectionalEvent"); - if backlog.remove(Chain::Solana, &sign_id).await.is_some() { + if backlog + .remove(event.source_chain(), &sign_id) + .await + .is_some() + { tracing::info!(?sign_id, "bidirectional tx completed"); } else { tracing::warn!(?sign_id, "bidirectional tx not found on completion"); diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index f3e88b7af..4d933ccba 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -127,23 +127,23 @@ pub struct HydrationSignatureRequestedEvent { pub params: String, } -impl From for HydrationSignatureRequestedEvent { - fn from(event: hydration::signet::events::SignatureRequested) -> Self { +impl HydrationSignatureRequestedEvent { + fn from(event: hydration::signet::events::SignatureRequested) -> anyhow::Result { let mut sender = [0u8; 32]; let sender_array: &[u8; 32] = >::as_ref(&event.sender); sender.copy_from_slice(sender_array); - Self { + Ok(Self { sender, payload: event.payload, - path: String::from_utf8(event.path).unwrap(), + path: String::from_utf8(event.path)?, key_version: event.key_version, - deposit: event.deposit.try_into().unwrap(), - chain_id: String::from_utf8(event.chain_id).unwrap(), - algo: String::from_utf8(event.algo).unwrap(), - dest: String::from_utf8(event.dest).unwrap(), - params: String::from_utf8(event.params).unwrap(), - } + deposit: event.deposit.try_into()?, + chain_id: String::from_utf8(event.chain_id)?, + algo: String::from_utf8(event.algo)?, + dest: String::from_utf8(event.dest)?, + params: String::from_utf8(event.params)?, + }) } } @@ -248,10 +248,8 @@ pub struct HydrationSignBidirectionalRequestedEvent { pub respond_serialization_schema: Vec, } -impl From - for HydrationSignBidirectionalRequestedEvent -{ - fn from(event: hydration::signet::events::SignBidirectionalRequested) -> Self { +impl HydrationSignBidirectionalRequestedEvent { + fn from(event: hydration::signet::events::SignBidirectionalRequested) -> anyhow::Result { let mut sender = [0u8; 32]; let sender_array: &[u8; 32] = >::as_ref(&event.sender); @@ -260,20 +258,20 @@ impl From let program_id_array: &[u8; 32] = >::as_ref(&event.program_id); program_id.copy_from_slice(program_id_array); - Self { + Ok(Self { sender, serialized_transaction: event.serialized_transaction, - caip2_id: String::from_utf8(event.caip2_id).unwrap(), - path: String::from_utf8(event.path).unwrap(), + caip2_id: String::from_utf8(event.caip2_id)?, + path: String::from_utf8(event.path)?, key_version: event.key_version, - deposit: event.deposit.try_into().unwrap(), - algo: String::from_utf8(event.algo).unwrap(), - dest: String::from_utf8(event.dest).unwrap(), - params: String::from_utf8(event.params).unwrap(), + deposit: event.deposit.try_into()?, + algo: String::from_utf8(event.algo)?, + dest: String::from_utf8(event.dest)?, + params: String::from_utf8(event.params)?, program_id, output_deserialization_schema: event.output_deserialization_schema, respond_serialization_schema: event.respond_serialization_schema, - } + }) } } @@ -372,18 +370,16 @@ pub struct HydrationRespondBidirectionalEvent { pub signature: Signature, } -impl From - for HydrationRespondBidirectionalEvent -{ - fn from(event: hydration::signet::events::RespondBidirectionalEvent) -> Self { - let signature = to_mpc_signature(event.signature).unwrap(); +impl HydrationRespondBidirectionalEvent { + fn from(event: hydration::signet::events::RespondBidirectionalEvent) -> anyhow::Result { + let signature = to_mpc_signature(event.signature)?; let responder = account32_to_bytes(&event.responder); - Self { + Ok(Self { request_id: event.request_id, responder, serialized_output: event.serialized_output, signature, - } + }) } } @@ -394,15 +390,15 @@ pub struct HydrationSignatureRespondedEvent { pub signature: Signature, } -impl From for HydrationSignatureRespondedEvent { - fn from(event: hydration::signet::events::SignatureResponded) -> Self { - let signature = to_mpc_signature(event.signature).unwrap(); +impl HydrationSignatureRespondedEvent { + fn from(event: hydration::signet::events::SignatureResponded) -> anyhow::Result { + let signature = to_mpc_signature(event.signature)?; let responder = account32_to_bytes(&event.responder); - Self { + Ok(Self { request_id: event.request_id, responder, signature, - } + }) } } @@ -606,13 +602,27 @@ pub async fn run( // SignatureRequested if let Ok(Some(req)) = ev.as_event::() { - let event = HydrationSignatureRequestedEvent::from(req); + let event = match HydrationSignatureRequestedEvent::from(req) { + Ok(event) => event, + Err(e) => { + tracing::error!( + "failed to convert event to HydrationSignatureRequestedEvent: {e}" + ); + continue; + } + }; tracing::info!( "Hydration::Signet::SignatureRequested in block #{number} ({hash:?}): {:?}", event ); - let entropy: [u8; 32] = ev.bytes().to_vec()[..32].try_into().unwrap(); + let entropy = match entropy_from_event(&ev) { + Ok(entropy) => entropy, + Err(e) => { + tracing::error!("failed to extract entropy from event: {e}"); + continue; + } + }; if let Err(e) = crate::indexer_common::process_sign_event( Box::new(event), @@ -630,7 +640,15 @@ pub async fn run( // SignatureResponded if let Ok(Some(resp)) = ev.as_event::() { - let event = HydrationSignatureRespondedEvent::from(resp); + let event = match HydrationSignatureRespondedEvent::from(resp) { + Ok(event) => event, + Err(e) => { + tracing::error!( + "failed to convert event to HydrationSignatureRespondedEvent: {e}" + ); + continue; + } + }; tracing::info!( "Hydration::Signet::SignatureResponded in block #{number} ({hash:?}): {:?}", event @@ -651,13 +669,25 @@ pub async fn run( if let Ok(Some(req_bi)) = ev.as_event::() { - let event = HydrationSignBidirectionalRequestedEvent::from(req_bi); + let event = match HydrationSignBidirectionalRequestedEvent::from(req_bi) { + Ok(event) => event, + Err(e) => { + tracing::error!("failed to convert event to HydrationSignBidirectionalRequestedEvent: {e}"); + continue; + } + }; tracing::info!( "Hydration::Signet::SignBidirectionalRequested in block #{number} ({hash:?}): {:?}", event ); - let entropy: [u8; 32] = ev.bytes().to_vec()[..32].try_into().unwrap(); + let entropy = match entropy_from_event(&ev) { + Ok(entropy) => entropy, + Err(e) => { + tracing::error!("failed to extract entropy from event: {e}"); + continue; + } + }; if let Err(e) = crate::indexer_common::process_sign_event( Box::new(event), @@ -677,7 +707,15 @@ pub async fn run( if let Ok(Some(resp_bi)) = ev.as_event::() { - let event = HydrationRespondBidirectionalEvent::from(resp_bi); + let event = match HydrationRespondBidirectionalEvent::from(resp_bi) { + Ok(event) => event, + Err(e) => { + tracing::error!( + "failed to convert event to HydrationRespondBidirectionalEvent: {e}" + ); + continue; + } + }; tracing::info!( "Hydration::Signet::RespondBidirectionalEvent in block #{number} ({hash:?}): {:?}", event @@ -695,3 +733,11 @@ pub async fn run( } } } + +fn entropy_from_event( + ev: &subxt::events::EventDetails, +) -> anyhow::Result<[u8; 32]> { + ev.bytes().to_vec()[..32] + .try_into() + .map_err(|_| anyhow::anyhow!("failed to convert event bytes to [u8; 32]")) +} From 22f9f9443f7289acc3392309515c53e82349f0e9 Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Fri, 19 Dec 2025 10:17:46 +0800 Subject: [PATCH 10/11] address comments and solve merge conflicts --- Cargo.lock | 2 -- chain-signatures/node/Cargo.toml | 2 -- chain-signatures/node/src/indexer_common.rs | 6 ++--- .../node/src/indexer_hydration/mod.rs | 12 ++++------ chain-signatures/node/src/indexer_sol.rs | 14 +++--------- .../node/src/respond_bidirectional.rs | 1 - chain-signatures/node/src/rpc.rs | 22 ++++--------------- chain-signatures/primitives/src/lib.rs | 2 +- 8 files changed, 14 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aceafc1d6..ccf1db43b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7958,7 +7958,6 @@ dependencies = [ "deadpool-redis", "ed25519-zebra", "ethabi", - "futures", "futures-util", "google-datastore1", "google-secretmanager1", @@ -7988,7 +7987,6 @@ dependencies = [ "opentelemetry-appender-tracing", "opentelemetry-otlp", "opentelemetry_sdk", - "parity-scale-codec", "prometheus 0.14.0", "rand 0.8.5", "redis", diff --git a/chain-signatures/node/Cargo.toml b/chain-signatures/node/Cargo.toml index 955327447..4112a95c0 100644 --- a/chain-signatures/node/Cargo.toml +++ b/chain-signatures/node/Cargo.toml @@ -40,13 +40,11 @@ alloy-signer-local = "1.0.38" alloy-sol-types = "1.4.1" helios = { git = "https://github.com/a16z/helios", rev = "e1f9a50f73d7af6f6ae4acf2b663d04aab6adb19" } ed25519-zebra = { version = "4.1", default-features = false, features = ["alloc"] } -futures = "0.3.31" subxt = { version = "0.44", default-features = false, features = ["jsonrpsee","native","unstable-light-client"] } sp-core = { version = "38.1", default-features = false, features = ["std"] } sp-runtime = { version = "44.0.0", default-features = false, features = ["std"] } sp-trie = { version = "41.1.0", default-features = false, features = ["std"] } sp-state-machine = { version = "0.48", default-features = false, features = ["std"] } -codec = { package = "parity-scale-codec", version = "3", features = ["derive"] } subxt-signer = "0.44" # workspace dependencies diff --git a/chain-signatures/node/src/indexer_common.rs b/chain-signatures/node/src/indexer_common.rs index 7c034875f..76209c78c 100644 --- a/chain-signatures/node/src/indexer_common.rs +++ b/chain-signatures/node/src/indexer_common.rs @@ -211,7 +211,7 @@ impl SignatureRespondedEvent { } } -pub(crate) trait SignatureEventTrait { +pub(crate) trait SignatureEvent: std::fmt::Debug { fn generate_request_id(&self) -> [u8; 32]; fn generate_sign_request( &self, @@ -222,8 +222,6 @@ pub(crate) trait SignatureEventTrait { fn sender_string(&self) -> String; } -pub(crate) trait SignatureEvent: SignatureEventTrait + std::fmt::Debug {} - pub(crate) type SignatureEventBox = Box; pub(crate) async fn process_sign_event( @@ -275,7 +273,7 @@ pub(crate) async fn process_sign_event( let chain = sign_event.source_chain(); tracing::error!(?err, chain = %chain, "Failed to send {} sign request into queue", chain.as_str()); } else { - crate::metrics::NUM_SIGN_REQUESTS + crate::metrics::requests::NUM_SIGN_REQUESTS .with_label_values(&[ sign_event.source_chain().as_str(), node_near_account_id.as_str(), diff --git a/chain-signatures/node/src/indexer_hydration/mod.rs b/chain-signatures/node/src/indexer_hydration/mod.rs index 4d933ccba..b40cff0b6 100644 --- a/chain-signatures/node/src/indexer_hydration/mod.rs +++ b/chain-signatures/node/src/indexer_hydration/mod.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] use crate::backlog::Backlog; -use crate::indexer_common::{SignatureEvent, SignatureEventTrait}; +use crate::indexer_common::SignatureEvent; use crate::indexer_sol::MAX_SECP256K1_SCALAR; use crate::mesh::MeshState; use crate::node_client::NodeClient; @@ -147,9 +147,7 @@ impl HydrationSignatureRequestedEvent { } } -impl SignatureEvent for HydrationSignatureRequestedEvent {} - -impl SignatureEventTrait for HydrationSignatureRequestedEvent { +impl SignatureEvent for HydrationSignatureRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { // Encode the event data in ABI format let encoded = encode(&[ @@ -275,9 +273,7 @@ impl HydrationSignBidirectionalRequestedEvent { } } -impl SignatureEvent for HydrationSignBidirectionalRequestedEvent {} - -impl SignatureEventTrait for HydrationSignBidirectionalRequestedEvent { +impl SignatureEvent for HydrationSignBidirectionalRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { // Match TypeScript implementation using ABI encoding let encoded = ( @@ -535,7 +531,6 @@ pub async fn run( }; while let Some(block_res) = blocks.next().await { - tracing::info!("received block from hydration rpc"); let block = match block_res { Ok(block) => block, Err(e) => { @@ -546,6 +541,7 @@ pub async fn run( let number = block.number(); let hash = block.hash(); let header = block.header().clone(); + tracing::info!("received block from hydration rpc: block number {number}, hash {hash:?}"); // Subxt's Substrate header uses H256 as state root (BlakeTwo256 hash). let state_root: H256 = header.state_root; diff --git a/chain-signatures/node/src/indexer_sol.rs b/chain-signatures/node/src/indexer_sol.rs index d9319e0ee..cf84a83ce 100644 --- a/chain-signatures/node/src/indexer_sol.rs +++ b/chain-signatures/node/src/indexer_sol.rs @@ -5,7 +5,7 @@ use crate::protocol::{Chain, IndexedSignRequest, Sign, SignRequestType}; use crate::rpc::ContractStateWatcher; use crate::sign_bidirectional::hash_rlp_data; -use crate::indexer_common::{SignatureEvent, SignatureEventBox, SignatureEventTrait}; +use crate::indexer_common::{SignatureEvent, SignatureEventBox}; use alloy_sol_types::SolValue; use anchor_client::anchor_lang::AnchorDeserialize; use anchor_client::{Client, Cluster, Program}; @@ -163,9 +163,7 @@ pub struct SolSignRequest { pub key_version: u32, } -impl SignatureEvent for SignatureRequestedEvent {} - -impl SignatureEventTrait for SignatureRequestedEvent { +impl SignatureEvent for SignatureRequestedEvent { fn generate_request_id(&self) -> [u8; 32] { // Encode the event data in ABI format let encoded = encode(&[ @@ -217,10 +215,6 @@ impl SignatureEventTrait for SignatureRequestedEvent { // to match the TypeScript implementation let epsilon = derive_epsilon_sol(self.key_version, &self.sender_string(), &self.path); - // Use transaction signature as entropy - // let mut entropy = [0u8; 32]; - // entropy.copy_from_slice(&tx_sig[..32]); - let sign_id = SignId::new(self.generate_request_id()); tracing::info!(?sign_id, "solana signature requested"); @@ -250,9 +244,7 @@ impl SignatureEventTrait for SignatureRequestedEvent { } } -impl SignatureEvent for SignBidirectionalEvent {} - -impl SignatureEventTrait for SignBidirectionalEvent { +impl SignatureEvent for SignBidirectionalEvent { fn generate_request_id(&self) -> [u8; 32] { // Match TypeScript implementation using ABI encoding let encoded = ( diff --git a/chain-signatures/node/src/respond_bidirectional.rs b/chain-signatures/node/src/respond_bidirectional.rs index cd638bab3..a44433ae5 100644 --- a/chain-signatures/node/src/respond_bidirectional.rs +++ b/chain-signatures/node/src/respond_bidirectional.rs @@ -123,7 +123,6 @@ impl CompletedTx { Chain::Hydration => HYDRATION_RESPOND_BIDIRECTIONAL_PATH.to_string(), _ => anyhow::bail!("Unsupported chain: {}", chain), }; - tracing::info!("requester to derive epsilon: {:?}", self.tx.sender); let epsilon = self.tx.epsilon(&path)?; let entropy = self.tx.id.0; Ok(IndexedSignRequest { diff --git a/chain-signatures/node/src/rpc.rs b/chain-signatures/node/src/rpc.rs index aba2afdff..595e75d39 100644 --- a/chain-signatures/node/src/rpc.rs +++ b/chain-signatures/node/src/rpc.rs @@ -734,9 +734,6 @@ impl HydrationClient { } /// Call the Signet pallet's `respond()` extrinsic for a *single* request. - /// - /// You can extend this to true batching later; for now we just send 1-element - /// vectors, which is perfectly fine for the BoundedVec arguments. pub async fn call_respond(&self, id: &SignId, response: &Signature) -> anyhow::Result<()> { // Signet's request_id is a [u8; 32]; you already use it for Ethereum: // DynSolValue::FixedBytes(action.indexed.id.request_id.into(), 32) @@ -1569,7 +1566,7 @@ async fn try_publish_hydration( action: &PublishAction, timestamp: &Instant, signature: &Signature, - near_account_id: &AccountId, // you already pass this around for metrics labels + near_account_id: &AccountId, ) -> Result<(), ()> { let chain = action.indexed.chain; let sign_id = action.indexed.id; @@ -1589,9 +1586,6 @@ async fn try_publish_hydration( .await .map_err(|e| { tracing::error!(?sign_id, ?e, "Hydration: failed to publish signature"); - crate::metrics::SIGNATURE_PUBLISH_FAILURES - .with_label_values(&[chain.as_str(), near_account_id.as_str()]) - .inc(); })?; tracing::info!( ?sign_id, @@ -1616,9 +1610,6 @@ async fn try_publish_hydration( ?e, "Hydration: failed to publish respond bidirectional signature" ); - crate::metrics::SIGNATURE_PUBLISH_FAILURES - .with_label_values(&[chain.as_str(), near_account_id.as_str()]) - .inc(); })?; tracing::info!( ?sign_id, @@ -1629,7 +1620,7 @@ async fn try_publish_hydration( } } - crate::metrics::NUM_SIGN_SUCCESS + crate::metrics::requests::NUM_SIGN_SUCCESS .with_label_values(&[chain.as_str(), near_account_id.as_str()]) .inc(); let sign_latency_in_secs = crate::util::duration_between_unix( @@ -1637,17 +1628,12 @@ async fn try_publish_hydration( crate::util::current_unix_timestamp(), ) .as_secs(); - crate::metrics::SIGN_TOTAL_LATENCY + crate::metrics::requests::SIGN_TOTAL_LATENCY .with_label_values(&[chain.as_str(), near_account_id.as_str()]) .observe(sign_latency_in_secs as f64); - crate::metrics::SIGN_RESPOND_LATENCY + crate::metrics::requests::SIGN_RESPOND_LATENCY .with_label_values(&[chain.as_str(), near_account_id.as_str()]) .observe(timestamp.elapsed().as_secs_f64()); - if sign_latency_in_secs <= 30 { - crate::metrics::NUM_SIGN_SUCCESS_30S - .with_label_values(&[chain.as_str(), near_account_id.as_str()]) - .inc(); - } Ok(()) } diff --git a/chain-signatures/primitives/src/lib.rs b/chain-signatures/primitives/src/lib.rs index 0e94a0a09..051dc7f7a 100644 --- a/chain-signatures/primitives/src/lib.rs +++ b/chain-signatures/primitives/src/lib.rs @@ -152,7 +152,7 @@ impl Chain { Chain::NEAR => return None, Chain::Ethereum => ("CHECKPOINT_INTERVAL_ETHEREUM", 20), Chain::Solana => ("CHECKPOINT_INTERVAL_SOLANA", 120), - Chain::Hydration => ("CHECKPOINT_INTERVAL_HYDRATION", 120), + Chain::Hydration => ("CHECKPOINT_INTERVAL_HYDRATION", 240), }; let interval = std::env::var(key) From 676e9e56d9fdd23aa6c9d5d725f16196388c6e6c Mon Sep 17 00:00:00 2001 From: Xiangyi Zheng Date: Fri, 19 Dec 2025 10:26:26 +0800 Subject: [PATCH 11/11] additional changes --- chain-signatures/node/src/rpc.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/chain-signatures/node/src/rpc.rs b/chain-signatures/node/src/rpc.rs index 595e75d39..1a1a66aba 100644 --- a/chain-signatures/node/src/rpc.rs +++ b/chain-signatures/node/src/rpc.rs @@ -735,11 +735,6 @@ impl HydrationClient { /// Call the Signet pallet's `respond()` extrinsic for a *single* request. pub async fn call_respond(&self, id: &SignId, response: &Signature) -> anyhow::Result<()> { - // Signet's request_id is a [u8; 32]; you already use it for Ethereum: - // DynSolValue::FixedBytes(action.indexed.id.request_id.into(), 32) - // - // The `respond` extrinsic expects `BoundedVec` arguments, so wrap the 1-element - // `Vec` values in the generated `HydrationBoundedVec` newtype. let request_ids = HydrationBoundedVec(vec![id.request_id]); let signatures = HydrationBoundedVec(vec![Self::to_hydration_signature(response)]);