diff --git a/Cargo.lock b/Cargo.lock index cc40cec7a4..2922adec42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -214,6 +214,18 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c7021f180a0cbea0380eba97c2af3c57074cdaffe0eef7e840e1c9f2841e55" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-models-ext", + "ark-std", +] + [[package]] name = "ark-bls12-381" version = "0.4.0" @@ -226,6 +238,65 @@ dependencies = [ "ark-std", ] +[[package]] +name = "ark-bls12-381-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-models-ext", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-bw6-761" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-bw6-761-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" +dependencies = [ + "ark-bw6-761", + "ark-ec", + "ark-ff", + "ark-models-ext", + "ark-std", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3a13b34da09176a8baba701233fdffbaa7c1b1192ce031a3da4e55ce1f1a56" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", + "ark-snark", + "ark-std", + "blake2 0.10.6", + "derivative", + "digest 0.10.7", + "sha2 0.10.8", + "tracing", +] + [[package]] name = "ark-ec" version = "0.4.2" @@ -240,9 +311,60 @@ dependencies = [ "hashbrown 0.13.2", "itertools 0.10.5", "num-traits", + "rayon", "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" +dependencies = [ + "ark-bls12-377", + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ed-on-bls12-377-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ff", + "ark-models-ext", + "ark-std", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff", + "ark-models-ext", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.4.2" @@ -286,6 +408,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-models-ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", +] + [[package]] name = "ark-poly" version = "0.4.2" @@ -299,6 +434,63 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "ark-r1cs-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de1d1472e5cb020cb3405ce2567c91c8d43f21b674aef37b0202f5c3304761db" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "derivative", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" +dependencies = [ + "ark-ff", + "ark-std", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "ark-scale" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ark-scale" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -322,6 +514,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-snark" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d3cc6833a335bb8a600241889ead68ee89a3cf8448081fb7694c0fe503da63" +dependencies = [ + "ark-ff", + "ark-relations", + "ark-serialize", + "ark-std", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -330,6 +534,7 @@ checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", "rand", + "rayon", ] [[package]] @@ -402,7 +607,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "synstructure 0.13.1", ] @@ -425,7 +630,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -483,7 +688,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -533,7 +738,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -620,7 +825,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1041,7 +1246,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1440,7 +1645,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1467,7 +1672,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1484,7 +1689,7 @@ checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1508,7 +1713,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1519,7 +1724,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1628,7 +1833,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1641,7 +1846,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1730,7 +1935,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1754,7 +1959,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.79", + "syn 2.0.87", "termcolor", "toml 0.8.19", "walkdir", @@ -1916,7 +2121,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1936,7 +2141,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1982,9 +2187,9 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", "tiny-keccak", ] @@ -2015,12 +2220,12 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", - "primitive-types", + "impl-serde 0.4.0", + "primitive-types 0.12.2", "scale-info", - "uint", + "uint 0.9.5", ] [[package]] @@ -2064,7 +2269,7 @@ dependencies = [ "evm-runtime", "log", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "rlp", "scale-info", "serde", @@ -2078,7 +2283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da6cedc5cedb4208e59467106db0d1f50db01b920920589f8e672c02fdc04f" dependencies = [ "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "scale-info", "serde", ] @@ -2092,7 +2297,7 @@ dependencies = [ "environmental", "evm-core", "evm-runtime", - "primitive-types", + "primitive-types 0.12.2", ] [[package]] @@ -2104,7 +2309,7 @@ dependencies = [ "auto_impl", "environmental", "evm-core", - "primitive-types", + "primitive-types 0.12.2", "sha3", ] @@ -2129,7 +2334,7 @@ dependencies = [ "prettyplease 0.2.22", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2273,12 +2478,12 @@ dependencies = [ "sp-consensus", "sp-consensus-aura", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "sp-inherents", "sp-io", "sp-runtime", "sp-state-machine", - "sp-storage", + "sp-storage 21.0.0", "sp-timestamp", "substrate-prometheus-endpoint", "thiserror", @@ -2314,7 +2519,7 @@ dependencies = [ "sp-api", "sp-io", "sp-runtime", - "sp-storage", + "sp-storage 21.0.0", ] [[package]] @@ -2435,12 +2640,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" - [[package]] name = "foreign-types" version = "0.3.2" @@ -2489,7 +2688,7 @@ version = "1.0.0-dev" source = "git+https://github.com/gztensor/frontier?rev=b8e3025#b8e3025aa30ea65144372bd68d26090c0f31bea2" dependencies = [ "hex", - "impl-serde", + "impl-serde 0.4.0", "libsecp256k1", "log", "parity-scale-codec", @@ -2498,7 +2697,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface", + "sp-runtime-interface 28.0.0", "staging-xcm", ] @@ -2612,8 +2811,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface", - "sp-storage", + "sp-runtime-interface 28.0.0", + "sp-storage 21.0.0", "static_assertions", ] @@ -2653,16 +2852,16 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-database", - "sp-externalities", + "sp-externalities 0.29.0", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-storage", + "sp-storage 21.0.0", "sp-trie", - "sp-wasm-interface", + "sp-wasm-interface 21.0.1", "thiserror", "thousands", ] @@ -2682,7 +2881,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-tracing", + "sp-tracing 17.0.1", ] [[package]] @@ -2738,7 +2937,7 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-crypto-hashing-proc-macro", - "sp-debug-derive", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -2746,8 +2945,8 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std", - "sp-tracing", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-tracing 17.0.1", "sp-weights", "static_assertions", "tt-call", @@ -2770,7 +2969,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2783,7 +2982,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2795,7 +2994,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2806,7 +3005,7 @@ checksum = "68672b9ec6fe72d259d3879dc212c5e42e977588cdac830c76f54d9f492aeb58" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2816,7 +3015,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2834,7 +3033,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-version", "sp-weights", ] @@ -2987,7 +3186,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -3208,6 +3407,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "handlebars" version = "5.1.2" @@ -3270,11 +3475,6 @@ name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] [[package]] name = "hashlink" @@ -3323,6 +3523,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" @@ -3495,9 +3698,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -3531,15 +3734,15 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.4.1", "pin-project-lite", "tokio", "tower-service", @@ -3662,6 +3865,26 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" +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" @@ -3680,6 +3903,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.2" @@ -3863,9 +4095,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.24.7" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" +checksum = "126b48a5acc3c52fbd5381a77898cb60e145123179588a29e7ac48f9c06e401b" dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", @@ -3877,9 +4109,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.7" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" +checksum = "b0e503369a76e195b65af35058add0e6900b794a4e9a9316900ddd3a87a80477" dependencies = [ "async-trait", "bytes", @@ -3900,28 +4132,28 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.7" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" +checksum = "fc660a9389e2748e794a40673a4155d501f32db667757cdb80edeff0306b489b" dependencies = [ "heck 0.5.0", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "jsonrpsee-server" -version = "0.24.7" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c" +checksum = "af6e6c9b6d975edcb443565d648b605f3e85a04ec63aa6941811a8894cc9cded" dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.4.1", "hyper-util", "jsonrpsee-core", "jsonrpsee-types", @@ -3940,9 +4172,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.7" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" +checksum = "d8fb16314327cbc94fdf7965ef7e4422509cd5597f76d137bd104eb34aeede67" dependencies = [ "http 1.1.0", "serde", @@ -4121,7 +4353,7 @@ dependencies = [ "libp2p-identity", "log", "multiaddr 0.18.2", - "multihash 0.19.2", + "multihash 0.19.1", "multistream-select", "once_cell", "parking_lot 0.12.3", @@ -4166,7 +4398,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "lru 0.12.5", + "lru 0.12.4", "quick-protobuf", "quick-protobuf-codec", "smallvec", @@ -4183,7 +4415,7 @@ dependencies = [ "bs58 0.5.1", "ed25519-dalek", "hkdf", - "multihash 0.19.2", + "multihash 0.19.1", "quick-protobuf", "rand", "sha2 0.10.8", @@ -4216,7 +4448,7 @@ dependencies = [ "sha2 0.10.8", "smallvec", "thiserror", - "uint", + "uint 0.9.5", "unsigned-varint 0.7.2", "void", ] @@ -4272,7 +4504,7 @@ dependencies = [ "libp2p-identity", "log", "multiaddr 0.18.2", - "multihash 0.19.2", + "multihash 0.19.1", "once_cell", "quick-protobuf", "rand", @@ -4377,7 +4609,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4678,7 +4910,7 @@ dependencies = [ "tokio-util", "tracing", "trust-dns-resolver", - "uint", + "uint 0.9.5", "unsigned-varint 0.8.0", "url", "webpki", @@ -4715,11 +4947,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.14.5", ] [[package]] @@ -4768,7 +5000,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4782,7 +5014,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4793,7 +5025,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -4804,7 +5036,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5018,7 +5250,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5051,7 +5283,7 @@ dependencies = [ "data-encoding", "libp2p-identity", "multibase", - "multihash 0.19.2", + "multihash 0.19.1", "percent-encoding", "serde", "static_assertions", @@ -5106,12 +5338,12 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.2" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc41f430805af9d1cf4adae4ed2149c759b877b01d909a1f40256188d09345d2" +checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" dependencies = [ "core2", - "unsigned-varint 0.8.0", + "unsigned-varint 0.7.2", ] [[package]] @@ -5134,6 +5366,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "multistream-select" version = "0.13.0" @@ -5172,7 +5410,7 @@ checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5335,6 +5573,7 @@ dependencies = [ "memmap2 0.9.5", "node-subtensor-runtime", "pallet-commitments", + "pallet-drand", "pallet-transaction-payment", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api", @@ -5370,9 +5609,11 @@ dependencies = [ "sp-consensus-aura", "sp-consensus-grandpa", "sp-core", + "sp-crypto-ec-utils 0.14.0", "sp-inherents", "sp-io", "sp-keyring", + "sp-keystore", "sp-offchain", "sp-runtime", "sp-session", @@ -5390,6 +5631,7 @@ dependencies = [ name = "node-subtensor-runtime" version = "4.0.0-dev" dependencies = [ + "ark-serialize", "ed25519-dalek", "fp-account", "fp-evm", @@ -5404,6 +5646,8 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "getrandom", + "hex", "log", "pallet-admin-utils", "pallet-aura", @@ -5411,6 +5655,7 @@ dependencies = [ "pallet-base-fee", "pallet-collective", "pallet-commitments", + "pallet-drand", "pallet-dynamic-fee", "pallet-ethereum", "pallet-evm", @@ -5435,8 +5680,10 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-utility", "parity-scale-codec", + "rand_chacha", "scale-info", "serde_json", + "sha2 0.10.8", "smallvec", "sp-api", "sp-block-builder", @@ -5448,14 +5695,16 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std", - "sp-storage", - "sp-tracing", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-storage 21.0.0", + "sp-tracing 17.0.1", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", "subtensor-custom-rpc-runtime-api", "subtensor-macros", + "tle", + "w3f-bls", ] [[package]] @@ -5617,10 +5866,10 @@ 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.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5694,9 +5943,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -5715,7 +5964,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -5726,18 +5975,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.4.0+3.4.0" +version = "300.3.2+3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -5767,6 +6016,7 @@ dependencies = [ "frame-system", "log", "pallet-balances", + "pallet-drand", "pallet-scheduler", "pallet-subtensor", "parity-scale-codec", @@ -5775,8 +6025,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-tracing 17.0.1", "sp-weights", "substrate-fixed", "subtensor-macros", @@ -5853,7 +6103,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "subtensor-macros", ] @@ -5871,10 +6121,43 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "subtensor-macros", ] +[[package]] +name = "pallet-drand" +version = "0.0.1" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-crypto-primitives", + "ark-ec", + "ark-ff", + "ark-scale 0.0.11", + "ark-serialize", + "ark-std", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "serde_json", + "sha2 0.10.8", + "sp-ark-bls12-381", + "sp-core", + "sp-io", + "sp-keyring", + "sp-keystore", + "sp-runtime", + "subtensor-macros", + "tle", + "w3f-bls", +] + [[package]] name = "pallet-dynamic-fee" version = "4.0.0-dev" @@ -6098,7 +6381,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "subtensor-macros", ] @@ -6162,6 +6445,8 @@ dependencies = [ name = "pallet-subtensor" version = "4.0.0-dev" dependencies = [ + "ark-bls12-381", + "ark-serialize", "frame-benchmarking", "frame-support", "frame-system", @@ -6172,6 +6457,7 @@ dependencies = [ "num-traits", "pallet-balances", "pallet-collective", + "pallet-drand", "pallet-membership", "pallet-preimage", "pallet-scheduler", @@ -6180,20 +6466,24 @@ dependencies = [ "parity-scale-codec", "parity-util-mem", "rand", + "rand_chacha", "scale-info", "serde", "serde-tuple-vec-map", "serde_bytes", "serde_json", "serde_with", + "sha2 0.10.8", "sp-core", "sp-io", "sp-runtime", - "sp-std", - "sp-tracing", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-tracing 17.0.1", "sp-version", "substrate-fixed", "subtensor-macros", + "tle", + "w3f-bls", ] [[package]] @@ -6226,7 +6516,7 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-storage", + "sp-storage 21.0.0", "sp-timestamp", ] @@ -6362,7 +6652,7 @@ dependencies = [ "lru 0.8.1", "parity-util-mem-derive", "parking_lot 0.12.3", - "primitive-types", + "primitive-types 0.12.2", "smallvec", "winapi", ] @@ -6523,7 +6813,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6564,7 +6854,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6644,7 +6934,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6654,7 +6944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6794,7 +7084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6804,11 +7094,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "impl-codec", + "impl-codec 0.6.0", "impl-rlp", - "impl-serde", + "impl-serde 0.4.0", "scale-info", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.0", + "impl-num-traits", + "uint 0.10.0", ] [[package]] @@ -6862,7 +7164,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6873,7 +7175,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6901,7 +7203,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6938,7 +7240,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -6972,7 +7274,7 @@ dependencies = [ "itertools 0.10.5", "lazy_static", "log", - "multimap", + "multimap 0.8.3", "petgraph", "prettyplease 0.1.25", "prost 0.11.9", @@ -6993,14 +7295,14 @@ dependencies = [ "heck 0.5.0", "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.0", "once_cell", "petgraph", "prettyplease 0.2.22", "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.79", + "syn 2.0.87", "tempfile", ] @@ -7027,7 +7329,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7354,7 +7656,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7748,7 +8050,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "log", "sp-core", - "sp-wasm-interface", + "sp-wasm-interface 21.0.1", "thiserror", ] @@ -7813,7 +8115,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-state-machine", - "sp-tracing", + "sp-tracing 17.0.1", ] [[package]] @@ -7824,7 +8126,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -7886,11 +8188,11 @@ dependencies = [ "sp-consensus", "sp-core", "sp-database", - "sp-externalities", + "sp-externalities 0.29.0", "sp-runtime", "sp-state-machine", "sp-statement-store", - "sp-storage", + "sp-storage 21.0.0", "sp-trie", "substrate-prometheus-endpoint", ] @@ -8158,13 +8460,13 @@ dependencies = [ "schnellru", "sp-api", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "sp-io", "sp-panic-handler", - "sp-runtime-interface", + "sp-runtime-interface 28.0.0", "sp-trie", "sp-version", - "sp-wasm-interface", + "sp-wasm-interface 21.0.1", "tracing", ] @@ -8176,7 +8478,7 @@ dependencies = [ "polkavm", "sc-allocator", "sp-maybe-compressed-blob", - "sp-wasm-interface", + "sp-wasm-interface 21.0.1", "thiserror", "wasm-instrument", ] @@ -8189,7 +8491,7 @@ dependencies = [ "log", "polkavm", "sc-executor-common", - "sp-wasm-interface", + "sp-wasm-interface 21.0.1", ] [[package]] @@ -8205,8 +8507,8 @@ dependencies = [ "rustix 0.36.17", "sc-allocator", "sc-executor-common", - "sp-runtime-interface", - "sp-wasm-interface", + "sp-runtime-interface 28.0.0", + "sp-wasm-interface 21.0.1", "wasmtime", ] @@ -8446,7 +8748,7 @@ dependencies = [ "litep2p", "log", "multiaddr 0.18.2", - "multihash 0.19.2", + "multihash 0.19.1", "rand", "thiserror", "zeroize", @@ -8478,7 +8780,7 @@ dependencies = [ "sc-utils", "sp-api", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "sp-keystore", "sp-offchain", "sp-runtime", @@ -8558,7 +8860,7 @@ dependencies = [ "governor", "http 1.1.0", "http-body-util", - "hyper 1.5.0", + "hyper 1.4.1", "ip_network", "jsonrpsee", "log", @@ -8648,12 +8950,12 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage", + "sp-storage 21.0.0", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", @@ -8696,7 +8998,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-io", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", ] [[package]] @@ -8741,11 +9043,11 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing", + "sp-tracing 17.0.1", "thiserror", "tracing", "tracing-log", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -8756,7 +9058,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -8780,7 +9082,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-runtime", - "sp-tracing", + "sp-tracing 17.0.1", "sp-transaction-pool", "substrate-prometheus-endpoint", "thiserror", @@ -9054,9 +9356,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] @@ -9079,15 +9381,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -9148,7 +9460,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -9387,10 +9699,10 @@ dependencies = [ "scale-info", "sp-api-proc-macro", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "sp-metadata-ir", "sp-runtime", - "sp-runtime-interface", + "sp-runtime-interface 28.0.0", "sp-state-machine", "sp-trie", "sp-version", @@ -9408,7 +9720,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -9437,6 +9749,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "sp-ark-bls12-381" +version = "0.4.2" +source = "git+https://github.com/paritytech/substrate-curves#caa2eed74beb885dd07c7db5f916f2281dad818f" +dependencies = [ + "ark-bls12-381-ext", + "sp-crypto-ec-utils 0.10.0", +] + [[package]] name = "sp-block-builder" version = "34.0.0" @@ -9558,7 +9879,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "itertools 0.11.0", "k256", "libsecp256k1", @@ -9568,7 +9889,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types", + "primitive-types 0.12.2", "rand", "scale-info", "schnorrkel", @@ -9576,11 +9897,11 @@ dependencies = [ "secrecy", "serde", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", - "sp-std", - "sp-storage", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-externalities 0.29.0", + "sp-runtime-interface 28.0.0", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-storage 21.0.0", "ss58-registry", "substrate-bip39", "thiserror", @@ -9589,6 +9910,46 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sp-crypto-ec-utils" +version = "0.10.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "ark-bls12-377", + "ark-bls12-377-ext", + "ark-bls12-381", + "ark-bls12-381-ext", + "ark-bw6-761", + "ark-bw6-761-ext", + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale 0.0.12", + "sp-runtime-interface 24.0.0", +] + +[[package]] +name = "sp-crypto-ec-utils" +version = "0.14.0" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" +dependencies = [ + "ark-bls12-377", + "ark-bls12-377-ext", + "ark-bls12-381", + "ark-bls12-381-ext", + "ark-bw6-761", + "ark-bw6-761-ext", + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-377-ext", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ed-on-bls12-381-bandersnatch-ext", + "ark-scale 0.0.12", + "sp-runtime-interface 28.0.0", +] + [[package]] name = "sp-crypto-hashing" version = "0.1.0" @@ -9623,7 +9984,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -9642,7 +10003,27 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "sp-externalities" +version = "0.25.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage 19.0.0", ] [[package]] @@ -9652,7 +10033,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable dependencies = [ "environmental", "parity-scale-codec", - "sp-storage", + "sp-storage 21.0.0", ] [[package]] @@ -9696,11 +10077,11 @@ dependencies = [ "secp256k1", "sp-core", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "sp-externalities", + "sp-externalities 0.29.0", "sp-keystore", - "sp-runtime-interface", + "sp-runtime-interface 28.0.0", "sp-state-machine", - "sp-tracing", + "sp-tracing 17.0.1", "sp-trie", "tracing", "tracing-core", @@ -9724,7 +10105,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", ] [[package]] @@ -9808,11 +10189,30 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-io", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-weights", "tracing", ] +[[package]] +name = "sp-runtime-interface" +version = "24.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive", + "primitive-types 0.13.1", + "sp-externalities 0.25.0", + "sp-runtime-interface-proc-macro 17.0.0", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", + "sp-storage 19.0.0", + "sp-tracing 16.0.0", + "sp-wasm-interface 20.0.0", + "static_assertions", +] + [[package]] name = "sp-runtime-interface" version = "28.0.0" @@ -9822,16 +10222,29 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive", - "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", - "sp-std", - "sp-storage", - "sp-tracing", - "sp-wasm-interface", + "primitive-types 0.12.2", + "sp-externalities 0.29.0", + "sp-runtime-interface-proc-macro 18.0.0", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "sp-storage 21.0.0", + "sp-tracing 17.0.1", + "sp-wasm-interface 21.0.1", "static_assertions", ] +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "17.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "Inflector", + "expander", + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sp-runtime-interface-proc-macro" version = "18.0.0" @@ -9842,7 +10255,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -9884,7 +10297,7 @@ dependencies = [ "rand", "smallvec", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "sp-panic-handler", "sp-trie", "thiserror", @@ -9909,9 +10322,9 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", - "sp-externalities", + "sp-externalities 0.29.0", "sp-runtime", - "sp-runtime-interface", + "sp-runtime-interface 28.0.0", "thiserror", "x25519-dalek", ] @@ -9921,16 +10334,33 @@ name = "sp-std" version = "14.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" +[[package]] +name = "sp-std" +version = "14.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" + +[[package]] +name = "sp-storage" +version = "19.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "impl-serde 0.5.0", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", +] + [[package]] name = "sp-storage" version = "21.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", ] [[package]] @@ -9945,6 +10375,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sp-tracing" +version = "16.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "parity-scale-codec", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.18", +] + [[package]] name = "sp-tracing" version = "17.0.1" @@ -9953,7 +10394,7 @@ dependencies = [ "parity-scale-codec", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.18", ] [[package]] @@ -9995,7 +10436,7 @@ dependencies = [ "scale-info", "schnellru", "sp-core", - "sp-externalities", + "sp-externalities 0.29.0", "thiserror", "tracing", "trie-db", @@ -10007,14 +10448,14 @@ name = "sp-version" version = "37.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "parity-wasm", "scale-info", "serde", "sp-crypto-hashing-proc-macro", "sp-runtime", - "sp-std", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", "sp-version-proc-macro", "thiserror", ] @@ -10027,7 +10468,18 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "sp-wasm-interface" +version = "20.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk#c1238b64431b4c74d88c082099caed36177b5b11" +dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", ] [[package]] @@ -10053,7 +10505,7 @@ dependencies = [ "serde", "smallvec", "sp-arithmetic", - "sp-debug-derive", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", ] [[package]] @@ -10160,7 +10612,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10181,7 +10633,7 @@ dependencies = [ "sha2 0.10.8", "sqlx-core", "sqlx-sqlite", - "syn 2.0.79", + "syn 2.0.87", "tempfile", "tokio", "url", @@ -10348,7 +10800,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10405,7 +10857,7 @@ version = "0.17.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ "http-body-util", - "hyper 1.5.0", + "hyper 1.4.1", "hyper-util", "log", "prometheus", @@ -10433,7 +10885,7 @@ dependencies = [ "sp-core", "sp-io", "sp-maybe-compressed-blob", - "sp-tracing", + "sp-tracing 17.0.1", "sp-version", "strum 0.26.3", "tempfile", @@ -10452,7 +10904,7 @@ dependencies = [ "quote", "rayon", "subtensor-linting", - "syn 2.0.79", + "syn 2.0.87", "walkdir", ] @@ -10488,7 +10940,7 @@ dependencies = [ "proc-macro2", "procedural-fork", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10498,7 +10950,7 @@ dependencies = [ "ahash 0.8.11", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10536,9 +10988,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -10565,7 +11017,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10656,7 +11108,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10749,6 +11201,34 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tle" +version = "0.1.0" +source = "git+https://github.com/ideal-lab5/timelock?rev=5416406cfd32799e31e1795393d4916894de4468#5416406cfd32799e31e1795393d4916894de4468" +dependencies = [ + "aes-gcm", + "ark-bls12-377", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "array-bytes", + "chacha20poly1305", + "generic-array 0.14.7", + "parity-scale-codec", + "rand_chacha", + "rand_core", + "scale-info", + "serde", + "serde_cbor", + "serde_json", + "sha2 0.10.8", + "sha3", + "w3f-bls", +] + [[package]] name = "tokio" version = "1.40.0" @@ -10775,7 +11255,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10935,7 +11415,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -10969,6 +11449,15 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -11159,6 +11648,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 = "unicode-bidi" version = "0.3.17" @@ -11291,9 +11792,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "w3f-bls" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" +checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" dependencies = [ "ark-bls12-377", "ark-bls12-381", @@ -11360,7 +11861,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -11394,7 +11895,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12098,7 +12599,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -12158,7 +12659,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -12178,7 +12679,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cfc12df31a..cdcd6643d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ members = [ "support/macros", "support/linting", "support/procedural-fork", + "pallets/drand", ] exclude = ["support/procedural-fork"] resolver = "2" @@ -49,7 +50,7 @@ manual_inspect = "allow" async-trait = "0.1" cargo-husky = { version = "1", default-features = false } clap = "4.5.4" -codec = { version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } ed25519-dalek = { version = "2.1.0", default-features = false, features = ["alloc"] } enumflags2 = "0.7.9" futures = "0.3.30" @@ -63,14 +64,14 @@ parity-util-mem = "0.12.0" rand = "0.8.5" scale-codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } scale-info = { version = "2.11.2", default-features = false } -serde = { version = "1.0.199", default-features = false } +serde = { version = "1.0.214", default-features = false } serde-tuple-vec-map = { version = "1.0.1", default-features = false } serde_bytes = { version = "0.11.14", default-features = false } serde_json = { version = "1.0.121", default-features = false } serde_with = { version = "=2.0.0", default-features = false } smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", tag = "v0.7.0" } -syn = { version = "2", features = [ +syn = { version = "2.0.87", features = [ "full", "visit-mut", "visit", @@ -142,7 +143,7 @@ sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", t sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409" } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } @@ -192,6 +193,25 @@ pallet-evm-precompile-sha3fips = { git = "https://github.com/gztensor/frontier", pallet-evm-precompile-simple = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } pallet-hotfix-sufficients = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } +#DRAND +pallet-drand = { path = "pallets/drand", default-features = false } +sp-crypto-ec-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", features = ["bls12-381"] } +getrandom = { version = "0.2.15", features = ["custom"], default-features = false } +sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-stable2409", default-features = false } +w3f-bls = { version = "=0.1.3", default-features = false } +ark-crypto-primitives = { version = "0.4.0", default-features = false, features = [ "r1cs", "snark" ] } +ark-scale = { version = "0.0.11", default-features = false, features = ["hazmat"] } +sp-ark-bls12-381 = { git = "https://github.com/paritytech/substrate-curves", default-features = false } +ark-bls12-381 = { version = "0.4.0", features = ["curve"], default-features = false } +ark-serialize = { version = "0.4.0", features = [ "derive" ], default-features = false } +ark-ff = { version = "0.4.0", default-features = false } +ark-ec = { version = "0.4.0", default-features = false } +ark-std = { version = "0.4.0", default-features = false } +anyhow = "1.0.81" +sha2 = { version = "0.10.8", default-features = false } +rand_chacha = { version = "0.3.1", default-features = false } +tle = { git = "https://github.com/ideal-lab5/timelock", rev = "5416406cfd32799e31e1795393d4916894de4468", default-features = false } + frame-metadata = "16" [profile.release] diff --git a/node/Cargo.toml b/node/Cargo.toml index 0206413425..3d14b74967 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -63,6 +63,10 @@ frame-metadata-hash-extension = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-commitments = { path = "../pallets/commitments" } +pallet-drand = { workspace = true } +sp-crypto-ec-utils = { workspace = true } +sp-keystore = { workspace = true, default-features = false } + # These dependencies are used for the subtensor's RPCs jsonrpsee = { workspace = true, features = ["server"] } @@ -128,6 +132,7 @@ runtime-benchmarks = [ "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "pallet-commitments/runtime-benchmarks", + "pallet-drand/runtime-benchmarks" ] pow-faucet = [] @@ -139,6 +144,7 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "sp-runtime/try-runtime", "pallet-commitments/try-runtime", + "pallet-drand/try-runtime" ] metadata-hash = ["node-subtensor-runtime/metadata-hash"] diff --git a/node/src/client.rs b/node/src/client.rs index dd7dfda451..0ee39d51de 100644 --- a/node/src/client.rs +++ b/node/src/client.rs @@ -4,13 +4,15 @@ use sc_executor::WasmExecutor; /// Full backend. pub type FullBackend = sc_service::TFullBackend; /// Full client. -pub type FullClient = sc_service::TFullClient>; +pub type FullClient = sc_service::TFullClient; /// Always enable runtime benchmark host functions, the genesis state /// was built with them so we're stuck with them forever. /// /// They're just a noop, never actually get used if the runtime was not compiled with /// `runtime-benchmarks`. -type HostFunctions = ( +pub type HostFunctions = ( sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions, + sp_crypto_ec_utils::bls12_381::host_calls::HostFunctions, ); +pub type RuntimeExecutor = WasmExecutor; diff --git a/node/src/service.rs b/node/src/service.rs index 060d972b2b..7b14015bf3 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -26,7 +26,7 @@ use std::{marker::PhantomData, sync::Arc, time::Duration}; use substrate_prometheus_endpoint::Registry; use crate::cli::Sealing; -use crate::client::{FullBackend, FullClient}; +use crate::client::{FullBackend, FullClient, HostFunctions, RuntimeExecutor}; use crate::ethereum::{ db_config_dir, new_frontier_partial, spawn_frontier_tasks, BackendType, EthConfiguration, FrontierBackend, FrontierBlockImport, FrontierPartialComponents, StorageOverride, @@ -84,13 +84,14 @@ where }) .transpose()?; - let executor = sc_service::new_wasm_executor(&config.executor); + let executor = sc_service::new_wasm_executor::(&config.executor); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::( + sc_service::new_full_parts::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, )?; + let client = Arc::new(client); let telemetry = telemetry.map(|(worker, telemetry)| { @@ -427,10 +428,31 @@ where metrics, })?; - if config.offchain_worker.enabled { - task_manager.spawn_handle().spawn( + if config.offchain_worker.enabled && config.role.is_authority() { + let public_keys = keystore_container + .keystore() + .sr25519_public_keys(pallet_drand::KEY_TYPE); + + if public_keys.is_empty() { + match sp_keystore::Keystore::sr25519_generate_new( + &*keystore_container.keystore(), + pallet_drand::KEY_TYPE, + None, + ) { + Ok(_) => { + log::debug!("Offchain worker key generated"); + } + Err(e) => { + log::error!("Failed to create SR25519 key for offchain worker: {:?}", e); + } + } + } else { + log::debug!("Offchain worker key already exists"); + } + + task_manager.spawn_essential_handle().spawn( "offchain-workers-runner", - "offchain-worker", + None, sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { runtime_api_provider: client.clone(), is_validator: config.role.is_authority(), diff --git a/pallets/admin-utils/Cargo.toml b/pallets/admin-utils/Cargo.toml index c67c009143..f7dfedbbe0 100644 --- a/pallets/admin-utils/Cargo.toml +++ b/pallets/admin-utils/Cargo.toml @@ -29,6 +29,7 @@ log = { workspace = true } pallet-subtensor = { version = "4.0.0-dev", default-features = false, path = "../subtensor" } sp-weights = { workspace = true } substrate-fixed = { workspace = true } +pallet-drand = { workspace = true, default-features = false } [dev-dependencies] @@ -60,6 +61,7 @@ std = [ "sp-io/std", "sp-std/std", "substrate-fixed/std", + "pallet-drand/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -69,6 +71,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "pallet-subtensor/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", + "pallet-drand/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", @@ -76,5 +79,6 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-scheduler/try-runtime", "sp-runtime/try-runtime", - "pallet-subtensor/try-runtime" + "pallet-subtensor/try-runtime", + "pallet-drand/try-runtime" ] diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index bb799a21dd..475a2cb8f5 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -34,7 +34,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Implementation of the AuraInterface - type Aura: crate::AuraInterface; + type Aura: crate::AuraInterface<::AuthorityId, Self::MaxAuthorities>; /// The identifier type for an authority. type AuthorityId: Member @@ -74,10 +74,10 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Aura pallet to change the authorities. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::swap_authorities(new_authorities.len() as u32))] + #[pallet::weight(::WeightInfo::swap_authorities(new_authorities.len() as u32))] pub fn swap_authorities( origin: OriginFor, - new_authorities: BoundedVec, + new_authorities: BoundedVec<::AuthorityId, T::MaxAuthorities>, ) -> DispatchResult { ensure_root(origin)?; @@ -93,7 +93,7 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the default take. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::sudo_set_default_take())] + #[pallet::weight(::WeightInfo::sudo_set_default_take())] pub fn sudo_set_default_take(origin: OriginFor, default_take: u16) -> DispatchResult { ensure_root(origin)?; pallet_subtensor::Pallet::::set_max_delegate_take(default_take); @@ -117,7 +117,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the serving rate limit. #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::sudo_set_serving_rate_limit())] + #[pallet::weight(::WeightInfo::sudo_set_serving_rate_limit())] pub fn sudo_set_serving_rate_limit( origin: OriginFor, netuid: u16, @@ -137,7 +137,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the minimum difficulty. #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::sudo_set_min_difficulty())] + #[pallet::weight(::WeightInfo::sudo_set_min_difficulty())] pub fn sudo_set_min_difficulty( origin: OriginFor, netuid: u16, @@ -162,7 +162,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the maximum difficulty. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::sudo_set_max_difficulty())] + #[pallet::weight(::WeightInfo::sudo_set_max_difficulty())] pub fn sudo_set_max_difficulty( origin: OriginFor, netuid: u16, @@ -187,7 +187,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the weights version key. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::sudo_set_weights_version_key())] + #[pallet::weight(::WeightInfo::sudo_set_weights_version_key())] pub fn sudo_set_weights_version_key( origin: OriginFor, netuid: u16, @@ -212,7 +212,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the weights set rate limit. #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::sudo_set_weights_set_rate_limit())] + #[pallet::weight(::WeightInfo::sudo_set_weights_set_rate_limit())] pub fn sudo_set_weights_set_rate_limit( origin: OriginFor, netuid: u16, @@ -240,7 +240,7 @@ pub mod pallet { /// It is only callable by the root account, not changeable by the subnet owner. /// The extrinsic will call the Subtensor pallet to set the adjustment interval. #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::sudo_set_adjustment_interval())] + #[pallet::weight(::WeightInfo::sudo_set_adjustment_interval())] pub fn sudo_set_adjustment_interval( origin: OriginFor, netuid: u16, @@ -295,7 +295,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the adjustment beta. #[pallet::call_index(12)] - #[pallet::weight(T::WeightInfo::sudo_set_max_weight_limit())] + #[pallet::weight(::WeightInfo::sudo_set_max_weight_limit())] pub fn sudo_set_max_weight_limit( origin: OriginFor, netuid: u16, @@ -320,7 +320,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the immunity period. #[pallet::call_index(13)] - #[pallet::weight(T::WeightInfo::sudo_set_immunity_period())] + #[pallet::weight(::WeightInfo::sudo_set_immunity_period())] pub fn sudo_set_immunity_period( origin: OriginFor, netuid: u16, @@ -345,7 +345,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the minimum allowed weights. #[pallet::call_index(14)] - #[pallet::weight(T::WeightInfo::sudo_set_min_allowed_weights())] + #[pallet::weight(::WeightInfo::sudo_set_min_allowed_weights())] pub fn sudo_set_min_allowed_weights( origin: OriginFor, netuid: u16, @@ -370,7 +370,7 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the maximum allowed UIDs for a subnet. #[pallet::call_index(15)] - #[pallet::weight(T::WeightInfo::sudo_set_max_allowed_uids())] + #[pallet::weight(::WeightInfo::sudo_set_max_allowed_uids())] pub fn sudo_set_max_allowed_uids( origin: OriginFor, netuid: u16, @@ -398,7 +398,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the kappa. #[pallet::call_index(16)] - #[pallet::weight(T::WeightInfo::sudo_set_kappa())] + #[pallet::weight(::WeightInfo::sudo_set_kappa())] pub fn sudo_set_kappa(origin: OriginFor, netuid: u16, kappa: u16) -> DispatchResult { pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; @@ -415,7 +415,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the rho. #[pallet::call_index(17)] - #[pallet::weight(T::WeightInfo::sudo_set_rho())] + #[pallet::weight(::WeightInfo::sudo_set_rho())] pub fn sudo_set_rho(origin: OriginFor, netuid: u16, rho: u16) -> DispatchResult { pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; @@ -432,7 +432,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the activity cutoff. #[pallet::call_index(18)] - #[pallet::weight(T::WeightInfo::sudo_set_activity_cutoff())] + #[pallet::weight(::WeightInfo::sudo_set_activity_cutoff())] pub fn sudo_set_activity_cutoff( origin: OriginFor, netuid: u16, @@ -514,7 +514,7 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the target registrations per interval. #[pallet::call_index(21)] - #[pallet::weight(T::WeightInfo::sudo_set_target_registrations_per_interval())] + #[pallet::weight(::WeightInfo::sudo_set_target_registrations_per_interval())] pub fn sudo_set_target_registrations_per_interval( origin: OriginFor, netuid: u16, @@ -542,7 +542,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the minimum burn. #[pallet::call_index(22)] - #[pallet::weight(T::WeightInfo::sudo_set_min_burn())] + #[pallet::weight(::WeightInfo::sudo_set_min_burn())] pub fn sudo_set_min_burn( origin: OriginFor, netuid: u16, @@ -567,7 +567,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the maximum burn. #[pallet::call_index(23)] - #[pallet::weight(T::WeightInfo::sudo_set_max_burn())] + #[pallet::weight(::WeightInfo::sudo_set_max_burn())] pub fn sudo_set_max_burn( origin: OriginFor, netuid: u16, @@ -592,7 +592,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the difficulty. #[pallet::call_index(24)] - #[pallet::weight(T::WeightInfo::sudo_set_difficulty())] + #[pallet::weight(::WeightInfo::sudo_set_difficulty())] pub fn sudo_set_difficulty( origin: OriginFor, netuid: u16, @@ -616,7 +616,7 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the maximum allowed validators. #[pallet::call_index(25)] - #[pallet::weight(T::WeightInfo::sudo_set_max_allowed_validators())] + #[pallet::weight(::WeightInfo::sudo_set_max_allowed_validators())] pub fn sudo_set_max_allowed_validators( origin: OriginFor, netuid: u16, @@ -649,7 +649,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the bonds moving average. #[pallet::call_index(26)] - #[pallet::weight(T::WeightInfo::sudo_set_bonds_moving_average())] + #[pallet::weight(::WeightInfo::sudo_set_bonds_moving_average())] pub fn sudo_set_bonds_moving_average( origin: OriginFor, netuid: u16, @@ -674,7 +674,7 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the maximum registrations per block. #[pallet::call_index(27)] - #[pallet::weight(T::WeightInfo::sudo_set_max_registrations_per_block())] + #[pallet::weight(::WeightInfo::sudo_set_max_registrations_per_block())] pub fn sudo_set_max_registrations_per_block( origin: OriginFor, netuid: u16, @@ -745,7 +745,7 @@ pub mod pallet { /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the tempo. #[pallet::call_index(30)] - #[pallet::weight(T::WeightInfo::sudo_set_tempo())] + #[pallet::weight(::WeightInfo::sudo_set_tempo())] pub fn sudo_set_tempo(origin: OriginFor, netuid: u16, tempo: u16) -> DispatchResult { ensure_root(origin)?; ensure!( @@ -967,7 +967,7 @@ pub mod pallet { /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the value. #[pallet::call_index(49)] - #[pallet::weight(T::WeightInfo::sudo_set_commit_reveal_weights_enabled())] + #[pallet::weight(::WeightInfo::sudo_set_commit_reveal_weights_enabled())] pub fn sudo_set_commit_reveal_weights_enabled( origin: OriginFor, netuid: u16, @@ -1040,7 +1040,7 @@ pub mod pallet { /// /// # Errors /// * `DispatchError::BadOrigin` - If the origin is not the root account. - // #[pallet::weight(T::WeightInfo::sudo_set_hotkey_emission_tempo())] + // #[pallet::weight(::WeightInfo::sudo_set_hotkey_emission_tempo())] #[pallet::call_index(52)] #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_hotkey_emission_tempo( @@ -1083,7 +1083,7 @@ pub mod pallet { /// // - Consider adding a check to ensure the `netuid` corresponds to an existing network. // - Implement a mechanism to gradually adjust the max stake to prevent sudden changes. - // #[pallet::weight(T::WeightInfo::sudo_set_network_max_stake())] + // #[pallet::weight(::WeightInfo::sudo_set_network_max_stake())] #[pallet::call_index(53)] #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_network_max_stake( @@ -1191,7 +1191,7 @@ pub mod pallet { /// # Weight /// Weight is handled by the `#[pallet::weight]` attribute. #[pallet::call_index(57)] - #[pallet::weight(T::WeightInfo::sudo_set_commit_reveal_weights_interval())] + #[pallet::weight(::WeightInfo::sudo_set_commit_reveal_weights_interval())] pub fn sudo_set_commit_reveal_weights_interval( origin: OriginFor, netuid: u16, @@ -1216,7 +1216,7 @@ pub mod pallet { } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { - type Public = T::AuthorityId; + type Public = ::AuthorityId; } // Interfaces to interact with other pallets diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 187e87d924..a3d530479c 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -11,8 +11,9 @@ use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::U256; use sp_core::{ConstU64, H256}; use sp_runtime::{ + testing::TestXt, traits::{BlakeTwo256, ConstU32, IdentityLookup}, - BuildStorage, Perbill, + BuildStorage, KeyTypeId, Perbill, }; use sp_std::cmp::Ordering; use sp_weights::Weight; @@ -21,13 +22,13 @@ type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test - { + pub enum Test { System: frame_system = 1, Balances: pallet_balances = 2, AdminUtils: crate = 3, SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event, Error} = 4, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 5, + Drand: pallet_drand::{Pallet, Call, Storage, Event} = 6, } ); @@ -63,6 +64,10 @@ pub type Balance = u64; #[allow(dead_code)] pub type BlockNumber = u64; +pub type TestAuthId = test_crypto::TestAuthId; +pub type Index = u64; +pub type UncheckedExtrinsic = TestXt; + parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -272,6 +277,74 @@ impl pallet_scheduler::Config for Test { type Preimages = (); } +impl pallet_drand::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_drand::weights::SubstrateWeight; + type AuthorityId = TestAuthId; + type Verifier = pallet_drand::verifier::QuicknetVerifier; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; + type HttpFetchTimeout = ConstU64<1_000>; +} + +impl frame_system::offchain::SigningTypes for Test { + type Public = test_crypto::Public; + type Signature = test_crypto::Signature; +} + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); + +mod test_crypto { + use super::KEY_TYPE; + use sp_core::sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}; + use sp_core::U256; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::IdentifyAccount, + }; + + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = Sr25519Signature; + type GenericPublic = Sr25519Public; + } + + impl IdentifyAccount for Public { + type AccountId = U256; + + fn into_account(self) -> U256 { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(self.as_ref()); + U256::from_big_endian(&bytes) + } + } +} + +impl frame_system::offchain::CreateSignedTransaction> for Test { + fn create_transaction>( + call: RuntimeCall, + _public: Self::Public, + _account: Self::AccountId, + nonce: Index, + ) -> Option<( + RuntimeCall, + ::SignaturePayload, + )> { + Some((call, (nonce, ()))) + } +} + +impl frame_system::offchain::SendTransactionTypes for Test +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; +} + // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { sp_tracing::try_init_simple(); diff --git a/pallets/drand/Cargo.toml b/pallets/drand/Cargo.toml new file mode 100644 index 0000000000..82aeab66b0 --- /dev/null +++ b/pallets/drand/Cargo.toml @@ -0,0 +1,90 @@ +[package] +name = "pallet-drand" +description = "FRAME pallet for briding to drand." +authors = ["Tony Riemer "] +version = "0.0.1" +license = "MIT-0" +edition = "2021" +homepage = "https://www.idealabs.network" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false, features = [ + "derive", +] } +scale-info = { workspace = true, default-features = false, features = [ + "derive", +] } +serde = { workspace = true, features = ["derive"], default-features = false } +serde_json = { workspace = true, default-features = false } +log = { workspace = true, default-features = false } +hex = { workspace = true, features = ["serde"], default-features = false } +sha2 = { workspace = true } +anyhow = { workspace = true } +# frame deps +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false} +sp-runtime = { workspace = true, default-features = false} +# arkworks dependencies +sp-ark-bls12-381 = { workspace = true, default-features = false } +ark-bls12-381 = { workspace = true, features = ["curve"], default-features = false } +ark-serialize = { workspace = true, features = [ "derive" ], default-features = false } +ark-ff = { workspace = true, default-features = false } +ark-ec = { workspace = true, default-features = false } +ark-std = { workspace = true, default-features = false } +ark-crypto-primitives = { workspace = true, default-features = false, features = [ "r1cs", "snark" ] } +ark-scale = { workspace = true, default-features = false, features = ["hazmat"] } +w3f-bls = { workspace = true, default-features = false } +sp-keyring = { workspace = true, default-features = false } +subtensor-macros.workspace = true +tle = { workspace = true, default-features = false } + +[dev-dependencies] +sp-keystore = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "sha2/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-keystore/std", + "sp-keyring/std", + "sp-runtime/std", + "serde/std", + "serde_json/std", + "hex/std", + "sp-ark-bls12-381/std", + "ark-bls12-381/std", + "ark-serialize/std", + "ark-ff/std", + "ark-ec/std", + "ark-std/std", + "ark-crypto-primitives/std", + "ark-scale/std", + "w3f-bls/std", + "tle/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/drand/README.md b/pallets/drand/README.md new file mode 100644 index 0000000000..d0bdf0b7e7 --- /dev/null +++ b/pallets/drand/README.md @@ -0,0 +1,83 @@ +# Drand Bridge Pallet + +This is a [FRAME](https://docs.substrate.io/reference/frame-pallets/) pallet that allows Substrate-based chains to bridge to drand. It only supports bridging to drand's [Quicknet](https://drand.love/blog/quicknet-is-live-on-the-league-of-entropy-mainnet), which provides fresh randomness every 3 seconds. Adding this pallet to a runtime allows it to acquire verifiable on-chain randomness which can be used in runtime modules or ink! smart contracts. + +Read [here](https://github.com/ideal-lab5/pallet-drand/blob/main/docs/how_it_works.md) for a deep-dive into the pallet. + +## Usage + +Use this pallet in a Substrate runtime to acquire verifiable randomness from drand's quicknet. + +### Node Requirements + +Usage of this pallet requires that the node support: +- arkworks host functions +- offchain workers +- (optional - in case of smart contracts) Contracts pallet and drand chain extension enabled + +We have included a node in this repo, [substrate-node-template](https://github.com/ideal-lab5/pallet-drand/tree/main/substrate-node-template), that meets these requirements that you can use to get started. + +See [here](https://github.com/ideal-lab5/pallet-drand/blob/main/docs/integration.md) for a detailed guide on integrating this pallet into a runtime. + +### For Pallets +This pallet implements the [Randomness](https://paritytech.github.io/polkadot-sdk/master/frame_support/traits/trait.Randomness.html) trait. FRAME pallets can use it by configuring their runtimes + +``` rust +impl pallet_with_randomness for Runtime { + type Randomness = Drand; +} +``` + +Subsequently in your pallet, fetch the latest round randomness with: + +``` rust +let latest_randomness = T::Randomness::random(b"ctx"); +``` + +For example, the [lottery pallet](https://github.com/paritytech/polkadot-sdk/blob/d3d1542c1d387408c141f9a1a8168e32435a4be9/substrate/frame/lottery/src/lib.rs#L518) + +### For Smart Contracts + +Add a [chain extension](https://use.ink/macros-attributes/chain-extension/) to your runtime to expose the drand pallet's randomness. An example can be found in the template [here](https://github.com/ideal-lab5/pallet-drand/blob/f00598d961a484fc3c47d1d7f3fa74e5a9f4d38a/substrate-node-template/runtime/src/lib.rs#L854). and then follow the guide [here](https://github.com/ideal-lab5/contracts). The [template contract](https://github.com/ideal-lab5/contracts/tree/main/template) provides a minimal working example. + +## Building + +``` shell +cargo build +``` + +## Testing + +We maintain a minimum of 85% coverage on all new code. You can check coverage with tarpauling by running + +``` shell +cargo tarpaulin --rustflags="-C opt-level=0" +``` + +### Unit Tests + +``` shell +cargo test +``` + +### Benchmarks + +The pallet can be benchmarked with a substrate node that adds the pallet to it's runtime, such as the substrate-node-template example included in this repo. + +``` shell +cd substrate-node-template +# build the node with benchmarks enables +cargo build --release --features runtime-benchmarks +# run the pallet benchmarks +./target/release/node-template benchmark pallet \ + --chain dev \ + --wasm-execution=compiled \ + --pallet pallet_drand \ + --extrinsic "*" \ + --steps 50 \ + --repeat 20 \ + --output ../src/new_weight.rs \ + --allow-missing-host-functions +``` + +License: MIT-0 diff --git a/pallets/drand/src/benchmarking.rs b/pallets/drand/src/benchmarking.rs new file mode 100644 index 0000000000..0abf59a478 --- /dev/null +++ b/pallets/drand/src/benchmarking.rs @@ -0,0 +1,83 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Benchmarking setup for pallet-drand +use super::*; + +#[allow(unused)] +use crate::Pallet as Drand; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +pub const DRAND_PULSE: &str = "{\"round\":1000,\"randomness\":\"fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}"; +pub const DRAND_INFO_RESPONSE: &str = "{\"public_key\":\"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a\",\"period\":3,\"genesis_time\":1692803367,\"hash\":\"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971\",\"groupHash\":\"f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e\",\"schemeID\":\"bls-unchained-g1-rfc9380\",\"metadata\":{\"beaconID\":\"quicknet\"}}"; + +#[benchmarks( + where + T::Public: From, + )] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_beacon_config() { + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config = info.try_into_beacon_config().unwrap(); + + let alice = sp_keyring::Sr25519Keyring::Alice.public(); + + let config_payload = BeaconConfigurationPayload { + block_number: 1u32.into(), + config: config.clone(), + public: alice.into(), + }; + + #[extrinsic_call] + set_beacon_config(RawOrigin::Root, config_payload.clone(), None); + + assert_eq!(BeaconConfig::::get(), config); + } + + #[benchmark] + fn write_pulse() { + // Deserialize the beacon info and pulse + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config = info.try_into_beacon_config().unwrap(); + let u_p: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap(); + let p: Pulse = u_p.try_into_pulse().unwrap(); + + let block_number = 1u32.into(); + let alice = sp_keyring::Sr25519Keyring::Alice.public(); + + // Set the beacon configuration + BeaconConfig::::put(config); + + // Create PulsesPayload with a vector of pulses + let pulses_payload = PulsesPayload { + block_number, + pulses: vec![p.clone()], // Wrap the pulse in a vector + public: alice.into(), + }; + + #[extrinsic_call] + write_pulse(RawOrigin::None, pulses_payload.clone(), None); + + // Check that the pulse has been stored + assert_eq!(Pulses::::get(p.round), Some(p)); + } + + impl_benchmark_test_suite!(Drand, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/pallets/drand/src/bls12_381.rs b/pallets/drand/src/bls12_381.rs new file mode 100644 index 0000000000..9db5695bde --- /dev/null +++ b/pallets/drand/src/bls12_381.rs @@ -0,0 +1,47 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use ark_ec::pairing::Pairing; +use ark_std::{ops::Neg, Zero}; +use sp_ark_bls12_381::{ + Bls12_381 as Bls12_381Opt, G1Affine as G1AffineOpt, G2Affine as G2AffineOpt, +}; + +/// An optimized way to verify Drand pulses from quicket +/// Instead of computing two pairings and comparing them, we instead compute a multi miller loop, +/// and then take the final exponentiation, saving a lot of computational cost. +/// +/// This function is also inlined as a way to optimize performance. +/// +/// * `signature`: +/// * `q`: +/// * `msg_on_curve`: The message signed by Drand, hashed to G1 +/// * `p_pub`: The beacon public key +#[inline] +pub fn fast_pairing_opt( + signature: G1AffineOpt, + q: G2AffineOpt, + r: G1AffineOpt, + s: G2AffineOpt, +) -> bool { + let looped = Bls12_381Opt::multi_miller_loop([signature.neg(), r], [q, s]); + let exp = Bls12_381Opt::final_exponentiation(looped); + + match exp { + Some(e) => e.is_zero(), + None => false, + } +} diff --git a/pallets/drand/src/lib.rs b/pallets/drand/src/lib.rs new file mode 100644 index 0000000000..2acda3567e --- /dev/null +++ b/pallets/drand/src/lib.rs @@ -0,0 +1,588 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! # Drand Bridge Pallet +//! +//! A pallet to bridge to [drand](drand.love)'s Quicknet, injecting publicly verifiable randomness +//! into the runtime +//! +//! ## Overview +//! +//! Quicknet chain runs in an 'unchained' mode, producing a fresh pulse of randomness every 3s +//! This pallet implements an offchain worker that consumes pulses from quicket and then sends a +//! signed transaction to encode them in the runtime. The runtime uses the optimized arkworks host +//! functions to efficiently verify the pulse. +//! +//! Run `cargo doc --package pallet-drand --open` to view this pallet's documentation. + +// We make sure this pallet uses `no_std` for compiling to Wasm. +#![cfg_attr(not(feature = "std"), no_std)] +// Many errors are transformed throughout the pallet. +#![allow(clippy::manual_inspect)] + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +extern crate alloc; +use crate::alloc::string::ToString; + +use alloc::{format, string::String, vec, vec::Vec}; +use codec::Encode; +use frame_support::{pallet_prelude::*, traits::Randomness}; +use frame_system::{ + offchain::{ + AppCrypto, CreateSignedTransaction, SendUnsignedTransaction, SignedPayload, Signer, + SigningTypes, + }, + pallet_prelude::BlockNumberFor, +}; +use scale_info::prelude::cmp; +use sha2::{Digest, Sha256}; +use sp_core::blake2_256; +use sp_runtime::{ + offchain::{http, Duration}, + traits::{Hash, One}, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + KeyTypeId, Saturating, +}; + +pub mod bls12_381; +pub mod types; +pub mod utils; +pub mod verifier; + +use types::*; +use verifier::Verifier; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; +pub use weights::*; + +/// the main drand api endpoint +pub const API_ENDPOINT: &str = "https://drand.cloudflare.com"; +/// the drand quicknet chain hash +/// quicknet uses 'Tiny' BLS381, with small 48-byte sigs in G1 and 96-byte pubkeys in G2 +pub const QUICKNET_CHAIN_HASH: &str = + "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"; + +const CHAIN_HASH: &str = QUICKNET_CHAIN_HASH; + +pub const MAX_PULSES_TO_FETCH: u64 = 50; + +/// Defines application identifier for crypto keys of this module. +/// +/// Every module that deals with signatures needs to declare its unique identifier for +/// its crypto keys. +/// When offchain worker is signing transactions it's going to request keys of type +/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction. +/// The keys can be inserted manually via RPC (see `author_insertKey`). +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"drnd"); + +/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers. +/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment +/// the types with this pallet-specific identifier. +pub mod crypto { + use super::KEY_TYPE; + use sp_core::sr25519::Signature as Sr25519Signature; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + MultiSignature, MultiSigner, + }; + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + // implemented for runtime + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } + + impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> + for TestAuthId + { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } +} + +impl SignedPayload + for BeaconConfigurationPayload> +{ + fn public(&self) -> T::Public { + self.public.clone() + } +} + +impl SignedPayload for PulsesPayload> { + fn public(&self) -> T::Public { + self.public.clone() + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: CreateSignedTransaction> + frame_system::Config { + /// The identifier type for an offchain worker. + type AuthorityId: AppCrypto; + /// The overarching runtime event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// A type representing the weights required by the dispatchables of this pallet. + type WeightInfo: WeightInfo; + /// something that knows how to verify beacon pulses + type Verifier: Verifier; + /// A configuration for base priority of unsigned transactions. + /// + /// This is exposed so that it can be tuned for particular runtime, when + /// multiple pallets send unsigned transactions. + #[pallet::constant] + type UnsignedPriority: Get; + /// The maximum number of milliseconds we are willing to wait for the HTTP request to + /// complete. + #[pallet::constant] + type HttpFetchTimeout: Get; + } + + /// the drand beacon configuration + #[pallet::storage] + pub type BeaconConfig = + StorageValue<_, BeaconConfiguration, ValueQuery, DefaultBeaconConfig>; + + #[pallet::type_value] + pub fn DefaultBeaconConfig() -> BeaconConfiguration { + BeaconConfiguration { + public_key: OpaquePublicKey::truncate_from(vec![ + 131, 207, 15, 40, 150, 173, 238, 126, 184, 181, 240, 31, 202, 211, 145, 34, 18, + 196, 55, 224, 7, 62, 145, 31, 185, 0, 34, 211, 231, 96, 24, 60, 140, 75, 69, 11, + 106, 10, 108, 58, 198, 165, 119, 106, 45, 16, 100, 81, 13, 31, 236, 117, 140, 146, + 28, 194, 43, 14, 23, 230, 58, 175, 75, 203, 94, 214, 99, 4, 222, 156, 248, 9, 189, + 39, 76, 167, 59, 171, 74, 245, 166, 233, 199, 106, 75, 192, 158, 118, 234, 232, + 153, 30, 245, 236, 228, 90, + ]), + period: 3, + genesis_time: 1_692_803_367, + hash: BoundedHash::truncate_from(vec![ + 82, 219, 155, 167, 14, 12, 192, 246, 234, 247, 128, 61, 208, 116, 71, 161, 245, 71, + 119, 53, 253, 63, 102, 23, 146, 186, 148, 96, 12, 132, 233, 113, + ]), + group_hash: BoundedHash::truncate_from(vec![ + 244, 119, 213, 200, 159, 33, 161, 124, 134, 58, 127, 147, 124, 106, 109, 21, 133, + 148, 20, 210, 190, 9, 205, 68, 141, 66, 121, 175, 51, 28, 93, 62, + ]), + scheme_id: BoundedHash::truncate_from(vec![ + 98, 108, 115, 45, 117, 110, 99, 104, 97, 105, 110, 101, 100, 45, 103, 49, 45, 114, + 102, 99, 57, 51, 56, 48, + ]), + metadata: Metadata { + beacon_id: BoundedVec::truncate_from(vec![113, 117, 105, 99, 107, 110, 101, 116]), + }, + } + } + + /// map round number to pulse + #[pallet::storage] + pub type Pulses = StorageMap<_, Blake2_128Concat, RoundNumber, Pulse, OptionQuery>; + + #[pallet::storage] + pub(super) type LastStoredRound = StorageValue<_, RoundNumber, ValueQuery>; + + /// Defines the block when next unsigned transaction will be accepted. + /// + /// To prevent spam of unsigned (and unpaid!) transactions on the network, + /// we only allow one transaction per block. + /// This storage entry defines when new transaction is going to be accepted. + #[pallet::storage] + pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + BeaconConfigChanged, + /// Successfully set a new pulse(s). + NewPulse { + rounds: Vec, + }, + } + + #[pallet::error] + pub enum Error { + /// The value retrieved was `None` as no value was previously set. + NoneValue, + /// There was an attempt to increment the value in storage over `u32::MAX`. + StorageOverflow, + /// failed to connect to the + DrandConnectionFailure, + /// the pulse is invalid + UnverifiedPulse, + /// the round number did not increment + InvalidRoundNumber, + /// the pulse could not be verified + PulseVerificationError, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn offchain_worker(block_number: BlockNumberFor) { + log::debug!("Drand OCW working on block: {:?}", block_number); + if let Err(e) = Self::fetch_drand_pulse_and_send_unsigned(block_number) { + log::debug!("Drand: Failed to fetch pulse from drand. {:?}", e); + } + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + /// Validate unsigned call to this module. + /// + /// By default unsigned transactions are disallowed, but implementing the validator + /// here we make sure that some particular calls (the ones produced by offchain worker) + /// are being whitelisted and marked as valid. + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + match call { + Call::set_beacon_config { + config_payload: ref payload, + ref signature, + } => { + let signature = signature.as_ref().ok_or(InvalidTransaction::BadSigner)?; + Self::validate_signature_and_parameters( + payload, + signature, + &payload.block_number, + &payload.public, + ) + } + Call::write_pulse { + pulses_payload: ref payload, + ref signature, + } => { + let signature = signature.as_ref().ok_or(InvalidTransaction::BadSigner)?; + Self::validate_signature_and_parameters( + payload, + signature, + &payload.block_number, + &payload.public, + ) + } + _ => InvalidTransaction::Call.into(), + } + } + } + + #[pallet::call] + impl Pallet { + /// Verify and write a pulse from the beacon into the runtime + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::write_pulse(pulses_payload.pulses.len() as u32))] + pub fn write_pulse( + origin: OriginFor, + pulses_payload: PulsesPayload>, + _signature: Option, + ) -> DispatchResult { + ensure_none(origin)?; + let config = BeaconConfig::::get(); + + let mut last_stored_round = LastStoredRound::::get(); + let mut new_rounds = Vec::new(); + + for pulse in &pulses_payload.pulses { + let is_verified = T::Verifier::verify(config.clone(), pulse.clone()) + .map_err(|_| Error::::PulseVerificationError)?; + + if is_verified { + ensure!( + pulse.round > last_stored_round, + Error::::InvalidRoundNumber + ); + + // Store the pulse + Pulses::::insert(pulse.round, pulse.clone()); + + // Update last stored round + last_stored_round = pulse.round; + + // Collect the new round + new_rounds.push(pulse.round); + } + } + + // Update LastStoredRound storage + LastStoredRound::::put(last_stored_round); + + // Update the next unsigned block number + let current_block = frame_system::Pallet::::block_number(); + >::put(current_block.saturating_add(One::one())); + + // Emit event with all new rounds + if !new_rounds.is_empty() { + Self::deposit_event(Event::NewPulse { rounds: new_rounds }); + } + + Ok(()) + } + /// allows the root user to set the beacon configuration + /// generally this would be called from an offchain worker context. + /// there is no verification of configurations, so be careful with this. + /// + /// * `origin`: the root user + /// * `config`: the beacon configuration + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::set_beacon_config())] + pub fn set_beacon_config( + origin: OriginFor, + config_payload: BeaconConfigurationPayload>, + _signature: Option, + ) -> DispatchResult { + ensure_root(origin)?; + BeaconConfig::::put(config_payload.config); + + // now increment the block number at which we expect next unsigned transaction. + let current_block = frame_system::Pallet::::block_number(); + >::put(current_block.saturating_add(One::one())); + + Self::deposit_event(Event::BeaconConfigChanged {}); + Ok(()) + } + } +} + +impl Pallet { + /// fetch the latest public pulse from the configured drand beacon + /// then send a signed transaction to include it on-chain + fn fetch_drand_pulse_and_send_unsigned( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Ensure we can send an unsigned transaction + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Drand: Too early to send unsigned transaction"); + } + + let mut last_stored_round = LastStoredRound::::get(); + let latest_pulse_body = Self::fetch_drand_latest().map_err(|_| "Failed to query drand")?; + let latest_unbounded_pulse: DrandResponseBody = serde_json::from_str(&latest_pulse_body) + .map_err(|_| "Drand: Failed to serialize response body to pulse")?; + let latest_pulse = latest_unbounded_pulse + .try_into_pulse() + .map_err(|_| "Drand: Received pulse contains invalid data")?; + let current_round = latest_pulse.round; + + // If last_stored_round is zero, set it to current_round - 1 + if last_stored_round == 0 { + last_stored_round = current_round.saturating_sub(1); + LastStoredRound::::put(last_stored_round); + } + + if current_round > last_stored_round { + let rounds_to_fetch = cmp::min( + current_round.saturating_sub(last_stored_round), + MAX_PULSES_TO_FETCH, + ); + let mut pulses = Vec::new(); + + for round in (last_stored_round.saturating_add(1)) + ..=(last_stored_round.saturating_add(rounds_to_fetch)) + { + let pulse_body = Self::fetch_drand_by_round(round) + .map_err(|_| "Drand: Failed to query drand for round")?; + let unbounded_pulse: DrandResponseBody = serde_json::from_str(&pulse_body) + .map_err(|_| "Drand: Failed to serialize response body to pulse")?; + let pulse = unbounded_pulse + .try_into_pulse() + .map_err(|_| "Drand: Received pulse contains invalid data")?; + pulses.push(pulse); + } + + let signer = Signer::::all_accounts(); + + let results = signer.send_unsigned_transaction( + |account| PulsesPayload { + block_number, + pulses: pulses.clone(), + public: account.public.clone(), + }, + |pulses_payload, signature| Call::write_pulse { + pulses_payload, + signature: Some(signature), + }, + ); + + for (acc, res) in &results { + match res { + Ok(()) => log::debug!( + "Drand: [{:?}] Submitted new pulses up to round: {:?}", + acc.id, + last_stored_round.saturating_add(rounds_to_fetch) + ), + Err(e) => log::error!( + "Drand: [{:?}] Failed to submit transaction: {:?}", + acc.id, + e + ), + } + } + } + + Ok(()) + } + + /// Query the endpoint `{api}/{chainHash}/info` to receive information about the drand chain + /// Valid response bodies are deserialized into `BeaconInfoResponse` + fn fetch_drand_by_round(round: RoundNumber) -> Result { + let uri: &str = &format!("{}/{}/public/{}", API_ENDPOINT, CHAIN_HASH, round); + Self::fetch(uri) + } + fn fetch_drand_latest() -> Result { + let uri: &str = &format!("{}/{}/public/latest", API_ENDPOINT, CHAIN_HASH); + Self::fetch(uri) + } + + /// Fetch a remote URL and return the body of the response as a string. + fn fetch(uri: &str) -> Result { + let deadline = + sp_io::offchain::timestamp().add(Duration::from_millis(T::HttpFetchTimeout::get())); + let request = http::Request::get(uri); + let pending = request.deadline(deadline).send().map_err(|_| { + log::warn!("Drand: HTTP IO Error"); + http::Error::IoError + })?; + let response = pending.try_wait(deadline).map_err(|_| { + log::warn!("Drand: HTTP Deadline Reached"); + http::Error::DeadlineReached + })??; + + if response.code != 200 { + log::warn!("Drand: Unexpected status code: {}", response.code); + return Err(http::Error::Unknown); + } + let body = response.body().collect::>(); + let body_str = alloc::str::from_utf8(&body).map_err(|_| { + log::warn!("Drand: No UTF8 body"); + http::Error::Unknown + })?; + + Ok(body_str.to_string()) + } + + /// get the randomness at a specific block height + /// returns [0u8;32] if it does not exist + pub fn random_at(round: RoundNumber) -> [u8; 32] { + let pulse = Pulses::::get(round).unwrap_or_default(); + let rand = pulse.randomness.clone(); + let bounded_rand: [u8; 32] = rand.into_inner().try_into().unwrap_or([0u8; 32]); + + bounded_rand + } + + fn validate_signature_and_parameters( + payload: &impl SignedPayload, + signature: &T::Signature, + block_number: &BlockNumberFor, + public: &T::Public, + ) -> TransactionValidity { + let signature_valid = + SignedPayload::::verify::(payload, signature.clone()); + if !signature_valid { + return InvalidTransaction::BadProof.into(); + } + Self::validate_transaction_parameters(block_number, public) + } + + fn validate_transaction_parameters( + block_number: &BlockNumberFor, + public: &T::Public, + ) -> TransactionValidity { + // Now let's check if the transaction has any chance to succeed. + let next_unsigned_at = NextUnsignedAt::::get(); + if &next_unsigned_at > block_number { + return InvalidTransaction::Stale.into(); + } + // Let's make sure to reject transactions from the future. + let current_block = frame_system::Pallet::::block_number(); + if ¤t_block < block_number { + return InvalidTransaction::Future.into(); + } + + let provides_tag = (next_unsigned_at, public.encode()).using_encoded(blake2_256); + + ValidTransaction::with_tag_prefix("DrandOffchainWorker") + // We set the priority to the value stored at `UnsignedPriority`. + .priority(T::UnsignedPriority::get()) + // This transaction does not require anything else to go before into the pool. + // In theory we could require `previous_unsigned_at` transaction to go first, + // but it's not necessary in our case. + // We set the `provides` tag to be the same as `next_unsigned_at`. This makes + // sure only one transaction produced after `next_unsigned_at` will ever + // get to the transaction pool and will end up in the block. + // We can still have multiple transactions compete for the same "spot", + // and the one with higher priority will replace other one in the pool. + .and_provides(provides_tag) + // The transaction is only valid for next block. After that it's + // going to be revalidated by the pool. + .longevity(1) + // It's fine to propagate that transaction to other peers, which means it can be + // created even by nodes that don't produce blocks. + // Note that sometimes it's better to keep it for yourself (if you are the block + // producer), since for instance in some schemes others may copy your solution and + // claim a reward. + .propagate(true) + .build() + } +} + +/// construct a message (e.g. signed by drand) +pub fn message(current_round: RoundNumber, prev_sig: &[u8]) -> Vec { + let mut hasher = Sha256::default(); + hasher.update(prev_sig); + hasher.update(current_round.to_be_bytes()); + hasher.finalize().to_vec() +} + +impl Randomness> for Pallet { + // this function hashes together the subject with the latest known randomness from quicknet + fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { + let block_number_minus_one = + >::block_number().saturating_sub(One::one()); + + let last_stored_round = LastStoredRound::::get(); + + let mut entropy = T::Hash::default(); + if let Some(pulse) = Pulses::::get(last_stored_round) { + entropy = (subject, block_number_minus_one, pulse.randomness.clone()) + .using_encoded(T::Hashing::hash); + } + + (entropy, block_number_minus_one) + } +} diff --git a/pallets/drand/src/mock.rs b/pallets/drand/src/mock.rs new file mode 100644 index 0000000000..b656e33a8a --- /dev/null +++ b/pallets/drand/src/mock.rs @@ -0,0 +1,113 @@ +use crate as pallet_drand_bridge; +use crate::verifier::*; +use crate::*; +use frame_support::{ + derive_impl, parameter_types, + traits::{ConstU16, ConstU64}, +}; +use sp_core::{sr25519::Signature, H256}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; +use sp_runtime::{ + testing::TestXt, + traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system = 1, + Drand: pallet_drand_bridge = 2, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = sp_core::sr25519::Public; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +type Extrinsic = TestXt; +type AccountId = <::Signer as IdentifyAccount>::AccountId; + +impl frame_system::offchain::SigningTypes for Test { + type Public = ::Signer; + type Signature = Signature; +} + +impl frame_system::offchain::SendTransactionTypes for Test +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +impl frame_system::offchain::CreateSignedTransaction for Test +where + RuntimeCall: From, +{ + fn create_transaction>( + call: RuntimeCall, + _public: ::Signer, + _account: AccountId, + nonce: u64, + ) -> Option<(RuntimeCall, ::SignaturePayload)> { + Some((call, (nonce, ()))) + } +} + +parameter_types! { + pub const UnsignedPriority: u64 = 1 << 20; +} + +impl pallet_drand_bridge::Config for Test { + type AuthorityId = crypto::TestAuthId; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_drand_bridge::weights::SubstrateWeight; + type Verifier = QuicknetVerifier; + type UnsignedPriority = UnsignedPriority; + type HttpFetchTimeout = ConstU64<1_000>; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + let keystore = MemoryKeystore::new(); + ext.register_extension(KeystoreExt::new(keystore.clone())); + sp_keystore::Keystore::sr25519_generate_new( + &keystore, + pallet_drand_bridge::KEY_TYPE, + Some("//Alice"), + ) + .expect("Creating key with account Alice should succeed."); + ext +} diff --git a/pallets/drand/src/tests.rs b/pallets/drand/src/tests.rs new file mode 100644 index 0000000000..a0f1c58fe9 --- /dev/null +++ b/pallets/drand/src/tests.rs @@ -0,0 +1,419 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ + mock::*, BeaconConfig, BeaconConfigurationPayload, BeaconInfoResponse, Call, DrandResponseBody, + Error, Pulse, Pulses, PulsesPayload, RoundNumber, +}; +use codec::Encode; +use frame_support::{ + assert_noop, assert_ok, + pallet_prelude::{InvalidTransaction, TransactionSource}, +}; +use frame_system::RawOrigin; +use sp_runtime::{ + offchain::{ + testing::{PendingRequest, TestOffchainExt}, + OffchainWorkerExt, + }, + traits::ValidateUnsigned, +}; + +// The round number used to collect drand pulses +pub const ROUND_NUMBER: u64 = 1000; + +// Quicknet parameters +pub const DRAND_PULSE: &str = "{\"round\":1000,\"randomness\":\"fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}"; +pub const DRAND_INFO_RESPONSE: &str = "{\"public_key\":\"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a\",\"period\":3,\"genesis_time\":1692803367,\"hash\":\"52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971\",\"groupHash\":\"f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e\",\"schemeID\":\"bls-unchained-g1-rfc9380\",\"metadata\":{\"beaconID\":\"quicknet\"}}"; + +#[test] +fn it_can_submit_valid_pulse_when_beacon_config_exists() { + new_test_ext().execute_with(|| { + let u_p: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap(); + let p: Pulse = u_p.try_into_pulse().unwrap(); + + let alice = sp_keyring::Sr25519Keyring::Alice; + let block_number = 1; + System::set_block_number(block_number); + + // Set the beacon config + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config_payload = BeaconConfigurationPayload { + block_number, + config: info.clone().try_into_beacon_config().unwrap(), + public: alice.public(), + }; + + // The signature doesn't really matter here because the signature is validated in the + // transaction validation phase not in the dispatchable itself. + let signature = None; + assert_ok!(Drand::set_beacon_config( + RuntimeOrigin::root(), + config_payload, + signature + )); + + let pulses_payload = PulsesPayload { + pulses: vec![p.clone()], + block_number, + public: alice.public(), + }; + + // Dispatch an unsigned extrinsic. + assert_ok!(Drand::write_pulse( + RuntimeOrigin::none(), + pulses_payload, + signature + )); + + // Read pallet storage and assert an expected result. + let pulse = Pulses::::get(ROUND_NUMBER); + assert!(pulse.is_some()); + assert_eq!(pulse, Some(p)); + }); +} + +#[test] +fn it_rejects_invalid_pulse_due_to_bad_signature() { + new_test_ext().execute_with(|| { + let alice = sp_keyring::Sr25519Keyring::Alice; + let block_number = 1; + System::set_block_number(block_number); + + // Set the beacon config using Root origin + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config_payload = BeaconConfigurationPayload { + block_number, + config: info.try_into_beacon_config().unwrap(), + public: alice.public(), + }; + // Signature is not required for Root origin + let config_signature = None; + assert_ok!(Drand::set_beacon_config( + RuntimeOrigin::root(), + config_payload.clone(), + config_signature + )); + + // Get a bad pulse (invalid signature within the pulse data) + let bad_http_response = "{\"round\":1000,\"randomness\":\"87f03ef5f62885390defedf60d5b8132b4dc2115b1efc6e99d166a37ab2f3a02\",\"signature\":\"b0a8b04e009cf72534321aca0f50048da596a3feec1172a0244d9a4a623a3123d0402da79854d4c705e94bc73224c341\"}"; + let u_p: DrandResponseBody = serde_json::from_str(bad_http_response).unwrap(); + let p: Pulse = u_p.try_into_pulse().unwrap(); + + // Prepare the pulses payload + let pulses_payload = PulsesPayload { + pulses: vec![p.clone()], + block_number, + public: alice.public(), + }; + let pulses_signature = alice.sign(&pulses_payload.encode()); + + assert_noop!( + Drand::write_pulse( + RawOrigin::None.into(), + pulses_payload.clone(), + Some(pulses_signature) + ), + Error::::PulseVerificationError + ); + + let pulse = Pulses::::get(ROUND_NUMBER); + assert!(pulse.is_none()); + }); +} + +#[test] +fn it_rejects_pulses_with_non_incremental_round_numbers() { + new_test_ext().execute_with(|| { + let block_number = 1; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + + // Set the beacon config + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config_payload = BeaconConfigurationPayload { + block_number, + config: info.clone().try_into_beacon_config().unwrap(), + public: alice.public(), + }; + // The signature doesn't really matter here because the signature is validated in the + // transaction validation phase not in the dispatchable itself. + let signature = None; + assert_ok!(Drand::set_beacon_config( + RuntimeOrigin::root(), + config_payload, + signature + )); + + let u_p: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap(); + let p: Pulse = u_p.try_into_pulse().unwrap(); + let pulses_payload = PulsesPayload { + pulses: vec![p.clone()], + block_number, + public: alice.public(), + }; + + // Dispatch an unsigned extrinsic. + assert_ok!(Drand::write_pulse( + RuntimeOrigin::none(), + pulses_payload.clone(), + signature + )); + let pulse = Pulses::::get(ROUND_NUMBER); + assert!(pulse.is_some()); + + System::set_block_number(2); + + // Attempt to submit the same pulse again, which should fail + assert_noop!( + Drand::write_pulse(RuntimeOrigin::none(), pulses_payload, signature), + Error::::InvalidRoundNumber, + ); + }); +} + +#[test] +fn it_blocks_non_root_from_submit_beacon_info() { + new_test_ext().execute_with(|| { + let block_number = 1; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + + // Prepare the beacon configuration payload + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config_payload = BeaconConfigurationPayload { + block_number, + config: info.try_into_beacon_config().unwrap(), + public: alice.public(), + }; + + // Signature is not required when using Root origin, but we'll include it for completeness + let signature = None; + + // Attempt to set the beacon config with a non-root origin (signed by Alice) + // Expect it to fail with BadOrigin + assert_noop!( + Drand::set_beacon_config( + RuntimeOrigin::signed(alice.public()), + config_payload.clone(), + signature + ), + sp_runtime::DispatchError::BadOrigin + ); + + // Attempt to set the beacon config with an unsigned origin + // Expect it to fail with BadOrigin + assert_noop!( + Drand::set_beacon_config(RuntimeOrigin::none(), config_payload.clone(), signature), + sp_runtime::DispatchError::BadOrigin + ); + + // Now attempt to set the beacon config with Root origin + // Expect it to succeed + assert_ok!(Drand::set_beacon_config( + RuntimeOrigin::root(), + config_payload, + signature + )); + + // Verify that the BeaconConfig storage item has been updated + let stored_config = BeaconConfig::::get(); + assert_eq!(stored_config, info.try_into_beacon_config().unwrap()); + }); +} + +#[test] +fn signed_cannot_submit_beacon_info() { + new_test_ext().execute_with(|| { + let block_number = 1; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + + // Set the beacon config + let info: BeaconInfoResponse = serde_json::from_str(DRAND_INFO_RESPONSE).unwrap(); + let config_payload = BeaconConfigurationPayload { + block_number, + config: info.clone().try_into_beacon_config().unwrap(), + public: alice.public(), + }; + // The signature doesn't really matter here because the signature is validated in the + // transaction validation phase not in the dispatchable itself. + let signature = None; + // Dispatch a signed extrinsic + assert_noop!( + Drand::set_beacon_config( + RuntimeOrigin::signed(alice.public()), + config_payload, + signature + ), + sp_runtime::DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_validate_unsigned_write_pulse() { + new_test_ext().execute_with(|| { + let block_number = 1; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + let pulses_payload = PulsesPayload { + block_number, + pulses: vec![], + public: alice.public(), + }; + let signature = alice.sign(&pulses_payload.encode()); + + let call = Call::write_pulse { + pulses_payload: pulses_payload.clone(), + signature: Some(signature), + }; + + let source = TransactionSource::External; + let validity = Drand::validate_unsigned(source, &call); + + assert_ok!(validity); + }); +} + +#[test] +fn test_not_validate_unsigned_write_pulse_with_bad_proof() { + new_test_ext().execute_with(|| { + let block_number = 1; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + let pulses_payload = PulsesPayload { + block_number, + pulses: vec![], + public: alice.public(), + }; + + // Bad signature + let signature = ::Signature::default(); + let call = Call::write_pulse { + pulses_payload: pulses_payload.clone(), + signature: Some(signature), + }; + + let source = TransactionSource::External; + let validity = Drand::validate_unsigned(source, &call); + + assert_noop!(validity, InvalidTransaction::BadProof); + }); +} + +#[test] +fn test_not_validate_unsigned_write_pulse_with_no_payload_signature() { + new_test_ext().execute_with(|| { + let block_number = 1; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + let pulses_payload = PulsesPayload { + block_number, + pulses: vec![], + public: alice.public(), + }; + + // No signature + let signature = None; + let call = Call::write_pulse { + pulses_payload: pulses_payload.clone(), + signature, + }; + + let source = TransactionSource::External; + let validity = Drand::validate_unsigned(source, &call); + + assert_noop!(validity, InvalidTransaction::BadSigner); + }); +} + +#[test] +#[ignore] +fn test_validate_unsigned_write_pulse_by_non_authority() { + // TODO: https://github.com/ideal-lab5/pallet-drand/issues/3 + todo!( + "the transaction should be validated even if the signer of the payload is not an authority" + ); +} + +#[test] +#[ignore] +fn test_not_validate_unsigned_set_beacon_config_by_non_authority() { + // TODO: https://github.com/ideal-lab5/pallet-drand/issues/3 + todo!( + "the transaction should not be validated if the signer of the payload is not an authority" + ); +} + +#[test] +fn can_execute_and_handle_valid_http_responses() { + let (offchain, state) = TestOffchainExt::new(); + let mut t = sp_io::TestExternalities::default(); + t.register_extension(OffchainWorkerExt::new(offchain)); + + { + let mut state = state.write(); + state.expect_request(PendingRequest { + method: "GET".into(), + uri: "https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/1".into(), + response: Some(DRAND_PULSE.as_bytes().to_vec()), + sent: true, + ..Default::default() + }); + state.expect_request(PendingRequest { + method: "GET".into(), + uri: "https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest".into(), + response: Some(DRAND_PULSE.as_bytes().to_vec()), + sent: true, + ..Default::default() + }); + } + + t.execute_with(|| { + let actual_specific = Drand::fetch_drand_by_round(RoundNumber::from(1u64)).unwrap(); + assert_eq!(actual_specific, DRAND_PULSE); + + let actual_pulse = Drand::fetch_drand_latest().unwrap(); + assert_eq!(actual_pulse, DRAND_PULSE); + }); +} + +#[test] +fn validate_unsigned_rejects_future_block_number() { + new_test_ext().execute_with(|| { + let block_number = 1; + let future_block_number = 100; + let alice = sp_keyring::Sr25519Keyring::Alice; + System::set_block_number(block_number); + let pulses_payload = PulsesPayload { + block_number: future_block_number, + pulses: vec![], + public: alice.public(), + }; + let signature = alice.sign(&pulses_payload.encode()); + + let call = Call::write_pulse { + pulses_payload: pulses_payload.clone(), + signature: Some(signature), + }; + + let source = TransactionSource::External; + let validity = Drand::validate_unsigned(source, &call); + + assert_noop!(validity, InvalidTransaction::Future); + }); +} diff --git a/pallets/drand/src/types.rs b/pallets/drand/src/types.rs new file mode 100644 index 0000000000..49a0a593f5 --- /dev/null +++ b/pallets/drand/src/types.rs @@ -0,0 +1,205 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use alloc::{string::String, vec::Vec}; +use codec::{Decode, Encode}; +use frame_support::pallet_prelude::*; +use serde::{Deserialize, Serialize}; +use subtensor_macros::freeze_struct; + +/// Represents an opaque public key used in drand's quicknet +pub type OpaquePublicKey = BoundedVec>; + +/// an opaque hash type +pub type BoundedHash = BoundedVec>; +/// the round number to track rounds of the beacon +pub type RoundNumber = u64; + +/// the expected response body from the drand api endpoint `api.drand.sh/{chainId}/info` +#[freeze_struct("f9e09b3273fe00cd")] +#[derive(Debug, Decode, Default, PartialEq, Encode, Serialize, Deserialize, TypeInfo, Clone)] +pub struct BeaconInfoResponse { + #[serde(with = "hex::serde")] + pub public_key: Vec, + pub period: u32, + pub genesis_time: u32, + #[serde(with = "hex::serde")] + pub hash: Vec, + #[serde(with = "hex::serde", rename = "groupHash")] + pub group_hash: Vec, + #[serde(rename = "schemeID")] + pub scheme_id: String, + pub metadata: MetadataInfoResponse, +} + +/// metadata associated with the drand info response +#[freeze_struct("91c762d05dbf1d21")] +#[derive(Debug, Decode, Default, PartialEq, Encode, Serialize, Deserialize, TypeInfo, Clone)] +pub struct MetadataInfoResponse { + #[serde(rename = "beaconID")] + beacon_id: String, +} + +impl BeaconInfoResponse { + /// the default configuration fetches from quicknet + pub fn try_into_beacon_config(&self) -> Result { + let bounded_pubkey = OpaquePublicKey::try_from(self.public_key.clone()) + .map_err(|_| "Failed to convert public_key")?; + let bounded_hash = + BoundedHash::try_from(self.hash.clone()).map_err(|_| "Failed to convert hash")?; + let bounded_group_hash = BoundedHash::try_from(self.group_hash.clone()) + .map_err(|_| "Failed to convert group_hash")?; + let bounded_scheme_id = BoundedHash::try_from(self.scheme_id.as_bytes().to_vec().clone()) + .map_err(|_| "Failed to convert scheme_id")?; + let bounded_beacon_id = + BoundedHash::try_from(self.metadata.beacon_id.as_bytes().to_vec().clone()) + .map_err(|_| "Failed to convert beacon_id")?; + + Ok(BeaconConfiguration { + public_key: bounded_pubkey, + period: self.period, + genesis_time: self.genesis_time, + hash: bounded_hash, + group_hash: bounded_group_hash, + scheme_id: bounded_scheme_id, + metadata: Metadata { + beacon_id: bounded_beacon_id, + }, + }) + } +} + +/// a pulse from the drand beacon +/// the expected response body from the drand api endpoint `api.drand.sh/{chainId}/public/latest` +#[freeze_struct("a3fed2c99a0638bf")] +#[derive(Debug, Decode, Default, PartialEq, Encode, Serialize, Deserialize)] +pub struct DrandResponseBody { + /// the randomness round number + pub round: RoundNumber, + /// the sha256 hash of the signature + // TODO: use Hash (https://github.com/ideal-lab5/pallet-drand/issues/2) + #[serde(with = "hex::serde")] + pub randomness: Vec, + /// BLS sig for the current round + // TODO: use Signature (https://github.com/ideal-lab5/pallet-drand/issues/2) + #[serde(with = "hex::serde")] + pub signature: Vec, +} + +impl DrandResponseBody { + pub fn try_into_pulse(&self) -> Result { + // TODO: update these bounded vecs + let bounded_randomness = BoundedVec::>::try_from(self.randomness.clone()) + .map_err(|_| "Failed to convert randomness")?; + // TODO: why is the sig size so big? + let bounded_signature = BoundedVec::>::try_from(self.signature.clone()) + .map_err(|_| "Failed to convert signature")?; + + Ok(Pulse { + round: self.round, + randomness: bounded_randomness, + signature: bounded_signature, + }) + } +} +/// A drand chain configuration +#[freeze_struct("1e01e739e2a5c940")] +#[derive( + Clone, + Debug, + Decode, + Default, + PartialEq, + Encode, + Serialize, + Deserialize, + MaxEncodedLen, + TypeInfo, +)] +pub struct BeaconConfiguration { + pub public_key: OpaquePublicKey, + pub period: u32, + pub genesis_time: u32, + pub hash: BoundedHash, + pub group_hash: BoundedHash, + pub scheme_id: BoundedHash, + pub metadata: Metadata, +} + +/// Payload used by to hold the beacon +/// config required to submit a transaction. +#[freeze_struct("2b7ebe4cb969cbd3")] +#[derive(Encode, Decode, Debug, Clone, PartialEq, scale_info::TypeInfo)] +pub struct BeaconConfigurationPayload { + pub block_number: BlockNumber, + pub config: BeaconConfiguration, + pub public: Public, +} + +/// metadata for the drand beacon configuration +#[freeze_struct("d87f51d2ad39c10e")] +#[derive( + Clone, + Debug, + Decode, + Default, + PartialEq, + Encode, + Serialize, + Deserialize, + MaxEncodedLen, + TypeInfo, +)] +pub struct Metadata { + pub beacon_id: BoundedHash, +} + +/// A pulse from the drand beacon +#[freeze_struct("de1a209f66f482b4")] +#[derive( + Clone, + Debug, + Decode, + Default, + PartialEq, + Encode, + Serialize, + Deserialize, + MaxEncodedLen, + TypeInfo, + Eq, +)] +pub struct Pulse { + /// the randomness round number + pub round: RoundNumber, + /// the sha256 hash of the signature + // TODO: use Hash (https://github.com/ideal-lab5/pallet-drand/issues/2) + pub randomness: BoundedVec>, + /// BLS sig for the current round + // TODO: use Signature (https://github.com/ideal-lab5/pallet-drand/issues/2) + // maybe add the sig size as a generic? + pub signature: BoundedVec>, +} + +/// Payload used by to hold the pulse +/// data required to submit a transaction. +#[freeze_struct("4a9f01b1d8fbbe89")] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct PulsesPayload { + pub block_number: BlockNumber, + pub pulses: Vec, + pub public: Public, +} diff --git a/pallets/drand/src/utils.rs b/pallets/drand/src/utils.rs new file mode 100644 index 0000000000..cc9408744d --- /dev/null +++ b/pallets/drand/src/utils.rs @@ -0,0 +1,81 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![allow(dead_code)] + +use crate::verifier::ArkScale; +use ark_ec::AffineRepr; +use ark_scale::hazmat::ArkScaleProjective; +use ark_serialize::{CanonicalSerialize, Compress}; +use ark_std::{test_rng, vec, vec::Vec, UniformRand}; +pub type ScalarFieldFor = ::ScalarField; + +// `words_count` is the scalar length in words, with 1 word assumed to be 64 bits. +// Most significant bit is set. +fn make_scalar(words_count: u32) -> Vec { + let mut scalar: Vec<_> = (0..words_count as usize) + .map(|_| u64::rand(&mut test_rng())) + .collect(); + // Arkworks assumes scalar to be in **big endian** + scalar[0] |= 1 << 63; + scalar +} + +fn make_base() -> Group { + Group::rand(&mut test_rng()) +} + +// `words_count` is the scalar length in words, with 1 word assumed to be 64 bits. +// Most significant bit is set. +pub fn make_scalar_args( + words_count: u32, +) -> (ArkScale, ArkScale>) { + (make_base::().into(), make_scalar(words_count).into()) +} + +// `words_count` is the scalar length in words, with 1 word assumed to be 64 bits. +// Most significant bit is set. +pub fn make_scalar_args_projective( + words_count: u32, +) -> (ArkScaleProjective, ArkScale>) { + (make_base::().into(), make_scalar(words_count).into()) +} + +pub fn make_pairing_args( +) -> (ArkScale, ArkScale) { + (make_base::().into(), make_base::().into()) +} + +pub fn make_msm_args( + size: u32, +) -> (ArkScale>, ArkScale>) { + let rng = &mut test_rng(); + let scalars = (0..size) + .map(|_| Group::ScalarField::rand(rng)) + .collect::>(); + let bases = (0..size).map(|_| Group::rand(rng)).collect::>(); + let bases: ArkScale> = bases.into(); + let scalars: ArkScale> = scalars.into(); + (bases, scalars) +} + +pub fn serialize_argument(argument: impl CanonicalSerialize) -> Vec { + let mut buf = vec![0; argument.serialized_size(Compress::No)]; + argument + .serialize_uncompressed(buf.as_mut_slice()) + .unwrap_or_default(); + buf +} diff --git a/pallets/drand/src/verifier.rs b/pallets/drand/src/verifier.rs new file mode 100644 index 0000000000..7c7acdb9c8 --- /dev/null +++ b/pallets/drand/src/verifier.rs @@ -0,0 +1,111 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! A collection of verifiers +//! +//! + +use crate::{ + bls12_381, + types::{BeaconConfiguration, Pulse, RoundNumber}, +}; +use alloc::{format, string::String, vec::Vec}; +use ark_ec::{hashing::HashToCurve, AffineRepr}; +use ark_serialize::CanonicalSerialize; +use codec::Decode; +use sha2::{Digest, Sha256}; +use sp_ark_bls12_381::{G1Affine as G1AffineOpt, G2Affine as G2AffineOpt}; +use tle::curves::drand::TinyBLS381; +use w3f_bls::engine::EngineBLS; + +const USAGE: ark_scale::Usage = ark_scale::WIRE; +pub type ArkScale = ark_scale::ArkScale; + +/// construct a message (e.g. signed by drand) +fn message(current_round: RoundNumber, prev_sig: &[u8]) -> Vec { + let mut hasher = Sha256::default(); + hasher.update(prev_sig); + hasher.update(current_round.to_be_bytes()); + hasher.finalize().to_vec() +} + +/// something to verify beacon pulses +pub trait Verifier { + /// verify the given pulse using beacon_config + fn verify(beacon_config: BeaconConfiguration, pulse: Pulse) -> Result; +} + +/// A verifier to check values received from quicknet. It outputs true if valid, false otherwise +/// +/// [Quicknet](https://drand.love/blog/quicknet-is-live-on-the-league-of-entropy-mainnet) operates in an unchained mode, +/// so messages contain only the round number. in addition, public keys are in G2 and signatures are +/// in G1 +/// +/// Values are valid if the pairing equality holds: +/// $e(sig, g_2) == e(msg_on_curve, pk)$ +/// where $sig \in \mathbb{G}_1$ is the signature +/// $g_2 \in \mathbb{G}_2$ is a generator +/// $msg_on_curve \in \mathbb{G}_1$ is a hash of the message that drand signed +/// (hash(round_number)) $pk \in \mathbb{G}_2$ is the public key, read from the input public +/// parameters +pub struct QuicknetVerifier; + +impl Verifier for QuicknetVerifier { + fn verify(beacon_config: BeaconConfiguration, pulse: Pulse) -> Result { + // decode public key (pk) + let pk = + ArkScale::::decode(&mut beacon_config.public_key.into_inner().as_slice()) + .map_err(|e| format!("Failed to decode public key: {}", e))?; + + // decode signature (sigma) + let signature = + ArkScale::::decode(&mut pulse.signature.into_inner().as_slice()) + .map_err(|e| format!("Failed to decode signature: {}", e))?; + + // m = sha256({} || {round}) + let message = message(pulse.round, &[]); + let hasher = ::hash_to_curve_map(); + // H(m) \in G1 + let message_hash = hasher + .hash(&message) + .map_err(|e| format!("Failed to hash message: {}", e))?; + + let mut bytes = Vec::new(); + message_hash + .serialize_compressed(&mut bytes) + .map_err(|e| format!("Failed to serialize message hash: {}", e))?; + + let message_on_curve = ArkScale::::decode(&mut &bytes[..]) + .map_err(|e| format!("Failed to decode message on curve: {}", e))?; + + let g2 = G2AffineOpt::generator(); + + Ok(bls12_381::fast_pairing_opt( + signature.0, + g2, + message_on_curve.0, + pk.0, + )) + } +} + +/// The unsafe skip verifier is just a pass-through verification, always returns true +pub struct UnsafeSkipVerifier; +impl Verifier for UnsafeSkipVerifier { + fn verify(_beacon_config: BeaconConfiguration, _pulse: Pulse) -> Result { + Ok(true) + } +} diff --git a/pallets/drand/src/weights.rs b/pallets/drand/src/weights.rs new file mode 100644 index 0000000000..6ab6e2905d --- /dev/null +++ b/pallets/drand/src/weights.rs @@ -0,0 +1,80 @@ +/* + * Copyright 2024 by Ideal Labs, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! Autogenerated weights for pallet_template +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-04-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Alexs-MacBook-Pro-2.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ../../target/release/node-template +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet_template +// --extrinsic +// * +// --steps=50 +// --repeat=20 +// --wasm-execution=compiled +// --output +// pallets/template/src/weights.rs +// --template +// ../../.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_template. +pub trait WeightInfo { + fn write_pulse(pulses_count: u32) -> Weight; + fn set_beacon_config() -> Weight; +} + +/// Weights for pallet_template using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Drand::BeaconConfig` (r:0 w:1) + /// Proof: `Drand::BeaconConfig` (`max_values`: Some(1), `max_size`: Some(238), added: 733, mode: `MaxEncodedLen`) + /// Storage: `Drand::NextUnsignedAt` (r:0 w:1) + /// Proof: `Drand::NextUnsignedAt` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn set_beacon_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(8_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Drand::BeaconConfig` (r:1 w:0) + /// Proof: `Drand::BeaconConfig` (`max_values`: Some(1), `max_size`: Some(238), added: 733, mode: `MaxEncodedLen`) + fn write_pulse(pulses_count: u32) -> Weight { + // Adjust the weight calculation based on pulses_count + Weight::from_parts(6_000_000 * pulses_count as u64, 0) + .saturating_add(Weight::from_parts(0, 1723 * pulses_count as u64)) + .saturating_add(T::DbWeight::get().reads_writes(1, pulses_count as u64)) + } +} diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index 100ce0dde9..da4cac3bef 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -41,11 +41,17 @@ pallet-utility = { workspace = true } ndarray = { workspace = true } hex = { workspace = true } -# Used for sudo decentralization pallet-collective = { version = "4.0.0-dev", default-features = false, path = "../collective" } +pallet-drand = { path = "../drand", default-features = false } pallet-membership = { workspace = true } hex-literal = { workspace = true } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } +tle = { workspace = true, default-features = false } +ark-bls12-381 = { workspace = true, default-features = false } +ark-serialize = { workspace = true, default-features = false } +w3f-bls = { workspace = true, default-features = false } +sha2 = { workspace = true } +rand_chacha = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } @@ -89,7 +95,14 @@ std = [ "serde_with/std", "substrate-fixed/std", "num-traits/std", - "serde_json/std" + "serde_json/std", + "tle/std", + "pallet-drand/std", + "ark-bls12-381/std", + "ark-serialize/std", + "w3f-bls/std", + "rand_chacha/std", + "sha2/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -102,6 +115,7 @@ runtime-benchmarks = [ "pallet-collective/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", + "pallet-drand/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", @@ -113,7 +127,8 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", "sp-runtime/try-runtime", - "pallet-collective/try-runtime" + "pallet-collective/try-runtime", + "pallet-drand/try-runtime" ] pow-faucet = [] fast-blocks = [] diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 3c621155f2..22addb3bab 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -2,7 +2,7 @@ use super::*; use frame_support::storage::IterableStorageMap; use substrate_fixed::types::I110F18; -impl Pallet { +impl Pallet { /// Executes the necessary operations for each block. pub fn block_step() -> Result<(), &'static str> { let block_number: u64 = Self::get_current_block_as_u64(); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 1cfe756a8f..6469ca3aca 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -1,6 +1,21 @@ use super::*; use substrate_fixed::types::I64F64; use substrate_fixed::types::I96F32; +use tle::stream_ciphers::AESGCMStreamCipherProvider; +use tle::tlock::tld; + +/// Contains all necesarry information to set weights. +/// +/// In the context of commit-reveal v3, this is the payload which should be +/// encrypted, compressed, serialized, and submitted to the `commit_crv3_weights` +/// extrinsic. +#[derive(Encode, Decode)] +#[freeze_struct("46e75a8326ba3665")] +pub struct WeightsTlockPayload { + pub uids: Vec, + pub values: Vec, + pub version_key: u64, +} impl Pallet { /// The `coinbase` function performs a four-part emission distribution process involving @@ -77,7 +92,18 @@ impl Pallet { for netuid in subnets.clone().iter() { // --- 4.1 Check to see if the subnet should run its epoch. if Self::should_run_epoch(*netuid, current_block) { - // --- 4.2 Drain the subnet emission. + // --- 4.2 Reveal weights from the n-2nd epoch. + if Self::get_commit_reveal_weights_enabled(*netuid) { + if let Err(e) = Self::reveal_crv3_commits(*netuid) { + log::warn!( + "Failed to reveal commits for subnet {} due to error: {:?}", + *netuid, + e + ); + }; + } + + // --- 4.3 Drain the subnet emission. let mut subnet_emission: u64 = PendingEmission::::get(*netuid); PendingEmission::::insert(*netuid, 0); log::debug!( @@ -86,7 +112,7 @@ impl Pallet { subnet_emission ); - // --- 4.3 Set last step counter. + // --- 4.4 Set last step counter. Self::set_blocks_since_last_step(*netuid, 0); Self::set_last_mechanism_step_block(*netuid, current_block); @@ -95,30 +121,30 @@ impl Pallet { continue; } - // --- 4.4 Distribute owner take. + // --- 4.5 Distribute owner take. if SubnetOwner::::contains_key(netuid) { // Does the subnet have an owner? - // --- 4.4.1 Compute the subnet owner cut. + // --- 4.5.1 Compute the subnet owner cut. let owner_cut: I96F32 = I96F32::from_num(subnet_emission).saturating_mul( I96F32::from_num(Self::get_subnet_owner_cut()) .saturating_div(I96F32::from_num(u16::MAX)), ); - // --- 4.4.2 Remove the cut from the subnet emission + // --- 4.5.2 Remove the cut from the subnet emission subnet_emission = subnet_emission.saturating_sub(owner_cut.to_num::()); - // --- 4.4.3 Add the cut to the balance of the owner + // --- 4.5.3 Add the cut to the balance of the owner Self::add_balance_to_coldkey_account( &Self::get_subnet_owner(*netuid), owner_cut.to_num::(), ); - // --- 4.4.4 Increase total issuance on the chain. + // --- 4.5.4 Increase total issuance on the chain. Self::coinbase(owner_cut.to_num::()); } - // 4.3 Pass emission through epoch() --> hotkey emission. + // 4.6 Pass emission through epoch() --> hotkey emission. let hotkey_emission: Vec<(T::AccountId, u64, u64)> = Self::epoch(*netuid, subnet_emission); log::debug!( @@ -127,9 +153,9 @@ impl Pallet { hotkey_emission ); - // 4.4 Accumulate the tuples on hotkeys: + // 4.7 Accumulate the tuples on hotkeys: for (hotkey, mining_emission, validator_emission) in hotkey_emission { - // 4.5 Accumulate the emission on the hotkey and parent hotkeys. + // 4.8 Accumulate the emission on the hotkey and parent hotkeys. Self::accumulate_hotkey_emission( &hotkey, *netuid, @@ -179,6 +205,136 @@ impl Pallet { } } + /// The `reveal_crv3_commits` function is run at the very beginning of epoch `n`, + pub fn reveal_crv3_commits(netuid: u16) -> dispatch::DispatchResult { + use ark_serialize::CanonicalDeserialize; + use frame_support::traits::OriginTrait; + use tle::curves::drand::TinyBLS381; + use tle::tlock::TLECiphertext; + use w3f_bls::EngineBLS; + + let cur_block = Self::get_current_block_as_u64(); + let cur_epoch = Self::get_epoch_index(netuid, cur_block); + + // Weights revealed must have been committed during epoch `cur_epoch - reveal_period`. + let reveal_epoch = + cur_epoch.saturating_sub(Self::get_reveal_period(netuid).saturating_sub(1)); + + // Clean expired commits + for (epoch, _) in CRV3WeightCommits::::iter_prefix(netuid) { + if epoch < reveal_epoch { + CRV3WeightCommits::::remove(netuid, epoch); + } + } + + // No commits to reveal until at least epoch 2. + if cur_epoch < 2 { + log::warn!("Failed to reveal commit for subnet {} Too early", netuid); + return Ok(()); + } + + let mut entries = CRV3WeightCommits::::take(netuid, reveal_epoch); + + // Keep popping item off the end of the queue until we sucessfully reveal a commit. + while let Some((who, serialized_compresssed_commit, round_number)) = entries.pop_front() { + let reader = &mut &serialized_compresssed_commit[..]; + let commit = match TLECiphertext::::deserialize_compressed(reader) { + Ok(c) => c, + Err(e) => { + log::warn!( + "Failed to reveal commit for subnet {} submitted by {:?} due to error deserializing the commit: {:?}", + netuid, + who, + e + ); + continue; + } + }; + + // Try to get the round number from pallet_drand. + let pulse = match pallet_drand::Pulses::::get(round_number) { + Some(p) => p, + None => { + // Round number used was not found on the chain. Skip this commit. + log::warn!( + "Failed to reveal commit for subnet {} submitted by {:?} due to missing round number {} at time of reveal.", + netuid, + who, + round_number + ); + continue; + } + }; + + let signature_bytes = pulse + .signature + .strip_prefix(b"0x") + .unwrap_or(&pulse.signature); + + let sig_reader = &mut &signature_bytes[..]; + let sig = match ::SignatureGroup::deserialize_compressed( + sig_reader, + ) { + Ok(s) => s, + Err(e) => { + log::error!( + "Failed to reveal commit for subnet {} submitted by {:?} due to error deserializing signature from drand pallet: {:?}", + netuid, + who, + e + ); + continue; + } + }; + + let decrypted_bytes: Vec = match tld::( + commit, sig, + ) { + Ok(d) => d, + Err(e) => { + log::warn!( + "Failed to reveal commit for subnet {} submitted by {:?} due to error decrypting the commit: {:?}", + netuid, + who, + e + ); + continue; + } + }; + + // Decrypt the bytes into WeightsPayload + let mut reader = &decrypted_bytes[..]; + let payload: WeightsTlockPayload = match Decode::decode(&mut reader) { + Ok(w) => w, + Err(e) => { + log::warn!("Failed to reveal commit for subnet {} submitted by {:?} due to error deserializing WeightsPayload: {:?}", netuid, who, e); + continue; + } + }; + + if let Err(e) = Self::do_set_weights( + T::RuntimeOrigin::signed(who.clone()), + netuid, + payload.uids, + payload.values, + payload.version_key, + ) { + log::warn!( + "Failed to `do_set_weights` for subnet {} submitted by {:?}: {:?}", + netuid, + who, + e + ); + continue; + }; + + // If we reached here, we sucessfully set weights! + return Ok(()); + } + + Ok(()) + } + /// Accumulates the mining and validator emissions on a hotkey and distributes the validator emission among its parents. /// /// This function is responsible for accumulating the mining and validator emissions associated with a hotkey onto a hotkey. diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 14693ee15c..8f9703d361 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -54,6 +54,8 @@ mod tests; // apparently this is stabilized since rust 1.36 extern crate alloc; +pub const MAX_CRV3_COMMIT_SIZE_BYTES: u32 = 2048; + #[deny(missing_docs)] #[import_section(errors::errors)] #[import_section(events::events)] @@ -73,7 +75,8 @@ pub mod pallet { BoundedVec, }; use frame_system::pallet_prelude::*; - use sp_core::H256; + use pallet_drand::types::RoundNumber; + use sp_core::{ConstU32, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::vec_deque::VecDeque; use sp_std::vec; @@ -1029,8 +1032,8 @@ pub mod pallet { /// --- MAP ( netuid ) --> bonds_moving_average pub type BondsMovingAverage = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; - #[pallet::storage] /// --- MAP ( netuid ) --> weights_set_rate_limit + #[pallet::storage] pub type WeightsSetRateLimit = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsSetRateLimit>; #[pallet::storage] @@ -1050,7 +1053,7 @@ pub mod pallet { pub type AdjustmentAlpha = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultAdjustmentAlpha>; #[pallet::storage] - /// --- MAP ( netuid ) --> interval + /// --- MAP ( netuid ) --> commit reveal v2 weights are enabled pub type CommitRevealWeightsEnabled = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; #[pallet::storage] @@ -1280,6 +1283,21 @@ pub mod pallet { OptionQuery, >; #[pallet::storage] + /// --- MAP (netuid, commit_epoch) --> VecDeque<(who, serialized_compressed_commit, reveal_round)> | Stores a queue of v3 commits for an account on a given netuid. + pub type CRV3WeightCommits = StorageDoubleMap< + _, + Twox64Concat, + u16, + Twox64Concat, + u64, + VecDeque<( + T::AccountId, + BoundedVec>, + RoundNumber, + )>, + ValueQuery, + >; + #[pallet::storage] /// --- Map (netuid) --> Number of epochs allowed for commit reveal periods pub type RevealPeriodEpochs = StorageMap<_, Twox64Concat, u16, u64, ValueQuery, DefaultRevealPeriodEpochs>; @@ -1515,6 +1533,18 @@ where Err(InvalidTransaction::Custom(4).into()) } } + Some(Call::commit_crv3_weights { netuid, .. }) => { + if Self::check_weights_min_stake(who, *netuid) { + let priority: u64 = Self::get_priority_set_weights(who, *netuid); + Ok(ValidTransaction { + priority, + longevity: 1, + ..Default::default() + }) + } else { + Err(InvalidTransaction::Custom(7).into()) + } + } Some(Call::add_stake { .. }) => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 414af7e6ba..1b0cf7c56c 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -7,7 +7,7 @@ use frame_support::pallet_macros::pallet_section; mod config { /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_drand::Config { /// call type type RuntimeCall: Parameter + Dispatchable diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index e98ecbd6ae..04a998e169 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1,3 +1,5 @@ +#![allow(clippy::crate_in_macro_def)] + use frame_support::pallet_macros::pallet_section; /// A [`pallet_section`] that defines the errors for a pallet. @@ -8,6 +10,8 @@ mod dispatches { use frame_support::traits::schedule::DispatchTime; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::traits::Saturating; + + use crate::MAX_CRV3_COMMIT_SIZE_BYTES; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. /// These functions materialize as "extrinsics", which are often compared to transactions. /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. @@ -82,11 +86,11 @@ mod dispatches { weights: Vec, version_key: u64, ) -> DispatchResult { - if !Self::get_commit_reveal_weights_enabled(netuid) { - return Self::do_set_weights(origin, netuid, dests, weights, version_key); + if Self::get_commit_reveal_weights_enabled(netuid) { + Err(Error::::CommitRevealEnabled.into()) + } else { + Self::do_set_weights(origin, netuid, dests, weights, version_key) } - - Err(Error::::CommitRevealEnabled.into()) } /// ---- Used to commit a hash of your weight values to later be revealed. @@ -172,6 +176,48 @@ mod dispatches { Self::do_reveal_weights(origin, netuid, uids, values, salt, version_key) } + /// ---- Used to commit encrypted commit-reveal v3 weight values to later be revealed. + /// + /// # Args: + /// * `origin`: (`::RuntimeOrigin`): + /// - The committing hotkey. + /// + /// * `netuid` (`u16`): + /// - The u16 network identifier. + /// + /// * `commit` (`Vec`): + /// - The encrypted compressed commit. + /// The steps for this are: + /// 1. Instantiate [`WeightsTlockPayload`] + /// 2. Serialize it using the `parity_scale_codec::Encode` trait + /// 3. Encrypt it following the steps (here)[https://github.com/ideal-lab5/tle/blob/f8e6019f0fb02c380ebfa6b30efb61786dede07b/timelock/src/tlock.rs#L283-L336] + /// to produce a [`TLECiphertext`] type. + /// 4. Serialize and compress using the `ark-serialize` `CanonicalSerialize` trait. + /// + /// * reveal_round (`u64`): + /// - The drand reveal round which will be avaliable during epoch `n+1` from the current + /// epoch. + /// + /// # Raises: + /// * `CommitRevealV3Disabled`: + /// - Attempting to commit when the commit-reveal mechanism is disabled. + /// + /// * `TooManyUnrevealedCommits`: + /// - Attempting to commit when the user has more than the allowed limit of unrevealed commits. + /// + #[pallet::call_index(99)] + #[pallet::weight((Weight::from_parts(46_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] + pub fn commit_crv3_weights( + origin: T::RuntimeOrigin, + netuid: u16, + commit: BoundedVec>, + reveal_round: u64, + ) -> DispatchResult { + Self::do_commit_crv3_weights(origin, netuid, commit, reveal_round) + } + /// ---- The implementation for batch revealing committed weights. /// /// # Args: diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index f3b03684d7..b403f39f56 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -204,6 +204,12 @@ mod events { ColdkeySwapScheduleDurationSet(BlockNumberFor), /// The duration of dissolve network has been set DissolveNetworkScheduleDurationSet(BlockNumberFor), + /// Commit-reveal v3 weights have been successfully committed. + /// + /// - **who**: The account ID of the user committing the weights. + /// - **netuid**: The network identifier. + /// - **commit_hash**: The hash representing the committed weights. + CRV3WeightsCommitted(T::AccountId, u16, H256), /// Weights have been successfully committed. /// /// - **who**: The account ID of the user committing the weights. diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index c511bceae5..6ddc639e0b 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -1,7 +1,10 @@ use super::*; use crate::epoch::math::*; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash}; +use sp_core::{ConstU32, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, Hash}, + BoundedVec, +}; use sp_std::{collections::vec_deque::VecDeque, vec}; impl Pallet { @@ -105,6 +108,106 @@ impl Pallet { }) } + /// ---- The implementation for committing commit-reveal v3 weights. + /// + /// # Args: + /// * `origin`: (`::RuntimeOrigin`): + /// - The signature of the committing hotkey. + /// + /// * `netuid` (`u16`): + /// - The u16 network identifier. + /// + /// * `commit` (`Vec`): + /// - The encrypted compressed commit. + /// The steps for this are: + /// 1. Instantiate [`WeightsPayload`] + /// 2. Serialize it using the `parity_scale_codec::Encode` trait + /// 3. Encrypt it following the steps (here)[https://github.com/ideal-lab5/tle/blob/f8e6019f0fb02c380ebfa6b30efb61786dede07b/timelock/src/tlock.rs#L283-L336] + /// to produce a [`TLECiphertext`] type. + /// 4. Serialize and compress using the `ark-serialize` `CanonicalSerialize` trait. + /// + /// * reveal_round (`u64`): + /// - The drand reveal round which will be avaliable during epoch `n+1` from the current + /// epoch. + /// + /// # Raises: + /// * `CommitRevealDisabled`: + /// - Raised if commit-reveal v3 is disabled for the specified network. + /// + /// * `HotKeyNotRegisteredInSubNet`: + /// - Raised if the hotkey is not registered on the specified network. + /// + /// * `CommittingWeightsTooFast`: + /// - Raised if the hotkey's commit rate exceeds the permitted limit. + /// + /// * `TooManyUnrevealedCommits`: + /// - Raised if the hotkey has reached the maximum number of unrevealed commits. + /// + /// # Events: + /// * `WeightsCommitted`: + /// - Emitted upon successfully storing the weight hash. + pub fn do_commit_crv3_weights( + origin: T::RuntimeOrigin, + netuid: u16, + commit: BoundedVec>, + reveal_round: u64, + ) -> DispatchResult { + // 1. Verify the caller's signature (hotkey). + let who = ensure_signed(origin)?; + + log::debug!( + "do_commit_v3_weights(hotkey: {:?}, netuid: {:?})", + who, + netuid + ); + + // 2. Ensure commit-reveal is enabled. + ensure!( + Self::get_commit_reveal_weights_enabled(netuid), + Error::::CommitRevealDisabled + ); + + // 3. Ensure the hotkey is registered on the network. + ensure!( + Self::is_hotkey_registered_on_network(netuid, &who), + Error::::HotKeyNotRegisteredInSubNet + ); + + // 4. Check that the commit rate does not exceed the allowed frequency. + let commit_block = Self::get_current_block_as_u64(); + let neuron_uid = Self::get_uid_for_net_and_hotkey(netuid, &who)?; + ensure!( + Self::check_rate_limit(netuid, neuron_uid, commit_block), + Error::::CommittingWeightsTooFast + ); + + // 5. Retrieve or initialize the VecDeque of commits for the hotkey. + let cur_block = Self::get_current_block_as_u64(); + let cur_epoch = Self::get_epoch_index(netuid, cur_block); + CRV3WeightCommits::::try_mutate(netuid, cur_epoch, |commits| -> DispatchResult { + // 6. Verify that the number of unrevealed commits is within the allowed limit. + ensure!(commits.len() < 10, Error::::TooManyUnrevealedCommits); + + // 7. Append the new commit with calculated reveal blocks. + // Hash the commit before it is moved, for the event + let commit_hash = BlakeTwo256::hash(&commit); + commits.push_back((who.clone(), commit, reveal_round)); + + // 8. Emit the WeightsCommitted event + Self::deposit_event(Event::CRV3WeightsCommitted( + who.clone(), + netuid, + commit_hash, + )); + + // 9. Update the last commit block for the hotkey's UID. + Self::set_last_update_for_uid(netuid, neuron_uid, commit_block); + + // 10. Return success. + Ok(()) + }) + } + /// ---- The implementation for revealing committed weights. /// /// # Args: diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 9da552ed74..b9ac2dcfcc 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -11,7 +11,7 @@ use frame_support::{ use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; use pallet_collective::MemberCount; -use sp_core::{Get, H256, U256}; +use sp_core::{offchain::KeyTypeId, ConstU64, Get, H256, U256}; use sp_runtime::Perbill; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, @@ -35,6 +35,7 @@ frame_support::construct_runtime!( Utility: pallet_utility::{Pallet, Call, Storage, Event} = 8, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 9, Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 10, + Drand: pallet_drand::{Pallet, Call, Storage, Event} = 11, } ); @@ -50,10 +51,9 @@ pub type BalanceCall = pallet_balances::Call; #[allow(dead_code)] pub type TestRuntimeCall = frame_system::Call; -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} +pub type Index = u64; + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); #[allow(dead_code)] pub type AccountId = U256; @@ -113,6 +113,11 @@ impl system::Config for Test { type Block = Block; } +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -453,6 +458,78 @@ impl pallet_preimage::Config for Test { type Consideration = (); } +mod test_crypto { + use super::KEY_TYPE; + use sp_core::{ + sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}, + U256, + }; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::IdentifyAccount, + }; + + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = Sr25519Signature; + type GenericPublic = Sr25519Public; + } + + impl IdentifyAccount for Public { + type AccountId = U256; + + fn into_account(self) -> U256 { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(self.as_ref()); + U256::from_big_endian(&bytes) + } + } +} + +pub type TestAuthId = test_crypto::TestAuthId; + +impl frame_system::offchain::SendTransactionTypes for Test +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; +} + +impl pallet_drand::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_drand::weights::SubstrateWeight; + type AuthorityId = TestAuthId; + type Verifier = pallet_drand::verifier::QuicknetVerifier; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; + type HttpFetchTimeout = ConstU64<1_000>; +} + +impl frame_system::offchain::SigningTypes for Test { + type Public = test_crypto::Public; + type Signature = test_crypto::Signature; +} + +pub type UncheckedExtrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::CreateSignedTransaction> for Test { + fn create_transaction>( + call: RuntimeCall, + _public: Self::Public, + _account: Self::AccountId, + nonce: Index, + ) -> Option<( + RuntimeCall, + ::SignaturePayload, + )> { + Some((call, (nonce, ()))) + } +} + #[allow(dead_code)] // Build genesis storage according to the mock runtime. pub fn new_test_ext(block_number: BlockNumber) -> sp_io::TestExternalities { @@ -532,7 +609,7 @@ pub(crate) fn step_epochs(count: u16, netuid: u16) { } } -/// Increments current block by `1`, running all hooks associated with doing so, and asserts +/// Increments current block by 1, running all hooks associated with doing so, and asserts /// that the block number was in fact incremented. /// /// Returns the new block number. diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index b6d0fc6163..63bf509a45 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -1,20 +1,36 @@ #![allow(clippy::indexing_slicing)] use super::mock::*; -use crate::{Error, Owner}; +use crate::{ + coinbase::run_coinbase::WeightsTlockPayload, CRV3WeightCommits, Error, Owner, + MAX_CRV3_COMMIT_SIZE_BYTES, +}; +use ark_serialize::CanonicalDeserialize; use frame_support::{ assert_err, assert_ok, dispatch::{DispatchClass, DispatchInfo, DispatchResult, GetDispatchInfo, Pays}, pallet_prelude::{InvalidTransaction, TransactionValidityError}, }; +use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use scale_info::prelude::collections::HashMap; +use sha2::Digest; use sp_core::{H256, U256}; use sp_runtime::{ - traits::{BlakeTwo256, DispatchInfoOf, Hash, SignedExtension}, - DispatchError, + traits::{BlakeTwo256, ConstU32, DispatchInfoOf, Hash, SignedExtension}, + BoundedVec, DispatchError, }; use sp_std::collections::vec_deque::VecDeque; use substrate_fixed::types::I32F32; +use tle::{ + curves::drand::TinyBLS381, + ibe::fullident::Identity, + stream_ciphers::AESGCMStreamCipherProvider, + tlock::{tld, tle}, +}; +use w3f_bls::EngineBLS; + +use pallet_drand::types::Pulse; +use sp_core::Encode; /*************************** pub fn set_weights() tests @@ -4186,3 +4202,1185 @@ fn test_commit_weights_rate_limit() { )); }); } + +#[test] +pub fn tlock_encrypt_decrypt_drand_quicknet_works() { + // using a pulse from drand's QuickNet + // https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/1000 + // the beacon public key + let pk_bytes = + b"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a" + ; // a round number that we know a signature for + let round: u64 = 1000; + // the signature produced in that round + let signature = + b"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39" + ; + + // Convert hex string to bytes + let pub_key_bytes = hex::decode(pk_bytes).expect("Failed to decode public key bytes"); + // Deserialize to G1Affine + let pub_key = + ::PublicKeyGroup::deserialize_compressed(&*pub_key_bytes) + .expect("Failed to deserialize public key"); + + // then we tlock a message for the pubkey + let plaintext = b"this is a test".as_slice(); + let esk = [2; 32]; + + let sig_bytes = hex::decode(signature).expect("Failed to decode signature bytes"); + let sig = ::SignatureGroup::deserialize_compressed(&*sig_bytes) + .expect("Failed to deserialize signature"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(round.to_be_bytes()); + hasher.finalize().to_vec() + }; + + let identity = Identity::new(b"", vec![message]); + + let rng = ChaCha20Rng::seed_from_u64(0); + let ct = tle::( + pub_key, esk, plaintext, identity, rng, + ) + .expect("Encryption failed"); + + // then we can decrypt the ciphertext using the signature + let result = tld::(ct, sig).expect("Decryption failed"); + assert!(result == plaintext); +} + +#[test] +fn test_reveal_crv3_commits_success() { + new_test_ext(100).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey1: AccountId = U256::from(1); + let hotkey2: AccountId = U256::from(2); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey1, U256::from(3), 100_000); + register_ok_neuron(netuid, hotkey2, U256::from(4), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 3); + + let neuron_uid1 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey1) + .expect("Failed to get neuron UID for hotkey1"); + let neuron_uid2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey2) + .expect("Failed to get neuron UID for hotkey2"); + + SubtensorModule::set_validator_permit_for_uid(netuid, neuron_uid1, true); + SubtensorModule::set_validator_permit_for_uid(netuid, neuron_uid2, true); + + let version_key = SubtensorModule::get_weights_version_key(netuid); + + let payload = WeightsTlockPayload { + values: vec![10, 20], + uids: vec![neuron_uid1, neuron_uid2], + version_key, + }; + + let serialized_payload = payload.encode(); + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(reveal_round.to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![message]); + + let ct = tle::( + pub_key, + esk, + &serialized_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes = Vec::new(); + ct.serialize_compressed(&mut commit_bytes) + .expect("Failed to serialize commit"); + + assert!( + !commit_bytes.is_empty(), + "commit_bytes is empty after serialization" + ); + + log::debug!( + "Commit bytes now contain {:#?}", + commit_bytes + ); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey1), + netuid, + commit_bytes.clone().try_into().expect("Failed to convert commit bytes into bounded vector"), + reveal_round + )); + + let sig_bytes = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39") + .expect("Failed to decode signature bytes"); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32].try_into().expect("Failed to convert randomness vector"), + signature: sig_bytes.try_into().expect("Failed to convert signature bytes"), + }, + ); + + // Step epochs to run the epoch via the blockstep + step_epochs(3, netuid); + + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse.get(neuron_uid1 as usize).cloned().unwrap_or_default(); + + assert!( + !weights.is_empty(), + "Weights for neuron_uid1 are empty, expected weights to be set." + ); + + let expected_weights: Vec<(u16, I32F32)> = payload + .uids + .iter() + .zip(payload.values.iter()) + .map(|(&uid, &value)| (uid, I32F32::from_num(value))) + .collect(); + + let total_weight: I32F32 = weights.iter().map(|(_, w)| *w).sum(); + + let normalized_weights: Vec<(u16, I32F32)> = weights + .iter() + .map(|&(uid, w)| (uid, w * I32F32::from_num(30) / total_weight)) + .collect(); + + for ((uid_a, w_a), (uid_b, w_b)) in normalized_weights.iter().zip(expected_weights.iter()) { + assert_eq!(uid_a, uid_b); + + let actual_weight_f64: f64 = w_a.to_num::(); + let rounded_actual_weight = actual_weight_f64.round() as i64; + + assert!( + rounded_actual_weight != 0, + "Actual weight for uid {} is zero", + uid_a + ); + + let expected_weight = w_b.to_num::(); + + assert_eq!( + rounded_actual_weight, expected_weight, + "Weight mismatch for uid {}: expected {}, got {}", + uid_a, expected_weight, rounded_actual_weight + ); + } + }); +} + +#[test] +fn test_reveal_crv3_commits_cannot_reveal_after_reveal_epoch() { + new_test_ext(100).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey1: AccountId = U256::from(1); + let hotkey2: AccountId = U256::from(2); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey1, U256::from(3), 100_000); + register_ok_neuron(netuid, hotkey2, U256::from(4), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 3); + + let neuron_uid1 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey1) + .expect("Failed to get neuron UID for hotkey1"); + let neuron_uid2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey2) + .expect("Failed to get neuron UID for hotkey2"); + + SubtensorModule::set_validator_permit_for_uid(netuid, neuron_uid1, true); + SubtensorModule::set_validator_permit_for_uid(netuid, neuron_uid2, true); + + let version_key = SubtensorModule::get_weights_version_key(netuid); + + let payload = WeightsTlockPayload { + values: vec![10, 20], + uids: vec![neuron_uid1, neuron_uid2], + version_key, + }; + + let serialized_payload = payload.encode(); + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(reveal_round.to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![message]); + + let ct = tle::( + pub_key, + esk, + &serialized_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes = Vec::new(); + ct.serialize_compressed(&mut commit_bytes) + .expect("Failed to serialize commit"); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey1), + netuid, + commit_bytes + .clone() + .try_into() + .expect("Failed to convert commit bytes into bounded vector"), + reveal_round + )); + + // Do NOT insert the pulse at this time; this simulates the missing pulse during the reveal epoch + // Advance epochs to reach the reveal epoch (3 epochs as reveal_period is 3) + step_epochs(3, netuid); + + // Verify that weights are not set + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse + .get(neuron_uid1 as usize) + .cloned() + .unwrap_or_default(); + + assert!( + weights.is_empty(), + "Weights for neuron_uid1 should be empty as the commit cannot be revealed without the pulse." + ); + + // Now, after the reveal epoch has passed, insert the pulse + let sig_bytes = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39") + .expect("Failed to decode signature bytes"); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32] + .try_into() + .expect("Failed to convert randomness vector"), + signature: sig_bytes + .try_into() + .expect("Failed to convert signature bytes"), + }, + ); + + // Advance one more epoch to be after the reveal epoch + step_epochs(1, netuid); + + // Attempt to reveal commits after the reveal epoch has passed + assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + + // Verify that the weights for the neuron have not been set + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse + .get(neuron_uid1 as usize) + .cloned() + .unwrap_or_default(); + + assert!( + weights.is_empty(), + "Weights for neuron_uid1 should be empty as the commit cannot be revealed after the reveal epoch." + ); + }); +} + +#[test] +fn test_do_commit_crv3_weights_success() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let commit_data: Vec = vec![1, 2, 3, 4, 5]; + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data + .clone() + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + + let cur_epoch = + SubtensorModule::get_epoch_index(netuid, SubtensorModule::get_current_block_as_u64()); + let commits = CRV3WeightCommits::::get(netuid, cur_epoch); + assert_eq!(commits.len(), 1); + assert_eq!(commits[0].0, hotkey); + assert_eq!(commits[0].1, commit_data); + assert_eq!(commits[0].2, reveal_round); + }); +} + +#[test] +fn test_do_commit_crv3_weights_disabled() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let commit_data: Vec = vec![1, 2, 3, 4, 5]; + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 5); + + SubtensorModule::set_commit_reveal_weights_enabled(netuid, false); + assert_err!( + SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + ), + Error::::CommitRevealDisabled + ); + }); +} + +#[test] +fn test_do_commit_crv3_weights_hotkey_not_registered() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let unregistered_hotkey: AccountId = U256::from(99); + let commit_data: Vec = vec![1, 2, 3, 4, 5]; + let reveal_round: u64 = 1000; + let hotkey: AccountId = U256::from(1); + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 5); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + + assert_err!( + SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(unregistered_hotkey), + netuid, + commit_data + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + ), + Error::::HotKeyNotRegisteredInSubNet + ); + }); +} + +#[test] +fn test_do_commit_crv3_weights_committing_too_fast() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let commit_data_1: Vec = vec![1, 2, 3]; + let commit_data_2: Vec = vec![4, 5, 6]; + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 5); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + let neuron_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).expect("Expected uid"); + SubtensorModule::set_last_update_for_uid(netuid, neuron_uid, 0); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data_1 + .clone() + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + + assert_err!( + SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data_2 + .clone() + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + ), + Error::::CommittingWeightsTooFast + ); + + step_block(2); + + assert_err!( + SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data_2 + .clone() + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + ), + Error::::CommittingWeightsTooFast + ); + + step_block(3); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data_2 + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + }); +} + +#[test] +fn test_do_commit_crv3_weights_too_many_unrevealed_commits() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + + // Simulate 10 unrevealed commits + let cur_epoch = + SubtensorModule::get_epoch_index(netuid, SubtensorModule::get_current_block_as_u64()); + for i in 0..10 { + let commit_data: Vec = vec![i as u8; 5]; + let bounded_commit_data = commit_data + .try_into() + .expect("Failed to convert commit data into bounded vector"); + assert_ok!(CRV3WeightCommits::::try_mutate( + netuid, + cur_epoch, + |commits| -> DispatchResult { + commits.push_back((hotkey, bounded_commit_data, reveal_round)); + Ok(()) + } + )); + } + + // Attempt to commit an 11th time, should fail + let new_commit_data: Vec = vec![11; 5]; + let bounded_new_commit_data = new_commit_data + .try_into() + .expect("Failed to convert new commit data into bounded vector"); + assert_err!( + SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + bounded_new_commit_data, + reveal_round + ), + Error::::TooManyUnrevealedCommits + ); + }); +} + +#[test] +fn test_reveal_crv3_commits_decryption_failure() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + + let commit_bytes: Vec = vec![0xff; 100]; + let bounded_commit_bytes = commit_bytes + .clone() + .try_into() + .expect("Failed to convert commit bytes into bounded vector"); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + bounded_commit_bytes, + reveal_round + )); + + step_epochs(1, netuid); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32] + .try_into() + .expect("Failed to convert randomness vector"), + signature: vec![0; 128] + .try_into() + .expect("Failed to convert signature vector"), + }, + ); + + assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + + let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) + .expect("Failed to get neuron UID for hotkey") as usize; + let weights_matrix = SubtensorModule::get_weights(netuid); + let weights = weights_matrix.get(neuron_uid).cloned().unwrap_or_default(); + assert!(weights.iter().all(|&w| w == I32F32::from_num(0))); + }); +} + +#[test] +fn test_reveal_crv3_commits_multiple_commits_some_fail_some_succeed() { + new_test_ext(100).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey1: AccountId = U256::from(1); + let hotkey2: AccountId = U256::from(2); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey1, U256::from(3), 100_000); + register_ok_neuron(netuid, hotkey2, U256::from(4), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 1); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + // Prepare a valid payload for hotkey1 + let neuron_uid1 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey1) + .expect("Failed to get neuron UID for hotkey1"); + let version_key = SubtensorModule::get_weights_version_key(netuid); + let valid_payload = WeightsTlockPayload { + values: vec![10], + uids: vec![neuron_uid1], + version_key, + }; + let serialized_valid_payload = valid_payload.encode(); + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(reveal_round.to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![message]); + + let ct_valid = tle::( + pub_key, + esk, + &serialized_valid_payload, + identity.clone(), + rng.clone(), + ) + .expect("Encryption failed"); + + let mut commit_bytes_valid = Vec::new(); + ct_valid + .serialize_compressed(&mut commit_bytes_valid) + .expect("Failed to serialize valid commit"); + + // Prepare an invalid payload for hotkey2 + let invalid_payload = vec![0u8; 10]; // Invalid payload + let ct_invalid = tle::( + pub_key, + esk, + &invalid_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes_invalid = Vec::new(); + ct_invalid + .serialize_compressed(&mut commit_bytes_invalid) + .expect("Failed to serialize invalid commit"); + + // Insert both commits + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey1), + netuid, + commit_bytes_valid.try_into().expect("Failed to convert valid commit data"), + reveal_round + )); + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey2), + netuid, + commit_bytes_invalid.try_into().expect("Failed to convert invalid commit data"), + reveal_round + )); + + // Insert the pulse + let sig_bytes = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39") + .expect("Failed to decode signature bytes"); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32].try_into().expect("Failed to convert randomness vector"), + signature: sig_bytes.try_into().expect("Failed to convert signature bytes"), + }, + ); + + step_epochs(1, netuid); + + // Verify that weights are set for hotkey1 + let neuron_uid1 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey1) + .expect("Failed to get neuron UID for hotkey1") as usize; + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights1 = weights_sparse.get(neuron_uid1).cloned().unwrap_or_default(); + assert!( + !weights1.is_empty(), + "Weights for neuron_uid1 should be set" + ); + + // Verify that weights are not set for hotkey2 + let neuron_uid2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey2) + .expect("Failed to get neuron UID for hotkey2") as usize; + let weights2 = weights_sparse.get(neuron_uid2).cloned().unwrap_or_default(); + assert!( + weights2.is_empty(), + "Weights for neuron_uid2 should be empty as commit could not be revealed" + ); + }); +} + +#[test] +fn test_reveal_crv3_commits_do_set_weights_failure() { + new_test_ext(1).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 3); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + // Prepare payload with mismatched uids and values lengths + let version_key = SubtensorModule::get_weights_version_key(netuid); + let payload = WeightsTlockPayload { + values: vec![10, 20], // Length 2 + uids: vec![0], // Length 1 + version_key, + }; + let serialized_payload = payload.encode(); + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(reveal_round.to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![message]); + + let ct = tle::( + pub_key, + esk, + &serialized_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes = Vec::new(); + ct.serialize_compressed(&mut commit_bytes) + .expect("Failed to serialize commit"); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_bytes.try_into().expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + + let sig_bytes = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39") + .expect("Failed to decode signature bytes"); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32].try_into().expect("Failed to convert randomness vector"), + signature: sig_bytes.try_into().expect("Failed to convert signature bytes"), + }, + ); + + step_epochs(3, netuid); + + // Verify that weights are not set due to `do_set_weights` failure + let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) + .expect("Failed to get neuron UID for hotkey") as usize; + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse.get(neuron_uid).cloned().unwrap_or_default(); + assert!( + weights.is_empty(), + "Weights for neuron_uid should be empty as do_set_weights should have failed" + ); + }); +} + +#[test] +fn test_reveal_crv3_commits_payload_decoding_failure() { + new_test_ext(1).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 3); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + let invalid_payload = vec![0u8; 10]; // Not a valid encoding of WeightsTlockPayload + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(reveal_round.to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![message]); + + let ct = tle::( + pub_key, + esk, + &invalid_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes = Vec::new(); + ct.serialize_compressed(&mut commit_bytes) + .expect("Failed to serialize commit"); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_bytes.try_into().expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + + let sig_bytes = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39") + .expect("Failed to decode signature bytes"); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32].try_into().expect("Failed to convert randomness vector"), + signature: sig_bytes.try_into().expect("Failed to convert signature bytes"), + }, + ); + + step_epochs(3, netuid); + + // Verify that weights are not set + let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) + .expect("Failed to get neuron UID for hotkey") as usize; + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse.get(neuron_uid).cloned().unwrap_or_default(); + assert!( + weights.is_empty(), + "Weights for neuron_uid should be empty as the payload could not be decoded" + ); + }); +} + +#[test] +fn test_reveal_crv3_commits_signature_deserialization_failure() { + new_test_ext(1).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 3); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + let version_key = SubtensorModule::get_weights_version_key(netuid); + let payload = WeightsTlockPayload { + values: vec![10, 20], + uids: vec![0, 1], + version_key, + }; + let serialized_payload = payload.encode(); + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(reveal_round.to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![message]); + + let ct = tle::( + pub_key, + esk, + &serialized_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes = Vec::new(); + ct.serialize_compressed(&mut commit_bytes) + .expect("Failed to serialize commit"); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_bytes.try_into().expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32].try_into().expect("Failed to convert randomness vector"), + signature: vec![0; 10].try_into().expect("Failed to create invalid signature"), // Invalid signature length + }, + ); + + step_epochs(3, netuid); + + // Verify that weights are not set + let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) + .expect("Failed to get neuron UID for hotkey") as usize; + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse.get(neuron_uid).cloned().unwrap_or_default(); + assert!( + weights.is_empty(), + "Weights for neuron_uid should be empty as the signature could not be deserialized" + ); + }); +} + +#[test] +fn test_do_commit_crv3_weights_commit_size_exceeds_limit() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + let max_commit_size = MAX_CRV3_COMMIT_SIZE_BYTES as usize; + let commit_data_exceeding: Vec = vec![0u8; max_commit_size + 1]; // Exceeds max size + + // Attempt to create a BoundedVec; this should fail + let bounded_commit_data_result = + BoundedVec::>::try_from( + commit_data_exceeding.clone(), + ); + + assert!( + bounded_commit_data_result.is_err(), + "Expected error when converting commit data exceeding max size into BoundedVec" + ); + + let commit_data_max_size: Vec = vec![0u8; max_commit_size]; // Exactly at max size + let bounded_commit_data = BoundedVec::>::try_from( + commit_data_max_size.clone(), + ) + .expect("Failed to create BoundedVec with data at max size"); + + // Now call the function with valid data at max size + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + bounded_commit_data, + reveal_round + )); + }); +} + +#[test] +fn test_reveal_crv3_commits_with_empty_commit_queue() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + + add_network(netuid, 5, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + step_epochs(2, netuid); + + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + assert!( + weights_sparse.is_empty(), + "Weights should be empty as there were no commits to reveal" + ); + }); +} + +#[test] +fn test_reveal_crv3_commits_with_incorrect_identity_message() { + new_test_ext(1).execute_with(|| { + use ark_serialize::CanonicalSerialize; + + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 1); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + // Prepare a valid payload but use incorrect identity message during encryption + let neuron_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey) + .expect("Failed to get neuron UID for hotkey"); + let version_key = SubtensorModule::get_weights_version_key(netuid); + let payload = WeightsTlockPayload { + values: vec![10], + uids: vec![neuron_uid], + version_key, + }; + let serialized_payload = payload.encode(); + + let esk = [2; 32]; + let rng = ChaCha20Rng::seed_from_u64(0); + + let pk_bytes = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a") + .expect("Failed to decode public key bytes"); + let pub_key = ::PublicKeyGroup::deserialize_compressed(&*pk_bytes) + .expect("Failed to deserialize public key"); + + // Use incorrect message for identity (e.g., reveal_round + 1) + let incorrect_message = { + let mut hasher = sha2::Sha256::new(); + hasher.update((reveal_round + 1).to_be_bytes()); + hasher.finalize().to_vec() + }; + let identity = Identity::new(b"", vec![incorrect_message]); + + let ct = tle::( + pub_key, + esk, + &serialized_payload, + identity, + rng, + ) + .expect("Encryption failed"); + + let mut commit_bytes = Vec::new(); + ct.serialize_compressed(&mut commit_bytes) + .expect("Failed to serialize commit"); + + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_bytes.try_into().expect("Failed to convert commit data into bounded vector"), + reveal_round + )); + + let sig_bytes = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39") + .expect("Failed to decode signature bytes"); + + pallet_drand::Pulses::::insert( + reveal_round, + Pulse { + round: reveal_round, + randomness: vec![0; 32].try_into().expect("Failed to convert randomness vector"), + signature: sig_bytes.try_into().expect("Failed to convert signature bytes"), + }, + ); + + step_epochs(1, netuid); + + // Verify that weights are not set due to decryption failure + let neuron_uid = neuron_uid as usize; + let weights_sparse = SubtensorModule::get_weights_sparse(netuid); + let weights = weights_sparse.get(neuron_uid).cloned().unwrap_or_default(); + assert!( + weights.is_empty(), + "Weights for neuron_uid should be empty due to incorrect identity message" + ); + }); +} + +#[test] +fn test_multiple_commits_by_same_hotkey_within_limit() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 1); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + for i in 0..10 { + let commit_data: Vec = vec![i; 5]; + assert_ok!(SubtensorModule::do_commit_crv3_weights( + RuntimeOrigin::signed(hotkey), + netuid, + commit_data + .try_into() + .expect("Failed to convert commit data into bounded vector"), + reveal_round + i as u64 + )); + } + + let cur_epoch = + SubtensorModule::get_epoch_index(netuid, SubtensorModule::get_current_block_as_u64()); + let commits = CRV3WeightCommits::::get(netuid, cur_epoch); + assert_eq!( + commits.len(), + 10, + "Expected 10 commits stored for the hotkey" + ); + }); +} + +#[test] +fn test_reveal_crv3_commits_removes_past_epoch_commits() { + new_test_ext(100).execute_with(|| { + let netuid: u16 = 1; + let hotkey: AccountId = U256::from(1); + let reveal_round: u64 = 1000; + + // Initialize network and neuron + add_network(netuid, 5, 0); + register_ok_neuron(netuid, hotkey, U256::from(2), 100_000); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_reveal_period(netuid, 1); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + let current_block = SubtensorModule::get_current_block_as_u64(); + let current_epoch = SubtensorModule::get_epoch_index(netuid, current_block); + + // Simulate commits in past epochs + let past_epochs = vec![current_epoch - 2, current_epoch - 1]; + for epoch in &past_epochs { + let commit_data: Vec = vec![*epoch as u8; 5]; + let bounded_commit_data = commit_data + .clone() + .try_into() + .expect("Failed to convert commit data into bounded vector"); + assert_ok!(CRV3WeightCommits::::try_mutate( + netuid, + *epoch, + |commits| -> DispatchResult { + commits.push_back((hotkey, bounded_commit_data, reveal_round)); + Ok(()) + } + )); + } + + for epoch in &past_epochs { + let commits = CRV3WeightCommits::::get(netuid, *epoch); + assert!( + !commits.is_empty(), + "Expected commits to be present for past epoch {}", + epoch + ); + } + + assert_ok!(SubtensorModule::reveal_crv3_commits(netuid)); + + for epoch in &past_epochs { + let commits = CRV3WeightCommits::::get(netuid, *epoch); + assert!( + commits.is_empty(), + "Expected commits for past epoch {} to be removed", + epoch + ); + } + + let current_epoch_commits = CRV3WeightCommits::::get(netuid, current_epoch); + assert!( + current_epoch_commits.is_empty(), + "Expected no commits for current epoch {}", + current_epoch + ); + }); +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 1c37e0bb49..cdb8fd52f2 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -111,6 +111,16 @@ pallet-evm-precompile-simple = { workspace = true } pallet-hotfix-sufficients = { workspace = true } fp-account = { workspace = true } +#drand +pallet-drand = { workspace = true, default-features = false } +getrandom = { workspace = true, default-features = false } +tle = { workspace = true } +hex = { workspace = true } +rand_chacha = { workspace = true } +w3f-bls = { workspace = true } +sha2 = { workspace = true } +ark-serialize = { workspace = true } + [dev-dependencies] frame-metadata = { workspace = true } sp-io = { workspace = true } @@ -192,6 +202,14 @@ std = [ "pallet-evm-precompile-simple/std", "pallet-hotfix-sufficients/std", "fp-account/std", + "pallet-drand/std", + "getrandom/std", + "tle/std", + "ark-serialize/std", + "hex/std", + "rand_chacha/std", + "sha2/std", + "w3f-bls/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -219,7 +237,8 @@ runtime-benchmarks = [ # EVM + Frontier "pallet-ethereum/runtime-benchmarks", "pallet-evm/runtime-benchmarks", - "pallet-hotfix-sufficients/runtime-benchmarks", + "pallet-hotfix-sufficients/runtime-benchmarks", + "pallet-drand/runtime-benchmarks" ] try-runtime = [ "frame-try-runtime/try-runtime", @@ -249,11 +268,12 @@ try-runtime = [ "pallet-registry/try-runtime", # EVM + Frontier - "fp-self-contained/try-runtime", + "fp-self-contained/try-runtime", "pallet-base-fee/try-runtime", "pallet-dynamic-fee/try-runtime", "pallet-ethereum/try-runtime", "pallet-evm/try-runtime", "pallet-evm-chain-id/try-runtime", + "pallet-drand/try-runtime" ] metadata-hash = ["substrate-wasm-builder/metadata-hash"] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 202ec4d1f0..a38b5071dc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -38,6 +38,7 @@ use sp_core::{ crypto::{ByteArray, KeyTypeId}, OpaqueMetadata, H160, H256, U256, }; +use sp_runtime::generic::Era; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{ @@ -87,6 +88,65 @@ use fp_rpc::TransactionStatus; use pallet_ethereum::{Call::transact, PostLogContent, Transaction as EthereumTransaction}; use pallet_evm::{Account as EVMAccount, BalanceConverter, FeeCalculator, Runner}; +// Drand +impl pallet_drand::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_drand::weights::SubstrateWeight; + type AuthorityId = pallet_drand::crypto::TestAuthId; + type Verifier = pallet_drand::verifier::QuicknetVerifier; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; + type HttpFetchTimeout = ConstU64<1_000>; +} + +impl frame_system::offchain::SigningTypes for Runtime { + type Public = ::Signer; + type Signature = Signature; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; +} + +impl frame_system::offchain::CreateSignedTransaction> for Runtime { + fn create_transaction>( + call: RuntimeCall, + public: ::Signer, + account: AccountId, + index: Index, + ) -> Option<( + RuntimeCall, + ::SignaturePayload, + )> { + use sp_runtime::traits::StaticLookup; + + let address = ::Lookup::unlookup(account.clone()); + let extra: SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(Era::Immortal), + check_nonce::CheckNonce::::from(index), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + pallet_subtensor::SubtensorSignedExtension::::new(), + pallet_commitments::CommitmentsSignedExtension::::new(), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), + ); + + let raw_payload = SignedPayload::new(call.clone(), extra.clone()).ok()?; + let signature = raw_payload.using_encoded(|payload| S::sign(payload, public))?; + + let signature_payload = (address, signature, extra); + + Some((call, signature_payload)) + } +} + // Subtensor module pub use pallet_scheduler; pub use pallet_subtensor; @@ -160,7 +220,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 212, + spec_version: 213, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1330,6 +1390,8 @@ construct_runtime!( EVMChainId: pallet_evm_chain_id = 23, DynamicFee: pallet_dynamic_fee = 24, BaseFee: pallet_base_fee = 25, + + Drand: pallet_drand = 26, } ); @@ -1398,6 +1460,7 @@ mod benches { [pallet_commitments, Commitments] [pallet_admin_utils, AdminUtils] [pallet_subtensor, SubtensorModule] + [pallet_drand, Drand] ); } diff --git a/scripts/localnet.sh b/scripts/localnet.sh index 1e9618954a..b82b5f9f59 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -96,6 +96,7 @@ bob_start=( --allow-private-ipv4 --discover-local --unsafe-force-node-key-generation +# --offchain-worker=Never ) trap 'pkill -P $$' EXIT SIGINT SIGTERM diff --git a/support/tools/Cargo.toml b/support/tools/Cargo.toml index fa3e1fd50b..2e15929d7a 100644 --- a/support/tools/Cargo.toml +++ b/support/tools/Cargo.toml @@ -13,7 +13,7 @@ name = "bump-version" path = "src/bump_version.rs" [dependencies] -anyhow = "1.0" +anyhow = { workspace = true } clap = { version = "4.5", features = ["derive"] } semver = "1.0" toml_edit = "0.22"