diff --git a/Cargo.lock b/Cargo.lock index 2e1cd47e0a336..36846702a3476 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,12 +70,11 @@ dependencies = [ [[package]] name = "alga" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658f9468113d34781f6ca9d014d174c74b73de870f1e0e3ad32079bbab253b19" +checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" dependencies = [ "approx", - "libm", "num-complex", "num-traits", ] @@ -100,9 +99,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +checksum = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785" [[package]] name = "app_dirs" @@ -133,9 +132,9 @@ checksum = "75153c95fdedd7db9732dfbfc3702324a1627eec91ba56e37cd0ac78314ab2ed" [[package]] name = "arc-swap" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" +checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" [[package]] name = "arrayref" @@ -179,9 +178,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6283bac8dd7226470d491bc4737816fea4ca1fba7a2847f2e9097fd6bfb4624c" +checksum = "35ad62275a8bda1c2c9a9303aea121eb04204272d3be0735d5dc1f49eb9ff9a9" dependencies = [ "doc-comment", "escargot", @@ -269,9 +268,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" dependencies = [ "backtrace-sys", "cfg-if", @@ -281,9 +280,9 @@ dependencies = [ [[package]] name = "backtrace-sys" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" +checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" dependencies = [ "cc", "libc", @@ -457,9 +456,9 @@ checksum = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae" [[package]] name = "bstr" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48" +checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" dependencies = [ "lazy_static", "memchr", @@ -1109,9 +1108,9 @@ dependencies = [ [[package]] name = "doc-comment" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807e5847c39ad6a11eac66de492ed1406f76a260eb8656e8740cad9eabc69c27" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "ed25519-dalek" @@ -1411,9 +1410,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" +checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" dependencies = [ "cfg-if", "crc32fast", @@ -1917,9 +1916,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" +checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" dependencies = [ "aho-corasick", "bstr", @@ -1972,16 +1971,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47" +checksum = "7938e6aa2a31df4e21f224dc84704bd31c089a6d1355c535b03667371cccc843" dependencies = [ "bytes 0.5.4", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.0", + "http 0.2.1", "indexmap", "log 0.4.8", "slab", @@ -2102,9 +2101,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" dependencies = [ "bytes 0.5.4", "fnv", @@ -2130,7 +2129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ "bytes 0.5.4", - "http 0.2.0", + "http 0.2.1", ] [[package]] @@ -2199,16 +2198,16 @@ dependencies = [ [[package]] name = "hyper" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7b15203263d1faa615f9337d79c1d37959439dc46c2b4faab33286fadc2a1c5" +checksum = "ed6081100e960d9d74734659ffc9cc91daf1c0fc7aceb8eaa94ee1a3f5046f2e" dependencies = [ "bytes 0.5.4", "futures-channel", "futures-core", "futures-util", - "h2 0.2.2", - "http 0.2.0", + "h2 0.2.3", + "http 0.2.1", "http-body 0.3.1", "httparse", "itoa", @@ -2230,7 +2229,7 @@ dependencies = [ "bytes 0.5.4", "ct-logs", "futures-util", - "hyper 0.13.3", + "hyper 0.13.4", "log 0.4.8", "rustls 0.17.0", "rustls-native-certs", @@ -2353,9 +2352,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a859057dc563d1388c1e816f98a1892629075fc046ed06e845b883bb8b2916fb" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" [[package]] name = "itertools" @@ -2383,9 +2382,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5" +checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" dependencies = [ "wasm-bindgen", ] @@ -2620,9 +2619,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "libloading" @@ -2636,9 +2635,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.1.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libp2p" @@ -3212,11 +3211,11 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" dependencies = [ - "rustc_version", + "autocfg 1.0.0", ] [[package]] @@ -3439,6 +3438,7 @@ dependencies = [ "pallet-contracts", "pallet-im-online", "pallet-indices", + "pallet-staking", "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", @@ -3834,9 +3834,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg 1.0.0", "num-bigint", @@ -3851,6 +3851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" dependencies = [ "autocfg 1.0.0", + "libm", ] [[package]] @@ -4035,12 +4036,9 @@ version = "2.0.0-alpha.5" dependencies = [ "frame-support", "frame-system", - "hex-literal", - "lazy_static", "pallet-session", "pallet-timestamp", "parity-scale-codec", - "parking_lot 0.10.0", "serde", "sp-consensus-babe", "sp-consensus-vrf", @@ -4051,8 +4049,6 @@ dependencies = [ "sp-staking", "sp-std", "sp-timestamp", - "sp-version", - "substrate-test-runtime", ] [[package]] @@ -4512,25 +4508,30 @@ dependencies = [ name = "pallet-staking" version = "2.0.0-alpha.5" dependencies = [ + "env_logger 0.7.1", "frame-benchmarking", "frame-support", "frame-system", "hex", "pallet-authorship", "pallet-balances", + "pallet-indices", "pallet-session", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", + "parking_lot 0.10.0", + "rand 0.7.3", "rand_chacha 0.2.2", "serde", + "sp-application-crypto", "sp-core", "sp-io", - "sp-keyring", "sp-phragmen", "sp-runtime", "sp-staking", "sp-std", + "static_assertions", "substrate-test-utils", ] @@ -4845,9 +4846,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" +checksum = "8292c1e1e81ddb552c4c90c36af201a0ce7e34995f55f0480f01052f242811c9" dependencies = [ "paste-impl", "proc-macro-hack", @@ -4855,9 +4856,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" +checksum = "5e9c43f2645f06ee452544ad032886a75f3d1797b9487dcadcae9100ba58a51c" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -5033,9 +5034,9 @@ dependencies = [ [[package]] name = "proc-macro-error" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" dependencies = [ "proc-macro-error-attr", "proc-macro2", @@ -5046,9 +5047,9 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" dependencies = [ "proc-macro2", "quote", @@ -5059,20 +5060,15 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.11" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" [[package]] name = "proc-macro-nested" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" +checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" @@ -5150,9 +5146,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.10.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a5325d019a4d837d3abde0a836920f959e33d350f77b5f1e289e061e774942" +checksum = "fc1b4a8efc42cf150049e8a490f618c7c60e82332405065f202a7e33aa5a1f06" [[package]] name = "pwasm-utils" @@ -5466,9 +5462,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.4" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ "aho-corasick", "memchr", @@ -5487,9 +5483,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.16" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "region" @@ -5529,9 +5525,9 @@ dependencies = [ [[package]] name = "rlp" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a44d5ae8afcb238af8b75640907edc6c931efcfab2c854e81ed35fa080f84cd" +checksum = "4a7d3f9bed94764eac15b8f14af59fac420c236adaff743b7bcc88e265cb4345" dependencies = [ "rustc-hex", ] @@ -5657,9 +5653,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" [[package]] name = "safe-mix" @@ -6412,7 +6408,7 @@ dependencies = [ "fnv", "futures 0.3.4", "futures-timer 3.0.2", - "hyper 0.13.3", + "hyper 0.13.4", "hyper-rustls", "log 0.4.8", "num_cpus", @@ -6705,9 +6701,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" +checksum = "039c25b130bd8c1321ee2d7de7fde2659fa9c2744e4bb29711cfc852ea53cd19" dependencies = [ "lazy_static", "winapi 0.3.8", @@ -7031,7 +7027,7 @@ dependencies = [ "bytes 0.5.4", "flate2", "futures 0.3.4", - "http 0.2.0", + "http 0.2.1", "httparse", "log 0.4.8", "rand 0.7.3", @@ -7405,14 +7401,26 @@ dependencies = [ name = "sp-phragmen" version = "2.0.0-alpha.5" dependencies = [ + "parity-scale-codec", "rand 0.7.3", "serde", - "sp-io", + "sp-phragmen", + "sp-phragmen-compact", "sp-runtime", "sp-std", "substrate-test-utils", ] +[[package]] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sp-rpc" version = "2.0.0-alpha.5" @@ -7722,9 +7730,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073" +checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d" dependencies = [ "clap", "lazy_static", @@ -7733,9 +7741,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd" +checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430" dependencies = [ "heck", "proc-macro-error", @@ -7881,7 +7889,7 @@ dependencies = [ "async-std", "derive_more", "futures-util", - "hyper 0.13.3", + "hyper 0.13.4", "log 0.4.8", "prometheus", "tokio 0.2.13", @@ -8086,9 +8094,9 @@ checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" [[package]] name = "syn" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ "proc-macro2", "quote", @@ -8198,18 +8206,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" +checksum = "e3711fd1c4e75b3eff12ba5c40dba762b6b65c5476e8174c1a664772060c49bf" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" +checksum = "ae2b85ba4c9aa32dd3343bd80eb8d22e9b54b7688c17ea3907f236885353b233" dependencies = [ "proc-macro2", "quote", @@ -8247,9 +8255,9 @@ dependencies = [ [[package]] name = "tiny-bip39" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6848cd8f566953ce1e8faeba12ee23cbdbb0437754792cd857d44628b5685e3" +checksum = "e255ec4f7d4aaccbede17dffcfb2e71434d17f5c921d5a06823b8e58a2bcd468" dependencies = [ "failure", "hmac", @@ -8897,9 +8905,9 @@ dependencies = [ [[package]] name = "wabt-sys" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5d153dc96aad7dc13ab90835b892c69867948112d95299e522d370c4e13a08" +checksum = "23d7043ebb3e5d96fad7a8d3ca22ee9880748ff8c3e18092cfb2a49d3b8f9084" dependencies = [ "cc", "cmake", @@ -8946,9 +8954,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d" +checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -8956,9 +8964,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" +checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" dependencies = [ "bumpalo", "lazy_static", @@ -8971,9 +8979,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457414a91863c0ec00090dba537f88ab955d93ca6555862c29b6d860990b8a8a" +checksum = "7add542ea1ac7fdaa9dc25e031a6af33b7d63376292bd24140c637d00d1c312a" dependencies = [ "cfg-if", "js-sys", @@ -8983,9 +8991,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205" +checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8993,9 +9001,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d" +checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" dependencies = [ "proc-macro2", "quote", @@ -9006,9 +9014,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8" +checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] name = "wasm-gc-api" @@ -9130,27 +9138,27 @@ dependencies = [ [[package]] name = "wast" -version = "10.0.0" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4efb62ecebf5cc9dbf2954309a20d816289c6550c0597a138b9e811cefc05007" +checksum = "df4d67ba9266f4fcaf2e8a1afadc5e2a959e51aecc07b1ecbdf85a6ddaf08bde" dependencies = [ "leb128", ] [[package]] name = "wat" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdea5e25273cc3a62f3ae3a1a4c7d7996625875b50c0b4475fee6698c2b069c" +checksum = "9a9400dc1c8512087b2d974b1b9b0a6c4e6e26e7e8acf629e3e351165a1ed301" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" +checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 37726e88baec9..39a6ff1f2dcda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,6 +130,7 @@ members = [ "primitives/offchain", "primitives/panic-handler", "primitives/phragmen", + "primitives/phragmen/compact", "primitives/rpc", "primitives/runtime-interface", "primitives/runtime-interface/proc-macro", diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 9a286c5880085..d45f5d7848baf 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -82,6 +82,7 @@ pallet-transaction-payment = { version = "2.0.0-alpha.5", path = "../../../frame frame-support = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/support" } pallet-im-online = { version = "2.0.0-alpha.5", default-features = false, path = "../../../frame/im-online" } pallet-authority-discovery = { version = "2.0.0-alpha.5", path = "../../../frame/authority-discovery" } +pallet-staking = { version = "2.0.0-alpha.5", path = "../../../frame/staking" } # node-specific dependencies node-runtime = { version = "2.0.0-alpha.5", path = "../runtime" } diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 7036a73dc0620..45cb01b0ba9b0 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -202,7 +202,14 @@ pub fn get_authority_keys_from_seed(seed: &str) -> ( /// Helper function to create GenesisConfig for testing pub fn testnet_genesis( - initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)>, + initial_authorities: Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + )>, root_key: AccountId, endowed_accounts: Option>, enable_println: bool, @@ -244,7 +251,12 @@ pub fn testnet_genesis( }), pallet_session: Some(SessionConfig { keys: initial_authorities.iter().map(|x| { - (x.0.clone(), x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone())) + (x.0.clone(), x.0.clone(), session_keys( + x.2.clone(), + x.3.clone(), + x.4.clone(), + x.5.clone(), + )) }).collect::>(), }), pallet_staking: Some(StakingConfig { diff --git a/bin/node/cli/src/factory_impl.rs b/bin/node/cli/src/factory_impl.rs index 1d1eabe29cbfb..cd7e3022e0b9a 100644 --- a/bin/node/cli/src/factory_impl.rs +++ b/bin/node/cli/src/factory_impl.rs @@ -58,6 +58,7 @@ impl FactoryState { frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(0), Default::default(), + Default::default(), ) } } @@ -122,7 +123,7 @@ impl RuntimeAdapter for FactoryState { (*amount).into() ) ) - }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ())) + }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), (), ())) } fn inherent_extrinsics(&self) -> InherentData { diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index d81ec5f031ff4..452b1fa3e62db 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -624,11 +624,12 @@ mod tests { check_weight, payment, Default::default(), + Default::default(), ); let raw_payload = SignedPayload::from_raw( function, extra, - (version, genesis_hash, genesis_hash, (), (), (), ()) + (version, genesis_hash, genesis_hash, (), (), (), (), ()) ); let signature = raw_payload.using_encoded(|payload| { signer.sign(payload) diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index 5e5be5bade6a4..784b140a13f1b 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -15,7 +15,7 @@ // along with Substrate. If not, see . use node_runtime::{ - Call, Executive, Indices, Runtime, SubmitTransaction, UncheckedExtrinsic, + Call, Executive, Indices, Runtime, TransactionSubmitterOf, UncheckedExtrinsic, }; use sp_application_crypto::AppKey; use sp_core::testing::KeyStore; @@ -31,6 +31,8 @@ use codec::Decode; pub mod common; use self::common::*; +type SubmitTransaction = TransactionSubmitterOf; + #[test] fn should_submit_unsigned_transaction() { let mut t = new_test_ext(COMPACT_CODE, false); diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 5c61eadf15752..8868f28557e70 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -59,7 +59,7 @@ pub use pallet_timestamp::Call as TimestampCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_contracts::Gas; pub use frame_support::StorageValue; -pub use pallet_staking::StakerStatus; +pub use pallet_staking::{StakerStatus, LockStakingStatus}; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; @@ -73,6 +73,52 @@ use constants::{time::*, currency::*}; #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +/// A transaction submitter with the given key type. +pub type TransactionSubmitterOf = TransactionSubmitter; + +/// Submits transaction with the node's public and signature type. Adheres to the signed extension +/// format of the chain. +impl frame_system::offchain::CreateTransaction for Runtime { + type Public = ::Signer; + type Signature = Signature; + + fn create_transaction>( + call: Call, + public: Self::Public, + account: AccountId, + index: Index, + ) -> Option<(Call, ::SignaturePayload)> { + // take the biggest period possible. + let period = BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let current_block = System::block_number() + .saturated_into::() + // The `System::block_number` is initialized with `n+1`, + // so the actual block number is `n`. + .saturating_sub(1); + let tip = 0; + let extra: SignedExtra = ( + frame_system::CheckVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::::from(index), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + Default::default(), + Default::default(), + ); + let raw_payload = SignedPayload::new(call, extra).map_err(|e| { + debug::warn!("Unable to create signed payload: {:?}", e); + }).ok()?; + let signature = TSigner::sign(public, &raw_payload)?; + let address = Indices::unlookup(account); + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (address, signature, extra))) + } +} + /// Runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), @@ -255,6 +301,7 @@ impl pallet_session::Trait for Runtime { type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = Babe; } impl pallet_session::historical::Trait for Runtime { @@ -278,6 +325,7 @@ parameter_types! { pub const BondingDuration: pallet_staking::EraIndex = 24 * 28; pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + pub const ElectionLookahead: BlockNumber = 25; // 10 minutes per session => 100 block. pub const MaxNominatorRewardedPerValidator: u32 = 64; } @@ -296,6 +344,10 @@ impl pallet_staking::Trait for Runtime { type SlashCancelOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>; type SessionInterface = Self; type RewardCurve = RewardCurve; + type NextNewSession = Session; + type ElectionLookahead = ElectionLookahead; + type Call = Call; + type SubmitTransaction = TransactionSubmitterOf<()>; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; } @@ -470,9 +522,6 @@ impl pallet_sudo::Trait for Runtime { type Call = Call; } -/// A runtime transaction submitter. -pub type SubmitTransaction = TransactionSubmitter; - parameter_types! { pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _; } @@ -481,7 +530,7 @@ impl pallet_im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Event = Event; type Call = Call; - type SubmitTransaction = SubmitTransaction; + type SubmitTransaction = TransactionSubmitterOf; type SessionDuration = SessionDuration; type ReportUnresponsiveness = Offences; } @@ -530,46 +579,6 @@ impl pallet_identity::Trait for Runtime { type RegistrarOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>; } -impl frame_system::offchain::CreateTransaction for Runtime { - type Public = ::Signer; - type Signature = Signature; - - fn create_transaction>( - call: Call, - public: Self::Public, - account: AccountId, - index: Index, - ) -> Option<(Call, ::SignaturePayload)> { - // take the biggest period possible. - let period = BlockHashCount::get() - .checked_next_power_of_two() - .map(|c| c / 2) - .unwrap_or(2) as u64; - let current_block = System::block_number() - .saturated_into::() - // The `System::block_number` is initialized with `n+1`, - // so the actual block number is `n`. - .saturating_sub(1); - let tip = 0; - let extra: SignedExtra = ( - frame_system::CheckVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), - frame_system::CheckNonce::::from(index), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - Default::default(), - ); - let raw_payload = SignedPayload::new(call, extra).map_err(|e| { - debug::warn!("Unable to create signed payload: {:?}", e); - }).ok()?; - let signature = TSigner::sign(public, &raw_payload)?; - let address = Indices::unlookup(account); - let (call, extra, _) = raw_payload.deconstruct(); - Some((call, (address, signature, extra))) - } -} - parameter_types! { pub const ConfigDepositBase: Balance = 5 * DOLLARS; pub const FriendDepositFactor: Balance = 50 * CENTS; @@ -638,7 +647,7 @@ construct_runtime!( Indices: pallet_indices::{Module, Call, Storage, Config, Event}, Balances: pallet_balances::{Module, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Module, Storage}, - Staking: pallet_staking::{Module, Call, Config, Storage, Event}, + Staking: pallet_staking::{Module, Call, Config, Storage, Event, ValidateUnsigned}, Session: pallet_session::{Module, Call, Storage, Event, Config}, Democracy: pallet_democracy::{Module, Call, Storage, Config, Event}, Council: pallet_collective::::{Module, Call, Storage, Origin, Event, Config}, @@ -680,6 +689,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, pallet_contracts::CheckBlockGasLimit, + pallet_staking::LockStakingStatus, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -965,7 +975,7 @@ mod tests { >, {} - is_submit_signed_transaction::(); - is_sign_and_submit_transaction::(); + is_submit_signed_transaction::>(); + is_sign_and_submit_transaction::>(); } } diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 6b0d06875d692..5fa1e48b03218 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -75,6 +75,7 @@ pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(extra_fee), Default::default(), + Default::default(), ) } diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index bc7d7a602abca..8f0f96c39ebc6 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -683,6 +683,7 @@ fn create_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(f), Default::default(), + Default::default(), ) }; let raw_payload = SignedPayload::from_raw( @@ -696,6 +697,7 @@ fn create_extrinsic( (), (), (), + (), ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)).into_runtime(); diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index 8ee4931e488c3..b8f28b432ba75 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -121,6 +121,7 @@ mod tests { type ValidatorId = AuthorityId; type ValidatorIdOf = ConvertInto; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = pallet_session::PeriodicSessions; } impl pallet_session::historical::Trait for Test { diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index e4acd52ed32fe..8790b3775caa2 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -9,7 +9,6 @@ repository = "https://github.com/paritytech/substrate/" description = "Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions." [dependencies] -hex-literal = "0.2.1" codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true } sp-inherents = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/inherents" } @@ -26,11 +25,7 @@ sp-consensus-vrf = { version = "0.8.0-alpha.5", default-features = false, path = sp-io = { path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} [dev-dependencies] -lazy_static = "1.4.0" -parking_lot = "0.10.0" -sp-version = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/version" } sp-core = { version = "2.0.0-alpha.5", path = "../../primitives/core" } -substrate-test-runtime = { version = "2.0.0-dev", path = "../../test-utils/runtime" } [features] default = ["std"] diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 29b6eb00a300d..7315d1761841d 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -19,8 +19,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![forbid(unused_must_use, unsafe_code, unused_variables, unused_must_use)] -#![deny(unused_imports)] -pub use pallet_timestamp; + +use pallet_timestamp; use sp_std::{result, prelude::*}; use frame_support::{ @@ -29,7 +29,7 @@ use frame_support::{ }; use sp_timestamp::OnTimestampSet; use sp_runtime::{generic::DigestItem, ConsensusEngineId, Perbill, PerThing}; -use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash}; +use sp_runtime::traits::{IsMember, SaturatedConversion, Saturating, Hash, One}; use sp_staking::{ SessionIndex, offence::{Offence, Kind}, @@ -307,12 +307,34 @@ impl Module { // epoch 0 as having started at the slot of block 1. We want to use // the same randomness and validator set as signalled in the genesis, // so we don't rotate the epoch. - now != sp_runtime::traits::One::one() && { + now != One::one() && { let diff = CurrentSlot::get().saturating_sub(Self::current_epoch_start()); diff >= T::EpochDuration::get() } } + /// Return the _best guess_ block number, at which the next epoch change is predicted to happen. + /// + /// Returns None if the prediction is in the past; This implies an error internally in the Babe + /// and should not happen under normal circumstances. + /// + /// In other word, this is only accurate if no slots are missed. Given missed slots, the slot + /// number will grow while the block number will not. Hence, the result can be interpreted as an + /// upper bound. + // -------------- IMPORTANT NOTE -------------- + // This implementation is linked to how [`should_epoch_change`] is working. This might need to + // be updated accordingly, if the underlying mechanics of slot and epochs change. + pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option { + let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get()); + next_slot + .checked_sub(CurrentSlot::get()) + .map(|slots_remaining| { + // This is a best effort guess. Drifts in the slot/block ratio will cause errors here. + let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into(); + now.saturating_add(blocks_remaining) + }) + } + /// DANGEROUS: Enact an epoch change. Should be done on every block where `should_epoch_change` has returned `true`, /// and the caller is the only caller of this function. /// @@ -324,10 +346,7 @@ impl Module { ) { // PRECONDITION: caller has done initialization and is guaranteed // by the session module to be called before this. - #[cfg(debug_assertions)] - { - assert!(Self::initialized().is_some()) - } + debug_assert!(Self::initialized().is_some()); // Update epoch index let epoch_index = EpochIndex::get() @@ -473,6 +492,12 @@ impl OnTimestampSet for Module { fn on_timestamp_set(_moment: T::Moment) { } } +impl frame_support::traits::EstimateNextSessionRotation for Module { + fn estimate_next_session_rotation(now: T::BlockNumber) -> Option { + Self::next_expected_epoch_change(now) + } +} + impl sp_runtime::BoundToRuntimeAppPublic for Module { type Public = AuthorityId; } diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 2ec083728e82c..ea802b268e399 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -16,14 +16,22 @@ //! Test utilities -use super::{Trait, Module, GenesisConfig}; +use codec::Encode; +use super::{Trait, Module, GenesisConfig, CurrentSlot}; use sp_runtime::{ - traits::IdentityLookup, Perbill, testing::{Header, UintAuthorityId}, impl_opaque_keys, + Perbill, impl_opaque_keys, + testing::{Header, UintAuthorityId, Digest, DigestItem}, + traits::IdentityLookup, +}; +use frame_system::InitKind; +use frame_support::{ + impl_outer_origin, parameter_types, StorageValue, + traits::OnInitialize, + weights::Weight, }; -use sp_version::RuntimeVersion; -use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; use sp_io; use sp_core::H256; +use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof}; impl_outer_origin!{ pub enum Origin for Test where system = frame_system {} @@ -43,7 +51,6 @@ parameter_types! { pub const MinimumPeriod: u64 = 1; pub const EpochDuration: u64 = 3; pub const ExpectedBlockTime: u64 = 1; - pub const Version: RuntimeVersion = substrate_test_runtime::VERSION; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16); } @@ -53,7 +60,7 @@ impl frame_system::Trait for Test { type BlockNumber = u64; type Call = (); type Hash = H256; - type Version = Version; + type Version = (); type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = DummyValidatorId; type Lookup = IdentityLookup; @@ -79,11 +86,12 @@ impl pallet_session::Trait for Test { type Event = (); type ValidatorId = ::AccountId; type ShouldEndSession = Babe; - type SessionHandler = (Babe,Babe,); + type SessionHandler = (Babe,); type SessionManager = (); type ValidatorIdOf = (); type Keys = MockSessionKeys; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = Babe; } impl pallet_timestamp::Trait for Test { @@ -106,5 +114,44 @@ pub fn new_test_ext(authorities: Vec) -> sp_io::TestExternalit t.into() } +pub fn go_to_block(n: u64, s: u64) { + let pre_digest = make_pre_digest(0, s, RawVRFOutput([1; 32]), RawVRFProof([0xff; 64])); + System::initialize(&n, &Default::default(), &Default::default(), &pre_digest, InitKind::Full); + System::set_block_number(n); + if s > 1 { + CurrentSlot::put(s); + } + // includes a call into `Babe::do_initialize`. + Session::on_initialize(n); +} + +/// Slots will grow accordingly to blocks +pub fn progress_to_block(n: u64) { + let mut slot = Babe::current_slot() + 1; + for i in System::block_number()+1..=n { + go_to_block(i, slot); + slot += 1; + } +} + +pub fn make_pre_digest( + authority_index: sp_consensus_babe::AuthorityIndex, + slot_number: sp_consensus_babe::SlotNumber, + vrf_output: RawVRFOutput, + vrf_proof: RawVRFProof, +) -> Digest { + let digest_data = sp_consensus_babe::digests::RawPreDigest::Primary( + sp_consensus_babe::digests::RawPrimaryPreDigest { + authority_index, + slot_number, + vrf_output, + vrf_proof, + } + ); + let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); + Digest { logs: vec![log] } +} + pub type System = frame_system::Module; pub type Babe = Module; +pub type Session = pallet_session::Module; diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 5769b1235c251..24aba100178e0 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -17,11 +17,10 @@ //! Consensus extension module tests for BABE consensus. use super::*; +use mock::*; use frame_support::traits::OnFinalize; -use mock::{new_test_ext, Babe, System}; -use sp_runtime::testing::{Digest, DigestItem}; -use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof}; use pallet_session::ShouldEndSession; +use sp_consensus_vrf::schnorrkel::{RawVRFOutput, RawVRFProof}; const EMPTY_RANDOMNESS: [u8; 32] = [ 74, 25, 49, 128, 53, 97, 244, 49, @@ -30,24 +29,6 @@ const EMPTY_RANDOMNESS: [u8; 32] = [ 217, 153, 138, 37, 48, 192, 248, 0, ]; -fn make_pre_digest( - authority_index: sp_consensus_babe::AuthorityIndex, - slot_number: sp_consensus_babe::SlotNumber, - vrf_output: RawVRFOutput, - vrf_proof: RawVRFProof, -) -> Digest { - let digest_data = sp_consensus_babe::digests::RawPreDigest::Primary( - sp_consensus_babe::digests::RawPrimaryPreDigest { - authority_index, - slot_number, - vrf_output, - vrf_proof, - } - ); - let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); - Digest { logs: vec![log] } -} - #[test] fn empty_randomness_is_correct() { let s = compute_randomness([0; RANDOMNESS_LENGTH], 0, std::iter::empty(), None); @@ -132,3 +113,24 @@ fn authority_index() { "Trivially invalid authorities are ignored") }) } + +#[test] +fn can_predict_next_epoch_change() { + new_test_ext(vec![]).execute_with(|| { + assert_eq!(::EpochDuration::get(), 3); + // this sets the genesis slot to 6; + go_to_block(1, 6); + assert_eq!(Babe::genesis_slot(), 6); + assert_eq!(Babe::current_slot(), 6); + assert_eq!(Babe::epoch_index(), 0); + + progress_to_block(5); + + assert_eq!(Babe::epoch_index(), 5 / 3); + assert_eq!(Babe::current_slot(), 10); + + // next epoch change will be at + assert_eq!(Babe::current_epoch_start(), 9); // next change will be 12, 2 slots from now + assert_eq!(Babe::next_expected_epoch_change(System::block_number()), Some(5 + 2)); + }) +} diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 0030f2608faff..9a9e3c3143856 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -94,7 +94,7 @@ use frame_support::{ ChangeMembers, OnUnbalanced, WithdrawReason, Contains, BalanceStatus } }; -use sp_phragmen::ExtendedBalance; +use sp_phragmen::{build_support_map, ExtendedBalance}; use frame_system::{self as system, ensure_signed, ensure_root}; const MODULE_ID: LockIdentifier = *b"phrelect"; @@ -692,12 +692,18 @@ impl Module { .filter_map(|(m, a)| if a.is_zero() { None } else { Some(m) } ) .collect::>(); - let support_map = sp_phragmen::build_support_map::<_, _, _, T::CurrencyToVote, Perbill>( - &new_set, - &phragmen_result.assignments, - Self::locked_stake_of, + let stake_of = |who: &T::AccountId| -> ExtendedBalance { + , u64>>::convert( + Self::locked_stake_of(who) + ) as ExtendedBalance + }; + let staked_assignments = sp_phragmen::assignment_ratio_to_staked( + phragmen_result.assignments, + stake_of, ); + let (support_map, _) = build_support_map::(&new_set, &staked_assignments); + let to_balance = |e: ExtendedBalance| >>::convert(e); let new_set_with_stake = new_set diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index 78b6409d543eb..1fd1bcbdfe386 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -141,6 +141,7 @@ impl pallet_session::Trait for Runtime { type Keys = UintAuthorityId; type Event = (); type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = pallet_session::PeriodicSessions; } impl pallet_session::historical::Trait for Runtime { diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index 3e7f8c9537118..0ba7cd87f2455 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -26,11 +26,12 @@ mod tests; use sp_std::vec::Vec; use frame_support::{ - decl_module, decl_event, decl_storage, Parameter, + decl_module, decl_event, decl_storage, Parameter, debug, weights::{Weight, SimpleDispatchInfo, WeighData}, }; -use sp_runtime::traits::Hash; +use sp_runtime::{traits::Hash, Perbill}; use sp_staking::{ + SessionIndex, offence::{Offence, ReportOffence, Kind, OnOffenceHandler, OffenceDetails, OffenceError}, }; use codec::{Encode, Decode}; @@ -42,6 +43,13 @@ type OpaqueTimeSlot = Vec; /// A type alias for a report identifier. type ReportIdOf = ::Hash; +/// Type of data stored as a deferred offence +type DeferredOffenceOf = ( + Vec::AccountId, ::IdentificationTuple>>, + Vec, + SessionIndex, +); + /// Offences trait pub trait Trait: frame_system::Trait { /// The overarching event type. @@ -59,6 +67,10 @@ decl_storage! { map hasher(twox_64_concat) ReportIdOf => Option>; + /// Deferred reports that have been rejected by the offence handler and need to be submitted + /// at a later time. + DeferredOffences get(deferred_offences): Vec>; + /// A vector of reports of the same kind that happened at the same time slot. ConcurrentReportsIndex: double_map hasher(twox_64_concat) Kind, hasher(twox_64_concat) OpaqueTimeSlot @@ -77,13 +89,13 @@ decl_storage! { decl_event!( pub enum Event { /// There is an offence reported of the given `kind` happened at the `session_index` and - /// (kind-specific) time slot. This event is not deposited for duplicate slashes. - Offence(Kind, OpaqueTimeSlot), + /// (kind-specific) time slot. This event is not deposited for duplicate slashes. last + /// element indicates of the offence was applied (true) or queued (false). + Offence(Kind, OpaqueTimeSlot, bool), } ); decl_module! { - /// Offences module, currently just responsible for taking offence reports. pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; @@ -94,6 +106,27 @@ decl_module! { SimpleDispatchInfo::default().weigh_data(()) } + + fn on_initialize(now: T::BlockNumber) -> Weight { + // only decode storage if we can actually submit anything again. + if T::OnOffenceHandler::can_report() { + >::mutate(|deferred| { + // keep those that fail to be reported again. An error log is emitted here; this + // should not happen if staking's `can_report` is implemented properly. + deferred.retain(|(o, p, s)| { + T::OnOffenceHandler::on_offence(&o, &p, *s).map_err(|_| { + debug::native::error!( + target: "pallet-offences", + "re-submitting a deferred slash returned Err at {}. This should not happen with pallet-staking", + now, + ); + }).is_err() + }) + }) + } + + SimpleDispatchInfo::default().weigh_data(()) + } } } @@ -119,9 +152,6 @@ where None => return Err(OffenceError::DuplicateReport), }; - // Deposit the event. - Self::deposit_event(Event::Offence(O::ID, time_slot.encode())); - let offenders_count = concurrent_offenders.len() as u32; // The amount new offenders are slashed @@ -130,17 +160,42 @@ where let slash_perbill: Vec<_> = (0..concurrent_offenders.len()) .map(|_| new_fraction.clone()).collect(); - T::OnOffenceHandler::on_offence( + let applied = Self::report_or_store_offence( &concurrent_offenders, &slash_perbill, offence.session_index(), ); + // Deposit the event. + Self::deposit_event(Event::Offence(O::ID, time_slot.encode(), applied)); + Ok(()) } } impl Module { + /// Tries (without checking) to report an offence. Stores them in [`DeferredOffences`] in case + /// it fails. Returns false in case it has to store the offence. + fn report_or_store_offence( + concurrent_offenders: &[OffenceDetails], + slash_perbill: &[Perbill], + session_index: SessionIndex, + ) -> bool { + match T::OnOffenceHandler::on_offence( + &concurrent_offenders, + &slash_perbill, + session_index, + ) { + Ok(_) => true, + Err(_) => { + >::mutate(|d| + d.push((concurrent_offenders.to_vec(), slash_perbill.to_vec(), session_index)) + ); + false + } + } + } + /// Compute the ID for the given report properties. /// /// The report id depends on the offence kind, time slot and the id of offender. diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index a003ad69157fc..7ddbc6726d05f 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -43,6 +43,7 @@ pub struct OnOffenceHandler; thread_local! { pub static ON_OFFENCE_PERBILL: RefCell> = RefCell::new(Default::default()); + pub static CAN_REPORT: RefCell = RefCell::new(true); } impl offence::OnOffenceHandler for OnOffenceHandler { @@ -50,11 +51,25 @@ impl offence::OnOffenceHandler for OnOff _offenders: &[OffenceDetails], slash_fraction: &[Perbill], _offence_session: SessionIndex, - ) { - ON_OFFENCE_PERBILL.with(|f| { - *f.borrow_mut() = slash_fraction.to_vec(); - }); + ) -> Result<(), ()> { + if >::can_report() { + ON_OFFENCE_PERBILL.with(|f| { + *f.borrow_mut() = slash_fraction.to_vec(); + }); + + Ok(()) + } else { + Err(()) + } } + + fn can_report() -> bool { + CAN_REPORT.with(|c| *c.borrow()) + } +} + +pub fn set_can_report(can_report: bool) { + CAN_REPORT.with(|c| *c.borrow_mut() = can_report); } pub fn with_on_offence_fractions) -> R>(f: F) -> R { diff --git a/frame/offences/src/tests.rs b/frame/offences/src/tests.rs index 7e9c3986ed7db..3179a0752318f 100644 --- a/frame/offences/src/tests.rs +++ b/frame/offences/src/tests.rs @@ -21,9 +21,10 @@ use super::*; use crate::mock::{ Offences, System, Offence, TestEvent, KIND, new_test_ext, with_on_offence_fractions, - offence_reports, + offence_reports, set_can_report, }; use sp_runtime::Perbill; +use frame_support::traits::OnInitialize; use frame_system::{EventRecord, Phase}; #[test] @@ -130,7 +131,7 @@ fn should_deposit_event() { System::events(), vec![EventRecord { phase: Phase::Initialization, - event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode())), + event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode(), true)), topics: vec![], }] ); @@ -165,7 +166,7 @@ fn doesnt_deposit_event_for_dups() { System::events(), vec![EventRecord { phase: Phase::Initialization, - event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode())), + event: TestEvent::offences(crate::Event::Offence(KIND, time_slot.encode(), true)), topics: vec![], }] ); @@ -212,3 +213,54 @@ fn should_properly_count_offences() { ); }); } + +#[test] +fn should_queue_and_resubmit_rejected_offence() { + new_test_ext().execute_with(|| { + set_can_report(false); + + // will get deferred + let offence = Offence { + validator_set_count: 5, + time_slot: 42, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence).unwrap(); + assert_eq!(Offences::deferred_offences().len(), 1); + // event also indicates unapplied. + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: TestEvent::offences(crate::Event::Offence(KIND, 42u128.encode(), false)), + topics: vec![], + }] + ); + + // will not dequeue + Offences::on_initialize(2); + + // again + let offence = Offence { + validator_set_count: 5, + time_slot: 62, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence).unwrap(); + assert_eq!(Offences::deferred_offences().len(), 2); + + set_can_report(true); + + // can be submitted + let offence = Offence { + validator_set_count: 5, + time_slot: 72, + offenders: vec![5], + }; + Offences::report_offence(vec![], offence).unwrap(); + assert_eq!(Offences::deferred_offences().len(), 2); + + Offences::on_initialize(3); + assert_eq!(Offences::deferred_offences().len(), 0); + }) +} diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index b11ae1d818578..9346b060fa48a 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -102,13 +102,15 @@ use sp_std::{prelude::*, marker::PhantomData, ops::{Sub, Rem}}; use codec::Decode; use sp_runtime::{KeyTypeId, Perbill, RuntimeAppPublic, BoundToRuntimeAppPublic}; -use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys}; +use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys, Saturating}; use sp_staking::SessionIndex; use frame_support::{ ensure, decl_module, decl_event, decl_storage, decl_error, ConsensusEngineId, Parameter, - weights::{Weight, SimpleDispatchInfo, WeighData}, - traits::{Get, FindAuthor, ValidatorRegistration}, + traits::{ + Get, FindAuthor, ValidatorRegistration, EstimateNextSessionRotation, EstimateNextNewSession, + }, dispatch::{self, DispatchResult, DispatchError}, + weights::{Weight, SimpleDispatchInfo, WeighData}, }; use frame_system::{self as system, ensure_signed}; @@ -147,6 +149,29 @@ impl< } } +impl< + BlockNumber: Rem + Sub + Zero + PartialOrd + Saturating + Clone, + Period: Get, + Offset: Get, +> EstimateNextSessionRotation for PeriodicSessions { + fn estimate_next_session_rotation(now: BlockNumber) -> Option { + let offset = Offset::get(); + let period = Period::get(); + Some(if now > offset { + let block_after_last_session = (now.clone() - offset) % period.clone(); + if block_after_last_session > Zero::zero() { + now.saturating_add( + period.saturating_sub(block_after_last_session) + ) + } else { + Zero::zero() + } + } else { + offset + }) + } +} + /// A trait for managing creation of new validator set. pub trait SessionManager { /// Plan a new session, and optionally provide the new validator set. @@ -330,6 +355,11 @@ pub trait Trait: frame_system::Trait { /// Indicator for when to end the session. type ShouldEndSession: ShouldEndSession; + /// Something that can predict the next session rotation. This should typically come from the + /// same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort estimate. + /// It is helpful to implement [`EstimateNextNewSession`]. + type NextSessionRotation: EstimateNextSessionRotation; + /// Handler for managing new session. type SessionManager: SessionManager; @@ -735,3 +765,11 @@ impl> FindAuthor validators.get(i as usize).map(|k| k.clone()) } } + +impl EstimateNextNewSession for Module { + /// This session module always calls new_session and next_session at the same time, hence we + /// do a simple proxy and pass the function to next rotation. + fn estimate_next_new_session(now: T::BlockNumber) -> Option { + T::NextSessionRotation::estimate_next_session_rotation(now) + } +} diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index 9d64285b900f8..dd28d357491da 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -205,6 +205,7 @@ impl Trait for Test { type Keys = MockSessionKeys; type Event = (); type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = (); } #[cfg(feature = "historical")] diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 7ea72b3d7a496..8a9310e22330e 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -11,7 +11,6 @@ description = "FRAME pallet staking" [dependencies] serde = { version = "1.0.101", optional = true } codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } -sp-keyring = { version = "2.0.0-alpha.5", optional = true, path = "../../primitives/keyring" } sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/std" } sp-phragmen = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/phragmen" } sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.5"} @@ -21,7 +20,15 @@ frame-support = { version = "2.0.0-alpha.5", default-features = false, path = ". frame-system = { version = "2.0.0-alpha.5", default-features = false, path = "../system" } pallet-session = { version = "2.0.0-alpha.5", features = ["historical"], path = "../session", default-features = false } pallet-authorship = { version = "2.0.0-alpha.5", default-features = false, path = "../authorship" } +sp-application-crypto = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/application-crypto" } +static_assertions = "1.1.0" +# Optional imports for tesing-utils feature +pallet-indices = { version = "2.0.0-alpha.4", optional = true, path = "../indices", default-features = false } +sp-core = { version = "2.0.0-alpha.4", optional = true, path = "../../primitives/core", default-features = false } +rand = { version = "0.7.3", optional = true, default-features = false } + +# Optional imports for benchmarking frame-benchmarking = { version = "2.0.0-alpha.5", default-features = false, path = "../benchmarking", optional = true } rand_chacha = { version = "0.2", default-features = false, optional = true } @@ -33,13 +40,20 @@ pallet-staking-reward-curve = { version = "2.0.0-alpha.5", path = "../staking/r substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" } frame-benchmarking = { version = "2.0.0-alpha.5", path = "../benchmarking" } rand_chacha = { version = "0.2" } +parking_lot = "0.10.0" +env_logger = "0.7.1" hex = "0.4" [features] +testing-utils = [ + "std", + "pallet-indices/std", + "sp-core/std", + "rand/std", +] default = ["std"] std = [ "serde", - "sp-keyring", "codec/std", "sp-std/std", "sp-phragmen/std", @@ -50,6 +64,8 @@ std = [ "pallet-session/std", "frame-system/std", "pallet-authorship/std", + "sp-application-crypto/std", + "sp-core/std", ] runtime-benchmarks = [ "rand_chacha", diff --git a/frame/staking/fuzz/.gitignore b/frame/staking/fuzz/.gitignore new file mode 100644 index 0000000000000..572e03bdf321b --- /dev/null +++ b/frame/staking/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/frame/staking/fuzz/Cargo.lock b/frame/staking/fuzz/Cargo.lock new file mode 100644 index 0000000000000..e8469483dbe5c --- /dev/null +++ b/frame/staking/fuzz/Cargo.lock @@ -0,0 +1,2189 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "ahash" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f33b5018f120946c1dcf279194f238a9f146725593ead1c08fa47ff22b0b5d3" +dependencies = [ + "const-random", +] + +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + +[[package]] +name = "arbitrary" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16971f2f0ce65c5cf2a1546cc6a0af102ecb11e265ddaa9433fb3e5bfdf676a4" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +dependencies = [ + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitmask" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da9b3d9f6f585199287a473f4f8dfab6566cf827d15c00c219f53c645687ead" + +[[package]] +name = "bitvec" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bumpalo" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" + +[[package]] +name = "byte-slice-cast" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +dependencies = [ + "ppv-lite86", +] + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clear_on_drop" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" +dependencies = [ + "cc", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "const-random" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a" +dependencies = [ + "getrandom", + "proc-macro-hack", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle 1.0.0", +] + +[[package]] +name = "curve25519-dalek" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" +dependencies = [ + "byteorder", + "clear_on_drop", + "digest", + "rand_core 0.3.1", + "subtle 2.2.2", +] + +[[package]] +name = "curve25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" +dependencies = [ + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle 2.2.2", + "zeroize 1.1.0", +] + +[[package]] +name = "derive_more" +version = "0.99.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a806e96c59a76a5ba6e18735b6cf833344671e61e7863f2edb5c518ea2cac95c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" +dependencies = [ + "clear_on_drop", + "curve25519-dalek 2.0.0", + "rand 0.7.3", + "sha2", +] + +[[package]] +name = "environmental" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516aa8d7a71cb00a1c4146f0798549b93d083d4f189b3ced8f3de6b8f11ee6c4" + +[[package]] +name = "failure" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fixed-hash" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" +dependencies = [ + "byteorder", + "libc", + "rand 0.7.3", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "frame-benchmarking" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "sp-api", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std", +] + +[[package]] +name = "frame-metadata" +version = "11.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-std", +] + +[[package]] +name = "frame-support" +version = "2.0.0-alpha.3" +dependencies = [ + "bitmask", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "serde", + "sp-arithmetic", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "tracing", +] + +[[package]] +name = "frame-support-procedural" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "impl-trait-for-tuples", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" + +[[package]] +name = "futures-executor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" + +[[package]] +name = "futures-macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" + +[[package]] +name = "futures-task" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" + +[[package]] +name = "futures-util" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6073d0ca812575946eb5f35ff68dbe519907b25c42530389ff946dc84c6ead" +dependencies = [ + "ahash", + "autocfg 0.1.7", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "hmac-drbg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +dependencies = [ + "digest", + "generic-array", + "hmac", +] + +[[package]] +name = "impl-codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e3cae7e99c7ff5a995da2cf78dd0a5383740eda71d98cf7b1910c301ac69b8" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-serde" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bbe9ea9b182f0fb1cabbd61f4ff9b7b7b9197955e95a7e4c27de5055eb29ff8" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65877bf7d44897a473350b1046277941cee20b263397e90869c50b6e766088b" + +[[package]] +name = "js-sys" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" + +[[package]] +name = "libfuzzer-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb789afcc589a08928d1e466087445ab740a0f70a2ee23d9349a0e3723d65e1b" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libsecp256k1" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +dependencies = [ + "arrayref", + "crunchy", + "digest", + "hmac-drbg", + "rand 0.7.3", + "sha2", + "subtle 2.2.2", + "typenum", +] + +[[package]] +name = "lock_api" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memory-db" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198831fe8722331a395bc199a5d08efbc197497ef354cb4c77b969c02ffc0fc4" +dependencies = [ + "ahash", + "hash-db", + "hashbrown", + "parity-util-mem", +] + +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" + +[[package]] +name = "merlin" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0942b357c1b4d0dc43ba724674ec89c3218e6ca2b3e8269e7cb53bcecd2f6e" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.4.2", + "zeroize 1.1.0", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +dependencies = [ + "autocfg 1.0.0", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +dependencies = [ + "parking_lot 0.9.0", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "pallet-authorship" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-authorship", + "sp-core", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-balances" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "serde", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-indices" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-session" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "pallet-timestamp", + "parity-scale-codec", + "serde", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-trie", +] + +[[package]] +name = "pallet-staking" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-support", + "frame-system", + "pallet-authorship", + "pallet-indices", + "pallet-session", + "parity-scale-codec", + "rand 0.7.3", + "serde", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-phragmen", + "sp-runtime", + "sp-staking", + "sp-std", + "static_assertions", +] + +[[package]] +name = "pallet-staking-fuzz" +version = "0.0.0" +dependencies = [ + "frame-support", + "frame-system", + "libfuzzer-sys", + "pallet-balances", + "pallet-indices", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "rand 0.7.3", + "sp-core", + "sp-io", + "sp-phragmen", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-staking-reward-curve" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pallet-timestamp" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "serde", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "parity-scale-codec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" +dependencies = [ + "arrayvec 0.5.1", + "bitvec", + "byte-slice-cast", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-util-mem" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1476e40bf8f5c6776e9600983435821ca86eb9819d74a6207cca69d091406a" +dependencies = [ + "cfg-if", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot 0.10.0", + "primitive-types", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "parity-wasm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core 0.6.2", + "rustc_version", +] + +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +dependencies = [ + "lock_api", + "parking_lot_core 0.7.0", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec 0.6.13", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec 1.2.0", + "winapi", +] + +[[package]] +name = "paste" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pbkdf2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" +dependencies = [ + "byteorder", + "crypto-mac", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + +[[package]] +name = "primitive-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde 0.3.0", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro-nested" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" + +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha 0.2.1", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +dependencies = [ + "c2-chacha", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "regex" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "schnorrkel" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eacd8381b3c37840c9c9f40472af529e49975bdcbc24f83c31059fd6539023d3" +dependencies = [ + "curve25519-dalek 1.2.3", + "failure", + "merlin", + "rand 0.6.5", + "rand_core 0.4.2", + "rand_os", + "sha2", + "subtle 2.2.2", + "zeroize 0.9.3", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "send_wrapper" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" + +[[package]] +name = "sp-api" +version = "2.0.0-alpha.3" +dependencies = [ + "hash-db", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-version", +] + +[[package]] +name = "sp-api-proc-macro" +version = "2.0.0-alpha.3" +dependencies = [ + "blake2-rfc", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-application-crypto" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "2.0.0-alpha.3" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-authorship" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-core" +version = "2.0.0-alpha.3" +dependencies = [ + "base58", + "blake2-rfc", + "byteorder", + "ed25519-dalek", + "hash-db", + "hash256-std-hasher", + "hex", + "impl-serde 0.3.0", + "lazy_static", + "libsecp256k1", + "log", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot 0.10.0", + "primitive-types", + "rand 0.7.3", + "regex", + "rustc-hex", + "schnorrkel", + "serde", + "sha2", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "substrate-bip39", + "tiny-bip39", + "tiny-keccak", + "twox-hash", + "wasmi", + "zeroize 1.1.0", +] + +[[package]] +name = "sp-debug-derive" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.8.0-alpha.3" +dependencies = [ + "environmental", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-inherents" +version = "2.0.0-alpha.3" +dependencies = [ + "derive_more", + "parity-scale-codec", + "parking_lot 0.10.0", + "sp-core", + "sp-std", +] + +[[package]] +name = "sp-io" +version = "2.0.0-alpha.3" +dependencies = [ + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "sp-core", + "sp-externalities", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-trie", + "sp-wasm-interface", +] + +[[package]] +name = "sp-keyring" +version = "2.0.0-alpha.3" +dependencies = [ + "lazy_static", + "sp-core", + "sp-runtime", + "strum", +] + +[[package]] +name = "sp-panic-handler" +version = "2.0.0-alpha.3" +dependencies = [ + "backtrace", + "log", +] + +[[package]] +name = "sp-phragmen" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-phragmen-compact", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-runtime" +version = "2.0.0-alpha.3" +dependencies = [ + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-inherents", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-runtime-interface" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "2.0.0-alpha.3" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-staking" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-state-machine" +version = "0.8.0-alpha.3" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot 0.10.0", + "rand 0.7.3", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-trie", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "2.0.0-alpha.3" + +[[package]] +name = "sp-storage" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-serde 0.2.3", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-timestamp" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", + "wasm-timer", +] + +[[package]] +name = "sp-trie" +version = "2.0.0-alpha.3" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "sp-core", + "sp-std", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-serde 0.2.3", + "parity-scale-codec", + "serde", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-wasm-interface" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-std", + "wasmi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "substrate-bip39" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be511be555a3633e71739a79e4ddff6a6aaa6579fa6114182a51d72c3eb93c5" +dependencies = [ + "hmac", + "pbkdf2", + "schnorrkel", + "sha2", +] + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" + +[[package]] +name = "syn" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tiny-bip39" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6848cd8f566953ce1e8faeba12ee23cbdbb0437754792cd857d44628b5685e3" +dependencies = [ + "failure", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.7.3", + "rustc-hash", + "sha2", + "unicode-normalization", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2953ca5148619bc99695c1274cb54c5275bbb913c6adad87e72eaf8db9787f69" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1721cc8cf7d770cc4257872507180f35a4797272f5962f24c806af9e7faf52ab" +dependencies = [ + "cfg-if", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbad39da2f9af1cae3016339ad7f2c7a9e870f12e8fd04c4fd7ef35b30c0d2b" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "trie-db" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9222c50cc325855621271157c973da27a0dcd26fa06f8edf81020bd2333df0" +dependencies = [ + "hash-db", + "hashbrown", + "log", + "rustc-hex", + "smallvec 1.2.0", +] + +[[package]] +name = "trie-root" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "652931506d2c1244d7217a70b99f56718a7b4161b37f04e7cd868072a99f68cd" +dependencies = [ + "hash-db", +] + +[[package]] +name = "twox-hash" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" +dependencies = [ + "rand 0.7.3", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + +[[package]] +name = "uint" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d" +dependencies = [ + "byteorder", + "crunchy", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +dependencies = [ + "smallvec 1.2.0", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457414a91863c0ec00090dba537f88ab955d93ca6555862c29b6d860990b8a8a" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8" + +[[package]] +name = "wasm-timer" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324c5e65a08699c9c4334ba136597ab22b85dccd4b65dd1e36ccf8f723a95b54" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.9.0", + "pin-utils", + "send_wrapper", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmi" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf617d864d25af3587aa745529f7aaa541066c876d57e050c0d0c85c61c92aff" +dependencies = [ + "libc", + "memory_units", + "num-rational", + "num-traits", + "parity-wasm", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "web-sys" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" + +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/frame/staking/fuzz/Cargo.toml b/frame/staking/fuzz/Cargo.toml new file mode 100644 index 0000000000000..e4e08a065c467 --- /dev/null +++ b/frame/staking/fuzz/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pallet-staking-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.3" +codec = { package = "parity-scale-codec", version = "1.2.0", default-features = false, features = ["derive"] } +pallet-staking = { version = "2.0.0-alpha.2", path = "..", features = ["testing-utils"] } +pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../reward-curve" } +pallet-session = { version = "2.0.0-alpha.2", path = "../../session" } +pallet-indices = { version = "2.0.0-alpha.2", path = "../../indices" } +pallet-balances = { version = "2.0.0-alpha.2", path = "../../balances" } +pallet-timestamp = { version = "2.0.0-alpha.2", path = "../../timestamp" } +frame-system = { version = "2.0.0-alpha.2", path = "../../system" } +frame-support = { version = "2.0.0-alpha.2", path = "../../support" } +sp-std = { version = "2.0.0-alpha.2", path = "../../../primitives/std" } +sp-io ={ version = "2.0.0-alpha.2", path = "../../../primitives/io" } +sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" } +sp-phragmen = { version = "2.0.0-alpha.2", path = "../../../primitives/phragmen" } +sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" } +rand = "0.7.3" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "submit_solution" +path = "fuzz_targets/submit_solution.rs" diff --git a/frame/staking/fuzz/fuzz_targets/mock.rs b/frame/staking/fuzz/fuzz_targets/mock.rs new file mode 100644 index 0000000000000..4bb3437f92368 --- /dev/null +++ b/frame/staking/fuzz/fuzz_targets/mock.rs @@ -0,0 +1,182 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Mock file for staking fuzzing. + +use sp_runtime::traits::{Convert, SaturatedConversion}; +use frame_support::{impl_outer_origin, impl_outer_dispatch, parameter_types}; + +type AccountId = u64; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u64; + +type System = frame_system::Module; +type Balances = pallet_balances::Module; +type Staking = pallet_staking::Module; +type Indices = pallet_indices::Module; +type Session = pallet_session::Module; + +impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} +} + +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + staking::Staking, + } +} + +pub struct CurrencyToVoteHandler; +impl Convert for CurrencyToVoteHandler { + fn convert(x: u64) -> u64 { + x + } +} +impl Convert for CurrencyToVoteHandler { + fn convert(x: u128) -> u64 { + x.saturated_into() + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; + +impl frame_system::Trait for Test { + type Origin = Origin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = Indices; + type Header = sp_runtime::testing::Header; + type Event = (); + type BlockHashCount = (); + type MaximumBlockWeight = (); + type AvailableBlockRatio = (); + type MaximumBlockLength = (); + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (Balances,); +} +parameter_types! { + pub const ExistentialDeposit: Balance = 10; +} +impl pallet_balances::Trait for Test { + type Balance = Balance; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} +impl pallet_indices::Trait for Test { + type AccountIndex = AccountIndex; + type Event = (); + type Currency = Balances; + type Deposit = (); +} +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Trait for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; +} +impl pallet_session::historical::Trait for Test { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +sp_runtime::impl_opaque_keys! { + pub struct SessionKeys { + pub foo: sp_runtime::testing::UintAuthorityId, + } +} + +pub struct TestSessionHandler; +impl pallet_session::SessionHandler for TestSessionHandler { + const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[]; + + fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} + + fn on_new_session( + _: bool, + _: &[(AccountId, Ks)], + _: &[(AccountId, Ks)], + ) {} + + fn on_disabled(_: usize) {} +} + +impl pallet_session::Trait for Test { + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type Keys = SessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions<(), ()>; + type NextSessionRotation = pallet_session::PeriodicSessions<(), ()>; + type SessionHandler = TestSessionHandler; + type Event = (); + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type DisabledValidatorsThreshold = (); +} +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub const MaxNominatorRewardedPerValidator: u32 = 64; +} + +pub type Extrinsic = sp_runtime::testing::TestXt; +type SubmitTransaction = frame_system::offchain::TransactionSubmitter< + sp_runtime::testing::UintAuthorityId, + Test, + Extrinsic, +>; + +impl pallet_staking::Trait for Test { + type Currency = Balances; + type Time = pallet_timestamp::Module; + type CurrencyToVote = CurrencyToVoteHandler; + type RewardRemainder = (); + type Event = (); + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = (); + type SessionInterface = Self; + type RewardCurve = RewardCurve; + type NextNewSession = Session; + type ElectionLookahead = (); + type Call = Call; + type SubmitTransaction = SubmitTransaction; + type KeyType = sp_runtime::testing::UintAuthorityId; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; +} diff --git a/frame/staking/fuzz/fuzz_targets/submit_solution.rs b/frame/staking/fuzz/fuzz_targets/submit_solution.rs new file mode 100644 index 0000000000000..5d1fcf1d7ea85 --- /dev/null +++ b/frame/staking/fuzz/fuzz_targets/submit_solution.rs @@ -0,0 +1,130 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Fuzzing for staking pallet. + +#![no_main] +use libfuzzer_sys::fuzz_target; +use mock::Test; +use pallet_staking::testing_utils::{ + self, USER, get_seq_phragmen_solution, get_weak_solution, setup_chain_stakers, + set_validator_count, signed_account, +}; +use frame_support::assert_ok; +use sp_runtime::{traits::Dispatchable, DispatchError}; + +mod mock; + +#[repr(u32)] +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +enum Mode { + /// Initial submission. This will be rather cheap. + InitialSubmission, + /// A better submission that will replace the previous ones. This is the most expensive. + StrongerSubmission, + /// A weak submission that will be rejected. This will be rather cheap. + WeakerSubmission, +} + +pub fn new_test_ext() -> Result { + frame_system::GenesisConfig::default().build_storage::().map(Into::into) +} + +fuzz_target!(|do_reduce: bool| { + let ext = new_test_ext(); + let mode: Mode = unsafe { std::mem::transmute(testing_utils::random(0, 2)) }; + let num_validators = testing_utils::random(50, 500); + let num_nominators = testing_utils::random(200, 2000); + let edge_per_voter = testing_utils::random(1, 16); + let to_elect = testing_utils::random(10, num_validators); + + println!("+++ instance with params {} / {} / {} / {:?} / {}", + num_nominators, + num_validators, + edge_per_voter, + mode, + to_elect, + ); + + ext.unwrap_or_default().execute_with(|| { + // initial setup + set_validator_count::(to_elect); + setup_chain_stakers::( + num_validators, + num_nominators, + edge_per_voter, + ); + + println!("++ Chain setup done."); + + // stuff to submit + let (winners, compact, score) = match mode { + Mode::InitialSubmission => { + /* No need to setup anything */ + get_seq_phragmen_solution::(do_reduce) + }, + Mode::StrongerSubmission => { + let (winners, compact, score) = get_weak_solution::(false); + assert_ok!( + >::submit_election_solution( + signed_account::(USER), + winners, + compact, + score, + ) + ); + get_seq_phragmen_solution::(do_reduce) + }, + Mode::WeakerSubmission => { + let (winners, compact, score) = get_seq_phragmen_solution::(do_reduce); + assert_ok!( + >::submit_election_solution( + signed_account::(USER), + winners, + compact, + score, + ) + ); + get_weak_solution::(false) + } + }; + + println!("++ Submission ready."); + + // must have chosen correct number of winners. + assert_eq!(winners.len() as u32, >::validator_count()); + + // final call and origin + let call = pallet_staking::Call::::submit_election_solution( + winners, + compact, + score, + ); + let caller = signed_account::(USER); + + // actually submit + match mode { + Mode::WeakerSubmission => { + assert_eq!( + call.dispatch(caller.into()).unwrap_err(), + DispatchError::Module { index: 0, error: 11, message: Some("PhragmenWeakSubmission") }, + ); + }, + _ => assert_ok!(call.dispatch(caller.into())), + }; + }) +}); diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 300e77bbda679..a88c00c144dd5 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -402,7 +402,7 @@ mod tests { #[test] fn create_validators_with_nominators_for_era_works() { - ExtBuilder::default().stakers(false).build().execute_with(|| { + ExtBuilder::default().has_stakers(false).build().execute_with(|| { let v = 10; let n = 100; @@ -418,7 +418,7 @@ mod tests { #[test] fn create_validator_with_nominators_works() { - ExtBuilder::default().stakers(false).build().execute_with(|| { + ExtBuilder::default().has_stakers(false).build().execute_with(|| { let n = 10; let validator = create_validator_with_nominators::( @@ -441,7 +441,7 @@ mod tests { #[test] fn create_nominator_with_validators_works() { - ExtBuilder::default().stakers(false).build().execute_with(|| { + ExtBuilder::default().has_stakers(false).build().execute_with(|| { let v = 5; let (nominator, validators) = create_nominator_with_validators::(v).unwrap(); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 31cbf3b8db1d6..b19caec258fa5 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -72,8 +72,8 @@ //! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` //! and `Idle` (defined in [`StakerStatus`](./enum.StakerStatus.html)). There are three //! corresponding instructions to change between roles, namely: -//! [`validate`](./enum.Call.html#variant.validate), [`nominate`](./enum.Call.html#variant.nominate), -//! and [`chill`](./enum.Call.html#variant.chill). +//! [`validate`](./enum.Call.html#variant.validate), +//! [`nominate`](./enum.Call.html#variant.nominate), and [`chill`](./enum.Call.html#variant.chill). //! //! #### Validating //! @@ -184,8 +184,8 @@ //! [`reward_by_indices`](./enum.Call.html#variant.reward_by_indices). //! //! [`Module`](./struct.Module.html) implements -//! [`pallet_authorship::EventHandler`](../pallet_authorship/trait.EventHandler.html) to add reward points -//! to block producer and block producer of referenced uncles. +//! [`pallet_authorship::EventHandler`](../pallet_authorship/trait.EventHandler.html) to add reward +//! points to block producer and block producer of referenced uncles. //! //! The validator and its nominator split their reward as following: //! @@ -244,40 +244,55 @@ //! ## Related Modules //! //! - [Balances](../pallet_balances/index.html): Used to manage values at stake. -//! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new validators -//! is stored in the Session module's `Validators` at the end of each era. +//! - [Session](../pallet_session/index.html): Used to manage sessions. Also, a list of new +//! validators is stored in the Session module's `Validators` at the end of each era. -#![recursion_limit="128"] +#![recursion_limit = "128"] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] mod mock; #[cfg(test)] mod tests; -mod slashing; +#[cfg(feature = "testing-utils")] +pub mod testing_utils; #[cfg(any(feature = "runtime-benchmarks", test))] pub mod benchmarking; +pub mod slashing; +pub mod offchain_election; pub mod inflation; -use sp_std::{prelude::*, result, collections::btree_map::BTreeMap}; +use sp_std::{ + result, + prelude::*, + collections::btree_map::BTreeMap, + convert::{TryInto, From}, + mem::size_of, +}; use codec::{HasCompact, Encode, Decode}; use frame_support::{ - decl_module, decl_event, decl_storage, ensure, decl_error, - dispatch::DispatchResult, storage::IterableStorageMap, traits::{ - Currency, LockIdentifier, LockableCurrency, WithdrawReasons, OnUnbalanced, Imbalance, Get, - UnixTime - }, + decl_module, decl_event, decl_storage, ensure, decl_error, debug, weights::{SimpleDispatchInfo, Weight}, + storage::IterableStorageMap, + dispatch::{IsSubType, DispatchResult}, + traits::{ + Currency, LockIdentifier, LockableCurrency, WithdrawReasons, OnUnbalanced, Imbalance, Get, + UnixTime, EstimateNextNewSession, + } }; -use pallet_session::historical::SessionManager; +use pallet_session::historical; use sp_runtime::{ - Perbill, PerThing, RuntimeDebug, + Perbill, PerU16, PerThing, RuntimeDebug, curve::PiecewiseLinear, traits::{ - Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion, - AtLeast32Bit, EnsureOrigin, - } + Convert, Zero, StaticLookup, CheckedSub, Saturating, SaturatedConversion, AtLeast32Bit, + EnsureOrigin, SignedExtension, + }, + transaction_validity::{ + TransactionValidityError, TransactionValidity, ValidTransaction, InvalidTransaction, + TransactionSource, + }, }; use sp_staking::{ SessionIndex, @@ -285,14 +300,33 @@ use sp_staking::{ }; #[cfg(feature = "std")] use sp_runtime::{Serialize, Deserialize}; -use frame_system::{self as system, ensure_signed, ensure_root}; - -use sp_phragmen::ExtendedBalance; +use frame_system::{ + self as system, ensure_signed, ensure_root, ensure_none, + offchain::SubmitUnsignedTransaction, +}; +use sp_phragmen::{ + ExtendedBalance, Assignment, PhragmenScore, PhragmenResult, build_support_map, evaluate_support, + elect, generate_compact_solution_type, is_score_better, VotingLimit, SupportMap, +}; const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; -pub const MAX_NOMINATIONS: usize = 16; -const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; +pub const MAX_UNLOCKING_CHUNKS: usize = 32; +pub const MAX_NOMINATIONS: usize = ::LIMIT; + +/// Data type used to index nominators in the compact type +pub type NominatorIndex = u32; + +/// Data type used to index validators in the compact type. +pub type ValidatorIndex = u16; + +// Ensure the size of both ValidatorIndex and NominatorIndex. They both need to be well below usize. +static_assertions::const_assert!(size_of::() <= size_of::()); +static_assertions::const_assert!(size_of::() <= size_of::()); + +/// Maximum number of stakers that can be stored in a snapshot. +pub(crate) const MAX_VALIDATORS: usize = ValidatorIndex::max_value() as usize; +pub(crate) const MAX_NOMINATORS: usize = NominatorIndex::max_value() as usize; /// Counter for the number of eras that have passed. pub type EraIndex = u32; @@ -300,6 +334,9 @@ pub type EraIndex = u32; /// Counter for the number of "reward" points earned by a given validator. pub type RewardPoint = u32; +// Note: Maximum nomination limit is set here -- 16. +generate_compact_solution_type!(pub GenericCompactAssignments, 16); + /// Information regarding the active era (era in used in session). #[derive(Encode, Decode, RuntimeDebug)] pub struct ActiveEraInfo { @@ -312,6 +349,25 @@ pub struct ActiveEraInfo { start: Option, } +/// Accuracy used for on-chain phragmen. +pub type ChainAccuracy = Perbill; + +/// Accuracy used for off-chain phragmen. This better be small. +pub type OffchainAccuracy = PerU16; + +/// The balance type of this module. +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// The compact type for election solutions. +pub type CompactAssignments = + GenericCompactAssignments; + +type PositiveImbalanceOf = + <::Currency as Currency<::AccountId>>::PositiveImbalance; +type NegativeImbalanceOf = + <::Currency as Currency<::AccountId>>::NegativeImbalance; + /// Reward points of an era. Used to split era total payout between validators. /// /// This points will be used to reward validators and their respective nominators. @@ -560,12 +616,61 @@ pub struct UnappliedSlash { payout: Balance, } -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type PositiveImbalanceOf = - <::Currency as Currency<::AccountId>>::PositiveImbalance; -type NegativeImbalanceOf = - <::Currency as Currency<::AccountId>>::NegativeImbalance; +/// Indicate how an election round was computed. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug)] +pub enum ElectionCompute { + /// Result was forcefully computed on chain at the end of the session. + OnChain, + /// Result was submitted and accepted to the chain via a signed transaction. + Signed, + /// Result was submitted and accepted to the chain via an unsigned transaction (by an + /// authority). + Unsigned, +} + +/// The result of an election round. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub struct ElectionResult { + /// Flat list of validators who have been elected. + elected_stashes: Vec, + /// Flat list of new exposures, to be updated in the [`Exposure`] storage. + exposures: Vec<(AccountId, Exposure)>, + /// Type of the result. This is kept on chain only to track and report the best score's + /// submission type. An optimisation could remove this. + compute: ElectionCompute, +} + +/// The status of the upcoming (offchain) election. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub enum ElectionStatus { + /// Nothing has and will happen for now. submission window is not open. + Closed, + /// The submission window has been open since the contained block number. + Open(BlockNumber), +} + +impl ElectionStatus { + fn is_open_at(&self, n: BlockNumber) -> bool { + *self == Self::Open(n) + } + + fn is_closed(&self) -> bool { + match self { + Self::Closed => true, + _ => false + } + } + + fn is_open(&self) -> bool { + !self.is_closed() + } +} + +impl Default for ElectionStatus { + fn default() -> Self { + Self::Closed + } +} /// Means for interacting with a specialized version of the `session` trait. /// @@ -591,7 +696,8 @@ impl SessionInterface<::AccountId> for T whe >, T::SessionHandler: pallet_session::SessionHandler<::AccountId>, T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert<::AccountId, Option<::AccountId>> + T::ValidatorIdOf: + Convert<::AccountId, Option<::AccountId>>, { fn disable_validator(validator: &::AccountId) -> Result { >::disable(validator) @@ -655,6 +761,20 @@ pub trait Trait: frame_system::Trait { /// The NPoS reward curve to use. type RewardCurve: Get<&'static PiecewiseLinear<'static>>; + /// Something that can estimate the next session change, accurately or as a best effort guess. + type NextNewSession: EstimateNextNewSession; + + /// How many blocks ahead of the era, within the last do we try to run the phragmen offchain? + /// Setting this to zero will disable the offchain compute and only on-chain seq-phragmen will + /// be used. + type ElectionLookahead: Get; + + /// The overarching call type. + type Call: From> + IsSubType, Self> + Clone; + + /// A transaction submitter. + type SubmitTransaction: SubmitUnsignedTransaction::Call>; + /// The maximum number of nominator rewarded for each validator. /// /// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can claim @@ -805,7 +925,7 @@ decl_storage! { pub ErasTotalStake get(fn eras_total_stake): map hasher(twox_64_concat) EraIndex => BalanceOf; - /// True if the next session change will be a new era regardless of index. + /// Mode of era forcing. pub ForceEra get(fn force_era) config(): Forcing; /// The percentage of the slash that is distributed to reporters. @@ -850,6 +970,30 @@ decl_storage! { /// The earliest era for which we have a pending, unapplied slash. EarliestUnappliedSlash: Option; + /// Snapshot of validators at the beginning of the current election window. This should only + /// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`. + pub SnapshotValidators get(fn snapshot_validators): Option>; + + /// Snapshot of nominators at the beginning of the current election window. This should only + /// have a value when [`EraElectionStatus`] == `ElectionStatus::Open(_)`. + pub SnapshotNominators get(fn snapshot_nominators): Option>; + + /// The next validator set. At the end of an era, if this is available (potentially from the + /// result of an offchain worker), it is immediately used. Otherwise, the on-chain election + /// is executed. + pub QueuedElected get(fn queued_elected): Option>>; + + /// The score of the current [`QueuedElected`]. + pub QueuedScore get(fn queued_score): Option; + + /// Flag to control the execution of the offchain election. When `Open(_)`, we accept + /// solutions to be submitted. + pub EraElectionStatus get(fn era_election_status): ElectionStatus; + + /// True if the current planned session is final. + pub IsCurrentSessionFinal get(fn is_current_session_final): bool = false; + + /// True if network has been upgraded to this version. /// Storage version of the pallet. /// /// This is set to v3.0.0 for new networks. @@ -898,6 +1042,8 @@ decl_event!( /// An old slashing report from a prior era was discarded because it could /// not be processed. OldSlashingReportDiscarded(SessionIndex), + /// A new set of stakers was elected with the given computation method. + StakingElection(ElectionCompute), /// An account has bonded this amount. /// /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, @@ -942,6 +1088,33 @@ decl_error! { InvalidNumberOfNominations, /// Items are not sorted and unique. NotSortedAndUnique, + /// The submitted result is received out of the open window. + PhragmenEarlySubmission, + /// The submitted result is not as good as the one stored on chain. + PhragmenWeakSubmission, + /// The snapshot data of the current window is missing. + SnapshotUnavailable, + /// Incorrect number of winners were presented. + PhragmenBogusWinnerCount, + /// One of the submitted winners is not an active candidate on chain (index is out of range + /// in snapshot). + PhragmenBogusWinner, + /// Error while building the assignment type from the compact. This can happen if an index + /// is invalid, or if the weights _overflow_. + PhragmenBogusCompact, + /// One of the submitted nominators is not an active nominator on chain. + PhragmenBogusNominator, + /// One of the submitted nominators has an edge to which they have not voted on chain. + PhragmenBogusNomination, + /// One of the submitted nominators has an edge which is submitted before the last non-zero + /// slash of the target. + PhragmenSlashedNomination, + /// A self vote must only be originated from a validator to ONLY themselves. + PhragmenBogusSelfVote, + /// The submitted result has unknown edges that are not among the presented winners. + PhragmenBogusEdge, + /// The claimed score does not match with the one computed from the data. + PhragmenBogusScore, } } @@ -957,6 +1130,84 @@ decl_module! { fn deposit_event() = default; + /// sets `ElectionStatus` to `Open(now)` where `now` is the block number at which the + /// election window has opened, if we are at the last session and less blocks than + /// `T::ElectionLookahead` is remaining until the next new session schedule. The offchain + /// worker, if applicable, will execute at the end of the current block, and solutions may + /// be submitted. + fn on_initialize(now: T::BlockNumber) -> Weight { + if + // if we don't have any ongoing offchain compute. + Self::era_election_status().is_closed() && + Self::is_current_session_final() + { + if let Some(next_session_change) = T::NextNewSession::estimate_next_new_session(now){ + if let Some(remaining) = next_session_change.checked_sub(&now) { + if remaining <= T::ElectionLookahead::get() && !remaining.is_zero() { + // create snapshot. + if Self::create_stakers_snapshot() { + // Set the flag to make sure we don't waste any compute here in the same era + // after we have triggered the offline compute. + >::put( + ElectionStatus::::Open(now) + ); + debug::native::info!( + target: "staking", + "Election window is Open({:?}). Snapshot created", + now, + ); + } else { + debug::native::warn!( + target: "staking", + "Failed to create snapshot at {:?}. Election window will remain closed.", + now, + ); + } + + } + } + } else { + debug::native::warn!( + target: "staking", + "estimate_next_new_session() failed to execute. Election status cannot be changed.", + ); + } + } + + // weight + 50_000 + } + + /// Check if the current block number is the one at which the election window has been set + /// to open. If so, it runs the offchain worker code. + fn offchain_worker(now: T::BlockNumber) { + use offchain_election::{set_check_offchain_execution_status, compute_offchain_election}; + + if Self::era_election_status().is_open_at(now) { + let offchain_status = set_check_offchain_execution_status::(now); + if let Err(why) = offchain_status { + debug::native::warn!( + target: "staking", + "skipping offchain worker in open election window due to [{}]", + why, + ); + } else { + if let Err(e) = compute_offchain_election::() { + debug::native::warn!( + target: "staking", + "Error in phragmen offchain worker: {:?}", + e, + ); + } else { + debug::native::debug!( + target: "staking", + "Executed offchain worker thread without errors. Transaction submitted to the pool.", + ); + } + } + } + } + fn on_finalize() { // Set the start of the first era. if let Some(mut active_era) = Self::active_era() { @@ -989,14 +1240,14 @@ decl_module! { /// - O(1). /// - Three extra DB entries. /// - /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned unless - /// the `origin` falls below _existential deposit_ and gets removed as dust. + /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned + /// unless the `origin` falls below _existential deposit_ and gets removed as dust. /// # #[weight = SimpleDispatchInfo::FixedNormal(500_000)] fn bond(origin, controller: ::Source, #[compact] value: BalanceOf, - payee: RewardDestination + payee: RewardDestination, ) { let stash = ensure_signed(origin)?; @@ -1199,7 +1450,7 @@ decl_module! { /// /// # /// - The transaction's complexity is proportional to the size of `targets`, - /// which is capped at `MAX_NOMINATIONS`. + /// which is capped at CompactAssignments::LIMIT. /// - Both the reads and writes follow a similar pattern. /// # #[weight = SimpleDispatchInfo::FixedNormal(750_000)] @@ -1209,7 +1460,7 @@ decl_module! { let stash = &ledger.stash; ensure!(!targets.is_empty(), Error::::EmptyTargets); let targets = targets.into_iter() - .take(MAX_NOMINATIONS) + .take(::LIMIT) .map(|t| T::Lookup::lookup(t)) .collect::, _>>()?; @@ -1483,18 +1734,179 @@ decl_module! { Self::kill_stash(&stash)?; T::Currency::remove_lock(STAKING_ID, &stash); } + + /// Submit a phragmen result to the chain. If the solution: + /// + /// 1. is valid. + /// 2. has a better score than a potentially existing solution on chain. + /// + /// then, it will be _put_ on chain. + /// + /// A solution consists of two pieces of data: + /// + /// 1. `winners`: a flat vector of all the winners of the round. + /// 2. `assignments`: the compact version of an assignment vector that encodes the edge + /// weights. + /// + /// Both of which may be computed using [`phragmen`], or any other algorithm. + /// + /// Additionally, the submitter must provide: + /// + /// - The `score` that they claim their solution has. + /// + /// Both validators and nominators will be represented by indices in the solution. The + /// indices should respect the corresponding types ([`ValidatorIndex`] and + /// [`NominatorIndex`]). Moreover, they should be valid when used to index into + /// [`SnapshotValidators`] and [`SnapshotNominators`]. Any invalid index will cause the + /// solution to be rejected. These two storage items are set during the election window and + /// may be used to determine the indices. + /// + /// A solution is valid if: + /// + /// 0. It is submitted when [`EraElectionStatus`] is `Open`. + /// 1. Its claimed score is equal to the score computed on-chain. + /// 2. Presents the correct number of winners. + /// 3. All indexes must be value according to the snapshot vectors. All edge values must + /// also be correct and should not overflow the granularity of the ratio type (i.e. 256 + /// or billion). + /// 4. For each edge, all targets are actually nominated by the voter. + /// 5. Has correct self-votes. + /// + /// A solutions score is consisted of 3 parameters: + /// + /// 1. `min { support.total }` for each support of a winner. This value should be maximized. + /// 2. `sum { support.total }` for each support of a winner. This value should be minimized. + /// 3. `sum { support.total^2 }` for each support of a winner. This value should be + /// minimized (to ensure less variance) + /// + /// # + /// E: number of edges. m: size of winner committee. n: number of nominators. d: edge degree + /// (16 for now) v: number of on-chain validator candidates. + /// + /// NOTE: given a solution which is reduced, we can enable a new check the ensure `|E| < n + + /// m`. We don't do this _yet_, but our offchain worker code executes it nonetheless. + /// + /// major steps (all done in `check_and_replace_solution`): + /// + /// - Storage: O(1) read `ElectionStatus`. + /// - Storage: O(1) read `PhragmenScore`. + /// - Storage: O(1) read `ValidatorCount`. + /// - Storage: O(1) length read from `SnapshotValidators`. + /// + /// - Storage: O(v) reads of `AccountId` to fetch `snapshot_validators`. + /// - Memory: O(m) iterations to map winner index to validator id. + /// - Storage: O(n) reads `AccountId` to fetch `snapshot_nominators`. + /// - Memory: O(n + m) reads to map index to `AccountId` for un-compact. + /// + /// - Storage: O(e) accountid reads from `Nomination` to read correct nominations. + /// - Storage: O(e) calls into `slashable_balance_of_extended` to convert ratio to staked. + /// + /// - Memory: build_support_map. O(e). + /// - Memory: evaluate_support: O(E). + /// + /// - Storage: O(e) writes to `QueuedElected`. + /// - Storage: O(1) write to `QueuedScore` + /// + /// The weight of this call is 1/10th of the blocks total weight. + /// # + #[weight = SimpleDispatchInfo::FixedNormal(100_000_000)] + pub fn submit_election_solution( + origin, + winners: Vec, + compact_assignments: CompactAssignments, + score: PhragmenScore, + era: EraIndex, + ) { + let _who = ensure_signed(origin)?; + Self::check_and_replace_solution( + winners, + compact_assignments, + ElectionCompute::Signed, + score, + era, + )? + } + + /// Unsigned version of `submit_election_solution`. + /// + /// Note that this must pass the [`ValidateUnsigned`] check which only allows transactions + /// from the local node to be included. In other words, only the block author can include a + /// transaction in the block. + #[weight = SimpleDispatchInfo::FixedNormal(100_000_000)] + pub fn submit_election_solution_unsigned( + origin, + winners: Vec, + compact_assignments: CompactAssignments, + score: PhragmenScore, + era: EraIndex, + ) { + ensure_none(origin)?; + Self::check_and_replace_solution( + winners, + compact_assignments, + ElectionCompute::Unsigned, + score, + era, + )? + // TODO: instead of returning an error, panic. This makes the entire produced block + // invalid. + // This ensures that block authors will not ever try and submit a solution which is not + // an improvement, since they will lose their authoring points/rewards. + } } } impl Module { - // PUBLIC IMMUTABLES - /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default() } - // MUTABLES (DANGEROUS) + /// internal impl of [`slashable_balance_of`] that returns [`ExtendedBalance`]. + fn slashable_balance_of_extended(stash: &T::AccountId) -> ExtendedBalance { + , u64>>::convert( + Self::slashable_balance_of(stash) + ) as ExtendedBalance + } + + /// Dump the list of validators and nominators into vectors and keep them on-chain. + /// + /// This data is used to efficiently evaluate election results. returns `true` if the operation + /// is successful. + fn create_stakers_snapshot() -> bool { + let validators = >::iter().map(|(v, _)| v).collect::>(); + let mut nominators = >::iter().map(|(n, _)| n).collect::>(); + + let num_validators = validators.len(); + let num_nominators = nominators.len(); + if + num_validators > MAX_VALIDATORS || + num_nominators.saturating_add(num_validators) > MAX_NOMINATORS + { + debug::native::warn!( + target: "staking", + "Snapshot size too big [{} <> {}][{} <> {}].", + num_validators, + MAX_VALIDATORS, + num_nominators, + MAX_NOMINATORS, + ); + false + } else { + // all validators nominate themselves; + nominators.extend(validators.clone()); + + >::put(validators); + >::put(nominators); + true + } + } + + /// Clears both snapshots of stakers. + fn kill_stakers_snapshot() { + >::kill(); + >::kill(); + } fn do_payout_nominator(who: T::AccountId, era: EraIndex, validators: Vec<(T::AccountId, u32)>) -> DispatchResult @@ -1663,9 +2075,17 @@ impl Module { Forcing::ForceNew => ForceEra::kill(), Forcing::ForceAlways => (), Forcing::NotForcing if era_length >= T::SessionsPerEra::get() => (), - _ => return None, + _ => { + // not forcing, not a new era either. If final, set the flag. + if era_length + 1 >= T::SessionsPerEra::get() { + IsCurrentSessionFinal::put(true); + } + return None + }, } + // new era. + IsCurrentSessionFinal::put(false); Self::new_era(session_index) } else { // Set initial era @@ -1673,6 +2093,188 @@ impl Module { } } + /// Basic and cheap checks that we perform in validate unsigned, and in the execution. + pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> Result<(), Error> { + // discard solutions that are not in-time + // check window open + ensure!( + Self::era_election_status().is_open(), + Error::::PhragmenEarlySubmission, + ); + + // check current era. + if let Some(current_era) = Self::active_era().map(|e| e.index) { + ensure!( + current_era == era, + Error::::PhragmenEarlySubmission, + ) + } + + // assume the given score is valid. Is it better than what we have on-chain, if we have any? + if let Some(queued_score) = Self::queued_score() { + ensure!( + is_score_better(queued_score, score), + Error::::PhragmenWeakSubmission, + ) + } + + Ok(()) + } + + /// Checks a given solution and if correct and improved, writes it on chain as the queued result + /// of the next round. This may be called by both a signed and an unsigned transaction. + pub fn check_and_replace_solution( + winners: Vec, + compact_assignments: CompactAssignments, + compute: ElectionCompute, + claimed_score: PhragmenScore, + era: EraIndex, + ) -> Result<(), Error> { + // Do the basic checks. era, claimed score and window open. + Self::pre_dispatch_checks(claimed_score, era)?; + + // Check that the number of presented winners is sane. Most often we have more candidates + // that we need. Then it should be Self::validator_count(). Else it should be all the + // candidates. + let snapshot_length = >::decode_len() + .map_err(|_| Error::::SnapshotUnavailable)?; + let desired_winners = Self::validator_count().min(snapshot_length as u32); + ensure!(winners.len() as u32 == desired_winners, Error::::PhragmenBogusWinnerCount); + + // decode snapshot validators. + let snapshot_validators = Self::snapshot_validators() + .ok_or(Error::::SnapshotUnavailable)?; + + // check if all winners were legit; this is rather cheap. Replace with accountId. + let winners = winners.into_iter().map(|widx| { + // NOTE: at the moment, since staking is explicitly blocking any offence until election + // is closed, we don't check here if the account id at `snapshot_validators[widx]` is + // actually a validator. If this ever changes, this loop needs to also check this. + snapshot_validators.get(widx as usize).cloned().ok_or(Error::::PhragmenBogusWinner) + }).collect::, Error>>()?; + + // decode the rest of the snapshot. + let snapshot_nominators = >::snapshot_nominators() + .ok_or(Error::::SnapshotUnavailable)?; + + // helpers + let nominator_at = |i: NominatorIndex| -> Option { + snapshot_nominators.get(i as usize).cloned() + }; + let validator_at = |i: ValidatorIndex| -> Option { + snapshot_validators.get(i as usize).cloned() + }; + + // un-compact. + let assignments = compact_assignments.into_assignment( + nominator_at, + validator_at, + ).map_err(|e| { + // log the error since it is not propagated into the runtime error. + debug::native::warn!( + target: "staking", + "un-compacting solution failed due to {:?}", + e, + ); + Error::::PhragmenBogusCompact + })?; + + // check all nominators actually including the claimed vote. Also check correct self votes. + // Note that we assume all validators and nominators in `assignments` are properly bonded, + // because they are coming from the snapshot via a given index. + for Assignment { who, distribution } in assignments.iter() { + let is_validator = >::contains_key(&who); + let maybe_nomination = Self::nominators(&who); + + if !(maybe_nomination.is_some() ^ is_validator) { + // all of the indices must map to either a validator or a nominator. If this is ever + // not the case, then the locking system of staking is most likely faulty, or we + // have bigger problems. + debug::native::error!( + target: "staking", + "detected an error in the staking locking and snapshot." + ); + // abort. + return Err(Error::::PhragmenBogusNominator); + } + + if !is_validator { + // a normal vote + let nomination = maybe_nomination.expect( + "exactly one of `maybe_validator` and `maybe_nomination.is_some` is true. \ + is_validator is false; maybe_nomination is some; qed" + ); + + // NOTE: we don't really have to check here if the sum of all edges are the + // nominator correct. Un-compacting assures this by definition. + + for (t, _) in distribution { + // each target in the provided distribution must be actually nominated by the + // nominator after the last non-zero slash. + if nomination.targets.iter().find(|&tt| tt == t).is_none() { + return Err(Error::::PhragmenBogusNomination); + } + + if ::SlashingSpans::get(&t).map_or( + false, + |spans| nomination.submitted_in < spans.last_nonzero_slash(), + ) { + return Err(Error::::PhragmenSlashedNomination); + } + } + } else { + // a self vote + ensure!(distribution.len() == 1, Error::::PhragmenBogusSelfVote); + ensure!(distribution[0].0 == *who, Error::::PhragmenBogusSelfVote); + // defensive only. A compact assignment of length one does NOT encode the weight and + // it is always created to be 100%. + ensure!( + distribution[0].1 == OffchainAccuracy::one(), + Error::::PhragmenBogusSelfVote, + ); + } + } + + // convert into staked assignments. + let staked_assignments = sp_phragmen::assignment_ratio_to_staked( + assignments, + Self::slashable_balance_of_extended, + ); + + // build the support map thereof in order to evaluate. + // OPTIMIZATION: loop to create the staked assignments but it would bloat the code. Okay for + // now as it does not add to the complexity order. + let (supports, num_error) = build_support_map::( + &winners, + &staked_assignments, + ); + // This technically checks that all targets in all nominators were among the winners. + ensure!(num_error == 0, Error::::PhragmenBogusEdge); + + // Check if the score is the same as the claimed one. + let submitted_score = evaluate_support(&supports); + ensure!(submitted_score == claimed_score, Error::::PhragmenBogusScore); + + // At last, alles Ok. Exposures and store the result. + let exposures = Self::collect_exposure(supports); + debug::native::info!( + target: "staking", + "A better solution (with compute {:?}) has been validated and stored on chain.", + compute, + ); + + // write new results. + >::put(ElectionResult { + elected_stashes: winners, + compute, + exposures, + }); + QueuedScore::put(submitted_score); + + Ok(()) + + } + /// Start a session potentially starting an era. fn start_session(start_session: SessionIndex) { let next_active_era = Self::active_era().map(|e| e.index + 1).unwrap_or(0); @@ -1779,55 +2381,160 @@ impl Module { } // Set staking information for new era. - let maybe_new_validators = Self::select_validators(current_era); + let maybe_new_validators = Self::select_and_update_validators(current_era); maybe_new_validators } - /// Clear all era information for given era. - fn clear_era_information(era_index: EraIndex) { - >::remove_prefix(era_index); - >::remove_prefix(era_index); - >::remove_prefix(era_index); - >::remove(era_index); - >::remove(era_index); - >::remove(era_index); - ErasStartSessionIndex::remove(era_index); - } + /// Select the new validator set at the end of the era. + /// + /// Runs [`try_do_phragmen`] and updates the following storage items: + /// - [`EraElectionStatus`]: with `None`. + /// - [`ErasStakers`]: with the new staker set. + /// - [`ErasStakersClipped`]. + /// - [`ErasValidatorPrefs`]. + /// - [`ErasTotalStake`]: with the new total stake. + /// - [`SnapshotValidators`] and [`SnapshotNominators`] are both removed. + /// + /// Internally, [`QueuedElected`], snapshots and [`QueuedScore`] are also consumed. + /// + /// If the election has been successful, It passes the new set upwards. + /// + /// This should only be called at the end of an era. + fn select_and_update_validators(current_era: EraIndex) -> Option> { + if let Some(ElectionResult::> { + elected_stashes, + exposures, + compute, + }) = Self::try_do_phragmen() { + // We have chosen the new validator set. Submission is no longer allowed. + >::put(ElectionStatus::Closed); + + // kill the snapshots. + Self::kill_stakers_snapshot(); + + // Populate Stakers and write slot stake. + let mut total_stake: BalanceOf = Zero::zero(); + exposures.into_iter().for_each(|(stash, exposure)| { + total_stake = total_stake.saturating_add(exposure.total); + >::insert(current_era, &stash, &exposure); - /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. - fn apply_unapplied_slashes(active_era: EraIndex) { - let slash_defer_duration = T::SlashDeferDuration::get(); - ::EarliestUnappliedSlash::mutate(|earliest| if let Some(ref mut earliest) = earliest { - let keep_from = active_era.saturating_sub(slash_defer_duration); - for era in (*earliest)..keep_from { - let era_slashes = ::UnappliedSlashes::take(&era); - for slash in era_slashes { - slashing::apply_slash::(slash); + let mut exposure_clipped = exposure; + let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; + if exposure_clipped.others.len() > clipped_max_len { + exposure_clipped.others.sort_unstable_by(|a, b| a.value.cmp(&b.value).reverse()); + exposure_clipped.others.truncate(clipped_max_len); } + >::insert(¤t_era, &stash, exposure_clipped); + }); + + // Insert current era staking information + >::insert(¤t_era, total_stake); + + // collect the pref of all winners + for stash in &elected_stashes { + let pref = Self::validators(stash); + >::insert(¤t_era, stash, pref); } - *earliest = (*earliest).max(keep_from) - }) + // emit event + Self::deposit_event(RawEvent::StakingElection(compute)); + + debug::native::info!( + target: "staking", + "new validator set of size {:?} has been elected via {:?} for era {:?}", + elected_stashes.len(), + compute, + current_era, + ); + + Some(elected_stashes) + } else { + None + } + } + + /// Select a new validator set from the assembled stakers and their role preferences. It tries + /// first to peek into [`QueuedElected`]. Otherwise, it runs a new phragmen. + /// + /// If [`QueuedElected`] and [`QueuedScore`] exists, they are both removed. No further storage + /// is updated. + fn try_do_phragmen() -> Option>> { + // a phragmen result from either a stored submission or locally executed one. + let next_result = >::take().or_else(|| + Self::do_phragmen_with_post_processing::(ElectionCompute::OnChain) + ); + + // either way, kill this. We remove it here to make sure it always has the exact same + // lifetime as `QueuedElected`. + QueuedScore::kill(); + + next_result } - /// Select a new validator set from the assembled stakers and their role preferences, and store - /// staking information for the new current era. + /// Execute phragmen and return the new results. The edge weights are processed into support + /// values. + /// + /// This is basically a wrapper around [`do_phragmen`] which translates `PhragmenResult` into + /// `ElectionResult`. /// - /// Fill the storages `ErasStakers`, `ErasStakersClipped`, `ErasValidatorPrefs` and - /// `ErasTotalStake` for current era. + /// No storage item is updated. + fn do_phragmen_with_post_processing(compute: ElectionCompute) + -> Option>> + where + Accuracy: sp_std::ops::Mul, + ExtendedBalance: From<::Inner>, + { + if let Some(phragmen_result) = Self::do_phragmen::() { + let elected_stashes = phragmen_result.winners.iter() + .map(|(s, _)| s.clone()) + .collect::>(); + let assignments = phragmen_result.assignments; + + let staked_assignments = sp_phragmen::assignment_ratio_to_staked( + assignments, + Self::slashable_balance_of_extended, + ); + + let (supports, _) = build_support_map::( + &elected_stashes, + &staked_assignments, + ); + + // collect exposures + let exposures = Self::collect_exposure(supports); + + // In order to keep the property required by `on_session_ending` that we must return the + // new validator set even if it's the same as the old, as long as any underlying + // economic conditions have changed, we don't attempt to do any optimization where we + // compare against the prior set. + Some(ElectionResult::> { + elected_stashes, + exposures, + compute, + }) + } else { + // There were not enough candidates for even our minimal level of functionality. This is + // bad. We should probably disable all functionality except for block production and let + // the chain keep producing blocks until we can decide on a sufficiently substantial + // set. TODO: #2494 + None + } + } + + /// Execute phragmen and return the new results. No post-processing is applied and the raw edge + /// weights are returned. /// - /// Returns a set of newly selected _stash_ IDs. + /// Self votes are added and nominations before the most recent slashing span are reaped. /// - /// Assumes storage is coherent with the declaration. - fn select_validators(current_era: EraIndex) -> Option> { + /// No storage item is updated. + fn do_phragmen() -> Option> { let mut all_nominators: Vec<(T::AccountId, BalanceOf, Vec)> = Vec::new(); - let mut all_validators_and_prefs = BTreeMap::new(); let mut all_validators = Vec::new(); - for (validator, preference) in >::iter() { + for (validator, _) in >::iter() { + // append self vote let self_vote = (validator.clone(), Self::slashable_balance_of(&validator), vec![validator.clone()]); all_nominators.push(self_vote); - all_validators_and_prefs.insert(validator.clone(), preference); all_validators.push(validator); } @@ -1835,7 +2542,7 @@ impl Module { let Nominations { submitted_in, mut targets, suppressed: _ } = nominations; // Filter out nomination targets which were nominated before the most recent - // non-zero slash. + // slashing span. targets.retain(|stash| { ::SlashingSpans::get(&stash).map_or( true, @@ -1850,92 +2557,44 @@ impl Module { (n, s, ns) })); - let maybe_phragmen_result = sp_phragmen::elect::<_, _, T::CurrencyToVote, Perbill>( + elect::<_, _, T::CurrencyToVote, Accuracy>( Self::validator_count() as usize, Self::minimum_validator_count().max(1) as usize, all_validators, all_nominators, - ); - - if let Some(phragmen_result) = maybe_phragmen_result { - let elected_stashes = phragmen_result.winners.into_iter() - .map(|(s, _)| s) - .collect::>(); - let assignments = phragmen_result.assignments; - - let to_balance = |e: ExtendedBalance| - >>::convert(e); - - let supports = sp_phragmen::build_support_map::<_, _, _, T::CurrencyToVote, Perbill>( - &elected_stashes, - &assignments, - Self::slashable_balance_of, - ); - - // Populate stakers information and figure out the total stake. - let mut total_staked = BalanceOf::::zero(); - for (c, s) in supports.into_iter() { - // build `struct exposure` from `support` - let mut others = Vec::new(); - let mut own: BalanceOf = Zero::zero(); - let mut total: BalanceOf = Zero::zero(); - s.voters - .into_iter() - .map(|(who, value)| (who, to_balance(value))) - .for_each(|(who, value)| { - if who == c { - own = own.saturating_add(value); - } else { - others.push(IndividualExposure { who, value }); - } - total = total.saturating_add(value); - }); - - total_staked = total_staked.saturating_add(total); - - let exposure = Exposure { - own, - others, - // This might reasonably saturate and we cannot do much about it. The sum of - // someone's stake might exceed the balance type if they have the maximum amount - // of balance and receive some support. This is super unlikely to happen, yet - // we simulate it in some tests. - total, - }; - >::insert(¤t_era, &c, &exposure); + ) + } - let mut exposure_clipped = exposure; - let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; - if exposure_clipped.others.len() > clipped_max_len { - exposure_clipped.others.sort_unstable_by(|a, b| a.value.cmp(&b.value).reverse()); - exposure_clipped.others.truncate(clipped_max_len); - } - >::insert(¤t_era, &c, exposure_clipped); - } + /// Consume a set of [`Supports`] from [`sp_phragmen`] and collect them into a [`Exposure`] + fn collect_exposure(supports: SupportMap) -> Vec<(T::AccountId, Exposure>)> { + let to_balance = |e: ExtendedBalance| + >>::convert(e); + + supports.into_iter().map(|(validator, support)| { + // build `struct exposure` from `support` + let mut others = Vec::new(); + let mut own: BalanceOf = Zero::zero(); + let mut total: BalanceOf = Zero::zero(); + support.voters + .into_iter() + .map(|(nominator, weight)| (nominator, to_balance(weight))) + .for_each(|(nominator, stake)| { + if nominator == validator { + own = own.saturating_add(stake); + } else { + others.push(IndividualExposure { who: nominator, value: stake }); + } + total = total.saturating_add(stake); + }); - // Insert current era staking informations - >::insert(¤t_era, total_staked); - let default_pref = ValidatorPrefs::default(); - for stash in &elected_stashes { - let pref = all_validators_and_prefs.get(stash) - .unwrap_or(&default_pref); // Must never happen, but better to be safe. - >::insert(¤t_era, stash, pref); - } + let exposure = Exposure { + own, + others, + total, + }; - // In order to keep the property required by `n_session_ending` - // that we must return the new validator set even if it's the same as the old, - // as long as any underlying economic conditions have changed, we don't attempt - // to do any optimization where we compare against the prior set. - Some(elected_stashes) - } else { - // There were not enough candidates for even our minimal level of functionality. - // This is bad. - // We should probably disable all functionality except for block production - // and let the chain keep producing blocks until we can decide on a sufficiently - // substantial set. - // TODO: #2494 - None - } + (validator, exposure) + }).collect::)>>() } /// Remove all associated data of a stash account from the staking system. @@ -1960,6 +2619,33 @@ impl Module { Ok(()) } + /// Clear all era information for given era. + fn clear_era_information(era_index: EraIndex) { + >::remove_prefix(era_index); + >::remove_prefix(era_index); + >::remove_prefix(era_index); + >::remove(era_index); + >::remove(era_index); + >::remove(era_index); + ErasStartSessionIndex::remove(era_index); + } + + /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. + fn apply_unapplied_slashes(active_era: EraIndex) { + let slash_defer_duration = T::SlashDeferDuration::get(); + ::EarliestUnappliedSlash::mutate(|earliest| if let Some(ref mut earliest) = earliest { + let keep_from = active_era.saturating_sub(slash_defer_duration); + for era in (*earliest)..keep_from { + let era_slashes = ::UnappliedSlashes::take(&era); + for slash in era_slashes { + slashing::apply_slash::(slash); + } + } + + *earliest = (*earliest).max(keep_from) + }) + } + /// Add reward points to validators using their stash account ID. /// /// Validators are keyed by stash account ID and must be in the current elected set. @@ -2011,9 +2697,7 @@ impl pallet_session::SessionManager for Module { } } -/// This implementation has the same constrains as the implementation of -/// `pallet_session::SessionManager`. -impl SessionManager>> for Module { +impl historical::SessionManager>> for Module { fn new_session(new_index: SessionIndex) -> Option>)>> { @@ -2099,15 +2783,20 @@ impl OnOffenceHandler>], slash_fraction: &[Perbill], slash_session: SessionIndex, - ) { + ) -> Result<(), ()> { + if !Self::can_report() { + return Err(()) + } + let reward_proportion = SlashRewardFraction::get(); let active_era = { let active_era = Self::active_era(); if active_era.is_none() { - return + // this offence need not be re-submitted. + return Ok(()) } - active_era.unwrap().index + active_era.expect("value checked not to be `None`; qed").index }; let active_era_start_session_index = Self::eras_start_session_index(active_era) .unwrap_or_else(|| { @@ -2126,7 +2815,7 @@ impl OnOffenceHandler return, // before bonding period. defensive - should be filtered out. + None => return Ok(()), // before bonding period. defensive - should be filtered out. Some(&(ref slash_era, _)) => *slash_era, } }; @@ -2140,8 +2829,7 @@ impl OnOffenceHandler OnOffenceHandler bool { + Self::era_election_status().is_closed() } } @@ -2202,6 +2896,158 @@ impl ReportOffence } } +/// Disallows any transactions that change the election result to be submitted after the election +/// window is open. +#[derive(Encode, Decode, Clone, Eq, PartialEq)] +pub struct LockStakingStatus(sp_std::marker::PhantomData); + +impl sp_std::fmt::Debug for LockStakingStatus { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "LockStakingStatus") + } +} + +impl LockStakingStatus { + /// Create new `LockStakingStatus`. + pub fn new() -> Self { + Self(sp_std::marker::PhantomData) + } +} + +impl Default for LockStakingStatus { + fn default() -> Self { + Self::new() + } +} + +impl SignedExtension for LockStakingStatus { + const IDENTIFIER: &'static str = "LockStakingStatus"; + type AccountId = T::AccountId; + type Call = ::Call; + type AdditionalSigned = (); + type DispatchInfo = frame_support::weights::DispatchInfo; + type Pre = (); + + fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: Self::DispatchInfo, + _len: usize, + ) -> TransactionValidity { + if let Some(inner_call) = call.is_sub_type() { + if let ElectionStatus::Open(_) = >::era_election_status() { + match inner_call { + Call::::set_payee(..) | + Call::::set_controller(..) | + Call::::set_validator_count(..) | + Call::::force_no_eras(..) | + Call::::force_new_era(..) | + Call::::set_invulnerables(..) | + Call::::force_unstake(..) | + Call::::force_new_era_always(..) | + Call::::cancel_deferred_slash(..) | + Call::::set_history_depth(..) | + Call::::reap_stash(..) | + Call::::submit_election_solution(..) | + Call::::submit_election_solution_unsigned(..) => { + // These calls are allowed. Nothing. + } + _ => { + return Err(InvalidTransaction::Stale.into()); + } + } + } + } + + Ok(Default::default()) + } +} + +impl From> for InvalidTransaction { + fn from(e: Error) -> Self { + match e { + >::PhragmenEarlySubmission => InvalidTransaction::Future, + _ => InvalidTransaction::Custom(e.as_u8()), + } + } +} + +#[allow(deprecated)] +impl frame_support::unsigned::ValidateUnsigned for Module { + type Call = Call; + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::submit_election_solution_unsigned( + _, + _, + score, + era, + ) = call { + use offchain_election::DEFAULT_LONGEVITY; + + // discard solution not coming from the local OCW. + match source { + TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ } + _ => { + debug::native::debug!( + target: "staking", + "rejecting unsigned transaction because it is not local/in-block." + ); + return InvalidTransaction::Call.into(); + } + } + + if let Err(e) = Self::pre_dispatch_checks(*score, *era) { + debug::native::debug!( + target: "staking", + "validate unsigned failed due to {:?}.", + e, + ); + let invalid: InvalidTransaction = e.into(); + return invalid.into(); + } + + debug::native::debug!( + target: "staking", + "Validated an unsigned transaction from the local node for era {}.", + era, + ); + + Ok(ValidTransaction { + // The higher the score[0], the better a solution is. + priority: score[0].saturated_into(), + // no requires. + requires: vec![], + // Defensive only. A single solution can exist in the pool per era. Each validator + // will run OCW at most once per era, hence there should never exist more than one + // transaction anyhow. + provides: vec![("StakingOffchain", era).encode()], + // Note: this can be more accurate in the future. We do something like + // `era_end_block - current_block` but that is not needed now as we eagerly run + // offchain workers now and the above should be same as `T::ElectionLookahead` + // without the need to query more storage in the validation phase. If we randomize + // offchain worker, then we might re-consider this. + longevity: TryInto::::try_into(T::ElectionLookahead::get()).unwrap_or(DEFAULT_LONGEVITY), + // We don't propagate this. This can never the validated at a remote node. + propagate: false, + }) + } else { + InvalidTransaction::Call.into() + } + } + + fn pre_dispatch(_: &Self::Call) -> Result<(), TransactionValidityError> { + // IMPORTANT NOTE: By default, a sane `pre-dispatch` should always do the same checks as + // `validate_unsigned` and overriding this should be done with care. this module has only + // one unsigned entry point, in which we call into `>::pre_dispatch_checks()` + // which is all the important checks that we do in `validate_unsigned`. Hence, we can safely + // override this to save some time. + Ok(()) + } +} + /// Check that list is sorted and has no duplicates. fn is_sorted_and_unique(list: &[u32]) -> bool { list.windows(2).all(|w| w[0] < w[1]) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 3e2b72dedf9d5..0045e96b3e3a0 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -17,57 +17,74 @@ //! Test utilities use std::{collections::{HashSet, HashMap}, cell::RefCell}; -use sp_runtime::{Perbill, KeyTypeId}; +use sp_runtime::Perbill; use sp_runtime::curve::PiecewiseLinear; -use sp_runtime::traits::{IdentityLookup, Convert, OpaqueKeys, SaturatedConversion}; -use sp_runtime::testing::{Header, UintAuthorityId}; +use sp_runtime::traits::{IdentityLookup, Convert, SaturatedConversion, Zero}; +use sp_runtime::testing::{Header, UintAuthorityId, TestXt}; use sp_staking::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}}; -use sp_core::{H256, crypto::key_types}; -use sp_io; +use sp_core::H256; use frame_support::{ - assert_ok, impl_outer_origin, parameter_types, StorageValue, StorageMap, - StorageDoubleMap, IterableStorageMap, - traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize}, weights::Weight, + assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch, impl_outer_event, + StorageValue, StorageMap, StorageDoubleMap, IterableStorageMap, + traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize}, + weights::Weight, +}; +use frame_system::offchain::TransactionSubmitter; +use sp_io; +use sp_phragmen::{ + build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore, }; use crate::{ EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination, - Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints + Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints, + CompactAssignments, ValidatorIndex, NominatorIndex, Validators, OffchainAccuracy, }; /// The AccountId alias in this test module. -pub type AccountId = u64; -pub type BlockNumber = u64; -pub type Balance = u64; +pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u64; +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u64; /// Simple structure that exposes how u64 currency can be represented as... u64. pub struct CurrencyToVoteHandler; impl Convert for CurrencyToVoteHandler { - fn convert(x: u64) -> u64 { x } + fn convert(x: u64) -> u64 { + x + } } impl Convert for CurrencyToVoteHandler { - fn convert(x: u128) -> u64 { x.saturated_into() } + fn convert(x: u128) -> u64 { + x.saturated_into() + } } thread_local! { static SESSION: RefCell<(Vec, HashSet)> = RefCell::new(Default::default()); + static SESSION_PER_ERA: RefCell = RefCell::new(3); static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); static SLASH_DEFER_DURATION: RefCell = RefCell::new(0); + static ELECTION_LOOKAHEAD: RefCell = RefCell::new(0); + static PERIOD: RefCell = RefCell::new(1); } -pub struct TestSessionHandler; -impl pallet_session::SessionHandler for TestSessionHandler { - const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY]; +/// Another session handler struct to test on_disabled. +pub struct OtherSessionHandler; +impl pallet_session::OneSessionHandler for OtherSessionHandler { + type Key = UintAuthorityId; - fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} + fn on_genesis_session<'a, I: 'a>(_: I) + where I: Iterator, AccountId: 'a {} - fn on_new_session( - _changed: bool, - validators: &[(AccountId, Ks)], - _queued_validators: &[(AccountId, Ks)], - ) { - SESSION.with(|x| - *x.borrow_mut() = (validators.iter().map(|x| x.0.clone()).collect(), HashSet::new()) - ); + fn on_new_session<'a, I: 'a>(_: bool, validators: I, _: I,) + where I: Iterator, AccountId: 'a + { + SESSION.with(|x| { + *x.borrow_mut() = ( + validators.map(|x| x.0.clone()).collect(), + HashSet::new(), + ) + }); } fn on_disabled(validator_index: usize) { @@ -79,6 +96,10 @@ impl pallet_session::SessionHandler for TestSessionHandler { } } +impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { + type Public = UintAuthorityId; +} + pub fn is_disabled(controller: AccountId) -> bool { let stash = Staking::ledger(&controller).unwrap().stash; SESSION.with(|d| d.borrow().1.contains(&stash)) @@ -91,6 +112,32 @@ impl Get for ExistentialDeposit { } } +pub struct SessionsPerEra; +impl Get for SessionsPerEra { + fn get() -> SessionIndex { + SESSION_PER_ERA.with(|v| *v.borrow()) + } +} +impl Get for SessionsPerEra { + fn get() -> BlockNumber { + SESSION_PER_ERA.with(|v| *v.borrow() as BlockNumber) + } +} + +pub struct ElectionLookahead; +impl Get for ElectionLookahead { + fn get() -> BlockNumber { + ELECTION_LOOKAHEAD.with(|v| *v.borrow()) + } +} + +pub struct Period; +impl Get for Period { + fn get() -> BlockNumber { + PERIOD.with(|v| *v.borrow()) + } +} + pub struct SlashDeferDuration; impl Get for SlashDeferDuration { fn get() -> EraIndex { @@ -98,23 +145,47 @@ impl Get for SlashDeferDuration { } } -impl_outer_origin!{ +impl_outer_origin! { pub enum Origin for Test where system = frame_system {} } +impl_outer_dispatch! { + pub enum Call for Test where origin: Origin { + staking::Staking, + } +} + +mod staking { + // Re-export needed for `impl_outer_event!`. + pub use super::super::*; +} +use frame_system as system; +use pallet_balances as balances; +use pallet_session as session; + +impl_outer_event! { + pub enum MetaEvent for Test { + system, + balances, + session, + staking, + } +} + /// Author of block is always 11 pub struct Author11; impl FindAuthor for Author11 { fn find_author<'a, I>(_digests: I) -> Option - where I: 'a + IntoIterator + where I: 'a + IntoIterator, { Some(11) } } // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug)] pub struct Test; + parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; @@ -123,15 +194,15 @@ parameter_types! { } impl frame_system::Trait for Test { type Origin = Origin; - type Index = u64; + type Index = AccountIndex; type BlockNumber = BlockNumber; - type Call = (); + type Call = Call; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; - type Event = (); + type Event = MetaEvent; type BlockHashCount = BlockHashCount; type MaximumBlockWeight = MaximumBlockWeight; type AvailableBlockRatio = AvailableBlockRatio; @@ -144,26 +215,31 @@ impl frame_system::Trait for Test { } impl pallet_balances::Trait for Test { type Balance = Balance; + type Event = MetaEvent; type DustRemoval = (); - type Event = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; } parameter_types! { - pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; pub const UncleGenerations: u64 = 0; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25); } +sp_runtime::impl_opaque_keys! { + pub struct SessionKeys { + pub other: OtherSessionHandler, + } +} impl pallet_session::Trait for Test { - type Event = (); + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type Keys = SessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type SessionHandler = (OtherSessionHandler,); + type Event = MetaEvent; type ValidatorId = AccountId; type ValidatorIdOf = crate::StashOf; - type ShouldEndSession = pallet_session::PeriodicSessions; - type SessionManager = pallet_session::historical::NoteHistoricalRoot; - type SessionHandler = TestSessionHandler; - type Keys = UintAuthorityId; type DisabledValidatorsThreshold = DisabledValidatorsThreshold; + type NextSessionRotation = pallet_session::PeriodicSessions; } impl pallet_session::historical::Trait for Test { @@ -195,17 +271,17 @@ pallet_staking_reward_curve::build! { ); } parameter_types! { - pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; pub const MaxNominatorRewardedPerValidator: u32 = 64; } + impl Trait for Test { type Currency = Balances; type UnixTime = Timestamp; type CurrencyToVote = CurrencyToVoteHandler; type RewardRemainder = (); - type Event = (); + type Event = MetaEvent; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; @@ -214,11 +290,21 @@ impl Trait for Test { type BondingDuration = BondingDuration; type SessionInterface = Self; type RewardCurve = RewardCurve; + type NextNewSession = Session; + type ElectionLookahead = ElectionLookahead; + type Call = Call; + type SubmitTransaction = SubmitTransaction; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; } +pub type Extrinsic = TestXt; +type SubmitTransaction = TransactionSubmitter<(), Test, Extrinsic>; + pub struct ExtBuilder { - existential_deposit: u64, + session_length: BlockNumber, + election_lookahead: BlockNumber, + session_per_era: SessionIndex, + existential_deposit: Balance, validator_pool: bool, nominate: bool, validator_count: u32, @@ -226,13 +312,16 @@ pub struct ExtBuilder { slash_defer_duration: EraIndex, fair: bool, num_validators: Option, - invulnerables: Vec, - stakers: bool, + invulnerables: Vec, + has_stakers: bool, } impl Default for ExtBuilder { fn default() -> Self { Self { + session_length: 1, + election_lookahead: 0, + session_per_era: 3, existential_deposit: 1, validator_pool: false, nominate: true, @@ -242,7 +331,7 @@ impl Default for ExtBuilder { fair: true, num_validators: None, invulnerables: vec![], - stakers: true, + has_stakers: true, } } } @@ -284,18 +373,40 @@ impl ExtBuilder { self.invulnerables = invulnerables; self } - pub fn set_associated_consts(&self) { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration); + pub fn session_per_era(mut self, length: SessionIndex) -> Self { + self.session_per_era = length; + self } - - pub fn stakers(mut self, has_stakers: bool) -> Self { - self.stakers = has_stakers; + pub fn election_lookahead(mut self, look: BlockNumber) -> Self { + self.election_lookahead = look; + self + } + pub fn session_length(mut self, length: BlockNumber) -> Self { + self.session_length = length; + self + } + pub fn has_stakers(mut self, has: bool) -> Self { + self.has_stakers = has; self } + pub fn offchain_phragmen_ext(self) -> Self { + self.session_per_era(4) + .session_length(5) + .election_lookahead(3) + } + pub fn set_associated_constants(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration); + SESSION_PER_ERA.with(|v| *v.borrow_mut() = self.session_per_era); + ELECTION_LOOKAHEAD.with(|v| *v.borrow_mut() = self.election_lookahead); + PERIOD.with(|v| *v.borrow_mut() = self.session_length); + } pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = env_logger::try_init(); + self.set_associated_constants(); + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); let balance_factor = if self.existential_deposit > 1 { 256 } else { @@ -307,7 +418,7 @@ impl ExtBuilder { .map(|x| ((x + 1) * 10 + 1) as u64) .collect::>(); - let _ = pallet_balances::GenesisConfig::{ + let _ = pallet_balances::GenesisConfig:: { balances: vec![ (1, 10 * balance_factor), (2, 20 * balance_factor), @@ -329,7 +440,7 @@ impl ExtBuilder { }.assimilate_storage(&mut storage); let mut stakers = vec![]; - if self.stakers { + if self.has_stakers { let stake_21 = if self.fair { 1000 } else { 2000 }; let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; let status_41 = if self.validator_pool { @@ -355,18 +466,21 @@ impl ExtBuilder { invulnerables: self.invulnerables, slash_reward_fraction: Perbill::from_percent(10), ..Default::default() - }.assimilate_storage(&mut storage); + } + .assimilate_storage(&mut storage); let _ = pallet_session::GenesisConfig:: { - keys: validators.iter().map(|x| (*x, *x, UintAuthorityId(*x))).collect(), + keys: validators.iter().map(|x| ( + *x, + *x, + SessionKeys { other: UintAuthorityId(*x) } + )).collect(), }.assimilate_storage(&mut storage); let mut ext = sp_io::TestExternalities::from(storage); ext.execute_with(|| { let validators = Session::validators(); - SESSION.with(|x| - *x.borrow_mut() = (validators.clone(), HashSet::new()) - ); + SESSION.with(|x| *x.borrow_mut() = (validators.clone(), HashSet::new())); }); ext } @@ -378,6 +492,10 @@ pub type Session = pallet_session::Module; pub type Timestamp = pallet_timestamp::Module; pub type Staking = Module; +pub fn active_era() -> EraIndex { + Staking::active_era().unwrap().index +} + pub fn check_exposure_all(era: EraIndex) { ErasStakers::::iter_prefix(era).for_each(check_exposure) } @@ -390,8 +508,9 @@ pub fn check_nominator_all(era: EraIndex) { /// Check for each selected validator: expo.total = Sum(expo.other) + expo.own pub fn check_exposure(expo: Exposure) { assert_eq!( - expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::(), - "wrong total exposure {:?}", expo, + expo.total as u128, + expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::(), + "wrong total exposure", ); } @@ -400,17 +519,18 @@ pub fn check_exposure(expo: Exposure) { pub fn check_nominator_exposure(era: EraIndex, stash: AccountId) { assert_is_stash(stash); let mut sum = 0; - ErasStakers::::iter_prefix(era) - .for_each(|exposure| { - exposure.others.iter() - .filter(|i| i.who == stash) - .for_each(|i| sum += i.value) - }); + Session::validators() + .iter() + .map(|v| Staking::eras_stakers(era, v)) + .for_each(|e| e.others.iter().filter(|i| i.who == stash).for_each(|i| sum += i.value)); let nominator_stake = Staking::slashable_balance_of(&stash); // a nominator cannot over-spend. assert!( nominator_stake >= sum, - "failed: Nominator({}) stake({}) >= sum divided({})", stash, nominator_stake, sum, + "failed: Nominator({}) stake({}) >= sum divided({})", + stash, + nominator_stake, + sum, ); } @@ -426,20 +546,41 @@ pub fn assert_ledger_consistent(stash: AccountId) { assert_eq!(real_total, ledger.total); } -pub fn bond_validator(acc: u64, val: u64) { - // a = controller - // a + 1 = stash - let _ = Balances::make_free_balance_be(&(acc + 1), val); - assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller)); - assert_ok!(Staking::validate(Origin::signed(acc), ValidatorPrefs::default())); -} - -pub fn bond_nominator(acc: u64, val: u64, target: Vec) { - // a = controller - // a + 1 = stash - let _ = Balances::make_free_balance_be(&(acc + 1), val); - assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller)); - assert_ok!(Staking::nominate(Origin::signed(acc), target)); +pub fn bond_validator(stash: u64, ctrl: u64, val: u64) { + let _ = Balances::make_free_balance_be(&stash, val); + assert_ok!(Staking::bond( + Origin::signed(stash), + ctrl, + val, + RewardDestination::Controller, + )); + assert_ok!(Staking::validate( + Origin::signed(ctrl), + ValidatorPrefs::default() + )); +} + +pub fn bond_nominator(stash: u64, ctrl: u64, val: u64, target: Vec) { + let _ = Balances::make_free_balance_be(&stash, val); + assert_ok!(Staking::bond( + Origin::signed(stash), + ctrl, + val, + RewardDestination::Controller, + )); + assert_ok!(Staking::nominate(Origin::signed(ctrl), target)); +} + +pub fn run_to_block(n: BlockNumber) { + Staking::on_finalize(System::block_number()); + for b in System::block_number() + 1..=n { + System::set_block_number(b); + Session::on_initialize(b); + Staking::on_initialize(b); + if b != n { + Staking::on_finalize(System::block_number()); + } + } } pub fn advance_session() { @@ -448,19 +589,21 @@ pub fn advance_session() { } pub fn start_session(session_index: SessionIndex) { + assert_eq!(>::get(), 1, "start_session can only be used with session length 1."); for i in Session::current_index()..session_index { Staking::on_finalize(System::block_number()); System::set_block_number((i + 1).into()); Timestamp::set_timestamp(System::block_number() * 1000); Session::on_initialize(System::block_number()); + Staking::on_initialize(System::block_number()); } assert_eq!(Session::current_index(), session_index); } pub fn start_era(era_index: EraIndex) { - start_session((era_index * 3).into()); - assert_eq!(Staking::active_era().unwrap().index, era_index); + start_session((era_index * >::get()).into()); + assert_eq!(Staking::current_era().unwrap(), era_index); } pub fn current_total_payout_for_duration(duration: u64) -> u64 { @@ -473,33 +616,45 @@ pub fn current_total_payout_for_duration(duration: u64) -> u64 { } pub fn reward_all_elected() { - let rewards = ::SessionInterface::validators().into_iter() + let rewards = ::SessionInterface::validators() + .into_iter() .map(|v| (v, 1)); >::reward_by_ids(rewards) } pub fn validator_controllers() -> Vec { - Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect() + Session::validators() + .into_iter() + .map(|s| Staking::bonded(&s).expect("no controller for validator")) + .collect() } pub fn on_offence_in_era( - offenders: &[OffenceDetails>], + offenders: &[OffenceDetails< + AccountId, + pallet_session::historical::IdentificationTuple, + >], slash_fraction: &[Perbill], era: EraIndex, ) { let bonded_eras = crate::BondedEras::get(); for &(bonded_era, start_session) in bonded_eras.iter() { if bonded_era == era { - Staking::on_offence(offenders, slash_fraction, start_session); - return + let _ = Staking::on_offence(offenders, slash_fraction, start_session).unwrap(); + return; } else if bonded_era > era { - break + break; } } if Staking::active_era().unwrap().index == era { - Staking::on_offence(offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap()); + let _ = + Staking::on_offence( + offenders, + slash_fraction, + Staking::eras_start_session_index(era).unwrap() + ).unwrap(); } else { panic!("cannot slash in era {}", era); } @@ -513,6 +668,193 @@ pub fn on_offence_now( on_offence_in_era(offenders, slash_fraction, now) } +// winners will be chosen by simply their unweighted total backing stake. Nominator stake is +// distributed evenly. +pub fn horrible_phragmen_with_post_processing( + do_reduce: bool, +) -> (CompactAssignments, Vec, PhragmenScore) { + use std::collections::BTreeMap; + + let mut backing_stake_of: BTreeMap = BTreeMap::new(); + + // self stake + >::iter().for_each(|(who, _p)| { + *backing_stake_of.entry(who).or_insert(Zero::zero()) += Staking::slashable_balance_of(&who) + }); + + // add nominator stuff + >::iter().for_each(|(who, nomination)| { + nomination.targets.iter().for_each(|v| { + *backing_stake_of.entry(*v).or_insert(Zero::zero()) += + Staking::slashable_balance_of(&who) + }) + }); + + // elect winners + let mut sorted: Vec = backing_stake_of.keys().cloned().collect(); + sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap()); + let winners: Vec = sorted + .iter() + .cloned() + .take(Staking::validator_count() as usize) + .collect(); + + // create assignments + let mut staked_assignment: Vec> = Vec::new(); + >::iter().for_each(|(who, nomination)| { + let mut dist: Vec<(AccountId, ExtendedBalance)> = Vec::new(); + nomination.targets.iter().for_each(|v| { + if winners.iter().find(|w| *w == v).is_some() { + dist.push((*v, ExtendedBalance::zero())); + } + }); + + if dist.len() == 0 { + return; + } + + // assign real stakes. just split the stake. + let stake = Staking::slashable_balance_of(&who) as ExtendedBalance; + let mut sum: ExtendedBalance = Zero::zero(); + let dist_len = dist.len(); + { + dist.iter_mut().for_each(|(_, w)| { + let partial = stake / (dist_len as ExtendedBalance); + *w = partial; + sum += partial; + }); + } + + // assign the leftover to last. + { + let leftover = stake - sum; + let last = dist.last_mut().unwrap(); + last.1 += leftover; + } + + staked_assignment.push(StakedAssignment { + who, + distribution: dist, + }); + }); + + // Ensure that this result is worse than seq-phragmen. Otherwise, it should not have been used + // for testing. + let score = { + let (_, _, better_score) = prepare_submission_with(true, |_| {}); + + let support = build_support_map::(&winners, &staked_assignment).0; + let score = evaluate_support(&support); + + assert!(sp_phragmen::is_score_better(score, better_score)); + + score + }; + + if do_reduce { + reduce(&mut staked_assignment); + } + + let snapshot_validators = Staking::snapshot_validators().unwrap(); + let snapshot_nominators = Staking::snapshot_nominators().unwrap(); + let nominator_index = |a: &AccountId| -> Option { + snapshot_nominators.iter().position(|x| x == a).map(|i| i as NominatorIndex) + }; + let validator_index = |a: &AccountId| -> Option { + snapshot_validators.iter().position(|x| x == a).map(|i| i as ValidatorIndex) + }; + + // convert back to ratio assignment. This takes less space. + let assignments_reduced = + sp_phragmen::assignment_staked_to_ratio::(staked_assignment); + + let compact = + CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index) + .unwrap(); + + // winner ids to index + let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::>(); + + (compact, winners, score) +} + +// Note: this should always logically reproduce [`offchain_election::prepare_submission`], yet we +// cannot do it since we want to have `tweak` injected into the process. +pub fn prepare_submission_with( + do_reduce: bool, + tweak: impl FnOnce(&mut Vec>), +) -> (CompactAssignments, Vec, PhragmenScore) { + // run phragmen on the default stuff. + let sp_phragmen::PhragmenResult { + winners, + assignments, + } = Staking::do_phragmen::().unwrap(); + let winners = winners.into_iter().map(|(w, _)| w).collect::>(); + + let stake_of = |who: &AccountId| -> ExtendedBalance { + >::convert( + Staking::slashable_balance_of(&who) + ) as ExtendedBalance + }; + let mut staked = sp_phragmen::assignment_ratio_to_staked(assignments, stake_of); + + // apply custom tweaks. awesome for testing. + tweak(&mut staked); + + if do_reduce { + reduce(&mut staked); + } + + // convert back to ratio assignment. This takes less space. + let snapshot_validators = Staking::snapshot_validators().expect("snapshot not created."); + let snapshot_nominators = Staking::snapshot_nominators().expect("snapshot not created."); + let nominator_index = |a: &AccountId| -> Option { + snapshot_nominators + .iter() + .position(|x| x == a) + .map_or_else( + || { println!("unable to find nominator index for {:?}", a); None }, + |i| Some(i as NominatorIndex), + ) + }; + let validator_index = |a: &AccountId| -> Option { + snapshot_validators + .iter() + .position(|x| x == a) + .map_or_else( + || { println!("unable to find validator index for {:?}", a); None }, + |i| Some(i as ValidatorIndex), + ) + }; + + let assignments_reduced = sp_phragmen::assignment_staked_to_ratio(staked); + + // re-compute score by converting, yet again, into staked type + let score = { + let staked = sp_phragmen::assignment_ratio_to_staked( + assignments_reduced.clone(), + Staking::slashable_balance_of_extended, + ); + + let (support_map, _) = build_support_map::( + winners.as_slice(), + staked.as_slice(), + ); + evaluate_support::(&support_map) + }; + + let compact = + CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index) + .map_err(|e| { println!("error in compact: {:?}", e); e }) + .expect("Failed to create compact"); + + + // winner ids to index + let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::>(); + + (compact, winners, score) +} + /// Make all validator and nominator request their payment pub fn make_all_reward_payment(era: EraIndex) { let validators_with_reward = ErasRewardPoints::::get(era).individual.keys() @@ -544,3 +886,23 @@ pub fn make_all_reward_payment(era: EraIndex) { assert_ok!(Staking::payout_validator(Origin::signed(validator_controller), era)); } } + +#[macro_export] +macro_rules! assert_session_era { + ($session:expr, $era:expr) => { + assert_eq!( + Session::current_index(), + $session, + "wrong session {} != {}", + Session::current_index(), + $session, + ); + assert_eq!( + Staking::active_era().unwrap().index, + $era, + "wrong active era {} != {}", + Staking::active_era().unwrap().index, + $era, + ); + }; +} diff --git a/frame/staking/src/offchain_election.rs b/frame/staking/src/offchain_election.rs new file mode 100644 index 0000000000000..0d4cf49f103b9 --- /dev/null +++ b/frame/staking/src/offchain_election.rs @@ -0,0 +1,219 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Helpers for offchain worker election. + +use crate::{ + Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex, +}; +use frame_system::offchain::SubmitUnsignedTransaction; +use sp_phragmen::{ + build_support_map, evaluate_support, reduce, Assignment, ExtendedBalance, PhragmenResult, + PhragmenScore, +}; +use sp_runtime::offchain::storage::StorageValueRef; +use sp_runtime::PerThing; +use sp_runtime::RuntimeDebug; +use sp_std::{convert::TryInto, prelude::*}; + +/// Error types related to the offchain election machinery. +#[derive(RuntimeDebug)] +pub enum OffchainElectionError { + /// Phragmen election returned None. This means less candidate that minimum number of needed + /// validators were present. The chain is in trouble and not much that we can do about it. + ElectionFailed, + /// Submission to the transaction pool failed. + PoolSubmissionFailed, + /// The snapshot data is not available. + SnapshotUnavailable, + /// Error from phragmen crate. This usually relates to compact operation. + PhragmenError(sp_phragmen::Error), + /// One of the computed winners is invalid. + InvalidWinner, +} + +impl From for OffchainElectionError { + fn from(e: sp_phragmen::Error) -> Self { + Self::PhragmenError(e) + } +} + +/// Storage key used to store the persistent offchain worker status. +pub(crate) const OFFCHAIN_HEAD_DB: &[u8] = b"parity/staking-election/"; +/// The repeat threshold of the offchain worker. This means we won't run the offchain worker twice +/// within a window of 5 blocks. +pub(crate) const OFFCHAIN_REPEAT: u32 = 5; +/// Default number of blocks for which the unsigned transaction should stay in the pool +pub(crate) const DEFAULT_LONGEVITY: u64 = 25; + +/// Checks if an execution of the offchain worker is permitted at the given block number, or not. +/// +/// This essentially makes sure that we don't run on previous blocks in case of a re-org, and we +/// don't run twice within a window of length [`OFFCHAIN_REPEAT`]. +/// +/// Returns `Ok(())` if offchain worker should happen, `Err(reason)` otherwise. +pub(crate) fn set_check_offchain_execution_status( + now: T::BlockNumber, +) -> Result<(), &'static str> { + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + let threshold = T::BlockNumber::from(OFFCHAIN_REPEAT); + + let mutate_stat = + storage.mutate::<_, &'static str, _>(|maybe_head: Option>| { + match maybe_head { + Some(Some(head)) if now < head => Err("fork."), + Some(Some(head)) if now >= head && now <= head + threshold => { + Err("recently executed.") + } + Some(Some(head)) if now > head + threshold => { + // we can run again now. Write the new head. + Ok(now) + } + _ => { + // value doesn't exists. Probably this node just booted up. Write, and run + Ok(now) + } + } + }); + + match mutate_stat { + // all good + Ok(Ok(_)) => Ok(()), + // failed to write. + Ok(Err(_)) => Err("failed to write to offchain db."), + // fork etc. + Err(why) => Err(why), + } +} + +/// The internal logic of the offchain worker of this module. This runs the phragmen election, +/// compacts and reduces the solution, computes the score and submits it back to the chain as an +/// unsigned transaction, without any signature. +pub(crate) fn compute_offchain_election() -> Result<(), OffchainElectionError> { + // compute raw solution. Note that we use `OffchainAccuracy`. + let PhragmenResult { + winners, + assignments, + } = >::do_phragmen::() + .ok_or(OffchainElectionError::ElectionFailed)?; + + // process and prepare it for submission. + let (winners, compact, score) = prepare_submission::(assignments, winners, true)?; + + // defensive-only: active era can never be none except genesis. + let era = >::active_era().map(|e| e.index).unwrap_or_default(); + + // send it. + let call: ::Call = Call::submit_election_solution_unsigned( + winners, + compact, + score, + era, + ).into(); + + T::SubmitTransaction::submit_unsigned(call) + .map_err(|_| OffchainElectionError::PoolSubmissionFailed) +} + +/// Takes a phragmen result and spits out some data that can be submitted to the chain. +/// +/// This does a lot of stuff; read the inline comments. +pub fn prepare_submission( + assignments: Vec>, + winners: Vec<(T::AccountId, ExtendedBalance)>, + do_reduce: bool, +) -> Result<(Vec, CompactAssignments, PhragmenScore), OffchainElectionError> where + ExtendedBalance: From<::Inner>, +{ + // make sure that the snapshot is available. + let snapshot_validators = + >::snapshot_validators().ok_or(OffchainElectionError::SnapshotUnavailable)?; + let snapshot_nominators = + >::snapshot_nominators().ok_or(OffchainElectionError::SnapshotUnavailable)?; + + // all helper closures + let nominator_index = |a: &T::AccountId| -> Option { + snapshot_nominators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + let validator_index = |a: &T::AccountId| -> Option { + snapshot_validators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + + // Clean winners. + let winners = winners + .into_iter() + .map(|(w, _)| w) + .collect::>(); + + // convert into absolute value and to obtain the reduced version. + let mut staked = sp_phragmen::assignment_ratio_to_staked( + assignments, + >::slashable_balance_of_extended, + ); + + if do_reduce { + reduce(&mut staked); + } + + // Convert back to ratio assignment. This takes less space. + let low_accuracy_assignment = sp_phragmen::assignment_staked_to_ratio(staked); + + // convert back to staked to compute the score in the receiver's accuracy. This can be done + // nicer, for now we do it as such since this code is not time-critical. This ensure that the + // score _predicted_ here is the same as the one computed on chain and you will not get a + // `PhragmenBogusScore` error. This is totally NOT needed if we don't do reduce. This whole + // _accuracy glitch_ happens because reduce breaks that assumption of rounding and **scale**. + // The initial phragmen results are computed in `OffchainAccuracy` and the initial `staked` + // assignment set is also all multiples of this value. After reduce, this no longer holds. Hence + // converting to ratio thereafter is not trivially reversible. + let score = { + let staked = sp_phragmen::assignment_ratio_to_staked( + low_accuracy_assignment.clone(), + >::slashable_balance_of_extended, + ); + + let (support_map, _) = build_support_map::(&winners, &staked); + evaluate_support::(&support_map) + }; + + // compact encode the assignment. + let compact = CompactAssignments::from_assignment( + low_accuracy_assignment, + nominator_index, + validator_index, + ).map_err(|e| OffchainElectionError::from(e))?; + + // winners to index. Use a simple for loop for a more expressive early exit in case of error. + let mut winners_indexed: Vec = Vec::with_capacity(winners.len()); + for w in winners { + if let Some(idx) = snapshot_validators.iter().position(|v| *v == w) { + let compact_index: ValidatorIndex = idx + .try_into() + .map_err(|_| OffchainElectionError::InvalidWinner)?; + winners_indexed.push(compact_index); + } else { + return Err(OffchainElectionError::InvalidWinner); + } + } + + Ok((winners_indexed, compact, score)) +} diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 160c08246aedf..3d5ea3bad5332 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -16,11 +16,11 @@ //! A slashing implementation for NPoS systems. //! -//! For the purposes of the economic model, it is easiest to think of each validator -//! of a nominator which nominates only its own identity. +//! For the purposes of the economic model, it is easiest to think of each validator as a nominator +//! which nominates only its own identity. //! -//! The act of nomination signals intent to unify economic identity with the validator - to take part in the -//! rewards of a job well done, and to take part in the punishment of a job done badly. +//! The act of nomination signals intent to unify economic identity with the validator - to take +//! part in the rewards of a job well done, and to take part in the punishment of a job done badly. //! //! There are 3 main difficulties to account for with slashing in NPoS: //! - A nominator can nominate multiple validators and be slashed via any of them. @@ -52,7 +52,7 @@ use super::{ EraIndex, Trait, Module, Store, BalanceOf, Exposure, Perbill, SessionInterface, NegativeImbalanceOf, UnappliedSlash, }; -use sp_runtime::{traits::{Zero, Saturating}, PerThing}; +use sp_runtime::{traits::{Zero, Saturating}, PerThing, RuntimeDebug}; use frame_support::{ StorageMap, StorageDoubleMap, traits::{Currency, OnUnbalanced, Imbalance}, @@ -65,7 +65,7 @@ use codec::{Encode, Decode}; const REWARD_F1: Perbill = Perbill::from_percent(50); /// The index of a slashing span - unique to each stash. -pub(crate) type SpanIndex = u32; +pub type SpanIndex = u32; // A range of start..end eras for a slashing span. #[derive(Encode, Decode)] @@ -83,7 +83,7 @@ impl SlashingSpan { } /// An encoding of all of a nominator's slashing spans. -#[derive(Encode, Decode)] +#[derive(Encode, Decode, RuntimeDebug)] pub struct SlashingSpans { // the index of the current slashing span of the nominator. different for // every stash, resets when the account hits free balance 0. @@ -143,7 +143,7 @@ impl SlashingSpans { } /// Yields the era index where the most recent non-zero slash occurred. - pub(crate) fn last_nonzero_slash(&self) -> EraIndex { + pub fn last_nonzero_slash(&self) -> EraIndex { self.last_nonzero_slash } diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs new file mode 100644 index 0000000000000..29a395b89de4d --- /dev/null +++ b/frame/staking/src/testing_utils.rs @@ -0,0 +1,340 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Testing utils for staking. Needs the `testing-utils` feature to be enabled. +//! +//! Note that these helpers should NOT be used with the actual crate tests, but are rather designed +//! for when the module is being externally tested (i.e. fuzzing, benchmarking, e2e tests). Enabling +//! this feature in the current crate's Cargo.toml will leak the all of this into a normal release +//! build. Just don't do it. + +use crate::*; +use codec::{Decode, Encode}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use pallet_indices::address::Address; +use rand::Rng; +use sp_core::hashing::blake2_256; +use sp_phragmen::{ + build_support_map, evaluate_support, reduce, Assignment, PhragmenScore, StakedAssignment, +}; + +const CTRL_PREFIX: u32 = 1000; +const NOMINATOR_PREFIX: u32 = 1_000_000; + +/// A dummy suer. +pub const USER: u32 = 999_999_999; + +/// Address type of the `T` +pub type AddressOf = Address<::AccountId, u32>; + +/// Random number in the range `[a, b]`. +pub fn random(a: u32, b: u32) -> u32 { + rand::thread_rng().gen_range(a, b) +} + +/// Set the desired validator count, with related storage items. +pub fn set_validator_count(to_elect: u32) { + ValidatorCount::put(to_elect); + MinimumValidatorCount::put(to_elect / 2); + >::put(ElectionStatus::Open(T::BlockNumber::from(1u32))); +} + +/// Build an account with the given index. +pub fn account(index: u32) -> T::AccountId { + let entropy = (b"benchmark/staking", index).using_encoded(blake2_256); + T::AccountId::decode(&mut &entropy[..]).unwrap_or_default() +} + +/// Build an address given Index +pub fn address(index: u32) -> AddressOf { + pallet_indices::address::Address::Id(account::(index)) +} + +/// Generate signed origin from `who`. +pub fn signed(who: T::AccountId) -> T::Origin { + RawOrigin::Signed(who).into() +} + +/// Generate signed origin from `index`. +pub fn signed_account(index: u32) -> T::Origin { + signed::(account::(index)) +} + +/// Bond a validator. +pub fn bond_validator(stash: T::AccountId, ctrl: u32, val: BalanceOf) +where + T::Lookup: StaticLookup>, +{ + let _ = T::Currency::make_free_balance_be(&stash, val); + assert_ok!(>::bond( + signed::(stash), + address::(ctrl), + val, + RewardDestination::Controller + )); + assert_ok!(>::validate( + signed_account::(ctrl), + ValidatorPrefs::default() + )); +} + +pub fn bond_nominator( + stash: T::AccountId, + ctrl: u32, + val: BalanceOf, + target: Vec>, +) where + T::Lookup: StaticLookup>, +{ + let _ = T::Currency::make_free_balance_be(&stash, val); + assert_ok!(>::bond( + signed::(stash), + address::(ctrl), + val, + RewardDestination::Controller + )); + assert_ok!(>::nominate(signed_account::(ctrl), target)); +} + +/// Bond `nun_validators` validators and `num_nominator` nominators with `edge_per_voter` random +/// votes per nominator. +pub fn setup_chain_stakers(num_validators: u32, num_voters: u32, edge_per_voter: u32) +where + T::Lookup: StaticLookup>, +{ + (0..num_validators).for_each(|i| { + bond_validator::( + account::(i), + i + CTRL_PREFIX, + >::from(random(1, 1000)) * T::Currency::minimum_balance(), + ); + }); + + (0..num_voters).for_each(|i| { + let mut targets: Vec> = Vec::with_capacity(edge_per_voter as usize); + let mut all_targets = (0..num_validators) + .map(|t| address::(t)) + .collect::>(); + assert!(num_validators >= edge_per_voter); + (0..edge_per_voter).for_each(|_| { + let target = all_targets.remove(random(0, all_targets.len() as u32 - 1) as usize); + targets.push(target); + }); + bond_nominator::( + account::(i + NOMINATOR_PREFIX), + i + NOMINATOR_PREFIX + CTRL_PREFIX, + >::from(random(1, 1000)) * T::Currency::minimum_balance(), + targets, + ); + }); + + >::create_stakers_snapshot(); +} + +/// Build a _really bad_ but acceptable solution for election. This should always yield a solution +/// which has a less score than the seq-phragmen. +pub fn get_weak_solution( + do_reduce: bool, +) -> (Vec, CompactAssignments, PhragmenScore) { + let mut backing_stake_of: BTreeMap> = BTreeMap::new(); + + // self stake + >::enumerate().for_each(|(who, _p)| { + *backing_stake_of.entry(who.clone()).or_insert(Zero::zero()) += + >::slashable_balance_of(&who) + }); + + // add nominator stuff + >::enumerate().for_each(|(who, nomination)| { + nomination.targets.into_iter().for_each(|v| { + *backing_stake_of.entry(v).or_insert(Zero::zero()) += + >::slashable_balance_of(&who) + }) + }); + + // elect winners + let mut sorted: Vec = backing_stake_of.keys().cloned().collect(); + sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap()); + let winners: Vec = sorted + .iter() + .cloned() + .take(>::validator_count() as usize) + .collect(); + + let mut staked_assignments: Vec> = Vec::new(); + >::enumerate().for_each(|(who, nomination)| { + let mut dist: Vec<(T::AccountId, ExtendedBalance)> = Vec::new(); + nomination.targets.into_iter().for_each(|v| { + if winners.iter().find(|&w| *w == v).is_some() { + dist.push((v, ExtendedBalance::zero())); + } + }); + + if dist.len() == 0 { + return; + } + + // assign real stakes. just split the stake. + let stake = , u64>>::convert( + >::slashable_balance_of(&who), + ) as ExtendedBalance; + + let mut sum: ExtendedBalance = Zero::zero(); + let dist_len = dist.len() as ExtendedBalance; + + // assign main portion + // only take the first half into account. This should highly imbalance stuff, which is good. + dist.iter_mut() + .take(if dist_len > 1 { + (dist_len as usize) / 2 + } else { + 1 + }) + .for_each(|(_, w)| { + let partial = stake / dist_len; + *w = partial; + sum += partial; + }); + + // assign the leftover to last. + let leftover = stake - sum; + let last = dist.last_mut().unwrap(); + last.1 += leftover; + + staked_assignments.push(StakedAssignment { + who, + distribution: dist, + }); + }); + + // add self support to winners. + winners.iter().for_each(|w| { + staked_assignments.push(StakedAssignment { + who: w.clone(), + distribution: vec![( + w.clone(), + , u64>>::convert( + >::slashable_balance_of(&w), + ) as ExtendedBalance, + )], + }) + }); + + if do_reduce { + reduce(&mut staked_assignments); + } + + // helpers for building the compact + let snapshot_validators = >::snapshot_validators().unwrap(); + let snapshot_nominators = >::snapshot_nominators().unwrap(); + + let nominator_index = |a: &T::AccountId| -> Option { + snapshot_nominators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + let validator_index = |a: &T::AccountId| -> Option { + snapshot_validators + .iter() + .position(|x| x == a) + .and_then(|i| >::try_into(i).ok()) + }; + let stake_of = |who: &T::AccountId| -> ExtendedBalance { + , u64>>::convert( + >::slashable_balance_of(who), + ) as ExtendedBalance + }; + + // convert back to ratio assignment. This takes less space. + let low_accuracy_assignment: Vec> = + staked_assignments + .into_iter() + .map(|sa| sa.into_assignment(true)) + .collect(); + + // re-calculate score based on what the chain will decode. + let score = { + let staked: Vec> = low_accuracy_assignment + .iter() + .map(|a| { + let stake = stake_of(&a.who); + a.clone().into_staked(stake, true) + }) + .collect(); + + let (support_map, _) = + build_support_map::(winners.as_slice(), staked.as_slice()); + evaluate_support::(&support_map) + }; + + // compact encode the assignment. + let compact = CompactAssignments::from_assignment( + low_accuracy_assignment, + nominator_index, + validator_index, + ) + .unwrap(); + + // winners to index. + let winners = winners + .into_iter() + .map(|w| { + snapshot_validators + .iter() + .position(|v| *v == w) + .unwrap() + .try_into() + .unwrap() + }) + .collect::>(); + + (winners, compact, score) +} + +/// Create a solution for seq-phragmen. This uses the same internal function as used by the offchain +/// worker code. +pub fn get_seq_phragmen_solution( + do_reduce: bool, +) -> (Vec, CompactAssignments, PhragmenScore) { + let sp_phragmen::PhragmenResult { + winners, + assignments, + } = >::do_phragmen::().unwrap(); + + offchain_election::prepare_submission::(assignments, winners, do_reduce).unwrap() +} + +/// Remove all validator, nominators, votes and exposures. +pub fn clean(era: EraIndex) + where + ::AccountId: codec::EncodeLike, + u32: codec::EncodeLike, +{ + >::enumerate().for_each(|(k, _)| { + let ctrl = >::bonded(&k).unwrap(); + >::remove(&k); + >::remove(&k); + >::remove(&ctrl); + >::remove(k, era); + }); + >::enumerate().for_each(|(k, _)| >::remove(k)); + >::remove_all(); + >::remove_all(); + >::kill(); + QueuedScore::kill(); +} diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 6687b83f0569f..f54fe05de088d 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,7 +18,9 @@ use super::*; use mock::*; -use sp_runtime::{assert_eq_error_rate, traits::BadOrigin}; +use sp_runtime::{ + assert_eq_error_rate, traits::BadOrigin, +}; use sp_staking::offence::OffenceDetails; use frame_support::{ assert_ok, assert_noop, StorageMap, @@ -647,51 +649,37 @@ fn double_controlling_should_fail() { fn session_and_eras_work() { ExtBuilder::default().build().execute_with(|| { assert_eq!(Staking::active_era().unwrap().index, 0); + assert_eq!(Session::current_index(), 0); - // Block 1: No change. + // Session 1: No change. start_session(1); assert_eq!(Session::current_index(), 1); assert_eq!(Staking::active_era().unwrap().index, 0); - // Block 2: No change. + // Session 2: No change. start_session(2); assert_eq!(Session::current_index(), 2); assert_eq!(Staking::active_era().unwrap().index, 0); - // Block 3: Era increment. + // Session 3: Era increment. start_session(3); assert_eq!(Session::current_index(), 3); assert_eq!(Staking::active_era().unwrap().index, 1); - // Block 4: No change. + // Session 4: No change. start_session(4); assert_eq!(Session::current_index(), 4); assert_eq!(Staking::active_era().unwrap().index, 1); - // Block 5: No change. + // Session 5: No change. start_session(5); assert_eq!(Session::current_index(), 5); assert_eq!(Staking::active_era().unwrap().index, 1); - // Block 6: Era increment. + // Session 6: Era increment. start_session(6); assert_eq!(Session::current_index(), 6); assert_eq!(Staking::active_era().unwrap().index, 2); - - // Block 7: No change. - start_session(7); - assert_eq!(Session::current_index(), 7); - assert_eq!(Staking::active_era().unwrap().index, 2); - - // Block 8: No change. - start_session(8); - assert_eq!(Session::current_index(), 8); - assert_eq!(Staking::active_era().unwrap().index, 2); - - // Block 9: Era increment. - start_session(9); - assert_eq!(Session::current_index(), 9); - assert_eq!(Staking::active_era().unwrap().index, 3); }); } @@ -1736,11 +1724,11 @@ fn phragmen_should_not_overflow_validators() { let _ = Staking::chill(Origin::signed(10)); let _ = Staking::chill(Origin::signed(20)); - bond_validator(2, u64::max_value()); - bond_validator(4, u64::max_value()); + bond_validator(3, 2, u64::max_value()); + bond_validator(5, 4, u64::max_value()); - bond_nominator(6, u64::max_value() / 2, vec![3, 5]); - bond_nominator(8, u64::max_value() / 2, vec![3, 5]); + bond_nominator(7, 6, u64::max_value() / 2, vec![3, 5]); + bond_nominator(9, 8, u64::max_value() / 2, vec![3, 5]); start_era(1); @@ -1759,11 +1747,11 @@ fn phragmen_should_not_overflow_nominators() { let _ = Staking::chill(Origin::signed(10)); let _ = Staking::chill(Origin::signed(20)); - bond_validator(2, u64::max_value() / 2); - bond_validator(4, u64::max_value() / 2); + bond_validator(3, 2, u64::max_value() / 2); + bond_validator(5, 4, u64::max_value() / 2); - bond_nominator(6, u64::max_value(), vec![3, 5]); - bond_nominator(8, u64::max_value(), vec![3, 5]); + bond_nominator(7, 6, u64::max_value(), vec![3, 5]); + bond_nominator(9, 8, u64::max_value(), vec![3, 5]); start_era(1); @@ -1778,11 +1766,11 @@ fn phragmen_should_not_overflow_nominators() { #[test] fn phragmen_should_not_overflow_ultimate() { ExtBuilder::default().nominate(false).build().execute_with(|| { - bond_validator(2, u64::max_value()); - bond_validator(4, u64::max_value()); + bond_validator(3, 2, u64::max_value()); + bond_validator(5, 4, u64::max_value()); - bond_nominator(6, u64::max_value(), vec![3, 5]); - bond_nominator(8, u64::max_value(), vec![3, 5]); + bond_nominator(7, 6, u64::max_value(), vec![3, 5]); + bond_nominator(9, 8, u64::max_value(), vec![3, 5]); start_era(1); @@ -1923,21 +1911,23 @@ fn era_is_always_same_length() { // This ensures that the sessions is always of the same length if there is no forcing no // session changes. ExtBuilder::default().build().execute_with(|| { + let session_per_era = >::get(); + start_era(1); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), SessionsPerEra::get()); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session_per_era); start_era(2); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), SessionsPerEra::get() * 2); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session_per_era * 2u32); let session = Session::current_index(); ForceEra::put(Forcing::ForceNew); advance_session(); advance_session(); assert_eq!(Staking::active_era().unwrap().index, 3); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), session + 2); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session + 2); start_era(4); - assert_eq!(Staking::eras_start_session_index(Staking::active_era().unwrap().index).unwrap(), session + 2 + SessionsPerEra::get()); + assert_eq!(Staking::eras_start_session_index(active_era()).unwrap(), session + 2u32 + session_per_era); }); } @@ -1963,6 +1953,7 @@ fn offence_forces_new_era() { fn offence_ensures_new_era_without_clobbering() { ExtBuilder::default().build().execute_with(|| { assert_ok!(Staking::force_new_era_always(Origin::ROOT)); + assert_eq!(Staking::force_era(), Forcing::ForceAlways); on_offence_now( &[OffenceDetails { @@ -1980,10 +1971,11 @@ fn offence_ensures_new_era_without_clobbering() { } #[test] -fn offence_deselects_validator_when_slash_is_zero() { +fn offence_deselects_validator_even_when_slash_is_zero() { ExtBuilder::default().build().execute_with(|| { assert!(Session::validators().contains(&11)); assert!(>::contains_key(11)); + on_offence_now( &[OffenceDetails { offender: ( @@ -1994,9 +1986,12 @@ fn offence_deselects_validator_when_slash_is_zero() { }], &[Perbill::from_percent(0)], ); + assert_eq!(Staking::force_era(), Forcing::ForceNew); assert!(!>::contains_key(11)); + start_era(1); + assert!(!Session::validators().contains(&11)); assert!(!>::contains_key(11)); }); @@ -2037,6 +2032,7 @@ fn slash_in_old_span_does_not_deselect() { assert!(>::contains_key(11)); assert!(Session::validators().contains(&11)); + on_offence_now( &[OffenceDetails { offender: ( @@ -2047,6 +2043,7 @@ fn slash_in_old_span_does_not_deselect() { }], &[Perbill::from_percent(0)], ); + assert_eq!(Staking::force_era(), Forcing::ForceNew); assert!(!>::contains_key(11)); @@ -2074,7 +2071,7 @@ fn slash_in_old_span_does_not_deselect() { 1, ); - // not for zero-slash. + // not forcing for zero-slash and previous span. assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::contains_key(11)); assert!(Session::validators().contains(&11)); @@ -2095,7 +2092,7 @@ fn slash_in_old_span_does_not_deselect() { // or non-zero. assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::contains_key(11)); - assert!(Session::validators().contains(&11)); + assert!(Session::validators().contains(&11)); assert_ledger_consistent(11); }); } @@ -2134,7 +2131,7 @@ fn reporters_receive_their_slice() { #[test] fn subsequent_reports_in_same_span_pay_out_less() { // This test verifies that the reporters of the offence receive their slice from the slashed - // amount. + // amount, but less and less if they submit multiple reports in one span. ExtBuilder::default().build().execute_with(|| { // The reporters' reward is calculated from the total exposure. let initial_balance = 1125; @@ -2241,12 +2238,16 @@ fn dont_slash_if_fraction_is_zero() { // The validator hasn't been slashed. The new era is not forced. assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Staking::force_era(), Forcing::ForceNew); + assert_ledger_consistent(11); }); } #[test] fn only_slash_for_max_in_era() { + // multiple slashes within one era are only applied if it is more than any previous slash in the + // same era. ExtBuilder::default().build().execute_with(|| { assert_eq!(Balances::free_balance(11), 1000); @@ -2295,6 +2296,7 @@ fn only_slash_for_max_in_era() { #[test] fn garbage_collection_after_slashing() { + // ensures that `SlashingSpans` and `SpanSlash` of an account is removed after reaping. ExtBuilder::default().existential_deposit(2).build().execute_with(|| { assert_eq!(Balances::free_balance(11), 256_000); @@ -2337,27 +2339,28 @@ fn garbage_collection_after_slashing() { #[test] fn garbage_collection_on_window_pruning() { + // ensures that `ValidatorSlashInEra` and `NominatorSlashInEra` are cleared after + // `BondingDuration`. ExtBuilder::default().build().execute_with(|| { start_era(1); assert_eq!(Balances::free_balance(11), 1000); + let now = Staking::active_era().unwrap().index; - let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); + let exposure = Staking::eras_stakers(now, 11); assert_eq!(Balances::free_balance(101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( &[ OffenceDetails { - offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)), + offender: (11, Staking::eras_stakers(now, 11)), reporters: vec![], }, ], &[Perbill::from_percent(10)], ); - let now = Staking::active_era().unwrap().index; - assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); @@ -2389,10 +2392,8 @@ fn slashing_nominators_by_span_max() { assert_eq!(Balances::free_balance(101), 2000); assert_eq!(Staking::slashable_balance_of(&21), 1000); - let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, 21); - assert_eq!(Balances::free_balance(101), 2000); let nominated_value_11 = exposure_11.others.iter().find(|o| o.who == 101).unwrap().value; let nominated_value_21 = exposure_21.others.iter().find(|o| o.who == 101).unwrap().value; @@ -2736,35 +2737,1101 @@ fn remove_multi_deferred() { let slashes = ::UnappliedSlashes::get(&1); assert_eq!(slashes.len(), 2); - println!("Slashes: {:?}", slashes); assert_eq!(slashes[0].validator, 21); assert_eq!(slashes[1].validator, 42); }) } +mod offchain_phragmen { + use crate::*; + use frame_support::{assert_noop, assert_ok}; + use sp_runtime::transaction_validity::TransactionSource; + use mock::*; + use parking_lot::RwLock; + use sp_core::offchain::{ + testing::{PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainExt, TransactionPoolExt, + }; + use sp_io::TestExternalities; + use sp_phragmen::StakedAssignment; + use frame_support::traits::OffchainWorker; + use std::sync::Arc; + use substrate_test_utils::assert_eq_uvec; + + fn percent(x: u16) -> OffchainAccuracy { + OffchainAccuracy::from_percent(x) + } + + /// setup a new set of validators and nominator storage items independent of the parent mock + /// file. This produces a edge graph that can be reduced. + fn build_offchain_phragmen_test_ext() { + for i in (10..=40).step_by(10) { + // Note: we respect the convention of the mock (10, 11 pairs etc.) since these accounts + // have corresponding keys in session which makes everything more ergonomic and + // realistic. + bond_validator(i + 1, i, 100); + } + + let mut voter = 1; + bond_nominator(voter, 1000 + voter, 100, vec![11]); + voter = 2; + bond_nominator(voter, 1000 + voter, 100, vec![11, 11]); + voter = 3; + bond_nominator(voter, 1000 + voter, 100, vec![21, 41]); + voter = 4; + bond_nominator(voter, 1000 + voter, 100, vec![21, 31, 41]); + voter = 5; + bond_nominator(voter, 1000 + voter, 100, vec![21, 31, 41]); + } + + fn offchainify(ext: &mut TestExternalities) -> Arc> { + let (offchain, _state) = TestOffchainExt::new(); + let (pool, state) = TestTransactionPoolExt::new(); + + ext.register_extension(OffchainExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + state + } + + #[test] + fn is_current_session_final_works() { + ExtBuilder::default() + .session_per_era(3) + .build() + .execute_with(|| { + start_era(1); + assert_eq!(Session::current_index(), 3); + assert_eq!(Staking::current_era(), Some(1)); + assert_eq!(Staking::is_current_session_final(), false); + + start_session(4); + assert_eq!(Session::current_index(), 4); + assert_eq!(Staking::current_era(), Some(1)); + assert_eq!(Staking::is_current_session_final(), true); + + start_session(5); + assert_eq!(Session::current_index(), 5); + // era changed. + assert_eq!(Staking::current_era(), Some(2)); + assert_eq!(Staking::is_current_session_final(), false); + }) + } + + #[test] + fn offchain_election_flag_is_triggered() { + ExtBuilder::default() + .session_per_era(5) + .session_length(10) + .election_lookahead(3) + .build() + .execute_with(|| { + run_to_block(7); + assert_session_era!(0, 0); + + run_to_block(10); + assert_session_era!(1, 0); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + + run_to_block(36); + assert_session_era!(3, 0); + + // fist era has session 0, which has 0 blocks length, so we have in total 40 blocks + // in the era. + run_to_block(37); + assert_session_era!(3, 0); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); + assert!(Staking::snapshot_nominators().is_some()); + assert!(Staking::snapshot_validators().is_some()); + + run_to_block(38); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); + + run_to_block(39); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37)); + + run_to_block(40); + assert_session_era!(4, 0); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + + run_to_block(86); + assert_session_era!(8, 1); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + + // second era onwards has 50 blocks per era. + run_to_block(87); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(87)); + assert!(Staking::snapshot_nominators().is_some()); + assert!(Staking::snapshot_validators().is_some()); + + run_to_block(90); + assert_session_era!(9, 1); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + assert!(Staking::snapshot_nominators().is_none()); + assert!(Staking::snapshot_validators().is_none()); + }) + } + + #[test] + fn election_on_chain_fallback_works() { + ExtBuilder::default().build().execute_with(|| { + start_session(1); + start_session(2); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + // some election must have happened by now. + assert_eq!( + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let MetaEvent::staking(inner) = e { + Some(inner) + } else { + None + } + }) + .last() + .unwrap(), + RawEvent::StakingElection(ElectionCompute::OnChain), + ); + }) + } + + #[test] + #[ignore] // This takes a few mins + fn offchain_wont_work_if_snapshot_fails() { + ExtBuilder::default() + .offchain_phragmen_ext() + .election_lookahead(3) + .build() + .execute_with(|| { + run_to_block(12); + assert!(Staking::snapshot_validators().is_some()); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + // validate more than the limit + let limit: NominatorIndex = ValidatorIndex::max_value() as NominatorIndex + 1; + let ctrl = 1_000_000; + for i in 0..limit { + bond_validator((1000 + i).into(), (1000 + i + ctrl).into(), 100); + } + + // window stays closed since no snapshot was taken. + run_to_block(27); + assert!(Staking::snapshot_validators().is_none()); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + }) + } + + #[test] + fn staking_is_locked_when_election_window_open() { + ExtBuilder::default() + .offchain_phragmen_ext() + .election_lookahead(3) + .build() + .execute_with(|| { + run_to_block(12); + assert!(Staking::snapshot_validators().is_some()); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + let call = crate::Call::bond(999, 998, Default::default()); + let outer: mock::Call = call.into(); + + let lock_staking: LockStakingStatus = Default::default(); + assert_eq!( + lock_staking.validate(&10, &outer, Default::default(), Default::default(),), + TransactionValidity::Err(InvalidTransaction::Stale.into()), + ) + }) + } + + #[test] + fn signed_result_can_be_submitted() { + // should check that we have a new validator set normally, + // event says that it comes from offchain. + ExtBuilder::default() + .offchain_phragmen_ext() + .build() + .execute_with(|| { + run_to_block(12); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + assert!(Staking::snapshot_validators().is_some()); + + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + let queued_result = Staking::queued_elected().unwrap(); + assert_eq!(queued_result.compute, ElectionCompute::Signed); + + run_to_block(15); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + + assert_eq!( + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let MetaEvent::staking(inner) = e { + Some(inner) + } else { + None + } + }) + .last() + .unwrap(), + RawEvent::StakingElection(ElectionCompute::Signed), + ); + }) + } + + #[test] + fn signed_result_can_be_submitted_later() { + // same as `signed_result_can_be_submitted` but at a later block. + ExtBuilder::default() + .offchain_phragmen_ext() + .build() + .execute_with(|| { + run_to_block(14); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + let queued_result = Staking::queued_elected().unwrap(); + assert_eq!(queued_result.compute, ElectionCompute::Signed); + + run_to_block(15); + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + + assert_eq!( + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let MetaEvent::staking(inner) = e { + Some(inner) + } else { + None + } + }) + .last() + .unwrap(), + RawEvent::StakingElection(ElectionCompute::Signed), + ); + }) + } + + #[test] + fn early_solution_submission_is_rejected() { + // should check that we have a new validator set normally, + // event says that it comes from offchain. + ExtBuilder::default() + .offchain_phragmen_ext() + .build() + .execute_with(|| { + run_to_block(11); + // submission is not yet allowed + assert_eq!(Staking::era_election_status(), ElectionStatus::Closed); + + // create all the indices just to build the solution. + Staking::create_stakers_snapshot(); + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + Staking::kill_stakers_snapshot(); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenEarlySubmission, + ); + }) + } + + #[test] + fn weak_solution_is_rejected() { + // A solution which is weaker than what we currently have on-chain is rejected. + ExtBuilder::default() + .offchain_phragmen_ext() + .has_stakers(false) + .validator_count(4) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + // a good solution + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + // a bad solution + let (compact, winners, score) = horrible_phragmen_with_post_processing(false); + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenWeakSubmission, + ); + }) + } + + #[test] + fn better_solution_is_accepted() { + // A solution which is better than what we currently have on-chain is accepted. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + // a meeeeh solution + let (compact, winners, score) = horrible_phragmen_with_post_processing(false); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + // a better solution + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + }) + } + + #[test] + fn offchain_worker_runs_when_window_open() { + // at the end of the first finalized block with ElectionStatus::open(_), it should execute. + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(2) + .build(); + let state = offchainify(&mut ext); + ext.execute_with(|| { + run_to_block(12); + + // local key 11 is in the elected set. + assert_eq_uvec!(Session::validators(), vec![11, 21]); + assert_eq!(state.read().transactions.len(), 0); + Staking::offchain_worker(12); + assert_eq!(state.read().transactions.len(), 1); + + let encoded = state.read().transactions[0].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + + let call = extrinsic.call; + let inner = match call { + mock::Call::Staking(inner) => inner, + }; + + assert_eq!( + ::validate_unsigned( + TransactionSource::Local, + &inner, + ), + TransactionValidity::Ok(ValidTransaction { + priority: 1125, // the proposed slot stake. + requires: vec![], + provides: vec![("StakingOffchain", active_era()).encode()], + longevity: 3, + propagate: false, + }) + ) + }) + } + + #[test] + fn mediocre_submission_from_authority_is_early_rejected() { + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .build(); + let state = offchainify(&mut ext); + ext.execute_with(|| { + run_to_block(12); + // put a good solution on-chain + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ),); + + // now run the offchain worker in the same chain state. + Staking::offchain_worker(12); + assert_eq!(state.read().transactions.len(), 1); + + let encoded = state.read().transactions[0].clone(); + let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap(); + + let call = extrinsic.call; + let inner = match call { + mock::Call::Staking(inner) => inner, + }; + + // pass this call to ValidateUnsigned + assert_eq!( + ::validate_unsigned( + TransactionSource::Local, + &inner, + ), + TransactionValidity::Err( + InvalidTransaction::Custom(>::PhragmenWeakSubmission.as_u8()).into(), + ), + ) + }) + } + + #[test] + fn invalid_phragmen_result_correct_number_of_winners() { + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + ValidatorCount::put(3); + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + ValidatorCount::put(4); + + assert_eq!(winners.len(), 3); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusWinnerCount, + ); + }) + } + + #[test] + fn invalid_phragmen_result_correct_number_of_winners_1() { + // if we have too little validators, then the number of candidates is the bound. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(8) // we simply cannot elect 8 + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + ValidatorCount::put(3); + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + ValidatorCount::put(4); + + assert_eq!(winners.len(), 3); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusWinnerCount, + ); + }) + } + + #[test] + fn invalid_phragmen_result_correct_number_of_winners_2() { + // if we have too little validators, then the number of candidates is the bound. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(8) // we simply cannot elect 8 + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(true, |_| {}); + + assert_eq!(winners.len(), 4); + + // all good. We chose 4 and it works. + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ),); + }) + } + + #[test] + fn invalid_phragmen_result_out_of_bound_nominator_index() { + // A nominator index which is simply invalid + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (mut compact, winners, score) = prepare_submission_with(true, |_| {}); + + // index 9 doesn't exist. + compact.votes1.push((9, 2)); + + // The error type sadly cannot be more specific now. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusCompact, + ); + }) + } + + #[test] + fn invalid_phragmen_result_out_of_bound_validator_index() { + // A validator index which is out of bound + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (mut compact, winners, score) = prepare_submission_with(true, |_| {}); + + // index 4 doesn't exist. + compact.votes1.push((3, 4)); + + // The error type sadly cannot be more specific now. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusCompact, + ); + }) + } + + #[test] + fn invalid_phragmen_result_out_of_bound_winner_index() { + // A winner index which is simply invalid + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (compact, _, score) = prepare_submission_with(true, |_| {}); + + // index 4 doesn't exist. + let winners = vec![0, 1, 2, 4]; + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusWinner, + ); + }) + } + + #[test] + fn invalid_phragmen_result_non_winner_validator_index() { + // An edge that points to a correct validator index who is NOT a winner. This is very + // similar to the test that raises `PhragmenBogusNomination`. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(2) // we select only 2. + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (compact, winners, score) = prepare_submission_with(true, |a| { + a.iter_mut() + .find(|x| x.who == 5) + // all 3 cannot be among the winners. Although, all of them are validator + // candidates. + .map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]); + }); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusEdge, + ); + }) + } + + #[test] + fn invalid_phragmen_result_wrong_self_vote() { + // A self vote for someone else. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(true, |a| { + // mutate a self vote to target someone else. That someone else is still among the + // winners + a.iter_mut().find(|x| x.who == 11).map(|x| { + x.distribution + .iter_mut() + .find(|y| y.0 == 11) + .map(|y| y.0 = 21) + }); + }); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusSelfVote, + ); + }) + } + + #[test] + fn invalid_phragmen_result_wrong_self_vote_2() { + // A self validator voting for someone else next to self vote. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(true, |a| { + // Remove the self vote. + a.retain(|x| x.who != 11); + // add is as a new double vote + a.push(StakedAssignment { + who: 11, + distribution: vec![(11, 50), (21, 50)], + }); + }); + + // This raises score issue. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusSelfVote, + ); + }) + } + + #[test] + fn invalid_phragmen_result_over_stake() { + // Someone's edge ratios sums to more than 100%. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + // Note: we don't reduce here to be able to tweak votes3. votes3 will vanish if you + // reduce. + let (mut compact, winners, score) = prepare_submission_with(false, |_| {}); + + if let Some(c) = compact.votes3.iter_mut().find(|x| x.0 == 0) { + // by default it should have been (0, [(2, 33%), (1, 33%)], 0) + // now the sum is above 100% + c.1 = [(2, percent(66)), (1, percent(66))]; + } + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusCompact, + ); + }) + } + + #[test] + fn invalid_phragmen_result_under_stake() { + // at the time of this writing, we cannot under stake someone. The compact assignment works + // in a way that some of the stakes are presented by the submitter, and the last one is read + // from chain by subtracting the rest from total. Hence, the sum is always correct. + // This test is only here as a demonstration. + } + + #[test] + fn invalid_phragmen_result_invalid_target_stealing() { + // A valid voter who voted for someone who is a candidate, and is a correct winner, but is + // actually NOT nominated by this nominator. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, score) = prepare_submission_with(false, |a| { + // 3 only voted for 20 and 40. We add a fake vote to 30. The stake sum is still + // correctly 100. + a.iter_mut() + .find(|x| x.who == 3) + .map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]); + }); + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusNomination, + ); + }) + } + + #[test] + fn nomination_slash_filter_is_checked() { + // If a nominator has voted for someone who has been recently slashed, that particular + // nomination should be disabled for the upcoming election. A solution must respect this + // rule. + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + + // finalize the round with fallback. This is needed since all nominator submission + // are in era zero and we want this one to pass with no problems. + run_to_block(15); + + // go to the next session to trigger start_era and bump the active era + run_to_block(20); + + // slash 10. This must happen outside of the election window. + let offender_expo = Staking::eras_stakers(active_era(), 11); + on_offence_now( + &[OffenceDetails { + offender: (11, offender_expo.clone()), + reporters: vec![], + }], + &[Perbill::from_percent(50)], + ); + + // validate 10 again for the next round. But this guy will not have the votes that + // it should have had from 1 and 2. + assert_ok!(Staking::validate( + Origin::signed(10), + Default::default() + )); + + // open the election window and create snapshots. + run_to_block(32); + + // a solution that has been prepared after the slash. + let (compact, winners, score) = prepare_submission_with(false, |a| { + // no one is allowed to vote for 10, except for itself. + a.into_iter() + .filter(|s| s.who != 11) + .for_each(|s| + assert!(s.distribution.iter().find(|(t, _)| *t == 11).is_none()) + ); + }); + + // can be submitted. + assert_ok!(Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + )); + + // a wrong solution. + let (compact, winners, score) = prepare_submission_with(false, |a| { + // add back the vote that has been filtered out. + a.push(StakedAssignment { + who: 1, + distribution: vec![(11, 100)] + }); + }); + + // is rejected. + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenSlashedNomination, + ); + }) + } + + #[test] + fn invalid_phragmen_result_wrong_score() { + // A valid voter who's total distributed stake is more than what they bond + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_phragmen_test_ext(); + run_to_block(12); + + let (compact, winners, mut score) = prepare_submission_with(true, |_| {}); + score[0] += 1; + + assert_noop!( + Staking::submit_election_solution( + Origin::signed(10), + winners, + compact, + score, + active_era(), + ), + Error::::PhragmenBogusScore, + ); + }) + } + + #[test] + fn offchain_storage_is_set() { + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .build(); + let state = offchainify(&mut ext); + + ext.execute_with(|| { + use offchain_election::OFFCHAIN_HEAD_DB; + use sp_runtime::offchain::storage::StorageValueRef; + + run_to_block(12); + + Staking::offchain_worker(12); + // it works + assert_eq!(state.read().transactions.len(), 1); + + // and it is set + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + assert_eq!(storage.get::().unwrap().unwrap(), 12); + }) + } + + #[test] + fn offchain_storage_prevents_duplicate() { + let mut ext = ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .build(); + let _ = offchainify(&mut ext); + + ext.execute_with(|| { + use offchain_election::OFFCHAIN_HEAD_DB; + use sp_runtime::offchain::storage::StorageValueRef; + let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB); + + run_to_block(12); + + // first run -- ok + assert_eq!( + offchain_election::set_check_offchain_execution_status::(12), + Ok(()), + ); + assert_eq!(storage.get::().unwrap().unwrap(), 12); + + // re-execute after the next. not allowed. + assert_eq!( + offchain_election::set_check_offchain_execution_status::(13), + Err("recently executed."), + ); + + // a fork like situation -- re-execute 10, 11, 12. But it won't go through. + assert_eq!( + offchain_election::set_check_offchain_execution_status::(10), + Err("fork."), + ); + assert_eq!( + offchain_election::set_check_offchain_execution_status::(11), + Err("fork."), + ); + assert_eq!( + offchain_election::set_check_offchain_execution_status::(12), + Err("recently executed."), + ); + }) + } + + #[test] + #[should_panic] + fn offence_is_blocked_when_window_open() { + ExtBuilder::default() + .offchain_phragmen_ext() + .validator_count(4) + .has_stakers(false) + .build() + .execute_with(|| { + run_to_block(12); + assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12)); + + let offender_expo = Staking::eras_stakers(active_era(), 10); + + // panic from the impl in mock + on_offence_now( + &[OffenceDetails { + offender: (10, offender_expo.clone()), + reporters: vec![], + }], + &[Perbill::from_percent(10)], + ); + }) + } +} + #[test] -fn slash_kicks_validators_not_nominators() { +fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_validator() { ExtBuilder::default().build().execute_with(|| { start_era(1); + assert_eq_uvec!(Session::validators(), vec![11, 21]); + // pre-slash balance assert_eq!(Balances::free_balance(11), 1000); - - let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); assert_eq!(Balances::free_balance(101), 2000); - let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + + // 11 and 21 both have the support of 100 + let exposure_11 = Staking::eras_stakers(active_era(), &11); + let exposure_21 = Staking::eras_stakers(active_era(), &21); + + assert_eq!(exposure_11.total, 1000 + 125); + assert_eq!(exposure_21.total, 1000 + 375); on_offence_now( - &[ - OffenceDetails { - offender: (11, exposure.clone()), - reporters: vec![], - }, - ], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: vec![], + }], &[Perbill::from_percent(10)], ); + // post-slash balance + let nominator_slash_amount_11 = 125 / 10; assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + assert_eq!( + Balances::free_balance(101), + 2000 - nominator_slash_amount_11 + ); // This is the best way to check that the validator was chilled; `get` will // return default value. @@ -2776,8 +3843,23 @@ fn slash_kicks_validators_not_nominators() { // and make sure that the vote will be ignored even if the validator // re-registers. - let last_slash = ::SlashingSpans::get(&11).unwrap().last_nonzero_slash(); + let last_slash = ::SlashingSpans::get(&11) + .unwrap() + .last_nonzero_slash(); assert!(nominations.submitted_in < last_slash); + + // actually re-bond the slashed validator + assert_ok!(Staking::validate(Origin::signed(10), Default::default())); + + start_era(2); + let exposure_11 = Staking::eras_stakers(active_era(), &11); + let exposure_21 = Staking::eras_stakers(active_era(), &21); + + // 10 is re-elected, but without the support of 100 + assert_eq!(exposure_11.total, 900); + + // 20 is re-elected, with the (almost) entire support of 100 + assert_eq!(exposure_21.total, 1000 + 500 - nominator_slash_amount_11); }); } @@ -2890,7 +3972,7 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(11), 1000); - let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 11); + let exposure = Staking::eras_stakers(active_era(), 11); assert_eq!(Balances::free_balance(101), 2000); on_offence_now( diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 507cda53ed866..77e4c679f4e68 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -24,7 +24,7 @@ use sp_core::u32_trait::Value as U32; use sp_runtime::{ RuntimeDebug, ConsensusEngineId, DispatchResult, DispatchError, - traits::{MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput}, + traits::{MaybeSerializeDeserialize, AtLeast32Bit, Saturating, TrailingZeroInput, Bounded}, }; use crate::dispatch::Parameter; use crate::storage::StorageMap; @@ -87,7 +87,7 @@ impl< Created: Happened, Removed: Happened, K: FullCodec, - T: FullCodec + T: FullCodec, > StoredMap for StorageMapShim { fn get(k: &K) -> T { S::get(k) } fn is_explicit(k: &K) -> bool { S::contains_key(k) } @@ -138,6 +138,35 @@ impl< } } +/// Something that can estimate at which block the next session rotation will happen. This should +/// be the same logical unit that dictates `ShouldEndSession` to the session module. No Assumptions +/// are made about the scheduling of the sessions. +pub trait EstimateNextSessionRotation { + /// Return the block number at which the next session rotation is estimated to happen. + /// + /// None should be returned if the estimation fails to come to an answer + fn estimate_next_session_rotation(now: BlockNumber) -> Option; +} + +impl EstimateNextSessionRotation for () { + fn estimate_next_session_rotation(_: BlockNumber) -> Option { + Default::default() + } +} + +/// Something that can estimate at which block the next `new_session` will be triggered. This must +/// always be implemented by the session module. +pub trait EstimateNextNewSession { + /// Return the block number at which the next new session is estimated to happen. + fn estimate_next_new_session(now: BlockNumber) -> Option; +} + +impl EstimateNextNewSession for () { + fn estimate_next_new_session(_: BlockNumber) -> Option { + Default::default() + } +} + /// Anything that can have a `::len()` method. pub trait Len { /// Return the length of data type. diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index 5abafe7655172..a3fe3e00ca4be 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -95,7 +95,8 @@ impl Signer for TAnyAppPubl } /// Retrieves a public key type for given `SignAndSubmitTransaction`. -pub type PublicOf = < +pub type PublicOf = +< >::CreateTransaction as CreateTransaction>::Extrinsic> @@ -109,7 +110,7 @@ pub type PublicOf = < /// you should use. pub trait SignAndSubmitTransaction { /// Unchecked extrinsic type. - type Extrinsic: ExtrinsicT + codec::Encode; + type Extrinsic: ExtrinsicT + Encode; /// A runtime-specific type to produce signed data for the extrinsic. type CreateTransaction: CreateTransaction; @@ -156,7 +157,7 @@ pub trait SignAndSubmitTransaction { /// you should use. pub trait SubmitUnsignedTransaction { /// Unchecked extrinsic type. - type Extrinsic: ExtrinsicT + codec::Encode; + type Extrinsic: ExtrinsicT + Encode; /// Submit given call to the transaction pool as unsigned transaction. /// @@ -164,7 +165,8 @@ pub trait SubmitUnsignedTransaction { /// and `Err` if transaction was rejected from the pool. fn submit_unsigned(call: impl Into) -> Result<(), ()> { let xt = Self::Extrinsic::new(call.into(), None).ok_or(())?; - sp_io::offchain::submit_transaction(xt.encode()) + let encoded_xt = xt.encode(); + sp_io::offchain::submit_transaction(encoded_xt) } } @@ -291,7 +293,7 @@ impl SignAndSubmitTransaction for TransactionSubmitte T: crate::Trait, C: CreateTransaction, S: Signer<>::Public, >::Signature>, - E: ExtrinsicT + codec::Encode, + E: ExtrinsicT + Encode, { type Extrinsic = E; type CreateTransaction = C; @@ -301,7 +303,7 @@ impl SignAndSubmitTransaction for TransactionSubmitte /// A blanket implementation to use the same submitter for unsigned transactions as well. impl SubmitUnsignedTransaction for TransactionSubmitter where T: crate::Trait, - E: ExtrinsicT + codec::Encode, + E: ExtrinsicT + Encode, { type Extrinsic = E; } @@ -310,7 +312,7 @@ impl SubmitUnsignedTransaction for TransactionSubmitt impl SubmitSignedTransaction for TransactionSubmitter where T: crate::Trait, C: CreateTransaction, - E: ExtrinsicT + codec::Encode, + E: ExtrinsicT + Encode, S: Signer<>::Public, >::Signature>, // Make sure we can unwrap the app crypto key. S: RuntimeAppPublic + AppPublic + Into<::Generic>, diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index a9da3830614cc..1078927747b22 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -106,11 +106,6 @@ impl Module { /// /// All dispatchables must be annotated with weight and will have some fee info. This function /// always returns. - // NOTE: we can actually make it understand `ChargeTransactionPayment`, but would be some hassle - // for sure. We have to make it aware of the index of `ChargeTransactionPayment` in `Extra`. - // Alternatively, we could actually execute the tx's per-dispatch and record the balance of the - // sender before and after the pipeline.. but this is way too much hassle for a very very little - // potential gain in the future. pub fn query_info( unchecked_extrinsic: Extrinsic, len: u32, @@ -119,6 +114,11 @@ impl Module { T: Send + Sync, BalanceOf: Send + Sync, { + // NOTE: we can actually make it understand `ChargeTransactionPayment`, but would be some + // hassle for sure. We have to make it aware of the index of `ChargeTransactionPayment` in + // `Extra`. Alternatively, we could actually execute the tx's per-dispatch and record the + // balance of the sender before and after the pipeline.. but this is way too much hassle for + // a very very little potential gain in the future. let dispatch_info = ::get_dispatch_info(&unchecked_extrinsic); let partial_fee = diff --git a/primitives/arithmetic/fuzzer/Cargo.lock b/primitives/arithmetic/fuzzer/Cargo.lock new file mode 100644 index 0000000000000..c7b703c139127 --- /dev/null +++ b/primitives/arithmetic/fuzzer/Cargo.lock @@ -0,0 +1,401 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arbitrary" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitvec" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" + +[[package]] +name = "byte-slice-cast" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "c2-chacha" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +dependencies = [ + "ppv-lite86", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "fixed-hash" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "honggfuzz" +version = "0.5.45" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap", +] + +[[package]] +name = "impl-codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65877bf7d44897a473350b1046277941cee20b263397e90869c50b6e766088b" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + +[[package]] +name = "parity-scale-codec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + +[[package]] +name = "primitive-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +dependencies = [ + "c2-chacha", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-arithmetic" +version = "2.0.0-alpha.3" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-arithmetic-fuzzer" +version = "2.0.0" +dependencies = [ + "honggfuzz", + "num-bigint", + "num-traits", + "primitive-types", + "sp-arithmetic", +] + +[[package]] +name = "sp-debug-derive" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-std" +version = "2.0.0-alpha.3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "uint" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d" +dependencies = [ + "byteorder", + "crunchy", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index 895ee60bd86a6..717ce2713ba10 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -20,6 +20,10 @@ num-traits = "0.2" name = "biguint" path = "src/biguint.rs" +[[bin]] +name = "per_thing_rational" +path = "src/per_thing_rational.rs" + [[bin]] name = "rational128" path = "src/rational128.rs" diff --git a/primitives/arithmetic/fuzzer/src/per_thing_rational.rs b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs new file mode 100644 index 0000000000000..84207cbd16469 --- /dev/null +++ b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs @@ -0,0 +1,123 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run per_thing_rational`. `honggfuzz` CLI options can +//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! +//! # Debugging a panic +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/per_thing_rational/*.fuzz`. + +use honggfuzz::fuzz; +use sp_arithmetic::{ + PerThing, PerU16, Percent, Perbill, Perquintill, assert_eq_error_rate, + traits::SaturatedConversion, +}; + +fn main() { + loop { + fuzz!(| + data: ((u16, u16), (u32, u32), (u64, u64)) + | { + + let (u16_pair, u32_pair, u64_pair) = data; + + // peru16 + let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1)); + let ratio = PerU16::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); + let ratio = PerU16::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = PerU16::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + PerU16::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + // percent + let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1)); + let ratio = Percent::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Percent::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); + let ratio = Percent::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Percent::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = Percent::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Percent::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1, + ); + + // perbill + let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); + let ratio = Perbill::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Perbill::from_fraction(smaller as f64 / bigger.max(1) as f64), + 100, + ); + + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = Perbill::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Perbill::from_fraction(smaller as f64 / bigger.max(1) as f64), + 100, + ); + + // perquintillion + let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); + let ratio = Perquintill::from_rational_approximation(smaller, bigger); + assert_per_thing_equal_error( + ratio, + Perquintill::from_fraction(smaller as f64 / bigger.max(1) as f64), + 1000, + ); + + }) + } +} + +fn assert_per_thing_equal_error(a: T, b: T, err: u128) { + let a_abs = a.deconstruct().saturated_into::(); + let b_abs = b.deconstruct().saturated_into::(); + let diff = a_abs.max(b_abs) - a_abs.min(b_abs); + dbg!(&diff); + assert!(diff <= err, "{:?} !~ {:?}", a, b); +} diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index c2feae00b74ff..f6d8b53e3499b 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -19,7 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] /// Copied from `sp-runtime` and documented there. -#[cfg(test)] +#[macro_export] macro_rules! assert_eq_error_rate { ($x:expr, $y:expr, $error:expr $(,)?) => { assert!( @@ -40,5 +40,17 @@ mod fixed64; mod rational128; pub use fixed64::Fixed64; -pub use per_things::{PerThing, Percent, Permill, Perbill, Perquintill}; +pub use per_things::{PerThing, Percent, PerU16, Permill, Perbill, Perquintill}; pub use rational128::Rational128; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn peru16_rational_does_not_overflow() { + // A historical example that will panic only for per_thing type that are created with + // maximum capacity of their type, e.g. PerU16. + let _ = PerU16::from_rational_approximation(17424870u32, 17424870); + } +} diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index cbb804baf5d49..ca6967456b1b9 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -17,18 +17,24 @@ #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; -use sp_std::{ops, prelude::*, convert::TryInto}; +use sp_std::{ops, fmt, prelude::*, convert::TryInto}; use codec::{Encode, Decode, CompactAs}; -use crate::traits::{ - SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, +use crate::{ + traits::{SaturatedConversion, UniqueSaturatedInto, Saturating, BaseArithmetic, Bounded}, }; use sp_debug_derive::RuntimeDebug; /// Something that implements a fixed point ration with an arbitrary granularity `X`, as _parts per /// `X`_. -pub trait PerThing: Sized + Saturating + Copy { +pub trait PerThing: + Sized + Saturating + Copy + Default + Eq + PartialEq + Ord + PartialOrd + Bounded + fmt::Debug +{ /// The data type used to build this per-thingy. - type Inner: BaseArithmetic + Copy; + type Inner: BaseArithmetic + Copy + fmt::Debug; + + /// The data type that is used to store values bigger than the maximum of this type. This must + /// at least be able to store `Self::ACCURACY * Self::ACCURACY`. + type Upper: BaseArithmetic + Copy + fmt::Debug; /// accuracy of this type const ACCURACY: Self::Inner; @@ -63,12 +69,53 @@ pub trait PerThing: Sized + Saturating + Copy { /// The computation of this approximation is performed in the generic type `N`. Given /// `M` as the data type that can hold the maximum value of this per-thing (e.g. u32 for /// perbill), this can only work if `N == M` or `N: From + TryInto`. + /// + /// Note that this always rounds _down_, i.e. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // 989/100 is technically closer to 99%. + /// assert_eq!( + /// Percent::from_rational_approximation(989, 1000), + /// Percent::from_parts(98), + /// ); + /// # } + /// ``` fn from_rational_approximation(p: N, q: N) -> Self - where N: Clone + Ord + From + TryInto + ops::Div; + where N: + Clone + Ord + From + TryInto + TryInto + + ops::Div + ops::Rem + ops::Add; + + /// A mul implementation that always rounds down, whilst the standard `Mul` implementation + /// rounds to the nearest numbers + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing}; + /// # fn main () { + /// // rounds to closest + /// assert_eq!(Percent::from_percent(34) * 10u64, 3); + /// assert_eq!(Percent::from_percent(36) * 10u64, 4); + /// + /// // collapse down + /// assert_eq!(Percent::from_percent(34).mul_collapse(10u64), 3); + /// assert_eq!(Percent::from_percent(36).mul_collapse(10u64), 3); + /// # } + /// ``` + fn mul_collapse(self, b: N) -> N + where N: Clone + From + UniqueSaturatedInto + ops::Rem + + ops::Div + ops::Mul + ops::Add; } macro_rules! implement_per_thing { - ($name:ident, $test_mod:ident, [$($test_units:tt),+], $max:tt, $type:ty, $upper_type:ty, $title:expr $(,)?) => { + ( + $name:ident, + $test_mod:ident, + [$($test_units:tt),+], + $max:tt, $type:ty, + $upper_type:ty, + $title:expr $(,)? + ) => { /// A fixed point representation of a number between in the range [0, 1]. /// #[doc = $title] @@ -78,33 +125,30 @@ macro_rules! implement_per_thing { impl PerThing for $name { type Inner = $type; + type Upper = $upper_type; - /// The accuracy of this type. const ACCURACY: Self::Inner = $max; - /// Nothing. fn zero() -> Self { Self(0) } - /// `true` if this is nothing. fn is_zero(&self) -> bool { self.0 == 0 } - /// Everything. fn one() -> Self { Self($max) } - /// Consume self and deconstruct into a raw numeric type. fn deconstruct(self) -> Self::Inner { self.0 } - /// From an explicitly defined number of parts per maximum of the type. + // needed only for peru16. Since peru16 is the only type in which $max == + // $type::max_value(), rustc is being a smart-a** here by warning that the comparison + // is not needed. + #[allow(unused_comparisons)] fn from_parts(parts: Self::Inner) -> Self { Self([parts, $max][(parts > $max) as usize]) } - /// Converts a percent into `Self`. Equal to `x / 100`. fn from_percent(x: Self::Inner) -> Self { - Self([x, 100][(x > 100) as usize] * ($max / 100)) + Self::from_rational_approximation([x, 100][(x > 100) as usize] as $upper_type, 100) } - /// Return the product of multiplication of this value by itself. fn square(self) -> Self { // both can be safely casted and multiplied. let p: $upper_type = self.0 as $upper_type * self.0 as $upper_type; @@ -112,39 +156,43 @@ macro_rules! implement_per_thing { Self::from_rational_approximation(p, q) } - /// Converts a fraction into `Self`. #[cfg(feature = "std")] fn from_fraction(x: f64) -> Self { Self((x * ($max as f64)) as Self::Inner) } - /// Approximate the fraction `p/q` into a per-thing fraction. This will never overflow. - /// - /// The computation of this approximation is performed in the generic type `N`. Given - /// `M` as the data type that can hold the maximum value of this per-thing (e.g. u32 for - /// perbill), this can only work if `N == M` or `N: From + TryInto`. fn from_rational_approximation(p: N, q: N) -> Self - where N: Clone + Ord + From + TryInto + ops::Div + where N: + Clone + Ord + From + TryInto + TryInto + + ops::Div + ops::Rem + ops::Add { + let div_ceil = |x: N, f: N| -> N { + let mut o = x.clone() / f.clone(); + let r = x.rem(f.clone()); + if r > N::from(0) { + o = o + N::from(1); + } + o + }; + // q cannot be zero. - let q = q.max((1 as Self::Inner).into()); + let q: N = q.max((1 as Self::Inner).into()); // p should not be bigger than q. - let p = p.min(q.clone()); + let p: N = p.min(q.clone()); - let factor = (q.clone() / $max.into()).max((1 as Self::Inner).into()); + let factor: N = div_ceil(q.clone(), $max.into()).max((1 as Self::Inner).into()); - // q cannot overflow: (q / (q/$max)) < 2 * $max. p < q hence p also cannot overflow. - // this implies that Self::Inner must be able to fit 2 * $max. - let q_reduce: Self::Inner = (q / factor.clone()) + // q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow. + let q_reduce: $type = (q.clone() / factor.clone()) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / (q/$max) < (2 * $max). Macro prevents any type being created that \ + "q / ceil(q/$max) < $max. Macro prevents any type being created that \ does not satisfy this; qed" ); - let p_reduce: Self::Inner = (p / factor.clone()) + let p_reduce: $type = (p / factor) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / (q/$max) < (2 * $max). Macro prevents any type being created that \ + "q / ceil(q/$max) < $max. Macro prevents any type being created that \ does not satisfy this; qed" ); @@ -157,13 +205,49 @@ macro_rules! implement_per_thing { $name(part as Self::Inner) } + + fn mul_collapse(self, b: N) -> N + where + N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem + + ops::Div + ops::Mul + ops::Add + { + let maximum: N = $max.into(); + let upper_max: $upper_type = $max.into(); + let part: N = self.0.into(); + + let rem_multiplied_divided = { + let rem = b.clone().rem(maximum.clone()); + + // `rem_sized` is inferior to $max, thus it fits into $type. This is assured by + // a test. + let rem_sized = rem.saturated_into::<$type>(); + + // `self` and `rem_sized` are inferior to $max, thus the product is less than + // $max^2 and fits into $upper_type. This is assured by a test. + let rem_multiplied_upper = rem_sized as $upper_type * self.0 as $upper_type; + + // `rem_multiplied_upper` is less than $max^2 therefore divided by $max it fits + // in $type. remember that $type always fits $max. + let rem_multiplied_divided_sized = + (rem_multiplied_upper / upper_max) as $type; + + // `rem_multiplied_divided_sized` is inferior to b, thus it can be converted + // back to N type + rem_multiplied_divided_sized.into() + }; + + (b / maximum) * part + rem_multiplied_divided + } } - /// Implement const functions impl $name { /// From an explicitly defined number of parts per maximum of the type. /// /// This can be called at compile time. + // needed only for peru16. Since peru16 is the only type in which $max == + // $type::max_value(), rustc is being a smart-a** here by warning that the comparison + // is not needed. + #[allow(unused_comparisons)] pub const fn from_parts(parts: $type) -> Self { Self([parts, $max][(parts > $max) as usize]) } @@ -202,6 +286,16 @@ macro_rules! implement_per_thing { } } + impl crate::traits::Bounded for $name { + fn min_value() -> Self { + ::zero() + } + + fn max_value() -> Self { + ::one() + } + } + impl ops::Div for $name { type Output = Self; @@ -212,9 +306,10 @@ macro_rules! implement_per_thing { } } - /// Overflow-prune multiplication. + /// Non-overflow multiplication. /// /// tailored to be used with a balance type. + /// impl ops::Mul for $name where N: Clone + From<$type> + UniqueSaturatedInto<$type> + ops::Rem @@ -241,6 +336,7 @@ macro_rules! implement_per_thing { // in $type. remember that $type always fits $max. let mut rem_multiplied_divided_sized = (rem_multiplied_upper / upper_max) as $type; + // fix a tiny rounding error if rem_multiplied_upper % upper_max > upper_max / 2 { rem_multiplied_divided_sized += 1; @@ -261,15 +357,17 @@ macro_rules! implement_per_thing { use super::{$name, Saturating, RuntimeDebug, PerThing}; use crate::traits::Zero; - #[test] fn macro_expanded_correctly() { - // needed for the `from_percent` to work. - assert!($max >= 100); - assert!($max % 100 == 0); + // needed for the `from_percent` to work. UPDATE: this is no longer needed; yet note + // that tests that use percentage or fractions such as $name::from_fraction(0.2) to + // create values will most likely be inaccurate when used with per_things that are + // not multiples of 100. + // assert!($max >= 100); + // assert!($max % 100 == 0); // needed for `from_rational_approximation` - assert!(2 * $max < <$type>::max_value()); + assert!(2 * ($max as $upper_type) < <$upper_type>::max_value()); assert!(<$upper_type>::from($max) < <$upper_type>::max_value()); // for something like percent they can be the same. @@ -298,7 +396,7 @@ macro_rules! implement_per_thing { (63, 1), (64, 2), (65, 2), - (<$type>::max_value(), <$type>::max_value().encode().len() + 1) + // (<$type>::max_value(), <$type>::max_value().encode().len() + 1) ]; for &(n, l) in &tests { let compact: codec::Compact<$name> = $name(n).into(); @@ -317,33 +415,73 @@ macro_rules! implement_per_thing { assert_eq!($name::zero(), $name::from_parts(Zero::zero())); assert_eq!($name::one(), $name::from_parts($max)); assert_eq!($name::ACCURACY, $max); - assert_eq!($name::from_percent(0), $name::from_parts(Zero::zero())); - assert_eq!($name::from_percent(10), $name::from_parts($max / 10)); - assert_eq!($name::from_percent(100), $name::from_parts($max)); + assert_eq!($name::from_fraction(0.0), $name::from_parts(Zero::zero())); + assert_eq!($name::from_fraction(0.1), $name::from_parts($max / 10)); + assert_eq!($name::from_fraction(1.0), $name::from_parts($max)); + } + + macro_rules! u256ify { + ($val:expr) => { + Into::::into($val) + }; } macro_rules! per_thing_mul_test { ($num_type:tt) => { // multiplication from all sort of from_percent assert_eq!( - $name::from_percent(100) * $num_type::max_value(), + $name::from_fraction(1.0) * $num_type::max_value(), $num_type::max_value() ); - assert_eq_error_rate!( - $name::from_percent(99) * $num_type::max_value(), - ((Into::::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type, - 1, - ); - assert_eq!( - $name::from_percent(50) * $num_type::max_value(), - $num_type::max_value() / 2, - ); - assert_eq_error_rate!( - $name::from_percent(1) * $num_type::max_value(), - $num_type::max_value() / 100, - 1, - ); - assert_eq!($name::from_percent(0) * $num_type::max_value(), 0); + if $max % 100 == 0 { + assert_eq_error_rate!( + $name::from_percent(99) * $num_type::max_value(), + ((Into::::into($num_type::max_value()) * 99u32) / 100u32).as_u128() as $num_type, + 1, + ); + assert_eq!( + $name::from_fraction(0.5) * $num_type::max_value(), + $num_type::max_value() / 2, + ); + assert_eq_error_rate!( + $name::from_percent(1) * $num_type::max_value(), + $num_type::max_value() / 100, + 1, + ); + } else { + assert_eq!( + $name::from_fraction(0.99) * <$num_type>::max_value(), + ( + ( + u256ify!($name::from_fraction(0.99).0) * + u256ify!(<$num_type>::max_value()) / + u256ify!($max) + ).as_u128() + ) as $num_type, + ); + assert_eq!( + $name::from_fraction(0.50) * <$num_type>::max_value(), + ( + ( + u256ify!($name::from_fraction(0.50).0) * + u256ify!(<$num_type>::max_value()) / + u256ify!($max) + ).as_u128() + ) as $num_type, + ); + assert_eq!( + $name::from_fraction(0.01) * <$num_type>::max_value(), + ( + ( + u256ify!($name::from_fraction(0.01).0) * + u256ify!(<$num_type>::max_value()) / + u256ify!($max) + ).as_u128() + ) as $num_type, + ); + } + + assert_eq!($name::from_fraction(0.0) * $num_type::max_value(), 0); // // multiplication with bounds assert_eq!($name::one() * $num_type::max_value(), $num_type::max_value()); @@ -356,17 +494,20 @@ macro_rules! implement_per_thing { use primitive_types::U256; // accuracy test - assert_eq!($name::from_rational_approximation(1 as $type, 3) * 30 as $type, 10); + assert_eq!( + $name::from_rational_approximation(1 as $type, 3) * 30 as $type, + 10, + ); $(per_thing_mul_test!($test_units);)* } #[test] fn per_thing_mul_rounds_to_nearest_number() { - assert_eq!($name::from_percent(33) * 10u64, 3); - assert_eq!($name::from_percent(34) * 10u64, 3); - assert_eq!($name::from_percent(35) * 10u64, 3); - assert_eq!($name::from_percent(36) * 10u64, 4); + assert_eq!($name::from_fraction(0.33) * 10u64, 3); + assert_eq!($name::from_fraction(0.34) * 10u64, 3); + assert_eq!($name::from_fraction(0.35) * 10u64, 3); + assert_eq!($name::from_fraction(0.36) * 10u64, 4); } #[test] @@ -398,31 +539,32 @@ macro_rules! implement_per_thing { ); assert_eq!( $name::from_rational_approximation(1 as $num_type, 10), - $name::from_percent(10), + $name::from_fraction(0.10), ); assert_eq!( $name::from_rational_approximation(1 as $num_type, 4), - $name::from_percent(25), + $name::from_fraction(0.25), ); assert_eq!( $name::from_rational_approximation(1 as $num_type, 4), $name::from_rational_approximation(2 as $num_type, 8), ); // no accurate anymore but won't overflow. - assert_eq!( + assert_eq_error_rate!( $name::from_rational_approximation( $num_type::max_value() - 1, $num_type::max_value() - ), - $name::one(), + ).0 as $upper_type, + $name::one().0 as $upper_type, + 2, ); assert_eq_error_rate!( $name::from_rational_approximation( $num_type::max_value() / 3, $num_type::max_value() - ).0, - $name::from_parts($max / 3).0, - 2 + ).0 as $upper_type, + $name::from_parts($max / 3).0 as $upper_type, + 2, ); assert_eq!( $name::from_rational_approximation(1, $num_type::max_value()), @@ -436,13 +578,14 @@ macro_rules! implement_per_thing { // This is just to make sure something like Percent which _might_ get built from a // u8 does not overflow in the context of this test. let max_value = <$upper_type>::from($max); + // almost at the edge assert_eq!( - $name::from_rational_approximation($max - 1, $max + 1), + $name::from_rational_approximation(max_value - 1, max_value + 1), $name::from_parts($max - 2), ); assert_eq!( - $name::from_rational_approximation(1, $max-1), + $name::from_rational_approximation(1, $max - 1), $name::from_parts(1), ); assert_eq!( @@ -450,76 +593,83 @@ macro_rules! implement_per_thing { $name::from_parts(1), ); assert_eq!( - $name::from_rational_approximation(2, 2 * $max - 1), + $name::from_rational_approximation(2, 2 * max_value - 1), $name::from_parts(1), ); assert_eq!( - $name::from_rational_approximation(1, $max+1), + $name::from_rational_approximation(1, max_value + 1), $name::zero(), ); assert_eq!( $name::from_rational_approximation(3 * max_value / 2, 3 * max_value), - $name::from_percent(50), + $name::from_fraction(0.5), ); + $(per_thing_from_rationale_approx_test!($test_units);)* } #[test] fn per_things_mul_operates_in_output_type() { - // assert_eq!($name::from_percent(50) * 100u32, 50u32); - assert_eq!($name::from_percent(50) * 100u64, 50u64); - assert_eq!($name::from_percent(50) * 100u128, 50u128); + // assert_eq!($name::from_fraction(0.5) * 100u32, 50u32); + assert_eq!($name::from_fraction(0.5) * 100u64, 50u64); + assert_eq!($name::from_fraction(0.5) * 100u128, 50u128); } #[test] fn per_thing_saturating_op_works() { - assert_eq!( - $name::from_percent(50).saturating_add($name::from_percent(40)), - $name::from_percent(90) + assert_eq_error_rate!( + $name::from_fraction(0.5).saturating_add($name::from_fraction(0.4)).0 as $upper_type, + $name::from_fraction(0.9).0 as $upper_type, + 2, ); - assert_eq!( - $name::from_percent(50).saturating_add($name::from_percent(50)), - $name::from_percent(100) + assert_eq_error_rate!( + $name::from_fraction(0.5).saturating_add($name::from_fraction(0.5)).0 as $upper_type, + $name::one().0 as $upper_type, + 2, ); assert_eq!( - $name::from_percent(60).saturating_add($name::from_percent(50)), - $name::from_percent(100) + $name::from_fraction(0.6).saturating_add($name::from_fraction(0.5)), + $name::one(), ); - assert_eq!( - $name::from_percent(60).saturating_sub($name::from_percent(50)), - $name::from_percent(10) + assert_eq_error_rate!( + $name::from_fraction(0.6).saturating_sub($name::from_fraction(0.5)).0 as $upper_type, + $name::from_fraction(0.1).0 as $upper_type, + 2, ); assert_eq!( - $name::from_percent(60).saturating_sub($name::from_percent(60)), - $name::from_percent(0) + $name::from_fraction(0.6).saturating_sub($name::from_fraction(0.6)), + $name::from_fraction(0.0), ); assert_eq!( - $name::from_percent(60).saturating_sub($name::from_percent(70)), - $name::from_percent(0) + $name::from_fraction(0.6).saturating_sub($name::from_fraction(0.7)), + $name::from_fraction(0.0), ); - assert_eq!( - $name::from_percent(50).saturating_mul($name::from_percent(50)), - $name::from_percent(25) + assert_eq_error_rate!( + $name::from_fraction(0.5).saturating_mul($name::from_fraction(0.5)).0 as $upper_type, + $name::from_fraction(0.25).0 as $upper_type, + 2, ); - assert_eq!( - $name::from_percent(20).saturating_mul($name::from_percent(20)), - $name::from_percent(4) + assert_eq_error_rate!( + $name::from_fraction(0.2).saturating_mul($name::from_fraction(0.2)).0 as $upper_type, + $name::from_fraction(0.04).0 as $upper_type, + 2, ); - assert_eq!( - $name::from_percent(10).saturating_mul($name::from_percent(10)), - $name::from_percent(1) + assert_eq_error_rate!( + $name::from_fraction(0.1).saturating_mul($name::from_fraction(0.1)).0 as $upper_type, + $name::from_fraction(0.01).0 as $upper_type, + 1, ); } #[test] fn per_thing_square_works() { - assert_eq!($name::from_percent(100).square(), $name::from_percent(100)); - assert_eq!($name::from_percent(50).square(), $name::from_percent(25)); - assert_eq!($name::from_percent(10).square(), $name::from_percent(1)); + assert_eq!($name::from_fraction(1.0).square(), $name::from_fraction(1.0)); + assert_eq!($name::from_fraction(0.5).square(), $name::from_fraction(0.25)); + assert_eq!($name::from_fraction(0.1).square(), $name::from_fraction(0.01)); assert_eq!( - $name::from_percent(2).square(), + $name::from_fraction(0.02).square(), $name::from_parts((4 * <$upper_type>::from($max) / 100 / 100) as $type) ); } @@ -527,22 +677,32 @@ macro_rules! implement_per_thing { #[test] fn per_things_div_works() { // normal - assert_eq!($name::from_percent(10) / $name::from_percent(20), - $name::from_percent(50) + assert_eq_error_rate!( + ($name::from_fraction(0.1) / $name::from_fraction(0.20)).0 as $upper_type, + $name::from_fraction(0.50).0 as $upper_type, + 2, ); - assert_eq!($name::from_percent(10) / $name::from_percent(10), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(0.1) / $name::from_fraction(0.10)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, ); - assert_eq!($name::from_percent(10) / $name::from_percent(0), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(0.1) / $name::from_fraction(0.0)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, ); // will not overflow - assert_eq!($name::from_percent(10) / $name::from_percent(5), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(0.10) / $name::from_fraction(0.05)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, ); - assert_eq!($name::from_percent(100) / $name::from_percent(50), - $name::from_percent(100) + assert_eq_error_rate!( + ($name::from_fraction(1.0) / $name::from_fraction(0.5)).0 as $upper_type, + $name::from_fraction(1.0).0 as $upper_type, + 2, ); } } @@ -558,6 +718,15 @@ implement_per_thing!( u16, "_Percent_", ); +implement_per_thing!( + PerU16, + test_peru16, + [u32, u64, u128], + 65535_u16, + u16, + u32, + "_Parts per 65535_", +); implement_per_thing!( Permill, test_permill, diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs index 635160aa00b71..265572dbdaee7 100644 --- a/primitives/consensus/vrf/src/schnorrkel.rs +++ b/primitives/consensus/vrf/src/schnorrkel.rs @@ -17,8 +17,6 @@ //! Schnorrkel-based VRF. use codec::{Encode, Decode}; -#[cfg(feature = "std")] -use sp_core::U512; use sp_runtime::RuntimeDebug; use sp_std::ops::{Deref, DerefMut}; #[cfg(feature = "std")] @@ -27,6 +25,8 @@ use std::convert::TryFrom; use codec::EncodeLike; #[cfg(feature = "std")] use schnorrkel::errors::MultiSignatureStage; +#[cfg(feature = "std")] +use sp_core::U512; #[cfg(feature = "std")] pub use schnorrkel::{SignatureError, vrf::{VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH}}; diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index a9c118ce8f5ed..c073862f29e06 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -962,6 +962,8 @@ pub mod key_types { pub const IM_ONLINE: KeyTypeId = KeyTypeId(*b"imon"); /// Key type for AuthorityDiscovery module, built-in. pub const AUTHORITY_DISCOVERY: KeyTypeId = KeyTypeId(*b"audi"); + /// Key type for staking, built-in. + pub const STAKING: KeyTypeId = KeyTypeId(*b"stak"); /// A key type ID useful for tests. pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy"); } diff --git a/primitives/phragmen/Cargo.toml b/primitives/phragmen/Cargo.toml index 2290b44c5ff0b..088466d9fbd9e 100644 --- a/primitives/phragmen/Cargo.toml +++ b/primitives/phragmen/Cargo.toml @@ -6,21 +6,25 @@ edition = "2018" license = "GPL-3.0" homepage = "https://substrate.dev" repository = "https://github.com/paritytech/substrate/" -description = "PHRAGMENT primitives" +description = "Phragmen primitives" [dependencies] +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } serde = { version = "1.0.101", optional = true, features = ["derive"] } sp-std = { version = "2.0.0-alpha.5", default-features = false, path = "../std" } sp-runtime = { version = "2.0.0-alpha.5", default-features = false, path = "../../primitives/runtime" } +sp-phragmen-compact = { version = "2.0.0-alpha.4", path = "./compact" } [dev-dependencies] substrate-test-utils = { version = "2.0.0-alpha.5", path = "../../test-utils" } -sp-io ={ version = "2.0.0-alpha.5", path = "../../primitives/io" } -rand = "0.7.2" +rand = "0.7.3" +sp-phragmen = { path = "." } [features] default = ["std"] +bench = [] std = [ + "codec/std", "serde", "sp-std/std", "sp-runtime/std", diff --git a/primitives/phragmen/benches/phragmen.rs b/primitives/phragmen/benches/phragmen.rs index aa99e6f384ba6..33da0b19563a8 100644 --- a/primitives/phragmen/benches/phragmen.rs +++ b/primitives/phragmen/benches/phragmen.rs @@ -16,6 +16,7 @@ //! Note that execution times will not be accurate in an absolute scale, since //! - Everything is executed in the context of `TestExternalities` //! - Everything is executed in native environment. + #![cfg(feature = "bench")] #![feature(test)] diff --git a/primitives/phragmen/compact/Cargo.toml b/primitives/phragmen/compact/Cargo.toml new file mode 100644 index 0000000000000..fb61a90b09b6e --- /dev/null +++ b/primitives/phragmen/compact/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "Phragmen Compact Solution" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0.7", features = ["full", "visit"] } +quote = "1.0" +proc-macro2 = "1.0.6" +proc-macro-crate = "0.1.4" diff --git a/primitives/phragmen/compact/src/assignment.rs b/primitives/phragmen/compact/src/assignment.rs new file mode 100644 index 0000000000000..587e482ccb220 --- /dev/null +++ b/primitives/phragmen/compact/src/assignment.rs @@ -0,0 +1,210 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Code generation for the ratio assignment type. + +use crate::field_name_for; +use proc_macro2::{TokenStream as TokenStream2}; +use syn::{GenericArgument}; +use quote::quote; + +fn from_impl(count: usize) -> TokenStream2 { + let from_impl_single = { + let name = field_name_for(1); + quote!(1 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_double = { + let name = field_name_for(2); + quote!(2 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ( + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution[0].1, + ), + index_of_target(&distribution[1].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_rest = (3..=count).map(|c| { + let inner = (0..c-1).map(|i| + quote!((index_of_target(&distribution[#i].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, distribution[#i].1),) + ).collect::(); + + let field_name = field_name_for(c); + let last_index = c - 1; + let last = quote!(index_of_target(&distribution[#last_index].0).ok_or(_phragmen::Error::CompactInvalidIndex)?); + + quote!( + #c => compact.#field_name.push((index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, [#inner], #last)), + ) + }).collect::(); + + quote!( + #from_impl_single + #from_impl_double + #from_impl_rest + ) +} + +fn into_impl(count: usize) -> TokenStream2 { + let into_impl_single = { + let name = field_name_for(1); + quote!( + for (voter_index, target_index) in self.#name { + assignments.push(_phragmen::Assignment { + who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution: vec![ + (target_at(target_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, Accuracy::one()) + ], + }) + } + ) + }; + + let into_impl_double = { + let name = field_name_for(2); + quote!( + for (voter_index, (t1_idx, p1), t2_idx) in self.#name { + if p1 >= Accuracy::one() { + return Err(_phragmen::Error::CompactStakeOverflow); + } + + // defensive only. Since Percent doesn't have `Sub`. + let p2 = _phragmen::sp_runtime::traits::Saturating::saturating_sub( + Accuracy::one(), + p1, + ); + + assignments.push( _phragmen::Assignment { + who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution: vec![ + (target_at(t1_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p1), + (target_at(t2_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p2), + ] + }); + } + ) + }; + + let into_impl_rest = (3..=count).map(|c| { + let name = field_name_for(c); + quote!( + for (voter_index, inners, t_last_idx) in self.#name { + let mut sum = Accuracy::zero(); + let mut inners_parsed = inners + .iter() + .map(|(ref t_idx, p)| { + sum = _phragmen::sp_runtime::traits::Saturating::saturating_add(sum, *p); + let target = target_at(*t_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?; + Ok((target, *p)) + }) + .collect::, _phragmen::Error>>()?; + + if sum >= Accuracy::one() { + return Err(_phragmen::Error::CompactStakeOverflow); + } + + // defensive only. Since Percent doesn't have `Sub`. + let p_last = _phragmen::sp_runtime::traits::Saturating::saturating_sub( + Accuracy::one(), + sum, + ); + + inners_parsed.push((target_at(t_last_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, p_last)); + + assignments.push(_phragmen::Assignment { + who: voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution: inners_parsed, + }); + } + ) + }).collect::(); + + quote!( + #into_impl_single + #into_impl_double + #into_impl_rest + ) +} + +pub(crate) fn assignment( + ident: syn::Ident, + voter_type: GenericArgument, + target_type: GenericArgument, + count: usize, +) -> TokenStream2 { + + let from_impl = from_impl(count); + let into_impl = into_impl(count); + + quote!( + impl< + #voter_type: _phragmen::codec::Codec + Default + Copy, + #target_type: _phragmen::codec::Codec + Default + Copy, + Accuracy: + _phragmen::codec::Codec + Default + Clone + _phragmen::sp_runtime::PerThing + + PartialOrd, + > + #ident<#voter_type, #target_type, Accuracy> + { + pub fn from_assignment( + assignments: Vec<_phragmen::Assignment>, + index_of_voter: FV, + index_of_target: FT, + ) -> Result + where + for<'r> FV: Fn(&'r A) -> Option<#voter_type>, + for<'r> FT: Fn(&'r A) -> Option<#target_type>, + A: _phragmen::IdentifierT, + { + let mut compact: #ident< + #voter_type, + #target_type, + Accuracy, + > = Default::default(); + + for _phragmen::Assignment { who, distribution } in assignments { + match distribution.len() { + 0 => continue, + #from_impl + _ => { + return Err(_phragmen::Error::CompactTargetOverflow); + } + } + }; + Ok(compact) + } + + pub fn into_assignment( + self, + voter_at: impl Fn(#voter_type) -> Option, + target_at: impl Fn(#target_type) -> Option, + ) -> Result>, _phragmen::Error> { + let mut assignments: Vec<_phragmen::Assignment> = Default::default(); + #into_impl + Ok(assignments) + } + } + ) +} diff --git a/primitives/phragmen/compact/src/lib.rs b/primitives/phragmen/compact/src/lib.rs new file mode 100644 index 0000000000000..114aeaeb32ed9 --- /dev/null +++ b/primitives/phragmen/compact/src/lib.rs @@ -0,0 +1,219 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Proc macro for phragmen compact assignment. + +use proc_macro::TokenStream; +use proc_macro2::{TokenStream as TokenStream2, Span, Ident}; +use proc_macro_crate::crate_name; +use quote::quote; +use syn::{GenericArgument, Type, parse::{Parse, ParseStream, Result}}; + +mod assignment; +mod staked; + +// prefix used for struct fields in compact. +const PREFIX: &'static str = "votes"; + +/// Generates a struct to store the phragmen assignments in a compact way. The struct can only store +/// distributions up to the given input count. The given count must be greater than 2. +/// +/// ```ignore +/// // generate a struct with nominator and edge weight u128, with maximum supported +/// // edge per voter of 16. +/// generate_compact_solution_type(pub TestCompact, 16) +/// ``` +/// +/// This generates: +/// +/// ```ignore +/// pub struct TestCompact { +/// votes1: Vec<(V, T)>, +/// votes2: Vec<(V, (T, W), T)>, +/// votes3: Vec<(V, [(T, W); 2usize], T)>, +/// votes4: Vec<(V, [(T, W); 3usize], T)>, +/// votes5: Vec<(V, [(T, W); 4usize], T)>, +/// votes6: Vec<(V, [(T, W); 5usize], T)>, +/// votes7: Vec<(V, [(T, W); 6usize], T)>, +/// votes8: Vec<(V, [(T, W); 7usize], T)>, +/// votes9: Vec<(V, [(T, W); 8usize], T)>, +/// votes10: Vec<(V, [(T, W); 9usize], T)>, +/// votes11: Vec<(V, [(T, W); 10usize], T)>, +/// votes12: Vec<(V, [(T, W); 11usize], T)>, +/// votes13: Vec<(V, [(T, W); 12usize], T)>, +/// votes14: Vec<(V, [(T, W); 13usize], T)>, +/// votes15: Vec<(V, [(T, W); 14usize], T)>, +/// votes16: Vec<(V, [(T, W); 15usize], T)>, +/// } +/// ``` +/// +/// The generic arguments are: +/// - `V`: identifier/index for voter (nominator) types. +/// - `T` identifier/index for candidate (validator) types. +/// - `W` weight type. +/// +/// Some conversion implementations are provided by default if +/// - `W` is u128, or +/// - `W` is anything that implements `PerThing` (such as `Perbill`) +/// +/// The ideas behind the structure are as follows: +/// +/// - For single distribution, no weight is stored. The weight is known to be 100%. +/// - For all the rest, the weight if the last distribution is omitted. This value can be computed +/// from the rest. +/// +#[proc_macro] +pub fn generate_compact_solution_type(item: TokenStream) -> TokenStream { + let CompactSolutionDef { + vis, + ident, + count, + } = syn::parse_macro_input!(item as CompactSolutionDef); + + let voter_type = GenericArgument::Type(Type::Verbatim(quote!(V))); + let target_type = GenericArgument::Type(Type::Verbatim(quote!(T))); + let weight_type = GenericArgument::Type(Type::Verbatim(quote!(W))); + + let imports = imports().unwrap_or_else(|e| e.to_compile_error()); + + let compact_def = struct_def( + vis, + ident.clone(), + count, + voter_type.clone(), + target_type.clone(), + weight_type, + ).unwrap_or_else(|e| e.to_compile_error()); + + let assignment_impls = assignment::assignment( + ident.clone(), + voter_type.clone(), + target_type.clone(), + count, + ); + + let staked_impls = staked::staked( + ident, + voter_type, + target_type, + count, + ); + + quote!( + #imports + #compact_def + #assignment_impls + #staked_impls + ).into() +} + +fn struct_def( + vis: syn::Visibility, + ident: syn::Ident, + count: usize, + voter_type: GenericArgument, + target_type: GenericArgument, + weight_type: GenericArgument, +) -> Result { + if count <= 2 { + Err(syn::Error::new( + Span::call_site(), + "cannot build compact solution struct with capacity less than 2." + ))? + } + + let singles = { + let name = field_name_for(1); + quote!(#name: Vec<(#voter_type, #target_type)>,) + }; + + let doubles = { + let name = field_name_for(2); + quote!(#name: Vec<(#voter_type, (#target_type, #weight_type), #target_type)>,) + }; + + let rest = (3..=count).map(|c| { + let field_name = field_name_for(c); + let array_len = c - 1; + quote!( + #field_name: Vec<( + #voter_type, + [(#target_type, #weight_type); #array_len], + #target_type + )>, + ) + }).collect::(); + + Ok(quote! ( + /// A struct to encode a Phragmen assignment in a compact way. + #[derive( + Default, + PartialEq, + Eq, + Clone, + _phragmen::sp_runtime::RuntimeDebug, + _phragmen::codec::Encode, + _phragmen::codec::Decode, + )] + #vis struct #ident<#voter_type, #target_type, #weight_type> { + // _marker: sp_std::marker::PhantomData, + #singles + #doubles + #rest + } + + impl<#voter_type, #target_type, #weight_type> _phragmen::VotingLimit + for #ident<#voter_type, #target_type, #weight_type> + { + const LIMIT: usize = #count; + } + )) +} + +fn imports() -> Result { + let sp_phragmen_imports = match crate_name("sp-phragmen") { + Ok(sp_phragmen) => { + let ident = syn::Ident::new(&sp_phragmen, Span::call_site()); + quote!( extern crate #ident as _phragmen; ) + } + Err(e) => return Err(syn::Error::new(Span::call_site(), &e)), + }; + + Ok(quote!( + #sp_phragmen_imports + )) +} + +struct CompactSolutionDef { + vis: syn::Visibility, + ident: syn::Ident, + count: usize, +} + +impl Parse for CompactSolutionDef { + fn parse(input: ParseStream) -> syn::Result { + let vis: syn::Visibility = input.parse()?; + let ident: syn::Ident = input.parse()?; + let _ = ::parse(input)?; + let count_literal: syn::LitInt = input.parse()?; + let count = count_literal.base10_parse::()?; + Ok(Self { vis, ident, count } ) + } +} + +fn field_name_for(n: usize) -> Ident { + Ident::new(&format!("{}{}", PREFIX, n), Span::call_site()) +} diff --git a/primitives/phragmen/compact/src/staked.rs b/primitives/phragmen/compact/src/staked.rs new file mode 100644 index 0000000000000..a7cf853f17086 --- /dev/null +++ b/primitives/phragmen/compact/src/staked.rs @@ -0,0 +1,208 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Code generation for the staked assignment type. + +use crate::field_name_for; +use proc_macro2::{TokenStream as TokenStream2}; +use syn::{GenericArgument}; +use quote::quote; + +fn from_impl(count: usize) -> TokenStream2 { + let from_impl_single = { + let name = field_name_for(1); + quote!(1 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_double = { + let name = field_name_for(2); + quote!(2 => compact.#name.push( + ( + index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ( + index_of_target(&distribution[0].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + distribution[0].1, + ), + index_of_target(&distribution[1].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, + ) + ),) + }; + + let from_impl_rest = (3..=count).map(|c| { + let inner = (0..c-1).map(|i| + quote!((index_of_target(&distribution[#i].0).ok_or(_phragmen::Error::CompactInvalidIndex)?, distribution[#i].1),) + ).collect::(); + + let field_name = field_name_for(c); + let last_index = c - 1; + let last = quote!(index_of_target(&distribution[#last_index].0).ok_or(_phragmen::Error::CompactInvalidIndex)?); + + quote!( + #c => compact.#field_name.push((index_of_voter(&who).ok_or(_phragmen::Error::CompactInvalidIndex)?, [#inner], #last)), + ) + }).collect::(); + + quote!( + #from_impl_single + #from_impl_double + #from_impl_rest + ) +} + +fn into_impl(count: usize) -> TokenStream2 { + let into_impl_single = { + let name = field_name_for(1); + quote!( + for (voter_index, target_index) in self.#name { + let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?; + let all_stake = max_of(&who); + assignments.push(_phragmen::StakedAssignment { + who, + distribution: vec![(target_at(target_index).ok_or(_phragmen::Error::CompactInvalidIndex)?, all_stake)], + }) + } + ) + }; + + let into_impl_double = { + let name = field_name_for(2); + quote!( + for (voter_index, (t1_idx, w1), t2_idx) in self.#name { + let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?; + let all_stake = max_of(&who); + + if w1 >= all_stake { + return Err(_phragmen::Error::CompactStakeOverflow); + } + + // w2 is ensured to be positive. + let w2 = all_stake - w1; + assignments.push( _phragmen::StakedAssignment { + who, + distribution: vec![ + (target_at(t1_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w1), + (target_at(t2_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w2), + ] + }); + } + ) + }; + + let into_impl_rest = (3..=count).map(|c| { + let name = field_name_for(c); + quote!( + for (voter_index, inners, t_last_idx) in self.#name { + let who = voter_at(voter_index).ok_or(_phragmen::Error::CompactInvalidIndex)?; + let mut sum = u128::min_value(); + let all_stake = max_of(&who); + + let mut inners_parsed = inners + .iter() + .map(|(ref t_idx, w)| { + sum = sum.saturating_add(*w); + let target = target_at(*t_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?; + Ok((target, *w)) + }).collect::, _phragmen::Error>>()?; + + if sum >= all_stake { + return Err(_phragmen::Error::CompactStakeOverflow); + } + // w_last is proved to be positive. + let w_last = all_stake - sum; + + inners_parsed.push((target_at(t_last_idx).ok_or(_phragmen::Error::CompactInvalidIndex)?, w_last)); + + assignments.push(_phragmen::StakedAssignment { + who, + distribution: inners_parsed, + }); + } + ) + }).collect::(); + + quote!( + #into_impl_single + #into_impl_double + #into_impl_rest + ) +} + +pub(crate) fn staked( + ident: syn::Ident, + voter_type: GenericArgument, + target_type: GenericArgument, + count: usize, +) -> TokenStream2 { + + let from_impl = from_impl(count); + let into_impl = into_impl(count); + quote!( + impl< + #voter_type: _phragmen::codec::Codec + Default + Copy, + #target_type: _phragmen::codec::Codec + Default + Copy, + > + #ident<#voter_type, #target_type, u128> + { + /// Generate self from a vector of `StakedAssignment`. + pub fn from_staked( + assignments: Vec<_phragmen::StakedAssignment>, + index_of_voter: FV, + index_of_target: FT, + ) -> Result + where + for<'r> FV: Fn(&'r A) -> Option<#voter_type>, + for<'r> FT: Fn(&'r A) -> Option<#target_type>, + A: _phragmen::IdentifierT + { + let mut compact: #ident<#voter_type, #target_type, u128> = Default::default(); + for _phragmen::StakedAssignment { who, distribution } in assignments { + match distribution.len() { + 0 => continue, + #from_impl + _ => { + return Err(_phragmen::Error::CompactTargetOverflow); + } + } + }; + Ok(compact) + } + + /// Convert self into `StakedAssignment`. The given function should return the total + /// weight of a voter. It is used to subtract the sum of all the encoded weights to + /// infer the last one. + pub fn into_staked( + self, + max_of: FM, + voter_at: impl Fn(#voter_type) -> Option, + target_at: impl Fn(#target_type) -> Option, + ) + -> Result>, _phragmen::Error> + where + for<'r> FM: Fn(&'r A) -> u128, + A: _phragmen::IdentifierT, + { + let mut assignments: Vec<_phragmen::StakedAssignment> = Default::default(); + #into_impl + Ok(assignments) + } + } + ) +} diff --git a/primitives/phragmen/fuzzer/.gitignore b/primitives/phragmen/fuzzer/.gitignore new file mode 100644 index 0000000000000..3ebcb104d4a50 --- /dev/null +++ b/primitives/phragmen/fuzzer/.gitignore @@ -0,0 +1,2 @@ +hfuzz_target +hfuzz_workspace diff --git a/primitives/phragmen/fuzzer/Cargo.lock b/primitives/phragmen/fuzzer/Cargo.lock new file mode 100644 index 0000000000000..5f4e9a2451611 --- /dev/null +++ b/primitives/phragmen/fuzzer/Cargo.lock @@ -0,0 +1,1602 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "ahash" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f33b5018f120946c1dcf279194f238a9f146725593ead1c08fa47ff22b0b5d3" +dependencies = [ + "const-random", +] + +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + +[[package]] +name = "arbitrary" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "autocfg" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "backtrace" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +dependencies = [ + "backtrace-sys", + "cfg-if", + "libc", + "rustc-demangle", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bitvec" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-slice-cast" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cc" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clear_on_drop" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" +dependencies = [ + "cc", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "const-random" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a" +dependencies = [ + "getrandom", + "proc-macro-hack", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array", + "subtle 1.0.0", +] + +[[package]] +name = "curve25519-dalek" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" +dependencies = [ + "byteorder", + "clear_on_drop", + "digest", + "rand_core 0.3.1", + "subtle 2.2.2", +] + +[[package]] +name = "curve25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" +dependencies = [ + "byteorder", + "digest", + "rand_core 0.5.1", + "subtle 2.2.2", + "zeroize 1.1.0", +] + +[[package]] +name = "derive_more" +version = "0.99.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a806e96c59a76a5ba6e18735b6cf833344671e61e7863f2edb5c518ea2cac95c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" +dependencies = [ + "clear_on_drop", + "curve25519-dalek 2.0.0", + "rand 0.7.3", + "sha2", +] + +[[package]] +name = "environmental" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516aa8d7a71cb00a1c4146f0798549b93d083d4f189b3ced8f3de6b8f11ee6c4" + +[[package]] +name = "failure" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fixed-hash" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" +dependencies = [ + "byteorder", + "libc", + "rand 0.7.3", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6073d0ca812575946eb5f35ff68dbe519907b25c42530389ff946dc84c6ead" +dependencies = [ + "ahash", + "autocfg 0.1.7", +] + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest", +] + +[[package]] +name = "hmac-drbg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +dependencies = [ + "digest", + "generic-array", + "hmac", +] + +[[package]] +name = "honggfuzz" +version = "0.5.45" +dependencies = [ + "arbitrary", + "lazy_static", + "memmap", +] + +[[package]] +name = "impl-codec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e3cae7e99c7ff5a995da2cf78dd0a5383740eda71d98cf7b1910c301ac69b8" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-serde" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bbe9ea9b182f0fb1cabbd61f4ff9b7b7b9197955e95a7e4c27de5055eb29ff8" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65877bf7d44897a473350b1046277941cee20b263397e90869c50b6e766088b" + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" + +[[package]] +name = "libsecp256k1" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +dependencies = [ + "arrayref", + "crunchy", + "digest", + "hmac-drbg", + "rand 0.7.3", + "sha2", + "subtle 2.2.2", + "typenum", +] + +[[package]] +name = "lock_api" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memmap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "memory-db" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198831fe8722331a395bc199a5d08efbc197497ef354cb4c77b969c02ffc0fc4" +dependencies = [ + "ahash", + "hash-db", + "hashbrown", + "parity-util-mem", +] + +[[package]] +name = "memory_units" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" + +[[package]] +name = "merlin" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b0942b357c1b4d0dc43ba724674ec89c3218e6ca2b3e8269e7cb53bcecd2f6e" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.4.2", + "zeroize 1.1.0", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4dc79f9e6c81bef96148c8f6b8e72ad4541caa4a24373e900a36da07de03a3" +dependencies = [ + "autocfg 1.0.0", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +dependencies = [ + "parking_lot 0.9.0", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "parity-scale-codec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" +dependencies = [ + "arrayvec 0.5.1", + "bitvec", + "byte-slice-cast", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-util-mem" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1476e40bf8f5c6776e9600983435821ca86eb9819d74a6207cca69d091406a" +dependencies = [ + "cfg-if", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot 0.10.0", + "primitive-types", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn", + "synstructure", +] + +[[package]] +name = "parity-wasm" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865" + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +dependencies = [ + "lock_api", + "parking_lot_core 0.6.2", + "rustc_version", +] + +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +dependencies = [ + "lock_api", + "parking_lot_core 0.7.0", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "rustc_version", + "smallvec 0.6.13", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec 1.2.0", + "winapi", +] + +[[package]] +name = "paste" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pbkdf2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" +dependencies = [ + "byteorder", + "crypto-mac", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + +[[package]] +name = "primitive-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde 0.3.0", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +dependencies = [ + "autocfg 0.1.7", + "libc", + "rand_chacha 0.1.1", + "rand_core 0.4.2", + "rand_hc 0.1.0", + "rand_isaac", + "rand_jitter", + "rand_os", + "rand_pcg", + "rand_xorshift", + "winapi", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rand_jitter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" +dependencies = [ + "libc", + "rand_core 0.4.2", + "winapi", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.4.2", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +dependencies = [ + "autocfg 0.1.7", + "rand_core 0.4.2", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" + +[[package]] +name = "regex" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "schnorrkel" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eacd8381b3c37840c9c9f40472af529e49975bdcbc24f83c31059fd6539023d3" +dependencies = [ + "curve25519-dalek 1.2.3", + "failure", + "merlin", + "rand 0.6.5", + "rand_core 0.4.2", + "rand_os", + "sha2", + "subtle 2.2.2", + "zeroize 0.9.3", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" + +[[package]] +name = "sp-application-crypto" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "2.0.0-alpha.3" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-core" +version = "2.0.0-alpha.3" +dependencies = [ + "base58", + "blake2-rfc", + "byteorder", + "ed25519-dalek", + "hash-db", + "hash256-std-hasher", + "hex", + "impl-serde 0.3.0", + "lazy_static", + "libsecp256k1", + "log", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot 0.10.0", + "primitive-types", + "rand 0.7.3", + "regex", + "rustc-hex", + "schnorrkel", + "serde", + "sha2", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "substrate-bip39", + "tiny-bip39", + "tiny-keccak", + "twox-hash", + "wasmi", + "zeroize 1.1.0", +] + +[[package]] +name = "sp-debug-derive" +version = "2.0.0-alpha.3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.8.0-alpha.3" +dependencies = [ + "environmental", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-inherents" +version = "2.0.0-alpha.3" +dependencies = [ + "derive_more", + "parity-scale-codec", + "parking_lot 0.10.0", + "sp-core", + "sp-std", +] + +[[package]] +name = "sp-io" +version = "2.0.0-alpha.3" +dependencies = [ + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "sp-core", + "sp-externalities", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-trie", + "sp-wasm-interface", +] + +[[package]] +name = "sp-panic-handler" +version = "2.0.0-alpha.3" +dependencies = [ + "backtrace", + "log", +] + +[[package]] +name = "sp-phragmen" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "serde", + "sp-core", + "sp-phragmen-compact", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-phragmen-compact" +version = "2.0.0-dev" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-phragmen-fuzzer" +version = "2.0.0" +dependencies = [ + "honggfuzz", + "rand 0.7.3", + "sp-phragmen", +] + +[[package]] +name = "sp-runtime" +version = "2.0.0-alpha.3" +dependencies = [ + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-inherents", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-runtime-interface" +version = "2.0.0-alpha.3" +dependencies = [ + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "2.0.0-alpha.3" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-state-machine" +version = "0.8.0-alpha.3" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot 0.10.0", + "rand 0.7.3", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-trie", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "2.0.0-alpha.3" + +[[package]] +name = "sp-storage" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-serde 0.2.3", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-trie" +version = "2.0.0-alpha.3" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "sp-core", + "sp-std", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-wasm-interface" +version = "2.0.0-alpha.3" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-std", + "wasmi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "substrate-bip39" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be511be555a3633e71739a79e4ddff6a6aaa6579fa6114182a51d72c3eb93c5" +dependencies = [ + "hmac", + "pbkdf2", + "schnorrkel", + "sha2", +] + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" + +[[package]] +name = "syn" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tiny-bip39" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6848cd8f566953ce1e8faeba12ee23cbdbb0437754792cd857d44628b5685e3" +dependencies = [ + "failure", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.7.3", + "rustc-hash", + "sha2", + "unicode-normalization", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2953ca5148619bc99695c1274cb54c5275bbb913c6adad87e72eaf8db9787f69" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "trie-db" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9222c50cc325855621271157c973da27a0dcd26fa06f8edf81020bd2333df0" +dependencies = [ + "hash-db", + "hashbrown", + "log", + "rustc-hex", + "smallvec 1.2.0", +] + +[[package]] +name = "trie-root" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "652931506d2c1244d7217a70b99f56718a7b4161b37f04e7cd868072a99f68cd" +dependencies = [ + "hash-db", +] + +[[package]] +name = "twox-hash" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" +dependencies = [ + "rand 0.7.3", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + +[[package]] +name = "uint" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d" +dependencies = [ + "byteorder", + "crunchy", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +dependencies = [ + "smallvec 1.2.0", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasmi" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf617d864d25af3587aa745529f7aaa541066c876d57e050c0d0c85c61c92aff" +dependencies = [ + "libc", + "memory_units", + "num-rational", + "num-traits", + "parity-wasm", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93" +dependencies = [ + "parity-wasm", +] + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" + +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/primitives/phragmen/fuzzer/Cargo.toml b/primitives/phragmen/fuzzer/Cargo.toml new file mode 100644 index 0000000000000..645b1c151bcfd --- /dev/null +++ b/primitives/phragmen/fuzzer/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "sp-phragmen-fuzzer" +version = "2.0.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +sp-phragmen = { version = "2.0.0-alpha.3", path = ".." } +honggfuzz = "0.5" +rand = "0.7.3" + +[workspace] + +[[bin]] +name = "reduce" +path = "src/reduce.rs" diff --git a/primitives/phragmen/fuzzer/src/reduce.rs b/primitives/phragmen/fuzzer/src/reduce.rs new file mode 100644 index 0000000000000..4bf08590a149f --- /dev/null +++ b/primitives/phragmen/fuzzer/src/reduce.rs @@ -0,0 +1,145 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! # Running +//! +//! Run with `cargo hfuzz run reduce`. `honggfuzz`. +//! +//! # Debugging a panic +//! +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug reduce hfuzz_workspace/reduce/*.fuzz`. + +use honggfuzz::fuzz; +use sp_phragmen::{StakedAssignment, ExtendedBalance, build_support_map, reduce}; +use rand::{self, Rng}; + +type Balance = u128; +type AccountId = u64; + +/// Or any other token type. +const KSM: Balance = 1_000_000_000_000; + +fn main() { + loop { + fuzz!(|_data: _| { + let (assignments, winners) = generate_random_phragmen_assignment( + rr(100, 1000), + rr(100, 2000), + 8, + 8, + ); + reduce_and_compare(&assignments, &winners); + }); + } +} + +fn generate_random_phragmen_assignment( + voter_count: usize, + target_count: usize, + avg_edge_per_voter: usize, + edge_per_voter_var: usize, +) -> (Vec>, Vec) { + // random in range of (a, b) + let rr_128 = |a: u128, b: u128| -> u128 { rand::thread_rng().gen_range(a, b) }; + + // prefix to distinguish the voter and target account ranges. + let target_prefix = 1_000_000; + // let target_prefix = 1000; + assert!(voter_count < target_prefix); + + let mut assignments = Vec::with_capacity(voter_count as usize); + let mut winners: Vec = Vec::new(); + + let all_targets = (target_prefix..(target_prefix + target_count)) + .map(|a| a as AccountId) + .collect::>(); + + (1..=voter_count).for_each(|acc| { + let mut targets_to_chose_from = all_targets.clone(); + let targets_to_chose = if edge_per_voter_var > 0 { rr( + avg_edge_per_voter - edge_per_voter_var, + avg_edge_per_voter + edge_per_voter_var, + ) } else { avg_edge_per_voter }; + + let distribution = (0..targets_to_chose).map(|_| { + let target = targets_to_chose_from.remove(rr(0, targets_to_chose_from.len())); + if winners.iter().find(|w| **w == target).is_none() { + winners.push(target.clone()); + } + (target, rr_128(1 * KSM, 100 * KSM)) + }).collect::>(); + + assignments.push(StakedAssignment { + who: (acc as AccountId), + distribution, + }); + }); + + (assignments, winners) +} + +fn assert_assignments_equal( + winners: &Vec, + ass1: &Vec>, + ass2: &Vec>, +) { + + let (support_1, _) = build_support_map::(winners, ass1); + let (support_2, _) = build_support_map::(winners, ass2); + + for (who, support) in support_1.iter() { + assert_eq!(support.total, support_2.get(who).unwrap().total); + } +} + +fn reduce_and_compare( + assignment: &Vec>, + winners: &Vec, +) { + let mut altered_assignment = assignment.clone(); + let n = assignment.len() as u32; + let m = winners.len() as u32; + + let edges_before = assignment_len(&assignment); + let num_changed = reduce(&mut altered_assignment); + let edges_after = edges_before - num_changed; + + assert!( + edges_after <= m + n, + "reduce bound not satisfied. n = {}, m = {}, edges after reduce = {} (removed {})", + n, + m, + edges_after, + num_changed, + ); + + assert_assignments_equal( + winners, + &assignment, + &altered_assignment, + ); +} + +fn assignment_len(assignments: &[StakedAssignment]) -> u32 { + let mut counter = 0; + assignments.iter().for_each(|x| x.distribution.iter().for_each(|_| counter += 1)); + counter +} + +fn rr(a: usize, b: usize) -> usize { + rand::thread_rng().gen_range(a, b) +} diff --git a/primitives/phragmen/src/helpers.rs b/primitives/phragmen/src/helpers.rs new file mode 100644 index 0000000000000..27f51b4a05fe2 --- /dev/null +++ b/primitives/phragmen/src/helpers.rs @@ -0,0 +1,94 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Helper methods for phragmen. + +use crate::{Assignment, ExtendedBalance, IdentifierT, StakedAssignment}; +use sp_runtime::PerThing; +use sp_std::prelude::*; + +/// Converts a vector of ratio assignments into ones with absolute budget value. +pub fn assignment_ratio_to_staked( + ratio: Vec>, + stake_of: FS, +) -> Vec> +where + for<'r> FS: Fn(&'r A) -> ExtendedBalance, + T: sp_std::ops::Mul, + ExtendedBalance: From<::Inner>, +{ + ratio + .into_iter() + .map(|a| { + let stake = stake_of(&a.who); + a.into_staked(stake, true) + }) + .collect() +} + +/// Converts a vector of staked assignments into ones with ratio values. +pub fn assignment_staked_to_ratio( + ratio: Vec>, +) -> Vec> +where + ExtendedBalance: From<::Inner>, +{ + ratio.into_iter().map(|a| a.into_assignment(true)).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ExtendedBalance; + use sp_runtime::Perbill; + + #[test] + fn into_staked_works() { + let ratio = vec![ + Assignment { + who: 1u32, + distribution: vec![ + (10u32, Perbill::from_fraction(0.5)), + (20, Perbill::from_fraction(0.5)), + ], + }, + Assignment { + who: 2u32, + distribution: vec![ + (10, Perbill::from_fraction(0.33)), + (20, Perbill::from_fraction(0.67)), + ], + }, + ]; + + let stake_of = |_: &u32| -> ExtendedBalance { 100u128 }; + let staked = assignment_ratio_to_staked(ratio, stake_of); + + assert_eq!( + staked, + vec![ + StakedAssignment { + who: 1u32, + distribution: vec![(10u32, 50), (20, 50),] + }, + StakedAssignment { + who: 2u32, + distribution: vec![(10u32, 33), (20, 67),] + } + ] + ); + } +} diff --git a/primitives/phragmen/src/lib.rs b/primitives/phragmen/src/lib.rs index aba714faeb19f..c0d94a71e1fc2 100644 --- a/primitives/phragmen/src/lib.rs +++ b/primitives/phragmen/src/lib.rs @@ -33,19 +33,60 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::{prelude::*, collections::btree_map::BTreeMap, convert::TryFrom}; -use sp_runtime::{ - PerThing, Rational128, RuntimeDebug, - helpers_128bit::multiply_by_rational, -}; -use sp_runtime::traits::{ - Zero, Convert, Member, AtLeast32Bit, SaturatedConversion, Bounded, Saturating, -}; +use sp_std::{prelude::*, collections::btree_map::BTreeMap, fmt::Debug, cmp::Ordering, convert::TryFrom}; +use sp_runtime::{helpers_128bit::multiply_by_rational, PerThing, Rational128, RuntimeDebug, SaturatedConversion}; +use sp_runtime::traits::{Zero, Convert, Member, AtLeast32Bit, Saturating, Bounded}; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; +#[cfg(feature = "std")] +use codec::{Encode, Decode}; + +mod node; +mod reduce; +mod helpers; + +// re-export reduce stuff. +pub use reduce::reduce; + +// re-export the helpers. +pub use helpers::*; + +// re-export the compact macro, with the dependencies of the macro. +#[doc(hidden)] +pub use codec; +#[doc(hidden)] +pub use sp_runtime; + +// re-export the compact solution type. +pub use sp_phragmen_compact::generate_compact_solution_type; + +/// A trait to limit the number of votes per voter. The generated compact type will implement this. +pub trait VotingLimit { + const LIMIT: usize; +} + +/// an aggregator trait for a generic type of a voter/target identifier. This usually maps to +/// substrate's account id. +pub trait IdentifierT: Clone + Eq + Default + Ord + Debug + codec::Codec {} + +impl IdentifierT for T {} + +/// The errors that might occur in the this crate and compact. +#[derive(Debug, Eq, PartialEq)] +pub enum Error { + /// While going from compact to staked, the stake of all the edges has gone above the + /// total and the last stake cannot be assigned. + CompactStakeOverflow, + /// The compact type has a voter who's number of targets is out of bound. + CompactTargetOverflow, + /// One of the index functions returned none. + CompactInvalidIndex, +} /// A type in which performing operations on balances and stakes of candidates and voters are safe. /// @@ -55,6 +96,9 @@ mod tests; /// Balance types converted to `ExtendedBalance` are referred to as `Votes`. pub type ExtendedBalance = u128; +/// The score of an assignment. This can be computed from the support map via [`evaluate_support`]. +pub type PhragmenScore = [ExtendedBalance; 3]; + /// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we /// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number /// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128; @@ -62,11 +106,11 @@ const DEN: u128 = u128::max_value(); /// A candidate entity for phragmen election. #[derive(Clone, Default, RuntimeDebug)] -pub struct Candidate { +struct Candidate { /// Identifier. - pub who: AccountId, + who: AccountId, /// Intermediary value used to sort candidates. - pub score: Rational128, + score: Rational128, /// Sum of the stake of this candidate based on received votes. approval_stake: ExtendedBalance, /// Flag for being elected. @@ -75,7 +119,7 @@ pub struct Candidate { /// A voter entity. #[derive(Clone, Default, RuntimeDebug)] -pub struct Voter { +struct Voter { /// Identifier. who: AccountId, /// List of candidates proposed by this voter. @@ -88,7 +132,7 @@ pub struct Voter { /// A candidate being backed by a voter. #[derive(Clone, Default, RuntimeDebug)] -pub struct Edge { +struct Edge { /// Identifier. who: AccountId, /// Load of this vote. @@ -97,12 +141,6 @@ pub struct Edge { candidate_index: usize, } -/// Particular `AccountId` was backed by `T`-ratio of a nominator's stake. -pub type PhragmenAssignment = (AccountId, T); - -/// Particular `AccountId` was backed by `ExtendedBalance` of a nominator's stake. -pub type PhragmenStakedAssignment = (AccountId, ExtendedBalance); - /// Final result of the phragmen election. #[derive(RuntimeDebug)] pub struct PhragmenResult { @@ -111,7 +149,142 @@ pub struct PhragmenResult { pub winners: Vec<(AccountId, ExtendedBalance)>, /// Individual assignments. for each tuple, the first elements is a voter and the second /// is the list of candidates that it supports. - pub assignments: Vec<(AccountId, Vec>)> + pub assignments: Vec>, +} + +/// A voter's stake assignment among a set of targets, represented as ratios. +#[derive(RuntimeDebug, Clone, Default)] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +pub struct Assignment { + /// Voter's identifier + pub who: AccountId, + /// The distribution of the voter's stake. + pub distribution: Vec<(AccountId, T)>, +} + +impl Assignment +where + ExtendedBalance: From<::Inner>, +{ + /// Convert from a ratio assignment into one with absolute values aka. [`StakedAssignment`]. + /// + /// It needs `stake` which is the total budget of the voter. If `fill` is set to true, + /// it _tries_ to ensure that all the potential rounding errors are compensated and the + /// distribution's sum is exactly equal to the total budget, by adding or subtracting the + /// remainder from the last distribution. + /// + /// If an edge ratio is [`Bounded::max_value()`], it is dropped. This edge can never mean + /// anything useful. + pub fn into_staked(self, stake: ExtendedBalance, fill: bool) -> StakedAssignment + where + T: sp_std::ops::Mul, + { + let mut sum: ExtendedBalance = Bounded::min_value(); + let mut distribution = self + .distribution + .into_iter() + .filter_map(|(target, p)| { + // if this ratio is zero, then skip it. + if p == Bounded::min_value() { + None + } else { + // NOTE: this mul impl will always round to the nearest number, so we might both + // overflow and underflow. + let distribution_stake = p * stake; + // defensive only. We assume that balance cannot exceed extended balance. + sum = sum.saturating_add(distribution_stake); + Some((target, distribution_stake)) + } + }) + .collect::>(); + + if fill { + // NOTE: we can do this better. + // https://revs.runtime-revolution.com/getting-100-with-rounded-percentages-273ffa70252b + if let Some(leftover) = stake.checked_sub(sum) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_add(leftover); + } + } else if let Some(excess) = sum.checked_sub(stake) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_sub(excess); + } + } + } + + StakedAssignment { + who: self.who, + distribution, + } + } +} + +/// A voter's stake assignment among a set of targets, represented as absolute values in the scale +/// of [`ExtendedBalance`]. +#[derive(RuntimeDebug, Clone, Default)] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +pub struct StakedAssignment { + /// Voter's identifier + pub who: AccountId, + /// The distribution of the voter's stake. + pub distribution: Vec<(AccountId, ExtendedBalance)>, +} + +impl StakedAssignment { + /// Converts self into the normal [`Assignment`] type. + /// + /// If `fill` is set to true, it _tries_ to ensure that all the potential rounding errors are + /// compensated and the distribution's sum is exactly equal to 100%, by adding or subtracting + /// the remainder from the last distribution. + /// + /// NOTE: it is quite critical that this attempt always works. The data type returned here will + /// potentially get used to create a compact type; a compact type requires sum of ratios to be + /// less than 100% upon un-compacting. + /// + /// If an edge stake is so small that it cannot be represented in `T`, it is ignored. This edge + /// can never be re-created and does not mean anything useful anymore. + pub fn into_assignment(self, fill: bool) -> Assignment + where + ExtendedBalance: From<::Inner>, + { + let accuracy: u128 = T::ACCURACY.saturated_into(); + let mut sum: u128 = Zero::zero(); + let stake = self.distribution.iter().map(|x| x.1).sum(); + let mut distribution = self + .distribution + .into_iter() + .filter_map(|(target, w)| { + let per_thing = T::from_rational_approximation(w, stake); + if per_thing == Bounded::min_value() { + None + } else { + sum += per_thing.clone().deconstruct().saturated_into(); + Some((target, per_thing)) + } + }) + .collect::>(); + + if fill { + if let Some(leftover) = accuracy.checked_sub(sum) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_add( + T::from_parts(leftover.saturated_into()) + ); + } + } else if let Some(excess) = sum.checked_sub(accuracy) { + if let Some(last) = distribution.last_mut() { + last.1 = last.1.saturating_sub( + T::from_parts(excess.saturated_into()) + ); + } + } + } + + Assignment { + who: self.who, + distribution, + } + } } /// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how @@ -122,12 +295,12 @@ pub struct PhragmenResult { /// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet /// they do not necessarily have to be the same. #[derive(Default, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Eq, PartialEq))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Eq, PartialEq))] pub struct Support { /// Total support. pub total: ExtendedBalance, /// Support from voters. - pub voters: Vec>, + pub voters: Vec<(AccountId, ExtendedBalance)>, } /// A linkage from a candidate and its [`Support`]. @@ -164,7 +337,7 @@ pub fn elect( // return structures let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>; - let mut assigned: Vec<(AccountId, Vec>)>; + let mut assigned: Vec>; // used to cache and access candidates index. let mut c_idx_cache = BTreeMap::::new(); @@ -271,7 +444,10 @@ pub fn elect( // update backing stake of candidates and voters for n in &mut voters { - let mut assignment = (n.who.clone(), vec![]); + let mut assignment = Assignment { + who: n.who.clone(), + ..Default::default() + }; for e in &mut n.edges { if elected_candidates.iter().position(|(ref c, _)| *c == e.who).is_some() { let per_bill_parts: R::Inner = @@ -298,44 +474,45 @@ pub fn elect( // R::Inner. .unwrap_or(Bounded::max_value()) } else { - // defensive only. Both edge and nominator loads are built from + // defensive only. Both edge and voter loads are built from // scores, hence MUST have the same denominator. Zero::zero() } } }; let per_thing = R::from_parts(per_bill_parts); - assignment.1.push((e.who.clone(), per_thing)); + assignment.distribution.push((e.who.clone(), per_thing)); } } - if assignment.1.len() > 0 { - // To ensure an assertion indicating: no stake from the nominator going to waste, + let len = assignment.distribution.len(); + if len > 0 { + // To ensure an assertion indicating: no stake from the voter going to waste, // we add a minimal post-processing to equally assign all of the leftover stake ratios. - let vote_count: R::Inner = assignment.1.len().saturated_into(); - let len = assignment.1.len(); - let mut sum: R::Inner = Zero::zero(); - assignment.1.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct())); + let vote_count: R::Inner = len.saturated_into(); let accuracy = R::ACCURACY; + let mut sum: R::Inner = Zero::zero(); + assignment.distribution.iter().for_each(|a| sum = sum.saturating_add(a.1.deconstruct())); + let diff = accuracy.saturating_sub(sum); let diff_per_vote = (diff / vote_count).min(accuracy); if !diff_per_vote.is_zero() { for i in 0..len { - let current_ratio = assignment.1[i % len].1; + let current_ratio = assignment.distribution[i % len].1; let next_ratio = current_ratio .saturating_add(R::from_parts(diff_per_vote)); - assignment.1[i % len].1 = next_ratio; + assignment.distribution[i % len].1 = next_ratio; } } - // `remainder` is set to be less than maximum votes of a nominator (currently 16). + // `remainder` is set to be less than maximum votes of a voter (currently 16). // safe to cast it to usize. let remainder = diff - diff_per_vote * vote_count; for i in 0..remainder.saturated_into::() { - let current_ratio = assignment.1[i % len].1; + let current_ratio = assignment.distribution[i % len].1; let next_ratio = current_ratio.saturating_add(R::from_parts(1u8.into())); - assignment.1[i % len].1 = next_ratio; + assignment.distribution[i % len].1 = next_ratio; } assigned.push(assignment); } @@ -347,39 +524,109 @@ pub fn elect( }) } -/// Build the support map from the given phragmen result. -pub fn build_support_map( - elected_stashes: &Vec, - assignments: &Vec<(AccountId, Vec>)>, - stake_of: FS, -) -> SupportMap where +/// Build the support map from the given phragmen result. It maps a flat structure like +/// +/// ```nocompile +/// assignments: vec![ +/// voter1, vec![(candidate1, w11), (candidate2, w12)], +/// voter2, vec![(candidate1, w21), (candidate2, w22)] +/// ] +/// ``` +/// +/// into a mapping of candidates and their respective support: +/// +/// ```nocompile +/// SupportMap { +/// candidate1: Support { +/// own:0, +/// total: w11 + w21, +/// others: vec![(candidate1, w11), (candidate2, w21)] +/// }, +/// candidate2: Support { +/// own:0, +/// total: w12 + w22, +/// others: vec![(candidate1, w12), (candidate2, w22)] +/// }, +/// } +/// ``` +/// +/// The second returned flag indicates the number of edges who didn't corresponded to an actual +/// winner from the given winner set. A value in this place larger than 0 indicates a potentially +/// faulty assignment. +/// +/// `O(E)` where `E` is the total number of edges. +pub fn build_support_map( + winners: &[AccountId], + assignments: &[StakedAssignment], +) -> (SupportMap, u32) where AccountId: Default + Ord + Member, - Balance: Default + Copy + AtLeast32Bit, - C: Convert + Convert, - for<'r> FS: Fn(&'r AccountId) -> Balance, - R: PerThing + sp_std::ops::Mul, { - let to_votes = |b: Balance| >::convert(b) as ExtendedBalance; + let mut errors = 0; // Initialize the support of each candidate. let mut supports = >::new(); - elected_stashes + winners .iter() .for_each(|e| { supports.insert(e.clone(), Default::default()); }); // build support struct. - for (n, assignment) in assignments.iter() { - for (c, per_thing) in assignment.iter() { - let nominator_stake = to_votes(stake_of(n)); - // AUDIT: it is crucially important for the `Mul` implementation of all - // per-things to be sound. - let other_stake = *per_thing * nominator_stake; + for StakedAssignment { who, distribution } in assignments.iter() { + for (c, weight_extended) in distribution.iter() { if let Some(support) = supports.get_mut(c) { - support.voters.push((n.clone(), other_stake)); - support.total = support.total.saturating_add(other_stake); + support.total = support.total.saturating_add(*weight_extended); + support.voters.push((who.clone(), *weight_extended)); + } else { + errors = errors.saturating_add(1); } } } - supports + (supports, errors) +} + +/// Evaluate a phragmen result, given the support map. The returned tuple contains: +/// +/// - Minimum support. This value must be **maximized**. +/// - Sum of all supports. This value must be **maximized**. +/// - Sum of all supports squared. This value must be **minimized**. +/// +/// `O(E)` where `E` is the total number of edges. +pub fn evaluate_support( + support: &SupportMap, +) -> PhragmenScore { + let mut min_support = ExtendedBalance::max_value(); + let mut sum: ExtendedBalance = Zero::zero(); + // NOTE: this will probably saturate but using big num makes it even slower. We'll have to see. + // This must run on chain.. + let mut sum_squared: ExtendedBalance = Zero::zero(); + for (_, support) in support.iter() { + sum += support.total; + let squared = support.total.saturating_mul(support.total); + sum_squared = sum_squared.saturating_add(squared); + if support.total < min_support { + min_support = support.total; + } + } + [min_support, sum, sum_squared] +} + +/// Compares two sets of phragmen scores based on desirability and returns true if `that` is +/// better than `this`. +/// +/// Evaluation is done in a lexicographic manner. +/// +/// Note that the third component should be minimized. +pub fn is_score_better(this: PhragmenScore, that: PhragmenScore) -> bool { + match that + .iter() + .enumerate() + .map(|(i, e)| e.cmp(&this[i])) + .collect::>() + .as_slice() + { + [Ordering::Greater, _, _] => true, + [Ordering::Equal, Ordering::Greater, _] => true, + [Ordering::Equal, Ordering::Equal, Ordering::Less] => true, + _ => false, + } } /// Performs equalize post-processing to the output of the election algorithm. This happens in @@ -388,13 +635,13 @@ pub fn build_support_map( /// /// No value is returned from the function and the `supports` parameter is updated. /// -/// * `assignments`: exactly the same is the output of phragmen. -/// * `supports`: mutable reference to s `SupportMap`. This parameter is updated. -/// * `tolerance`: maximum difference that can occur before an early quite happens. -/// * `iterations`: maximum number of iterations that will be processed. -/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. +/// - `assignments`: exactly the same is the output of phragmen. +/// - `supports`: mutable reference to s `SupportMap`. This parameter is updated. +/// - `tolerance`: maximum difference that can occur before an early quite happens. +/// - `iterations`: maximum number of iterations that will be processed. +/// - `stake_of`: something that can return the stake stake of a particular candidate or voter. pub fn equalize( - mut assignments: Vec<(AccountId, Vec>)>, + mut assignments: Vec>, supports: &mut SupportMap, tolerance: ExtendedBalance, iterations: usize, @@ -408,13 +655,13 @@ pub fn equalize( for _i in 0..iterations { let mut max_diff = 0; - for (voter, assignment) in assignments.iter_mut() { - let voter_budget = stake_of(&voter); + for StakedAssignment { who, distribution } in assignments.iter_mut() { + let voter_budget = stake_of(&who); let diff = do_equalize::<_, _, C>( - voter, + who, voter_budget, - assignment, + distribution, supports, tolerance, ); @@ -432,7 +679,7 @@ pub fn equalize( fn do_equalize( voter: &AccountId, budget_balance: Balance, - elected_edges: &mut Vec>, + elected_edges: &mut Vec<(AccountId, ExtendedBalance)>, support_map: &mut SupportMap, tolerance: ExtendedBalance ) -> ExtendedBalance where diff --git a/primitives/phragmen/src/mock.rs b/primitives/phragmen/src/mock.rs index 66ef64a6c2336..31ce3d38c3500 100644 --- a/primitives/phragmen/src/mock.rs +++ b/primitives/phragmen/src/mock.rs @@ -18,10 +18,10 @@ #![cfg(test)] -use crate::{elect, PhragmenResult, PhragmenAssignment}; +use crate::{elect, PhragmenResult, Assignment}; use sp_runtime::{ - assert_eq_error_rate, Perbill, PerThing, - traits::{Convert, Member, SaturatedConversion} + assert_eq_error_rate, PerThing, + traits::{Convert, Member, SaturatedConversion, Zero, One} }; use sp_std::collections::btree_map::BTreeMap; @@ -320,22 +320,23 @@ pub(crate) fn create_stake_of(stakes: &[(AccountId, Balance)]) } -pub fn check_assignments(assignments: Vec<(AccountId, Vec>)>) { - for (_, a) in assignments { - let sum: u32 = a.iter().map(|(_, p)| p.deconstruct()).sum(); - assert_eq_error_rate!(sum, Perbill::ACCURACY, 5); +pub fn check_assignments_sum(assignments: Vec>) { + for Assignment { distribution, .. } in assignments { + let mut sum: u128 = Zero::zero(); + distribution.iter().for_each(|(_, p)| sum += p.deconstruct().saturated_into()); + assert_eq_error_rate!(sum, T::ACCURACY.saturated_into(), 1); } } -pub(crate) fn run_and_compare( +pub(crate) fn run_and_compare( candidates: Vec, voters: Vec<(AccountId, Vec)>, - stake_of: Box Balance>, + stake_of: &Box Balance>, to_elect: usize, min_to_elect: usize, ) { // run fixed point code. - let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( to_elect, min_to_elect, candidates.clone(), @@ -353,14 +354,14 @@ pub(crate) fn run_and_compare( assert_eq!(winners, truth_value.winners); - for (nominator, assigned) in assignments.clone() { - if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == nominator) { - for (candidate, per_thingy) in assigned { + for Assignment { who, distribution } in assignments.clone() { + if let Some(float_assignments) = truth_value.assignments.iter().find(|x| x.0 == who) { + for (candidate, per_thingy) in distribution { if let Some(float_assignment) = float_assignments.1.iter().find(|x| x.0 == candidate ) { assert_eq_error_rate!( - Perbill::from_fraction(float_assignment.1).deconstruct(), + Output::from_fraction(float_assignment.1).deconstruct(), per_thingy.deconstruct(), - 1, + Output::Inner::one(), ); } else { panic!("candidate mismatch. This should never happen.") @@ -371,7 +372,7 @@ pub(crate) fn run_and_compare( } } - check_assignments(assignments); + check_assignments_sum(assignments); } pub(crate) fn build_support_map_float( diff --git a/primitives/phragmen/src/node.rs b/primitives/phragmen/src/node.rs new file mode 100644 index 0000000000000..92ef325a34872 --- /dev/null +++ b/primitives/phragmen/src/node.rs @@ -0,0 +1,287 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! (very) Basic implementation of a graph node used in the reduce algorithm. + +use sp_runtime::RuntimeDebug; +use sp_std::{cell::RefCell, fmt, prelude::*, rc::Rc}; + +/// The role that a node can accept. +#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, RuntimeDebug)] +pub(crate) enum NodeRole { + /// A voter. This is synonym to a nominator in a staking context. + Voter, + /// A target. This is synonym to a candidate/validator in a staking context. + Target, +} + +pub(crate) type RefCellOf = Rc>; +pub(crate) type NodeRef = RefCellOf>; + +/// Identifier of a node. This is particularly handy to have a proper `PartialEq` implementation. +/// Otherwise, self votes wouldn't have been indistinguishable. +#[derive(PartialOrd, Ord, Clone, PartialEq, Eq)] +pub(crate) struct NodeId { + /// An account-like identifier representing the node. + pub who: A, + /// The role of the node. + pub role: NodeRole, +} + +impl NodeId { + /// Create a new [`NodeId`]. + pub fn from(who: A, role: NodeRole) -> Self { + Self { who, role } + } +} + +#[cfg(feature = "std")] +impl sp_std::fmt::Debug for NodeId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> sp_std::fmt::Result { + write!( + f, + "Node({:?}, {:?})", + self.who, + if self.role == NodeRole::Voter { + "V" + } else { + "T" + } + ) + } +} + +/// A one-way graph note. This can only store a pointer to its parent. +#[derive(Clone)] +pub(crate) struct Node { + /// The identifier of the note. + pub(crate) id: NodeId, + /// The parent pointer. + pub(crate) parent: Option>, +} + +impl PartialEq for Node { + fn eq(&self, other: &Node) -> bool { + self.id == other.id + } +} + +impl Eq for Node {} + +#[cfg(feature = "std")] +impl fmt::Debug for Node { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "({:?} --> {:?})", + self.id, + self.parent.as_ref().map(|p| p.borrow().id.clone()) + ) + } +} + +impl Node { + /// Create a new [`Node`] + pub fn new(id: NodeId) -> Node { + Self { id, parent: None } + } + + /// Returns true if `other` is the parent of `who`. + pub fn is_parent_of(who: &NodeRef, other: &NodeRef) -> bool { + if who.borrow().parent.is_none() { + return false; + } + who.borrow().parent.as_ref() == Some(other) + } + + /// Removes the parent of `who`. + pub fn remove_parent(who: &NodeRef) { + who.borrow_mut().parent = None; + } + + /// Sets `who`'s parent to be `parent`. + pub fn set_parent_of(who: &NodeRef, parent: &NodeRef) { + who.borrow_mut().parent = Some(parent.clone()); + } + + /// Finds the root of `start`. It return a tuple of `(root, root_vec)` where `root_vec` is the + /// vector of Nodes leading to the root. Hence the first element is the start itself and the + /// last one is the root. As convenient, the root itself is also returned as the first element + /// of the tuple. + /// + /// This function detects cycles and breaks as soon a duplicate node is visited, returning the + /// cycle up to but not including the duplicate node. + /// + /// If you are certain that no cycles exist, you can use [`root_unchecked`]. + pub fn root(start: &NodeRef) -> (NodeRef, Vec>) { + let mut parent_path: Vec> = Vec::new(); + let mut visited: Vec> = Vec::new(); + + parent_path.push(start.clone()); + visited.push(start.clone()); + let mut current = start.clone(); + + while let Some(ref next_parent) = current.clone().borrow().parent { + if visited.contains(next_parent) { + break; + } + parent_path.push(next_parent.clone()); + current = next_parent.clone(); + visited.push(current.clone()); + } + + (current, parent_path) + } + + /// Consumes self and wraps it in a `Rc>`. This type can be used as the pointer type + /// to a parent node. + pub fn into_ref(self) -> NodeRef { + Rc::from(RefCell::from(self)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn id(i: u32) -> NodeId { + NodeId::from(i, NodeRole::Target) + } + + #[test] + fn basic_create_works() { + let node = Node::new(id(10)); + assert_eq!( + node, + Node { + id: NodeId { + who: 10, + role: NodeRole::Target + }, + parent: None + } + ); + } + + #[test] + fn set_parent_works() { + let a = Node::new(id(10)).into_ref(); + let b = Node::new(id(20)).into_ref(); + + assert_eq!(a.borrow().parent, None); + Node::set_parent_of(&a, &b); + assert_eq!(*a.borrow().parent.as_ref().unwrap(), b); + } + + #[test] + fn get_root_singular() { + let a = Node::new(id(1)).into_ref(); + assert_eq!(Node::root(&a), (a.clone(), vec![a.clone()])); + } + + #[test] + fn get_root_works() { + // D <-- A <-- B <-- C + // \ + // <-- E + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + let d = Node::new(id(4)).into_ref(); + let e = Node::new(id(5)).into_ref(); + let f = Node::new(id(6)).into_ref(); + + Node::set_parent_of(&c, &b); + Node::set_parent_of(&b, &a); + Node::set_parent_of(&e, &a); + Node::set_parent_of(&a, &d); + + assert_eq!( + Node::root(&e), + (d.clone(), vec![e.clone(), a.clone(), d.clone()]), + ); + + assert_eq!(Node::root(&a), (d.clone(), vec![a.clone(), d.clone()]),); + + assert_eq!( + Node::root(&c), + (d.clone(), vec![c.clone(), b.clone(), a.clone(), d.clone()]), + ); + + // D A <-- B <-- C + // F <-- / \ + // <-- E + Node::set_parent_of(&a, &f); + + assert_eq!(Node::root(&a), (f.clone(), vec![a.clone(), f.clone()]),); + + assert_eq!( + Node::root(&c), + (f.clone(), vec![c.clone(), b.clone(), a.clone(), f.clone()]), + ); + } + + #[test] + fn get_root_on_cycle() { + // A ---> B + // | | + // <---- C + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + + Node::set_parent_of(&a, &b); + Node::set_parent_of(&b, &c); + Node::set_parent_of(&c, &a); + + let (root, path) = Node::root(&a); + assert_eq!(root, c); + assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]); + } + + #[test] + fn get_root_on_cycle_2() { + // A ---> B + // | | | + // - C + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + + Node::set_parent_of(&a, &b); + Node::set_parent_of(&b, &c); + Node::set_parent_of(&c, &b); + + let (root, path) = Node::root(&a); + assert_eq!(root, c); + assert_eq!(path.clone(), vec![a.clone(), b.clone(), c.clone()]); + } + + #[test] + fn node_cmp_stack_overflows_on_non_unique_elements() { + // To make sure we don't stack overflow on duplicate who. This needs manual impl of + // PartialEq. + let a = Node::new(id(1)).into_ref(); + let b = Node::new(id(2)).into_ref(); + let c = Node::new(id(3)).into_ref(); + + Node::set_parent_of(&a, &b); + Node::set_parent_of(&b, &c); + Node::set_parent_of(&c, &a); + + Node::root(&a); + } +} diff --git a/primitives/phragmen/src/reduce.rs b/primitives/phragmen/src/reduce.rs new file mode 100644 index 0000000000000..54a71a7ff29aa --- /dev/null +++ b/primitives/phragmen/src/reduce.rs @@ -0,0 +1,1076 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Rust implementation of the Phragmén reduce algorithm. This can be used by any off chain +//! application to reduce cycles from the edge assignment, which will result in smaller size. +//! +//! ### Notions +//! - `m`: size of the committee to elect. +//! - `k`: maximum allowed votes (16 as of this writing). +//! - `nv ∈ E` means that nominator `n ∈ N` supports the election of candidate `v ∈ V`. +//! - A valid solution consists of a tuple `(S, W)` , where `S ⊆ V` is a committee of m validators, +//! and `W : E → R ≥ 0` is an edge weight vector which describes how the budget of each nominator +//! n is fractionally assigned to n 's elected neighbors. +//! - `E_w := { e ∈ E : w_e > 0 }`. +//! +//! ### Algorithm overview +//! +//! > We consider the input edge weight vector `w` as a directed flow over `E_w` , where the flow in +//! > each edge is directed from the nominator to the validator. We build `w′` from `w` by removing +//! > **circulations** to cancel out the flow over as many edges as possible, while preserving flow +//! > demands over all vertices and without reverting the flow direction over any edge. As long as +//! > there is a cycle, we can remove an additional circulation and eliminate at least one new edge +//! > from `E_w′` . This shows that the algorithm always progresses and will eventually finish with +//! > an acyclic edge support. We keep a data structure that represents a forest of rooted trees, +//! > which is initialized as a collection of singletons – one per vertex – and to which edges in +//! > `E_w` are added one by one, causing the trees to merge. Whenever a new edge creates a cycle, +//! > we detect it and destroy it by removing a circulation. We also run a pre-computation which is +//! > designed to detect and remove cycles of length four exclusively. This pre-computation is +//! > optional, and if we skip it then the running time becomes `O (|E_w| ⋅ m), so the +//! > pre-computation makes sense only if `m >> k` and `|E_w| >> m^2`. +//! +//! ### Resources: +//! +//! 1. https://hackmd.io/JOn9x98iS0e0DPWQ87zGWg?view + +use crate::node::{Node, NodeId, NodeRef, NodeRole}; +use crate::{ExtendedBalance, IdentifierT, StakedAssignment}; +use sp_runtime::traits::{Bounded, Zero}; +use sp_std::{ + collections::btree_map::{BTreeMap, Entry::*}, + prelude::*, +}; + +/// Map type used for reduce_4. Can be easily swapped with HashMap. +type Map = BTreeMap<(A, A), A>; + +/// Returns all combinations of size two in the collection `input` with no repetition. +fn combinations_2(input: &[T]) -> Vec<(T, T)> { + let n = input.len(); + if n < 2 { + return Default::default(); + } + + let mut comb = Vec::with_capacity(n * (n - 1) / 2); + for i in 0..n { + for j in i + 1..n { + comb.push((input[i].clone(), input[j].clone())) + } + } + comb +} + +/// Returns the count of trailing common elements in two slices. +pub(crate) fn trailing_common(t1: &[T], t2: &[T]) -> usize { + t1.iter().rev().zip(t2.iter().rev()).take_while(|e| e.0 == e.1).count() +} + +/// Merges two parent roots as described by the reduce algorithm. +fn merge(voter_root_path: Vec>, target_root_path: Vec>) { + let (shorter_path, longer_path) = if voter_root_path.len() <= target_root_path.len() { + (voter_root_path, target_root_path) + } else { + (target_root_path, voter_root_path) + }; + + // iterate from last to beginning, skipping the first one. This asserts that + // indexing is always correct. + shorter_path + .iter() + .zip(shorter_path.iter().skip(1)) + .for_each(|(voter, next)| Node::set_parent_of(&next, &voter)); + Node::set_parent_of(&shorter_path[0], &longer_path[0]); +} + +/// Reduce only redundant edges with cycle length of 4. +/// +/// Returns the number of edges removed. +/// +/// It is strictly assumed that the `who` attribute of all provided assignments are unique. The +/// result will most likely be corrupt otherwise. +/// +/// O(|E_w| ⋅ k). +fn reduce_4(assignments: &mut Vec>) -> u32 { + let mut combination_map: Map = Map::new(); + let mut num_changed: u32 = Zero::zero(); + + // we have to use the old fashioned loops here with manual indexing. Borrowing assignments will + // not work since then there is NO way to mutate it inside. + for assignment_index in 0..assignments.len() { + let who = assignments[assignment_index].who.clone(); + + // all combinations for this particular voter + let distribution_ids = &assignments[assignment_index] + .distribution + .iter() + .map(|(t, _p)| t.clone()) + .collect::>(); + let candidate_combinations = combinations_2(distribution_ids); + + for (v1, v2) in candidate_combinations { + match combination_map.entry((v1.clone(), v2.clone())) { + Vacant(entry) => { + entry.insert(who.clone()); + } + Occupied(mut entry) => { + let other_who = entry.get_mut(); + + // double check if who is still voting for this pair. If not, it means that this + // pair is no longer valid and must have been removed in previous rounds. The + // reason for this is subtle; candidate_combinations is created once while the + // inner loop might remove some edges. Note that if count() > 2, the we have + // duplicates. + if assignments[assignment_index] + .distribution + .iter() + .filter(|(t, _)| *t == v1 || *t == v2) + .count() != 2 + { + continue; + } + + // check if other_who voted for the same pair v1, v2. + let maybe_other_assignments = assignments.iter().find(|a| a.who == *other_who); + if maybe_other_assignments.is_none() { + continue; + } + let other_assignment = + maybe_other_assignments.expect("value is checked to be 'Some'"); + + // Collect potential cycle votes + let mut other_cycle_votes = other_assignment + .distribution + .iter() + .filter_map(|(t, w)| { + if *t == v1 || *t == v2 { + Some((t.clone(), *w)) + } else { + None + } + }) + .collect::>(); + + let other_votes_count = other_cycle_votes.len(); + + // If the length is more than 2, then we have identified duplicates. For now, we + // just skip. Later on we can early exit and stop processing this data since it + // is corrupt anyhow. + debug_assert!(other_votes_count <= 2); + + if other_votes_count < 2 { + // This is not a cycle. Replace and continue. + *other_who = who.clone(); + continue; + } else if other_votes_count == 2 { + // This is a cycle. + let mut who_cycle_votes: Vec<(A, ExtendedBalance)> = Vec::with_capacity(2); + assignments[assignment_index] + .distribution + .iter() + .for_each(|(t, w)| { + if *t == v1 || *t == v2 { + who_cycle_votes.push((t.clone(), *w)); + } + }); + + if who_cycle_votes.len() != 2 { + continue; + } + + // Align the targets similarly. This helps with the circulation below. + if other_cycle_votes[0].0 != who_cycle_votes[0].0 { + other_cycle_votes.swap(0, 1); + } + + // Find min + let mut min_value: ExtendedBalance = Bounded::max_value(); + let mut min_index: usize = 0; + let cycle = who_cycle_votes + .iter() + .chain(other_cycle_votes.iter()) + .enumerate() + .map(|(index, (t, w))| { + if *w <= min_value { + min_value = *w; + min_index = index; + } + (t.clone(), *w) + }) + .collect::>(); + + // min was in the first part of the chained iters + let mut increase_indices: Vec = Vec::new(); + let mut decrease_indices: Vec = Vec::new(); + decrease_indices.push(min_index); + if min_index < 2 { + // min_index == 0 => sibling_index <- 1 + // min_index == 1 => sibling_index <- 0 + let sibling_index = 1 - min_index; + increase_indices.push(sibling_index); + // valid because the two chained sections of `cycle` are aligned; + // index [0, 2] are both voting for v1 or both v2. Same goes for [1, 3]. + decrease_indices.push(sibling_index + 2); + increase_indices.push(min_index + 2); + } else { + // min_index == 2 => sibling_index <- 3 + // min_index == 3 => sibling_index <- 2 + let sibling_index = 3 - min_index % 2; + increase_indices.push(sibling_index); + // valid because the two chained sections of `cycle` are aligned; + // index [0, 2] are both voting for v1 or both v2. Same goes for [1, 3]. + decrease_indices.push(sibling_index - 2); + increase_indices.push(min_index - 2); + } + + // apply changes + let mut remove_indices: Vec = Vec::with_capacity(1); + increase_indices.into_iter().for_each(|i| { + let voter = if i < 2 { + who.clone() + } else { + other_who.clone() + }; + // Note: so this is pretty ambiguous. We should only look for one + // assignment that meets this criteria and if we find multiple then that + // is a corrupt input. Same goes for the next block. + assignments + .iter_mut() + .filter(|a| a.who == voter) + .for_each(|ass| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == cycle[i].0) + .map(|idx| { + let next_value = + ass.distribution[idx].1.saturating_add(min_value); + ass.distribution[idx].1 = next_value; + }); + }); + }); + decrease_indices.into_iter().for_each(|i| { + let voter = if i < 2 { + who.clone() + } else { + other_who.clone() + }; + assignments + .iter_mut() + .filter(|a| a.who == voter) + .for_each(|ass| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == cycle[i].0) + .map(|idx| { + let next_value = + ass.distribution[idx].1.saturating_sub(min_value); + if next_value.is_zero() { + ass.distribution.remove(idx); + remove_indices.push(i); + num_changed += 1; + } else { + ass.distribution[idx].1 = next_value; + } + }); + }); + }); + + // remove either one of them. + let who_removed = remove_indices.iter().find(|i| **i < 2usize).is_some(); + let other_removed = + remove_indices.into_iter().find(|i| *i >= 2usize).is_some(); + + match (who_removed, other_removed) { + (false, true) => { + *other_who = who.clone(); + } + (true, false) => { + // nothing, other_who can stay there. + } + (true, true) => { + // remove and don't replace + entry.remove(); + } + (false, false) => { + // Neither of the edges was removed? impossible. + debug_assert!(false, "Duplicate voter (or other corrupt input)."); + } + } + } + } + } + } + } + + num_changed +} + +/// Reduce redundant edges from the edge weight graph, with all possible length. +/// +/// To get the best performance, this should be called after `reduce_4()`. +/// +/// Returns the number of edges removed. +/// +/// It is strictly assumed that the `who` attribute of all provided assignments are unique. The +/// result will most likely be corrupt otherwise. +/// +/// O(|Ew| ⋅ m) +fn reduce_all(assignments: &mut Vec>) -> u32 { + let mut num_changed: u32 = Zero::zero(); + let mut tree: BTreeMap, NodeRef> = BTreeMap::new(); + + // NOTE: This code can heavily use an index cache. Looking up a pair of (voter, target) in the + // assignments happens numerous times and and we can save time. For now it is written as such + // because abstracting some of this code into a function/closure is super hard due to borrow + // checks (and most likely needs unsafe code at the end). For now I will keep it as it and + // refactor later. + + // a flat iterator of (voter, target) over all pairs of votes. Similar to reduce_4, we loop + // without borrowing. + for assignment_index in 0..assignments.len() { + let voter = assignments[assignment_index].who.clone(); + + let mut dist_index = 0; + loop { + // A distribution could have been removed. We don't know for sure. Hence, we check. + let maybe_dist = assignments[assignment_index].distribution.get(dist_index); + if maybe_dist.is_none() { + // The rest of this loop is moot. + break; + } + let (target, _) = maybe_dist.expect("Value checked to be some").clone(); + + // store if they existed already. + let voter_id = NodeId::from(voter.clone(), NodeRole::Voter); + let target_id = NodeId::from(target.clone(), NodeRole::Target); + let voter_exists = tree.contains_key(&voter_id); + let target_exists = tree.contains_key(&target_id); + + // create both. + let voter_node = tree + .entry(voter_id.clone()) + .or_insert(Node::new(voter_id).into_ref()) + .clone(); + let target_node = tree + .entry(target_id.clone()) + .or_insert(Node::new(target_id).into_ref()) + .clone(); + + // If one exists but the other one doesn't, or if both does not, then set the existing + // one as the parent of the non-existing one and move on. Else, continue with the rest + // of the code. + match (voter_exists, target_exists) { + (false, false) => { + Node::set_parent_of(&target_node, &voter_node); + dist_index += 1; + continue; + } + (false, true) => { + Node::set_parent_of(&voter_node, &target_node); + dist_index += 1; + continue; + } + (true, false) => { + Node::set_parent_of(&target_node, &voter_node); + dist_index += 1; + continue; + } + (true, true) => { /* don't continue and execute the rest */ } + }; + + let (voter_root, voter_root_path) = Node::root(&voter_node); + let (target_root, target_root_path) = Node::root(&target_node); + + if voter_root != target_root { + // swap + merge(voter_root_path, target_root_path); + dist_index += 1; + } else { + // find common and cycle. + let common_count = trailing_common(&voter_root_path, &target_root_path); + + // because roots are the same. + #[cfg(feature = "std")] + debug_assert_eq!( + target_root_path.last().unwrap(), + voter_root_path.last().unwrap() + ); + debug_assert!(common_count > 0); + + // cycle part of each path will be `path[path.len() - common_count - 1 : 0]` + // NOTE: the order of chaining is important! it is always build from [target, ..., + // voter] + let cycle = target_root_path + .iter() + .take(target_root_path.len() - common_count + 1) + .cloned() + .chain( + voter_root_path + .iter() + .take(voter_root_path.len() - common_count) + .rev() + .cloned(), + ) + .collect::>>(); + + // a cycle's length shall always be multiple of two. + #[cfg(feature = "std")] + debug_assert_eq!(cycle.len() % 2, 0); + + // find minimum of cycle. + let mut min_value: ExtendedBalance = Bounded::max_value(); + // The voter and the target pair that create the min edge. + let mut min_target: A = Default::default(); + let mut min_voter: A = Default::default(); + // The index of the min in opaque cycle list. + let mut min_index = 0usize; + // 1 -> next // 0 -> prev + let mut min_direction = 0u32; + // helpers + let next_index = |i| { + if i < (cycle.len() - 1) { + i + 1 + } else { + 0 + } + }; + let prev_index = |i| { + if i > 0 { + i - 1 + } else { + cycle.len() - 1 + } + }; + for i in 0..cycle.len() { + if cycle[i].borrow().id.role == NodeRole::Voter { + // NOTE: sadly way too many clones since I don't want to make A: Copy + let current = cycle[i].borrow().id.who.clone(); + let next = cycle[next_index(i)].borrow().id.who.clone(); + let prev = cycle[prev_index(i)].borrow().id.who.clone(); + assignments.iter().find(|a| a.who == current).map(|ass| { + ass.distribution.iter().find(|d| d.0 == next).map(|(_, w)| { + if *w < min_value { + min_value = *w; + min_target = next.clone(); + min_voter = current.clone(); + min_index = i; + min_direction = 1; + } + }) + }); + assignments.iter().find(|a| a.who == current).map(|ass| { + ass.distribution.iter().find(|d| d.0 == prev).map(|(_, w)| { + if *w < min_value { + min_value = *w; + min_target = prev.clone(); + min_voter = current.clone(); + min_index = i; + min_direction = 0; + } + }) + }); + } + } + + // if the min edge is in the voter's sub-chain. + // [target, ..., X, Y, ... voter] + let target_chunk = target_root_path.len() - common_count; + let min_chain_in_voter = (min_index + min_direction as usize) > target_chunk; + + // walk over the cycle and update the weights + let mut should_inc_counter = true; + let start_operation_add = ((min_index % 2) + min_direction as usize) % 2 == 1; + let mut additional_removed = Vec::new(); + for i in 0..cycle.len() { + let current = cycle[i].borrow(); + if current.id.role == NodeRole::Voter { + let prev = cycle[prev_index(i)].borrow(); + assignments + .iter_mut() + .enumerate() + .filter(|(_, a)| a.who == current.id.who) + .for_each(|(target_ass_index, ass)| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == prev.id.who) + .map(|idx| { + let next_value = if i % 2 == 0 { + if start_operation_add { + ass.distribution[idx].1.saturating_add(min_value) + } else { + ass.distribution[idx].1.saturating_sub(min_value) + } + } else { + if start_operation_add { + ass.distribution[idx].1.saturating_sub(min_value) + } else { + ass.distribution[idx].1.saturating_add(min_value) + } + }; + + if next_value.is_zero() { + // if the removed edge is from the current assignment, dis_index + // should NOT be increased. + if target_ass_index == assignment_index { + should_inc_counter = false + } + ass.distribution.remove(idx); + num_changed += 1; + // only add if this is not the min itself. + if !(i == min_index && min_direction == 0) { + additional_removed.push(( + cycle[i].clone(), + cycle[prev_index(i)].clone(), + )); + } + } else { + ass.distribution[idx].1 = next_value; + } + }); + }); + + let next = cycle[next_index(i)].borrow(); + assignments + .iter_mut() + .enumerate() + .filter(|(_, a)| a.who == current.id.who) + .for_each(|(target_ass_index, ass)| { + ass.distribution + .iter_mut() + .position(|(t, _)| *t == next.id.who) + .map(|idx| { + let next_value = if i % 2 == 0 { + if start_operation_add { + ass.distribution[idx].1.saturating_sub(min_value) + } else { + ass.distribution[idx].1.saturating_add(min_value) + } + } else { + if start_operation_add { + ass.distribution[idx].1.saturating_add(min_value) + } else { + ass.distribution[idx].1.saturating_sub(min_value) + } + }; + + if next_value.is_zero() { + // if the removed edge is from the current assignment, dis_index + // should NOT be increased. + if target_ass_index == assignment_index { + should_inc_counter = false + } + ass.distribution.remove(idx); + num_changed += 1; + if !(i == min_index && min_direction == 1) { + additional_removed.push(( + cycle[i].clone(), + cycle[next_index(i)].clone(), + )); + } + } else { + ass.distribution[idx].1 = next_value; + } + }); + }); + } + } + + // don't do anything if the edge removed itself. This is always the first and last + // element + let should_reorg = !(min_index == (cycle.len() - 1) && min_direction == 1); + + // re-org. + if should_reorg { + let min_edge = vec![min_voter, min_target]; + if min_chain_in_voter { + // NOTE: safe; voter_root_path is always bigger than 1 element. + for i in 0..voter_root_path.len() - 1 { + let current = voter_root_path[i].clone().borrow().id.who.clone(); + let next = voter_root_path[i + 1].clone().borrow().id.who.clone(); + if min_edge.contains(¤t) && min_edge.contains(&next) { + break; + } + Node::set_parent_of(&voter_root_path[i + 1], &voter_root_path[i]); + } + Node::set_parent_of(&voter_node, &target_node); + } else { + // NOTE: safe; target_root_path is always bigger than 1 element. + for i in 0..target_root_path.len() - 1 { + let current = target_root_path[i].clone().borrow().id.who.clone(); + let next = target_root_path[i + 1].clone().borrow().id.who.clone(); + if min_edge.contains(¤t) && min_edge.contains(&next) { + break; + } + Node::set_parent_of(&target_root_path[i + 1], &target_root_path[i]); + } + Node::set_parent_of(&target_node, &voter_node); + } + } + + // remove every other node which has collapsed to zero + for (r1, r2) in additional_removed { + if Node::is_parent_of(&r1, &r2) { + Node::remove_parent(&r1); + } else if Node::is_parent_of(&r2, &r1) { + Node::remove_parent(&r2); + } + } + + // increment the counter if needed. + if should_inc_counter { + dist_index += 1; + } + } + } + } + + num_changed +} + +/// Reduce the given [`PhragmenResult`]. This removes redundant edges from without changing the +/// overall backing of any of the elected candidates. +/// +/// Returns the number of edges removed. +/// +/// IMPORTANT: It is strictly assumed that the `who` attribute of all provided assignments are +/// unique. The result will most likely be corrupt otherwise. Furthermore, if the _distribution +/// vector_ contains duplicate ids, only the first instance is ever updates. +/// +/// O(min{ |Ew| ⋅ k + m3 , |Ew| ⋅ m }) +pub fn reduce(assignments: &mut Vec>) -> u32 where { + let mut num_changed = reduce_4(assignments); + num_changed += reduce_all(assignments); + num_changed +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn merging_works() { + // D <-- A <-- B <-- C + // + // F <-- E + let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref(); + let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref(); + let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref(); + let c = Node::new(NodeId::from(4, NodeRole::Target)).into_ref(); + let e = Node::new(NodeId::from(5, NodeRole::Target)).into_ref(); + let f = Node::new(NodeId::from(6, NodeRole::Target)).into_ref(); + + Node::set_parent_of(&c, &b); + Node::set_parent_of(&b, &a); + Node::set_parent_of(&a, &d); + Node::set_parent_of(&e, &f); + + let path1 = vec![c.clone(), b.clone(), a.clone(), d.clone()]; + let path2 = vec![e.clone(), f.clone()]; + + merge(path1, path2); + // D <-- A <-- B <-- C + // | + // F --> E --> --> + assert_eq!(e.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c + } + + #[test] + fn merge_with_len_one() { + // D <-- A <-- B <-- C + // + // F <-- E + let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref(); + let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref(); + let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref(); + let c = Node::new(NodeId::from(4, NodeRole::Target)).into_ref(); + let f = Node::new(NodeId::from(6, NodeRole::Target)).into_ref(); + + Node::set_parent_of(&c, &b); + Node::set_parent_of(&b, &a); + Node::set_parent_of(&a, &d); + + let path1 = vec![c.clone(), b.clone(), a.clone(), d.clone()]; + let path2 = vec![f.clone()]; + + merge(path1, path2); + // D <-- A <-- B <-- C + // | + // F --> --> + assert_eq!(f.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c + } + + #[test] + fn basic_reduce_4_cycle_works() { + use super::*; + + let assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 25), (20, 75)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 50), (20, 50)], + }, + ]; + + let mut new_assignments = assignments.clone(); + let num_reduced = reduce_4(&mut new_assignments); + + assert_eq!(num_reduced, 1); + assert_eq!( + new_assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(20, 100),], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 75), (20, 25),], + }, + ], + ); + } + + #[test] + fn basic_reduce_all_cycles_works() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 15), (40, 15)], + }, + StakedAssignment { + who: 4, + distribution: vec![(20, 10), (30, 10), (40, 20)], + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 20), (30, 10), (40, 20)], + }, + ]; + + assert_eq!(3, reduce_all(&mut assignments)); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10),] + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5),], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 30),], + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 40),] + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 15), (30, 20), (40, 15),], + }, + ], + ) + } + + #[test] + fn basic_reduce_works() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 15), (40, 15)], + }, + StakedAssignment { + who: 4, + distribution: vec![(20, 10), (30, 10), (40, 20)], + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 20), (30, 10), (40, 20)], + }, + ]; + + assert_eq!(3, reduce(&mut assignments)); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10),] + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5),], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 30),], + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 40),] + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 15), (30, 20), (40, 15),], + }, + ], + ) + } + + #[test] + fn should_deal_with_self_vote() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 15), (40, 15)], + }, + StakedAssignment { + who: 4, + distribution: vec![(20, 10), (30, 10), (40, 20)], + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 20), (30, 10), (40, 20)], + }, + // self vote from 10 and 20 to itself. + StakedAssignment { + who: 10, + distribution: vec![(10, 100)], + }, + StakedAssignment { + who: 20, + distribution: vec![(20, 200)], + }, + ]; + + assert_eq!(3, reduce(&mut assignments)); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10),] + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 5),], + }, + StakedAssignment { + who: 3, + distribution: vec![(20, 30),], + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 40),] + }, + StakedAssignment { + who: 5, + distribution: vec![(20, 15), (30, 20), (40, 15),], + }, + // should stay untouched. + StakedAssignment { + who: 10, + distribution: vec![(10, 100)] + }, + StakedAssignment { + who: 20, + distribution: vec![(20, 200)] + }, + ], + ) + } + + #[test] + fn reduce_3_common_votes_same_weight() { + let mut assignments = vec![ + StakedAssignment { + who: 4, + distribution: vec![ + ( + 1000000, + 100, + ), + ( + 1000002, + 100, + ), + ( + 1000004, + 100, + ), + ], + }, + StakedAssignment { + who: 5, + distribution: vec![ + ( + 1000000, + 100, + ), + ( + 1000002, + 100, + ), + ( + 1000004, + 100, + ), + ], + }, + ]; + + reduce_4(&mut assignments); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 4, + distribution: vec![(1000000, 200,), (1000004, 100,),], + }, + StakedAssignment { + who: 5, + distribution: vec![(1000002, 200,), (1000004, 100,),], + }, + ], + ) + } + + #[test] + #[should_panic] + fn reduce_panics_on_duplicate_voter() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 10), (20, 10)], + }, + StakedAssignment { + who: 1, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 2, + distribution: vec![(10, 15), (20, 15)], + }, + ]; + + reduce(&mut assignments); + } + + #[test] + fn should_deal_with_duplicates_target() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 15), (20, 5)], + }, + StakedAssignment { + who: 2, + distribution: vec![ + (10, 15), + (20, 15), + // duplicate + (10, 1), + // duplicate + (20, 1), + ], + }, + ]; + + reduce(&mut assignments); + + assert_eq!( + assignments, + vec![ + StakedAssignment { + who: 1, + distribution: vec![(10, 20),], + }, + StakedAssignment { + who: 2, + distribution: vec![ + (10, 10), + (20, 20), + // duplicate votes are silently ignored. + (10, 1), + (20, 1), + ], + }, + ], + ) + } + + #[test] + fn bound_should_be_kept() { + let mut assignments = vec![ + StakedAssignment { + who: 1, + distribution: vec![(103, 72), (101, 53), (100, 83), (102, 38)], + }, + StakedAssignment { + who: 2, + distribution: vec![(103, 18), (101, 36), (102, 54), (100, 94)], + }, + StakedAssignment { + who: 3, + distribution: vec![(100, 96), (101, 35), (102, 52), (103, 69)], + }, + StakedAssignment { + who: 4, + distribution: vec![(102, 34), (100, 47), (103, 91), (101, 73)], + }, + ]; + + let winners = vec![103, 101, 100, 102]; + + let n = 4; + let m = winners.len() as u32; + let num_reduced = reduce_all(&mut assignments); + assert!(16 - num_reduced <= n + m); + } +} diff --git a/primitives/phragmen/src/tests.rs b/primitives/phragmen/src/tests.rs index 8bcf007c7b4c7..e9861ede723cf 100644 --- a/primitives/phragmen/src/tests.rs +++ b/primitives/phragmen/src/tests.rs @@ -19,11 +19,12 @@ #![cfg(test)] use crate::mock::*; -use crate::{elect, PhragmenResult, PhragmenStakedAssignment, build_support_map, Support, equalize}; +use crate::{ + elect, equalize, build_support_map, is_score_better, + Support, StakedAssignment, Assignment, PhragmenResult, ExtendedBalance, +}; use substrate_test_utils::assert_eq_uvec; -use sp_runtime::Perbill; - -type Output = Perbill; +use sp_runtime::{Perbill, Permill, Percent, PerU16, traits::Convert}; #[test] fn float_phragmen_poc_works() { @@ -81,7 +82,7 @@ fn phragmen_poc_works() { ]; let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); - let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, @@ -92,9 +93,21 @@ fn phragmen_poc_works() { assert_eq_uvec!( assignments, vec![ - (10, vec![(2, Perbill::from_percent(100))]), - (20, vec![(3, Perbill::from_percent(100))]), - (30, vec![(2, Perbill::from_percent(100/2)), (3, Perbill::from_percent(100/2))]), + Assignment { + who: 10u64, + distribution: vec![(2, Perbill::from_percent(100))], + }, + Assignment { + who: 20, + distribution: vec![(3, Perbill::from_percent(100))], + }, + Assignment { + who: 30, + distribution: vec![ + (2, Perbill::from_percent(100/2)), + (3, Perbill::from_percent(100/2)), + ], + }, ] ); } @@ -115,7 +128,10 @@ fn phragmen_poc_2_works() { (4, 500), ]); - run_and_compare(candidates, voters, stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates, voters, &stake_of, 2, 2); } #[test] @@ -133,7 +149,10 @@ fn phragmen_poc_3_works() { (4, 1000), ]); - run_and_compare(candidates, voters, stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates.clone(), voters.clone(), &stake_of, 2, 2); + run_and_compare::(candidates, voters, &stake_of, 2, 2); } #[test] @@ -149,7 +168,7 @@ fn phragmen_accuracy_on_large_scale_only_validators() { (5, (u64::max_value() - 2).into()), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates.clone(), @@ -158,7 +177,7 @@ fn phragmen_accuracy_on_large_scale_only_validators() { assert_eq_uvec!(winners, vec![(1, 18446744073709551614u128), (5, 18446744073709551613u128)]); assert_eq!(assignments.len(), 2); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -179,7 +198,7 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() { (14, u64::max_value().into()), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, @@ -190,13 +209,25 @@ fn phragmen_accuracy_on_large_scale_validators_and_nominators() { assert_eq!( assignments, vec![ - (13, vec![(1, Perbill::one())]), - (14, vec![(2, Perbill::one())]), - (1, vec![(1, Perbill::one())]), - (2, vec![(2, Perbill::one())]), + Assignment { + who: 13u64, + distribution: vec![(1, Perbill::one())], + }, + Assignment { + who: 14, + distribution: vec![(2, Perbill::one())], + }, + Assignment { + who: 1, + distribution: vec![(1, Perbill::one())], + }, + Assignment { + who: 2, + distribution: vec![(2, Perbill::one())], + }, ] ); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -210,7 +241,7 @@ fn phragmen_accuracy_on_small_scale_self_vote() { (30, 1), ]); - let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>( 3, 3, candidates, @@ -240,7 +271,7 @@ fn phragmen_accuracy_on_small_scale_no_self_vote() { (3, 1), ]); - let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>( 3, 3, candidates, @@ -273,7 +304,7 @@ fn phragmen_large_scale_test() { (50, 990000000000000000), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, @@ -281,7 +312,7 @@ fn phragmen_large_scale_test() { ).unwrap(); assert_eq_uvec!(winners, vec![(24, 1490000000000200000u128), (22, 1490000000000100000u128)]); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -299,7 +330,7 @@ fn phragmen_large_scale_test_2() { (50, nom_budget.into()), ]); - let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments } = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, @@ -310,12 +341,24 @@ fn phragmen_large_scale_test_2() { assert_eq!( assignments, vec![ - (50, vec![(2, Perbill::from_parts(500000001)), (4, Perbill::from_parts(499999999))]), - (2, vec![(2, Perbill::one())]), - (4, vec![(4, Perbill::one())]), + Assignment { + who: 50u64, + distribution: vec![ + (2, Perbill::from_parts(500000001)), + (4, Perbill::from_parts(499999999)) + ], + }, + Assignment { + who: 2, + distribution: vec![(2, Perbill::one())], + }, + Assignment { + who: 4, + distribution: vec![(4, Perbill::one())], + }, ], ); - check_assignments(assignments); + check_assignments_sum(assignments); } #[test] @@ -348,7 +391,7 @@ fn phragmen_linear_equalize() { (130, 1000), ]); - run_and_compare(candidates, voters, stake_of, 2, 2); + run_and_compare::(candidates, voters, &stake_of, 2, 2); } #[test] @@ -363,7 +406,7 @@ fn elect_has_no_entry_barrier() { (2, 10), ]); - let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Output>( + let PhragmenResult { winners, assignments: _ } = elect::<_, _, TestCurrencyToVote, Perbill>( 3, 3, candidates, @@ -390,7 +433,7 @@ fn minimum_to_elect_is_respected() { (2, 10), ]); - let maybe_result = elect::<_, _, TestCurrencyToVote, Output>( + let maybe_result = elect::<_, _, TestCurrencyToVote, Perbill>( 10, 10, candidates, @@ -416,7 +459,7 @@ fn self_votes_should_be_kept() { (1, 8), ]); - let result = elect::<_, _, TestCurrencyToVote, Output>( + let result = elect::<_, _, TestCurrencyToVote, Perbill>( 2, 2, candidates, @@ -427,27 +470,28 @@ fn self_votes_should_be_kept() { assert_eq!( result.assignments, vec![ - (10, vec![(10, Perbill::from_percent(100))]), - (20, vec![(20, Perbill::from_percent(100))]), - (1, vec![ + Assignment { who: 10, distribution: vec![(10, Perbill::from_percent(100))] }, + Assignment { who: 20, distribution: vec![(20, Perbill::from_percent(100))] }, + Assignment { who: 1, distribution: vec![ (10, Perbill::from_percent(50)), (20, Perbill::from_percent(50)) ] - ) + }, ], ); - let mut supports = build_support_map::< - Balance, - AccountId, - _, - TestCurrencyToVote, - Output, - >( - &result.winners.into_iter().map(|(who, _)| who).collect(), - &result.assignments, - &stake_of - ); + let staked_assignments: Vec> = result.assignments + .into_iter() + .map(|a| { + let stake = >::convert(stake_of(&a.who)) as ExtendedBalance; + a.into_staked(stake, true) + }).collect(); + + let winners = result.winners.into_iter().map(|(who, _)| who).collect::>(); + let (mut supports, _) = build_support_map::( + winners.as_slice(), + &staked_assignments, + ); assert_eq!(supports.get(&5u64), None); assert_eq!( @@ -459,23 +503,13 @@ fn self_votes_should_be_kept() { &Support { total: 24u128, voters: vec![(20u64, 20u128), (1u64, 4u128)] }, ); - let assignments = result.assignments; - let mut staked_assignments - : Vec<(AccountId, Vec>)> - = Vec::with_capacity(assignments.len()); - for (n, assignment) in assignments.iter() { - let mut staked_assignment - : Vec> - = Vec::with_capacity(assignment.len()); - let stake = stake_of(&n); - for (c, per_thing) in assignment.iter() { - let vote_stake = *per_thing * stake; - staked_assignment.push((c.clone(), vote_stake)); - } - staked_assignments.push((n.clone(), staked_assignment)); - } - - equalize::(staked_assignments, &mut supports, 0, 2usize, &stake_of); + equalize::( + staked_assignments, + &mut supports, + 0, + 2usize, + &stake_of, + ); assert_eq!( supports.get(&10u64).unwrap(), @@ -486,3 +520,462 @@ fn self_votes_should_be_kept() { &Support { total: 20u128, voters: vec![(20u64, 20u128)] }, ); } + +#[test] +fn assignment_convert_works() { + let staked = StakedAssignment { + who: 1 as AccountId, + distribution: vec![ + (20, 100 as Balance), + (30, 25), + ], + }; + + let assignment = staked.clone().into_assignment(true); + assert_eq!( + assignment, + Assignment { + who: 1, + distribution: vec![ + (20, Perbill::from_percent(80)), + (30, Perbill::from_percent(20)), + ] + } + ); + + assert_eq!( + assignment.into_staked(125, true), + staked, + ); +} + +#[test] +fn score_comparison_is_lexicographical() { + // only better in the fist parameter, worse in the other two ✅ + assert_eq!( + is_score_better([10, 20, 30], [12, 10, 35]), + true, + ); + + // worse in the first, better in the other two ❌ + assert_eq!( + is_score_better([10, 20, 30], [9, 30, 10]), + false, + ); + + // equal in the first, the second one dictates. + assert_eq!( + is_score_better([10, 20, 30], [10, 25, 40]), + true, + ); + + // equal in the first two, the last one dictates. + assert_eq!( + is_score_better([10, 20, 30], [10, 20, 40]), + false, + ); +} + +mod compact { + use codec::{Decode, Encode}; + use crate::generate_compact_solution_type; + use super::{AccountId, Balance}; + // these need to come from the same dev-dependency `sp-phragmen`, not from the crate. + use sp_phragmen::{Assignment, StakedAssignment, Error as PhragmenError}; + use sp_std::{convert::{TryInto, TryFrom}, fmt::Debug}; + use sp_runtime::Percent; + + type Accuracy = Percent; + + generate_compact_solution_type!(TestCompact, 16); + + #[test] + fn compact_struct_is_codec() { + let compact = TestCompact::<_, _, _> { + votes1: vec![(2u64, 20), (4, 40)], + votes2: vec![ + (1, (10, Accuracy::from_percent(80)), 11), + (5, (50, Accuracy::from_percent(85)), 51), + ], + ..Default::default() + }; + + let encoded = compact.encode(); + + assert_eq!( + compact, + Decode::decode(&mut &encoded[..]).unwrap(), + ); + } + + fn basic_ratio_test_with() where + V: codec::Codec + Copy + Default + PartialEq + Eq + TryInto + TryFrom + From + Debug, + T: codec::Codec + Copy + Default + PartialEq + Eq + TryInto + TryFrom + From + Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + >::Error: std::fmt::Debug, + { + let voters = vec![ + 2 as AccountId, + 4, + 1, + 5, + 3, + ]; + let targets = vec![ + 10 as AccountId, + 11, + 20, // 2 + 30, + 31, // 4 + 32, + 40, // 6 + 50, + 51, // 8 + ]; + + let assignments = vec![ + Assignment { + who: 2 as AccountId, + distribution: vec![(20u64, Accuracy::from_percent(100))] + }, + Assignment { + who: 4, + distribution: vec![(40, Accuracy::from_percent(100))], + }, + Assignment { + who: 1, + distribution: vec![ + (10, Accuracy::from_percent(80)), + (11, Accuracy::from_percent(20)) + ], + }, + Assignment { + who: 5, + distribution: vec![ + (50, Accuracy::from_percent(85)), + (51, Accuracy::from_percent(15)), + ] + }, + Assignment { + who: 3, + distribution: vec![ + (30, Accuracy::from_percent(50)), + (31, Accuracy::from_percent(25)), + (32, Accuracy::from_percent(25)), + ], + }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let compacted = >::from_assignment( + assignments.clone(), + voter_index, + target_index, + ).unwrap(); + + assert_eq!( + compacted, + TestCompact { + votes1: vec![(V::from(0u8), T::from(2u8)), (V::from(1u8), T::from(6u8))], + votes2: vec![ + (V::from(2u8), (T::from(0u8), Accuracy::from_percent(80)), T::from(1u8)), + (V::from(3u8), (T::from(7u8), Accuracy::from_percent(85)), T::from(8u8)), + ], + votes3: vec![ + ( + V::from(4), + [(T::from(3u8), Accuracy::from_percent(50)), (T::from(4u8), Accuracy::from_percent(25))], + T::from(5u8), + ), + ], + ..Default::default() + } + ); + + let voter_at = |a: V| -> Option { voters.get(>::try_into(a).unwrap()).cloned() }; + let target_at = |a: T| -> Option { targets.get(>::try_into(a).unwrap()).cloned() }; + + assert_eq!( + compacted.into_assignment(voter_at, target_at).unwrap(), + assignments, + ); + } + + #[test] + fn basic_from_and_into_compact_works_assignments() { + basic_ratio_test_with::(); + basic_ratio_test_with::(); + basic_ratio_test_with::(); + } + + #[test] + fn basic_from_and_into_compact_works_staked_assignments() { + let voters = vec![ + 2 as AccountId, + 4, + 1, + 5, + 3, + ]; + let targets = vec![ + 10 as AccountId, 11, + 20, + 30, 31, 32, + 40, + 50, 51, + ]; + + let assignments = vec![ + StakedAssignment { + who: 2 as AccountId, + distribution: vec![(20, 100 as Balance)] + }, + StakedAssignment { + who: 4, + distribution: vec![(40, 100)], + }, + StakedAssignment { + who: 1, + distribution: vec![ + (10, 80), + (11, 20) + ], + }, + StakedAssignment { + who: 5, distribution: + vec![ + (50, 85), + (51, 15), + ] + }, + StakedAssignment { + who: 3, + distribution: vec![ + (30, 50), + (31, 25), + (32, 25), + ], + }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let compacted = >::from_staked( + assignments.clone(), + voter_index, + target_index, + ).unwrap(); + + assert_eq!( + compacted, + TestCompact { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![ + (2, (0, 80), 1), + (3, (7, 85), 8), + ], + votes3: vec![ + (4, [(3, 50), (4, 25)], 5), + ], + ..Default::default() + } + ); + + let max_of_fn = |_: &AccountId| -> Balance { 100u128 }; + let voter_at = |a: u16| -> Option { voters.get(a as usize).cloned() }; + let target_at = |a: u16| -> Option { targets.get(a as usize).cloned() }; + + assert_eq!( + compacted.into_staked( + max_of_fn, + voter_at, + target_at, + ).unwrap(), + assignments, + ); + } + + #[test] + fn compact_into_stake_must_report_overflow() { + // The last edge which is computed from the rest should ALWAYS be positive. + // in votes2 + let compact = TestCompact:: { + votes1: Default::default(), + votes2: vec![(0, (1, 10), 2)], + ..Default::default() + }; + + let entity_at = |a: u16| -> Option { Some(a as AccountId) }; + let max_of = |_: &AccountId| -> Balance { 5 }; + + assert_eq!( + compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + + // in votes3 onwards + let compact = TestCompact:: { + votes1: Default::default(), + votes2: Default::default(), + votes3: vec![(0, [(1, 7), (2, 8)], 3)], + ..Default::default() + }; + + assert_eq!( + compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + + // Also if equal + let compact = TestCompact:: { + votes1: Default::default(), + votes2: Default::default(), + // 5 is total, we cannot leave none for 30 here. + votes3: vec![(0, [(1, 3), (2, 2)], 3)], + ..Default::default() + }; + + assert_eq!( + compact.into_staked(&max_of, &entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + } + + #[test] + fn compact_into_assignment_must_report_overflow() { + // in votes2 + let compact = TestCompact:: { + votes1: Default::default(), + votes2: vec![(0, (1, Accuracy::from_percent(100)), 2)], + ..Default::default() + }; + + let entity_at = |a: u16| -> Option { Some(a as AccountId) }; + + assert_eq!( + compact.into_assignment(&entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + + // in votes3 onwards + let compact = TestCompact:: { + votes1: Default::default(), + votes2: Default::default(), + votes3: vec![(0, [(1, Accuracy::from_percent(70)), (2, Accuracy::from_percent(80))], 3)], + ..Default::default() + }; + + assert_eq!( + compact.into_assignment(&entity_at, &entity_at).unwrap_err(), + PhragmenError::CompactStakeOverflow, + ); + } + + #[test] + fn target_count_overflow_is_detected() { + let assignments = vec![ + StakedAssignment { + who: 1 as AccountId, + distribution: (10..26).map(|i| (i as AccountId, i as Balance)).collect::>(), + }, + ]; + + let entity_index = |a: &AccountId| -> Option { Some(*a as u16) }; + + let compacted = >::from_staked( + assignments.clone(), + entity_index, + entity_index, + ); + + assert!(compacted.is_ok()); + + let assignments = vec![ + StakedAssignment { + who: 1 as AccountId, + distribution: (10..27).map(|i| (i as AccountId, i as Balance)).collect::>(), + }, + ]; + + let compacted = >::from_staked( + assignments.clone(), + entity_index, + entity_index, + ); + + assert_eq!( + compacted.unwrap_err(), + PhragmenError::CompactTargetOverflow, + ); + + let assignments = vec![ + Assignment { + who: 1 as AccountId, + distribution: (10..27).map(|i| (i as AccountId, Percent::from_parts(i as u8))).collect::>(), + }, + ]; + + let compacted = >::from_assignment( + assignments.clone(), + entity_index, + entity_index, + ); + + assert_eq!( + compacted.unwrap_err(), + PhragmenError::CompactTargetOverflow, + ); + } + + #[test] + fn zero_target_count_is_ignored() { + let voters = vec![1 as AccountId, 2]; + let targets = vec![10 as AccountId, 11]; + + let assignments = vec![ + StakedAssignment { + who: 1 as AccountId, + distribution: vec![(10, 100 as Balance), (11, 100)] + }, + StakedAssignment { + who: 2, + distribution: vec![], + }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let compacted = >::from_staked( + assignments.clone(), + voter_index, + target_index, + ).unwrap(); + + assert_eq!( + compacted, + TestCompact { + votes1: Default::default(), + votes2: vec![(0, (0, 100), 1)], + ..Default::default() + } + ); + } +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 632deb13cbb45..1a9aad6bdf132 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -68,7 +68,10 @@ pub use sp_application_crypto::{RuntimeAppPublic, BoundToRuntimeAppPublic}; pub use sp_core::RuntimeDebug; /// Re-export top-level arithmetic stuff. -pub use sp_arithmetic::{Perquintill, Perbill, Permill, Percent, Rational128, Fixed64, PerThing}; +pub use sp_arithmetic::{ + Perquintill, Perbill, Permill, Percent, PerU16, Rational128, Fixed64, PerThing, + traits::SaturatedConversion, +}; /// Re-export 128 bit helpers. pub use sp_arithmetic::helpers_128bit; /// Re-export big_uint stuff. diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 0ddb5c4dbf28e..6bd4b263c39e3 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -929,7 +929,7 @@ pub trait OpaqueKeys: Clone { fn key_ids() -> &'static [crate::KeyTypeId]; /// Get the raw bytes of key with key-type ID `i`. fn get_raw(&self, i: super::KeyTypeId) -> &[u8]; - /// Get the decoded key with index `i`. + /// Get the decoded key with key-type ID `i`. fn get(&self, i: super::KeyTypeId) -> Option { T::decode(&mut self.get_raw(i)).ok() } diff --git a/primitives/staking/src/offence.rs b/primitives/staking/src/offence.rs index 06e73f018b765..584f3a75ea3ab 100644 --- a/primitives/staking/src/offence.rs +++ b/primitives/staking/src/offence.rs @@ -142,11 +142,20 @@ pub trait OnOffenceHandler { /// Zero is a valid value for a fraction. /// /// The `session` parameter is the session index of the offence. + /// + /// The receiver might decide to not accept this offence. In this case, the call site is + /// responsible for queuing the report and re-submitting again. fn on_offence( offenders: &[OffenceDetails], slash_fraction: &[Perbill], session: SessionIndex, - ); + ) -> Result<(), ()>; + + /// Can an offence be reported now or not. This is an method to short-circuit a call into + /// `on_offence`. Ideally, a correct implementation should return `false` if `on_offence` will + /// return `Err`. Nonetheless, this is up to the implementation and this trait cannot guarantee + /// it. + fn can_report() -> bool; } impl OnOffenceHandler for () { @@ -154,7 +163,9 @@ impl OnOffenceHandler for () { _offenders: &[OffenceDetails], _slash_fraction: &[Perbill], _session: SessionIndex, - ) {} + ) -> Result<(), ()> { Ok(()) } + + fn can_report() -> bool { true } } /// A details about an offending authority for a particular kind of offence.