diff --git a/Cargo.lock b/Cargo.lock index f4ae07eae343e..68de650126d5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom 0.2.3", "once_cell", - "version_check 0.9.2", + "version_check 0.9.4", ] [[package]] @@ -141,6 +141,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" +[[package]] +name = "array-bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f72e9d6fac4bc80778ea470b20197b88d28c292bb7d60c3fb099280003cd19" + [[package]] name = "arrayref" version = "0.3.6" @@ -255,7 +261,7 @@ dependencies = [ "fastrand", "futures-lite", "libc", - "log 0.4.14", + "log 0.4.17", "nb-connect", "once_cell", "parking", @@ -301,7 +307,7 @@ dependencies = [ "futures-lite", "gloo-timers", "kv-log-macro", - "log 0.4.14", + "log 0.4.17", "memchr", "num_cpus", "once_cell", @@ -369,7 +375,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4401f0a3622dad2e0763fa79e0eb328bc70fb7dccfdd645341f00d671247d6" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-sink", "futures-util", "memchr", @@ -382,7 +388,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-sink", "futures-util", "memchr", @@ -485,6 +491,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.3.3" @@ -509,7 +521,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "hex", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", @@ -554,7 +566,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-rpc", @@ -574,7 +586,7 @@ dependencies = [ "env_logger 0.9.0", "hex", "hex-literal", - "log 0.4.14", + "log 0.4.17", "tiny-keccak", ] @@ -858,9 +870,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "bzip2-sys" @@ -1206,7 +1218,7 @@ dependencies = [ "cranelift-codegen-shared 0.76.0", "cranelift-entity 0.76.0", "gimli 0.25.0", - "log 0.4.14", + "log 0.4.17", "regalloc 0.0.31", "smallvec 1.8.0", "target-lexicon", @@ -1223,7 +1235,7 @@ dependencies = [ "cranelift-codegen-shared 0.82.3", "cranelift-entity 0.82.3", "gimli 0.26.1", - "log 0.4.14", + "log 0.4.17", "regalloc 0.0.34", "smallvec 1.8.0", "target-lexicon", @@ -1282,7 +1294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen 0.76.0", - "log 0.4.14", + "log 0.4.17", "smallvec 1.8.0", "target-lexicon", ] @@ -1294,7 +1306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ "cranelift-codegen 0.82.3", - "log 0.4.14", + "log 0.4.17", "smallvec 1.8.0", "target-lexicon", ] @@ -1320,7 +1332,7 @@ dependencies = [ "cranelift-entity 0.82.3", "cranelift-frontend 0.82.3", "itertools", - "log 0.4.14", + "log 0.4.17", "smallvec 1.8.0", "wasmparser 0.83.0", "wasmtime-types", @@ -1977,7 +1989,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.14", + "log 0.4.17", "regex", "termcolor", ] @@ -1988,7 +2000,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ - "log 0.4.14", + "log 0.4.17", "regex", ] @@ -2000,7 +2012,7 @@ checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime 2.1.0", - "log 0.4.14", + "log 0.4.17", "regex", "termcolor", ] @@ -2061,9 +2073,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" dependencies = [ "instant", ] @@ -2094,7 +2106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" dependencies = [ "env_logger 0.7.1", - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -2106,7 +2118,7 @@ dependencies = [ "either", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.17", "num-traits", "parity-scale-codec", "parking_lot 0.11.2", @@ -2191,7 +2203,7 @@ dependencies = [ "frame-system", "hex-literal", "linregress", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "paste", "scale-info", @@ -2222,7 +2234,7 @@ dependencies = [ "itertools", "kvdb", "linked-hash-map", - "log 0.4.14", + "log 0.4.17", "memory-db", "parity-scale-codec", "prettytable-rs", @@ -2346,7 +2358,7 @@ dependencies = [ "frame-support-procedural", "frame-system", "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.17", "once_cell", "parity-scale-codec", "parity-util-mem", @@ -2450,7 +2462,7 @@ version = "4.0.0-dev" dependencies = [ "criterion", "frame-support", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "serde", @@ -2717,7 +2729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check 0.9.2", + "version_check 0.9.4", ] [[package]] @@ -2785,7 +2797,7 @@ dependencies = [ "bitflags", "libc", "libgit2-sys", - "log 0.4.14", + "log 0.4.17", "url 2.2.1", ] @@ -2804,7 +2816,7 @@ dependencies = [ "aho-corasick", "bstr", "fnv", - "log 0.4.14", + "log 0.4.17", "regex", ] @@ -2838,7 +2850,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "fnv", "futures-core", "futures-sink", @@ -2863,7 +2875,7 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" dependencies = [ - "log 0.4.14", + "log 0.4.17", "pest", "pest_derive", "quick-error 2.0.0", @@ -2886,6 +2898,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + [[package]] name = "hashbrown" version = "0.11.2" @@ -3005,7 +3023,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "fnv", "itoa 0.4.8", ] @@ -3016,7 +3034,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "http", "pin-project-lite 0.2.6", ] @@ -3073,7 +3091,7 @@ version = "0.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-channel", "futures-core", "futures-util", @@ -3100,7 +3118,7 @@ dependencies = [ "ct-logs", "futures-util", "hyper 0.14.16", - "log 0.4.14", + "log 0.4.17", "rustls 0.19.1", "rustls-native-certs 0.5.0", "tokio", @@ -3114,7 +3132,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "hyper 0.14.16", "native-tls", "tokio", @@ -3182,7 +3200,7 @@ dependencies = [ "if-addrs", "ipnet", "libc", - "log 0.4.14", + "log 0.4.17", "winapi 0.3.9", ] @@ -3217,12 +3235,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" dependencies = [ "autocfg 1.0.1", - "hashbrown 0.11.2", + "hashbrown 0.9.1", "serde", ] @@ -3334,7 +3352,7 @@ dependencies = [ "hyper-tls", "jsonrpc-core", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.17", "serde", "serde_json 1.0.79", "tokio", @@ -3351,7 +3369,7 @@ dependencies = [ "futures 0.3.21", "futures-executor", "futures-util", - "log 0.4.14", + "log 0.4.17", "serde", "serde_derive", "serde_json 1.0.79", @@ -3389,7 +3407,7 @@ dependencies = [ "hyper 0.14.16", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.14", + "log 0.4.17", "net2", "parking_lot 0.11.2", "unicase 2.6.0", @@ -3404,7 +3422,7 @@ dependencies = [ "futures 0.3.21", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.14", + "log 0.4.17", "parity-tokio-ipc", "parking_lot 0.11.2", "tower-service", @@ -3419,7 +3437,7 @@ dependencies = [ "futures 0.3.21", "jsonrpc-core", "lazy_static", - "log 0.4.14", + "log 0.4.17", "parking_lot 0.11.2", "rand 0.7.3", "serde", @@ -3431,12 +3449,12 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", "globset", "jsonrpc-core", "lazy_static", - "log 0.4.14", + "log 0.4.17", "tokio", "tokio-stream", "tokio-util 0.6.7", @@ -3452,7 +3470,7 @@ dependencies = [ "futures 0.3.21", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.14", + "log 0.4.17", "parity-ws", "parking_lot 0.11.2", "slab", @@ -3596,7 +3614,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -3628,7 +3646,7 @@ checksum = "ca7fbdfd71cd663dceb0faf3367a99f8cf724514933e9867cec4995b6027cbc1" dependencies = [ "fs-swap", "kvdb", - "log 0.4.14", + "log 0.4.17", "num_cpus", "owning_ref", "parity-util-mem", @@ -3702,9 +3720,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.1" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libp2p" @@ -3713,7 +3731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bec54343492ba5940a6c555e512c6721139835d28c59bc22febece72dfd0d9d" dependencies = [ "atomic", - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", "lazy_static", "libp2p-core", @@ -3762,7 +3780,7 @@ dependencies = [ "futures-timer", "lazy_static", "libsecp256k1", - "log 0.4.14", + "log 0.4.17", "multiaddr", "multihash 0.14.0", "multistream-select", @@ -3801,7 +3819,7 @@ dependencies = [ "async-std-resolver", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "smallvec 1.8.0", "trust-dns-resolver", ] @@ -3817,7 +3835,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost", "prost-build", "rand 0.7.3", @@ -3833,13 +3851,13 @@ dependencies = [ "asynchronous-codec 0.6.0", "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.4.0", "fnv", "futures 0.3.21", "hex_fmt", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost", "prost-build", "rand 0.7.3", @@ -3859,7 +3877,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "lru 0.6.6", "prost", "prost-build", @@ -3875,13 +3893,13 @@ checksum = "a2297dc0ca285f3a09d1368bde02449e539b46f94d32d53233f53f6625bcd3ba" dependencies = [ "arrayvec 0.5.2", "asynchronous-codec 0.6.0", - "bytes 1.1.0", + "bytes 1.4.0", "either", "fnv", "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost", "prost-build", "rand 0.7.3", @@ -3907,7 +3925,7 @@ dependencies = [ "lazy_static", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "rand 0.8.4", "smallvec 1.8.0", "socket2 0.4.4", @@ -3935,10 +3953,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2cd64ef597f40e14bfce0497f50ecb63dd6d201c61796daeb4227078834fbf" dependencies = [ "asynchronous-codec 0.6.0", - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", @@ -3952,12 +3970,12 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8772c7a99088221bb7ca9c5c0574bf55046a7ab4c319f3619b275f28c8fb87a" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "curve25519-dalek 3.0.2", "futures 0.3.21", "lazy_static", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "prost", "prost-build", "rand 0.8.4", @@ -3977,7 +3995,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "void", "wasm-timer", @@ -3990,10 +4008,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fba1a6ff33e4a274c89a3b1d78b9f34f32af13265cc5c46c16938262d4e945a" dependencies = [ "asynchronous-codec 0.6.0", - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "prost", "prost-build", "unsigned-varint 0.7.0", @@ -4007,7 +4025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f1a458bbda880107b5b36fcb9b5a1ef0c329685da0e203ed692a8ebe64cc92c" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "pin-project 1.0.10", "rand 0.7.3", "salsa20", @@ -4021,12 +4039,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2852b61c90fa8ce3c8fcc2aba76e6cefc20d648f9df29157d6b3a916278ef3e3" dependencies = [ "asynchronous-codec 0.6.0", - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", "futures-timer", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "pin-project 1.0.10", "prost", "prost-build", @@ -4048,7 +4066,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost", "prost-build", "rand 0.8.4", @@ -4066,12 +4084,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a877a4ced6d46bf84677e1974e8cf61fb434af73b2e96fb48d6cb6223a4634d8" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", - "lru 0.7.5", + "log 0.4.17", + "lru 0.7.8", "rand 0.7.3", "smallvec 1.8.0", "unsigned-varint 0.7.0", @@ -4087,7 +4105,7 @@ dependencies = [ "either", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "smallvec 1.8.0", "void", @@ -4117,7 +4135,7 @@ dependencies = [ "ipnet", "libc", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "socket2 0.4.4", ] @@ -4130,7 +4148,7 @@ dependencies = [ "async-std", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -4157,7 +4175,7 @@ dependencies = [ "futures 0.3.21", "futures-rustls", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "quicksink", "rw-stream-sink", "soketto", @@ -4326,14 +4344,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.14", + "log 0.4.17", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", "value-bag", @@ -4371,11 +4389,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.5" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.12.0", ] [[package]] @@ -4567,7 +4585,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.14", + "log 0.4.17", "miow 0.2.2", "net2", "slab", @@ -4581,7 +4599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", - "log 0.4.14", + "log 0.4.17", "miow 0.3.6", "ntapi", "winapi 0.3.9", @@ -4594,7 +4612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log 0.4.14", + "log 0.4.17", "mio 0.6.23", "slab", ] @@ -4712,9 +4730,9 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "pin-project 1.0.10", "smallvec 1.8.0", "unsigned-varint 0.7.0", @@ -4766,7 +4784,7 @@ checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", - "log 0.4.14", + "log 0.4.17", "openssl", "openssl-probe", "openssl-sys", @@ -4823,7 +4841,7 @@ dependencies = [ "kvdb", "kvdb-rocksdb", "lazy_static", - "log 0.4.14", + "log 0.4.17", "node-primitives", "node-runtime", "node-testing", @@ -4861,7 +4879,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "futures 0.3.21", "hex-literal", - "log 0.4.14", + "log 0.4.17", "nix", "node-executor", "node-inspect", @@ -5033,7 +5051,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", - "log 0.4.14", + "log 0.4.17", "node-primitives", "pallet-authority-discovery", "pallet-authorship", @@ -5048,8 +5066,10 @@ dependencies = [ "pallet-contracts", "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", + "pallet-ddc-accounts", "pallet-ddc-metrics-offchain-worker", "pallet-ddc-staking", + "pallet-ddc-validator", "pallet-democracy", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", @@ -5121,7 +5141,7 @@ dependencies = [ "frame-system", "fs_extra", "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "node-executor", "node-primitives", "node-runtime", @@ -5167,7 +5187,7 @@ checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ "memchr", "minimal-lexical", - "version_check 0.9.2", + "version_check 0.9.4", ] [[package]] @@ -5287,9 +5307,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "oorandom" @@ -5481,7 +5501,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -5510,7 +5530,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5538,7 +5558,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-bags-list", "pallet-staking", "remote-externalities", @@ -5557,7 +5577,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-transaction-payment", "parity-scale-codec", "scale-info", @@ -5596,7 +5616,7 @@ dependencies = [ "hex", "hex-literal", "k256", - "log 0.4.14", + "log 0.4.17", "pallet-beefy", "pallet-mmr", "pallet-session", @@ -5617,7 +5637,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -5668,7 +5688,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "pallet-bounties", "pallet-treasury", @@ -5687,7 +5707,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "sp-core", @@ -5707,7 +5727,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "pallet-contracts-primitives", "pallet-contracts-proc-macro", @@ -5805,6 +5825,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-ddc-accounts" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "log 0.4.17", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "substrate-test-utils", +] + [[package]] name = "pallet-ddc-metrics-offchain-worker" version = "2.0.1" @@ -5838,6 +5874,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log 0.4.17", "parity-scale-codec", "scale-info", "sp-io", @@ -5847,6 +5884,38 @@ dependencies = [ "substrate-test-utils", ] +[[package]] +name = "pallet-ddc-validator" +version = "0.1.0" +dependencies = [ + "alt_serde", + "array-bytes", + "base64 0.21.0", + "frame-election-provider-support", + "frame-support", + "frame-system", + "lite-json", + "log 0.4.17", + "pallet-balances", + "pallet-contracts", + "pallet-ddc-staking", + "pallet-randomness-collective-flip", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "serde_json 1.0.44", + "sp-core", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-democracy" version = "4.0.0-dev" @@ -5873,7 +5942,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "pallet-election-provider-support-benchmarking", "parity-scale-codec", @@ -5910,7 +5979,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5986,7 +6055,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -6031,7 +6100,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-authorship", "pallet-session", "parity-scale-codec", @@ -6085,7 +6154,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "sp-core", @@ -6167,7 +6236,7 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "sp-core", @@ -6222,7 +6291,7 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6350,7 +6419,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-preimage", "parity-scale-codec", "scale-info", @@ -6383,7 +6452,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.17", "pallet-timestamp", "parity-scale-codec", "scale-info", @@ -6444,7 +6513,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-authorship", "pallet-bags-list", "pallet-balances", @@ -6481,7 +6550,7 @@ dependencies = [ name = "pallet-staking-reward-fn" version = "4.0.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.17", "sp-arithmetic", ] @@ -6506,7 +6575,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "sp-core", @@ -6524,7 +6593,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -6626,7 +6695,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6659,7 +6728,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.17", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6698,7 +6767,7 @@ dependencies = [ "fs2", "hex", "libc", - "log 0.4.14", + "log 0.4.17", "lz4", "memmap2 0.2.1", "parking_lot 0.11.2", @@ -6746,7 +6815,7 @@ checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ "futures 0.3.21", "libc", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "tokio", "winapi 0.3.9", @@ -6803,7 +6872,7 @@ dependencies = [ "byteorder", "bytes 0.4.12", "httparse", - "log 0.4.14", + "log 0.4.17", "mio 0.6.23", "mio-extras", "rand 0.7.3", @@ -7104,7 +7173,7 @@ checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" dependencies = [ "cfg-if 0.1.10", "libc", - "log 0.4.14", + "log 0.4.17", "wepoll-sys", "winapi 0.3.9", ] @@ -7245,7 +7314,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "version_check 0.9.2", + "version_check 0.9.4", ] [[package]] @@ -7256,7 +7325,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check 0.9.2", + "version_check 0.9.4", ] [[package]] @@ -7288,7 +7357,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "prost-derive", ] @@ -7298,11 +7367,11 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "heck 0.3.2", "itertools", "lazy_static", - "log 0.4.14", + "log 0.4.17", "multimap", "petgraph", "prost", @@ -7331,7 +7400,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "prost", ] @@ -7383,7 +7452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger 0.8.4", - "log 0.4.14", + "log 0.4.17", "rand 0.8.4", ] @@ -7731,7 +7800,7 @@ version = "0.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ - "log 0.4.14", + "log 0.4.17", "rustc-hash", "smallvec 1.8.0", ] @@ -7742,7 +7811,7 @@ version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ - "log 0.4.14", + "log 0.4.17", "rustc-hash", "smallvec 1.8.0", ] @@ -7805,7 +7874,7 @@ dependencies = [ "env_logger 0.9.0", "frame-support", "jsonrpsee", - "log 0.4.14", + "log 0.4.17", "pallet-elections-phragmen", "parity-scale-codec", "serde", @@ -7961,9 +8030,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.33.5" +version = "0.33.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03627528abcc4a365554d32a9f3bbf67f7694c102cfeda792dc86a2d6057cc85" +checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" dependencies = [ "bitflags", "errno", @@ -7980,7 +8049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", - "log 0.4.14", + "log 0.4.17", "ring", "sct 0.6.0", "webpki 0.21.4", @@ -7992,7 +8061,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ - "log 0.4.14", + "log 0.4.17", "ring", "sct 0.7.0", "webpki 0.22.0", @@ -8091,7 +8160,7 @@ dependencies = [ name = "sc-allocator" version = "4.1.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.17", "sp-core", "sp-wasm-interface", "thiserror", @@ -8106,7 +8175,7 @@ dependencies = [ "futures-timer", "ip_network", "libp2p", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "prost", "prost-build", @@ -8132,7 +8201,7 @@ version = "0.10.0-dev" dependencies = [ "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8203,7 +8272,7 @@ dependencies = [ "futures 0.3.21", "hex", "libp2p", - "log 0.4.14", + "log 0.4.17", "names", "parity-scale-codec", "rand 0.7.3", @@ -8238,7 +8307,7 @@ dependencies = [ "fnv", "futures 0.3.21", "hash-db", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-executor", @@ -8270,7 +8339,7 @@ dependencies = [ "kvdb-memorydb", "kvdb-rocksdb", "linked-hash-map", - "log 0.4.14", + "log 0.4.17", "parity-db", "parity-scale-codec", "parking_lot 0.12.0", @@ -8297,7 +8366,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "libp2p", - "log 0.4.14", + "log 0.4.17", "parking_lot 0.12.0", "sc-client-api", "sc-utils", @@ -8319,7 +8388,7 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8357,7 +8426,7 @@ dependencies = [ "async-trait", "fork-tree", "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "merlin", "num-bigint", "num-rational 0.2.4", @@ -8451,7 +8520,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sc-basic-authorship", "sc-client-api", @@ -8487,7 +8556,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-client-api", @@ -8511,7 +8580,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sc-client-api", "sc-consensus", @@ -8547,7 +8616,7 @@ dependencies = [ "env_logger 0.9.0", "hex-literal", "lazy_static", - "lru 0.7.5", + "lru 0.7.8", "parity-scale-codec", "parking_lot 0.12.0", "paste", @@ -8599,7 +8668,7 @@ dependencies = [ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sc-allocator", "sc-executor-common", @@ -8616,7 +8685,7 @@ version = "0.10.0-dev" dependencies = [ "cfg-if 1.0.0", "libc", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parity-wasm 0.42.2", "sc-allocator", @@ -8643,7 +8712,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "hex", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "rand 0.8.4", @@ -8687,7 +8756,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -8711,7 +8780,7 @@ dependencies = [ "ansi_term 0.12.1", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.17", "parity-util-mem", "sc-client-api", "sc-network", @@ -8744,7 +8813,7 @@ dependencies = [ "async-trait", "asynchronous-codec 0.5.0", "bitflags", - "bytes 1.1.0", + "bytes 1.4.0", "cid", "either", "fnv", @@ -8756,8 +8825,8 @@ dependencies = [ "libp2p", "linked-hash-map", "linked_hash_set", - "log 0.4.14", - "lru 0.7.5", + "log 0.4.17", + "lru 0.7.8", "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -8800,8 +8869,8 @@ dependencies = [ "futures 0.3.21", "futures-timer", "libp2p", - "log 0.4.14", - "lru 0.7.5", + "log 0.4.17", + "lru 0.7.8", "quickcheck", "sc-network", "sp-runtime", @@ -8819,7 +8888,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "libp2p", - "log 0.4.14", + "log 0.4.17", "parking_lot 0.12.0", "rand 0.7.3", "sc-block-builder", @@ -8841,7 +8910,7 @@ dependencies = [ name = "sc-offchain" version = "4.0.0-dev" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "fnv", "futures 0.3.21", "futures-timer", @@ -8879,7 +8948,7 @@ version = "4.0.0-dev" dependencies = [ "futures 0.3.21", "libp2p", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "sc-utils", "serde_json 1.0.79", @@ -8890,7 +8959,7 @@ dependencies = [ name = "sc-proposer-metrics" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.17", "substrate-prometheus-endpoint", ] @@ -8904,7 +8973,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-pubsub", "lazy_static", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8940,7 +9009,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", @@ -8966,7 +9035,7 @@ dependencies = [ "jsonrpc-ipc-server", "jsonrpc-pubsub", "jsonrpc-ws-server", - "log 0.4.14", + "log 0.4.17", "serde_json 1.0.79", "substrate-prometheus-endpoint", "tokio", @@ -8999,7 +9068,7 @@ dependencies = [ "hash-db", "jsonrpc-core", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", @@ -9061,7 +9130,7 @@ dependencies = [ "futures 0.3.21", "hex", "hex-literal", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -9093,7 +9162,7 @@ dependencies = [ name = "sc-state-db" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", @@ -9128,7 +9197,7 @@ version = "6.0.0-dev" dependencies = [ "futures 0.3.21", "libc", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "rand_pcg 0.2.1", "regex", @@ -9147,7 +9216,7 @@ dependencies = [ "chrono", "futures 0.3.21", "libp2p", - "log 0.4.14", + "log 0.4.17", "parking_lot 0.12.0", "pin-project 1.0.10", "rand 0.7.3", @@ -9167,7 +9236,7 @@ dependencies = [ "criterion", "lazy_static", "libc", - "log 0.4.14", + "log 0.4.17", "once_cell", "parking_lot 0.12.0", "regex", @@ -9208,7 +9277,7 @@ dependencies = [ "futures-timer", "hex", "linked-hash-map", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", @@ -9237,7 +9306,7 @@ name = "sc-transaction-pool-api" version = "4.0.0-dev" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "serde", "sp-blockchain", "sp-runtime", @@ -9251,7 +9320,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "lazy_static", - "log 0.4.14", + "log 0.4.17", "parking_lot 0.12.0", "prometheus", "tokio-test", @@ -9740,11 +9809,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.4.0", "flate2", "futures 0.3.21", "httparse", - "log 0.4.14", + "log 0.4.17", "rand 0.8.4", "sha-1 0.9.4", ] @@ -9754,7 +9823,7 @@ name = "sp-api" version = "4.0.0-dev" dependencies = [ "hash-db", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sp-api-proc-macro", "sp-core", @@ -9783,7 +9852,7 @@ version = "2.0.1" dependencies = [ "criterion", "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "rustversion", "sc-block-builder", @@ -9888,8 +9957,8 @@ name = "sp-blockchain" version = "4.0.0-dev" dependencies = [ "futures 0.3.21", - "log 0.4.14", - "lru 0.7.5", + "log 0.4.17", + "lru 0.7.8", "parity-scale-codec", "parking_lot 0.12.0", "sp-api", @@ -9907,7 +9976,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sp-core", "sp-inherents", @@ -10012,7 +10081,7 @@ dependencies = [ "impl-serde", "lazy_static", "libsecp256k1", - "log 0.4.14", + "log 0.4.17", "merlin", "num-traits", "parity-scale-codec", @@ -10098,7 +10167,7 @@ name = "sp-finality-grandpa" version = "4.0.0-dev" dependencies = [ "finality-grandpa", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "serde", @@ -10131,7 +10200,7 @@ dependencies = [ "futures 0.3.21", "hash-db", "libsecp256k1", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parking_lot 0.12.0", "secp256k1", @@ -10189,7 +10258,7 @@ name = "sp-mmr-primitives" version = "4.0.0-dev" dependencies = [ "hex-literal", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "serde", "sp-api", @@ -10262,7 +10331,7 @@ dependencies = [ "either", "hash256-std-hasher", "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "parity-util-mem", "paste", @@ -10358,7 +10427,7 @@ name = "sp-sandbox" version = "0.10.0-dev" dependencies = [ "assert_matches", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sp-core", "sp-io", @@ -10405,7 +10474,7 @@ version = "0.12.0" dependencies = [ "hash-db", "hex-literal", - "log 0.4.14", + "log 0.4.17", "num-traits", "parity-scale-codec", "parking_lot 0.12.0", @@ -10443,7 +10512,7 @@ dependencies = [ name = "sp-tasks" version = "4.0.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sp-core", "sp-externalities", @@ -10470,7 +10539,7 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "futures-timer", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sp-api", "sp-inherents", @@ -10503,7 +10572,7 @@ name = "sp-transaction-storage-proof" version = "4.0.0-dev" dependencies = [ "async-trait", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "scale-info", "sp-core", @@ -10565,7 +10634,7 @@ name = "sp-wasm-interface" version = "6.0.0" dependencies = [ "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sp-std", "wasmi", @@ -10721,7 +10790,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -10742,7 +10811,7 @@ version = "0.10.0-dev" dependencies = [ "futures-util", "hyper 0.14.16", - "log 0.4.14", + "log 0.4.17", "prometheus", "thiserror", "tokio", @@ -10755,7 +10824,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -10806,7 +10875,7 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "memory-db", "pallet-babe", "pallet-timestamp", @@ -10964,13 +11033,13 @@ checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" -version = "3.3.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", - "fastrand", "libc", + "rand 0.8.4", "redox_syscall 0.2.10", "remove_dir_all", "winapi 0.3.9", @@ -11136,7 +11205,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "libc", "memchr", "mio 0.8.0", @@ -11179,7 +11248,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.31", - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -11212,7 +11281,7 @@ dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", "lazy_static", - "log 0.4.14", + "log 0.4.17", "mio 0.6.23", "num_cpus", "parking_lot 0.9.0", @@ -11286,7 +11355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.1.0", + "bytes 1.4.0", "futures-core", "tokio", "tokio-stream", @@ -11309,10 +11378,10 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-core", "futures-sink", - "log 0.4.14", + "log 0.4.17", "pin-project-lite 0.2.6", "tokio", ] @@ -11323,7 +11392,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-core", "futures-io", "futures-sink", @@ -11353,7 +11422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", - "log 0.4.14", + "log 0.4.17", "pin-project-lite 0.2.6", "tracing-attributes", "tracing-core", @@ -11396,7 +11465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" dependencies = [ "lazy_static", - "log 0.4.14", + "log 0.4.17", "tracing-core", ] @@ -11469,7 +11538,7 @@ checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" dependencies = [ "hash-db", "hashbrown 0.12.0", - "log 0.4.14", + "log 0.4.17", "rustc-hex", "smallvec 1.8.0", ] @@ -11509,7 +11578,7 @@ dependencies = [ "idna 0.2.2", "ipnet", "lazy_static", - "log 0.4.14", + "log 0.4.17", "rand 0.8.4", "smallvec 1.8.0", "thiserror", @@ -11527,7 +11596,7 @@ dependencies = [ "futures-util", "ipconfig", "lazy_static", - "log 0.4.14", + "log 0.4.17", "lru-cache", "parking_lot 0.11.2", "resolv-conf", @@ -11548,7 +11617,7 @@ version = "0.10.0-dev" dependencies = [ "clap 3.1.6", "jsonrpsee", - "log 0.4.14", + "log 0.4.17", "parity-scale-codec", "remote-externalities", "sc-chain-spec", @@ -11644,7 +11713,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.2", + "version_check 0.9.4", ] [[package]] @@ -11712,7 +11781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35581ff83d4101e58b582e607120c7f5ffb17e632a980b1f38334d76b36908b2" dependencies = [ "asynchronous-codec 0.5.0", - "bytes 1.1.0", + "bytes 1.4.0", "futures-io", "futures-util", ] @@ -11724,7 +11793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f8d425fafb8cd76bc3f22aace4af471d3156301d7508f2107e98fbeae10bc7f" dependencies = [ "asynchronous-codec 0.6.0", - "bytes 1.1.0", + "bytes 1.4.0", "futures-io", "futures-util", ] @@ -11760,11 +11829,12 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.0.0-alpha.6" +version = "1.0.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b676010e055c99033117c2343b33a40a30b91fecd6c49055ac9cd2d6c305ab1" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ "ctor", + "version_check 0.9.4", ] [[package]] @@ -11787,9 +11857,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -11829,7 +11899,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.14", + "log 0.4.17", "try-lock", ] @@ -11863,7 +11933,7 @@ checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.14", + "log 0.4.17", "proc-macro2", "quote", "syn", @@ -11917,7 +11987,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" dependencies = [ - "log 0.4.14", + "log 0.4.17", "parity-wasm 0.32.0", "rustc-demangle", ] @@ -12210,7 +12280,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "log 0.4.14", + "log 0.4.17", "object 0.27.1", "once_cell", "paste", @@ -12239,7 +12309,7 @@ dependencies = [ "bincode", "directories-next", "file-per-thread-logger", - "log 0.4.14", + "log 0.4.17", "rustix", "serde", "sha2 0.9.8", @@ -12261,7 +12331,7 @@ dependencies = [ "cranelift-native", "cranelift-wasm", "gimli 0.26.1", - "log 0.4.14", + "log 0.4.17", "more-asserts", "object 0.27.1", "target-lexicon", @@ -12280,7 +12350,7 @@ dependencies = [ "cranelift-entity 0.82.3", "gimli 0.26.1", "indexmap", - "log 0.4.14", + "log 0.4.17", "more-asserts", "object 0.27.1", "serde", @@ -12302,7 +12372,7 @@ dependencies = [ "cfg-if 1.0.0", "cpp_demangle", "gimli 0.26.1", - "log 0.4.14", + "log 0.4.17", "object 0.27.1", "region 2.2.0", "rustc-demangle", @@ -12339,7 +12409,7 @@ dependencies = [ "cfg-if 1.0.0", "indexmap", "libc", - "log 0.4.14", + "log 0.4.17", "mach", "memoffset", "more-asserts", @@ -12628,7 +12698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.8.4", @@ -12658,18 +12728,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.10.0+zstd.1.5.2" +version = "0.10.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" +checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.4+zstd.1.5.2" +version = "4.1.6+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" +checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" dependencies = [ "libc", "zstd-sys", diff --git a/Cargo.toml b/Cargo.toml index 680a7394d7221..26444267aa22b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,9 +87,11 @@ members = [ "frame/election-provider-support/benchmarking", "frame/election-provider-support/solution-type", "frame/election-provider-support/solution-type/fuzzer", + "frame/ddc-accounts", "frame/ddc-metrics-offchain-worker", "frame/ddc-pallet", "frame/ddc-staking", + "frame/ddc-validator", "frame/executive", "frame/gilt", "frame/grandpa", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index b992c97961718..2af6af29703c2 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -360,6 +360,7 @@ pub fn testnet_genesis( }, vesting: Default::default(), transaction_payment: Default::default(), + ddc_accounts: Default::default(), } } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 853f66ce02f58..aae22edcda732 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -99,6 +99,8 @@ pallet-erc721 = { version = "2.0.0", default-features = false, path = "../../../ pallet-erc20 = { version = "2.0.0", default-features = false, path = "../../../frame/erc20" } pallet-ddc-metrics-offchain-worker = { version = "2.0.0", default-features = false, path = "../../../frame/ddc-metrics-offchain-worker" } pallet-ddc-staking = { version = "0.1.0", default-features = false, path = "../../../frame/ddc-staking" } +pallet-ddc-validator= { version = "0.1.0", default-features = false, path = "../../../frame/ddc-validator" } +pallet-ddc-accounts = { version = "0.1.0", default-features = false, path = "../../../frame/ddc-accounts" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } @@ -174,6 +176,8 @@ std = [ "pallet-child-bounties/std", "pallet-ddc-metrics-offchain-worker/std", "pallet-ddc-staking/std", + "pallet-ddc-validator/std", + "pallet-ddc-accounts/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index fee2436f242a5..efb01ba9566de 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -81,6 +81,8 @@ pub use pallet_cere_ddc; pub use pallet_chainbridge; pub use pallet_ddc_metrics_offchain_worker; pub use pallet_ddc_staking; +pub use pallet_ddc_validator; +pub use pallet_ddc_accounts; #[cfg(any(feature = "std", test))] pub use frame_system::Call as SystemCall; @@ -129,7 +131,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 30400, + spec_version: 30403, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -1229,9 +1231,39 @@ impl pallet_ddc_staking::Config for Runtime { type BondingDuration = BondingDuration; type Currency = Balances; type Event = Event; + type StakersPayoutSource = Ddc_Accounts_Pallet_Id; type WeightInfo = pallet_ddc_staking::weights::SubstrateWeight; } +parameter_types! { + pub const Ddc_Accounts_Pallet_Id: PalletId = PalletId(*b"accounts"); // DDC maintainer's stake +} + +impl pallet_ddc_accounts::Config for Runtime { + type BondingDuration = BondingDuration; + type Currency = Balances; + type Event = Event; + type PalletId = Ddc_Accounts_Pallet_Id; + type TimeProvider = pallet_timestamp::Pallet; +} + +parameter_types! { + pub const DdcValidatorsQuorumSize: u32 = 3; + pub const ValidationThreshold: u32 = 5; + pub const ValidatorsMax: u32 = 64; +} + +impl pallet_ddc_validator::Config for Runtime { + type DdcValidatorsQuorumSize = DdcValidatorsQuorumSize; + type Event = Event; + type Randomness = RandomnessCollectiveFlip; + type Call = Call; + type AuthorityId = pallet_ddc_validator::crypto::TestAuthId; + type TimeProvider = pallet_timestamp::Pallet; + type ValidationThreshold = ValidationThreshold; + type ValidatorsMax = ValidatorsMax; +} + construct_runtime!( pub enum Runtime where Block = Block, @@ -1282,6 +1314,8 @@ construct_runtime!( Erc20: pallet_erc20::{Pallet, Call, Storage, Event}, DdcMetricsOffchainWorker: pallet_ddc_metrics_offchain_worker::{Pallet, Call, Storage, Event}, DdcStaking: pallet_ddc_staking, + DdcValidator: pallet_ddc_validator, + DdcAccounts: pallet_ddc_accounts } ); diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index bfae67285d5d2..961ed3fe87564 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -89,5 +89,6 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen society: SocietyConfig { members: vec![alice(), bob()], pot: 0, max_members: 999 }, vesting: Default::default(), transaction_payment: Default::default(), + ddc_accounts: Default::default(), } } diff --git a/frame/ddc-accounts/Cargo.toml b/frame/ddc-accounts/Cargo.toml new file mode 100644 index 0000000000000..38e73bde4614a --- /dev/null +++ b/frame/ddc-accounts/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pallet-ddc-accounts" +version = "0.1.0" +edition = "2021" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } +log = { version = "0.4.17", default-features = false } + +[dev-dependencies] +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", +] diff --git a/frame/ddc-accounts/src/lib.rs b/frame/ddc-accounts/src/lib.rs new file mode 100644 index 0000000000000..62acb3a827ec9 --- /dev/null +++ b/frame/ddc-accounts/src/lib.rs @@ -0,0 +1,447 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] + +use codec::{Decode, Encode, HasCompact}; + +use frame_support::{ + parameter_types, + traits::{Currency, DefensiveSaturating, ExistenceRequirement, UnixTime}, + BoundedVec, + PalletId +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{AccountIdConversion, AtLeast32BitUnsigned, Saturating, Zero}, + RuntimeDebug, +}; + +use sp_staking::EraIndex; +use sp_std::prelude::*; + +pub use pallet::*; + +pub const TIME_START_MS: u128 = 1_672_531_200_000; +pub const ERA_DURATION_MS: u128 = 120_000; +pub const ERA_IN_BLOCKS: u8 = 20; + +/// The balance type of this pallet. +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +parameter_types! { + /// A limit to the number of pending unlocks an account may have in parallel. + pub MaxUnlockingChunks: u32 = 32; +} + +/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct UnlockChunk { + /// Amount of funds to be unlocked. + #[codec(compact)] + value: Balance, + /// Era number at which point it'll be unlocked. + #[codec(compact)] + era: EraIndex, +} + +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct AccountsLedger { + /// The stash account whose balance is actually locked and can be used for CDN usage. + pub stash: AccountId, + /// The total amount of the stash's balance that we are currently accounting for. + /// It's just `active` plus all the `unlocking` balances. + #[codec(compact)] + pub total: Balance, + /// The total amount of the stash's balance that will be accessible for CDN payments in any forthcoming + /// rounds. + #[codec(compact)] + pub active: Balance, + /// Any balance that is becoming free, which may eventually be transferred out of the stash + /// (assuming that the content owner has to pay for network usage). It is assumed that this will be treated as a first + /// in, first out queue where the new (higher value) eras get pushed on the back. + pub unlocking: BoundedVec, MaxUnlockingChunks>, +} + +impl + AccountsLedger +{ + /// Initializes the default object using the given stash. + pub fn default_from(stash: AccountId) -> Self { + Self { stash, total: Zero::zero(), active: Zero::zero(), unlocking: Default::default() } + } + + /// Remove entries from `unlocking` that are sufficiently old and reduce the + /// total by the sum of their balances. + fn consolidate_unlocked(self, current_era: EraIndex) -> Self { + let mut total = self.total; + let unlocking: BoundedVec<_, _> = self + .unlocking + .into_iter() + .filter(|chunk| { + log::info!("Chunk era: {:?}", chunk.era); + if chunk.era > current_era { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }) + .collect::>() + .try_into() + .expect( + "filtering items from a bounded vec always leaves length less than bounds. qed", + ); + + Self { stash: self.stash, total, active: self.active, unlocking } + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, sp_runtime::traits::StaticLookup, traits::LockableCurrency, + Blake2_128Concat, + }; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The accounts's pallet id, used for deriving its sovereign account ID. + #[pallet::constant] + type PalletId: Get; + type Currency: LockableCurrency; + type Event: From> + IsType<::Event>; + /// Number of eras that staked funds must remain bonded for. + #[pallet::constant] + type BondingDuration: Get; + type TimeProvider: UnixTime; + } + + /// Map from all locked "stash" accounts to the controller account. + #[pallet::storage] + #[pallet::getter(fn bonded)] + pub type Bonded = StorageMap<_, Twox64Concat, T::AccountId, T::AccountId>; + + /// Map from all (unlocked) "controller" accounts to the info regarding the staking. + #[pallet::storage] + #[pallet::getter(fn ledger)] + pub type Ledger = + StorageMap<_, Blake2_128Concat, T::AccountId, AccountsLedger>>; + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// An account has bonded this amount. \[stash, amount\] + /// + /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, + /// it will not be emitted for staking rewards when they are added to stake. + Deposited(T::AccountId, BalanceOf), + /// An account has unbonded this amount. \[stash, amount\] + Unbonded(T::AccountId, BalanceOf), + /// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance` + /// from the unlocking queue. \[stash, amount\] + Withdrawn(T::AccountId, BalanceOf), + } + + #[pallet::error] + pub enum Error { + /// Not a controller account. + NotController, + /// Not a stash account. + NotStash, + /// Stash is already bonded. + AlreadyBonded, + /// Controller is already paired. + AlreadyPaired, + /// Cannot deposit dust + InsufficientDeposit, + /// Can not schedule more unlock chunks. + NoMoreChunks, + /// Internal state has become somehow corrupted and the operation cannot continue. + BadState, + } + + #[pallet::genesis_config] + pub struct GenesisConfig; + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + let account_id = >::account_id(); + let min = T::Currency::minimum_balance(); + if T::Currency::free_balance(&account_id) < min { + let _ = T::Currency::make_free_balance_be(&account_id, min); + } + } + } + + #[pallet::call] + impl Pallet { + /// Take the origin account as a stash and lock up `value` of its balance. `controller` will + /// be the account that controls it. + /// + /// `value` must be more than the `minimum_balance` specified by `T::Currency`. + /// + /// The dispatch origin for this call must be _Signed_ by the stash account. + /// + /// Emits `Deposited`. + #[pallet::weight(10_000)] pub fn deposit( + origin: OriginFor, + controller: ::Source, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResult { + let stash = ensure_signed(origin)?; + + if >::contains_key(&stash) { + Err(Error::::AlreadyBonded)? + } + + let controller = T::Lookup::lookup(controller)?; + + if >::contains_key(&controller) { + Err(Error::::AlreadyPaired)? + } + + // Reject a deposit which is considered to be _dust_. + if value < T::Currency::minimum_balance() { + Err(Error::::InsufficientDeposit)? + } + + frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; + + >::insert(&stash, &controller); + + let stash_balance = T::Currency::free_balance(&stash); + let value = value.min(stash_balance); + Self::deposit_event(Event::::Deposited(stash.clone(), value)); + let item = + AccountsLedger { stash: stash.clone(), total: value, active: value, unlocking: Default::default() }; + Self::update_ledger_and_deposit( &stash, &controller, &item)?; + Ok(()) + } + + /// Add some extra amount that have appeared in the stash `free_balance` into the balance up + /// for CDN payments. + /// + /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. + /// + /// Emits `Deposited`. + #[pallet::weight(10_000)] + pub fn deposit_extra( + origin: OriginFor, + #[pallet::compact] max_additional: BalanceOf, + ) -> DispatchResult { + let stash = ensure_signed(origin)?; + + let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + + let stash_balance = T::Currency::free_balance(&stash); + let extra = stash_balance.min(max_additional); + ledger.total += extra; + ledger.active += extra; + // Last check: the new active amount of ledger must be more than ED. + ensure!( + ledger.active >= T::Currency::minimum_balance(), + Error::::InsufficientDeposit + ); + + Self::update_ledger_and_deposit(&stash, &controller, &ledger)?; + + Self::deposit_event(Event::::Deposited(stash.clone(), extra)); + + Ok(()) + } + + /// Schedule a portion of the stash to be unlocked ready for transfer out after the bond + /// period ends. If this leaves an amount actively bonded less than + /// T::Currency::minimum_balance(), then it is increased to the full amount. + /// + /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. + /// + /// Once the unlock period is done, you can call `withdraw_unbonded` to actually move + /// the funds out of management ready for transfer. + /// + /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) + /// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need + /// to be called first to remove some of the chunks (if possible). + /// + /// Emits `Unbonded`. + /// + /// See also [`Call::withdraw_unbonded`]. + #[pallet::weight(10_000)] + pub fn unbond( + origin: OriginFor, + #[pallet::compact] value: BalanceOf, + ) -> DispatchResult { + let controller = ensure_signed(origin)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + ensure!( + ledger.unlocking.len() < MaxUnlockingChunks::get() as usize, + Error::::NoMoreChunks, + ); + + let mut value = value.min(ledger.active); + + if !value.is_zero() { + ledger.active -= value; + + // Avoid there being a dust balance left in the accounts system. + if ledger.active < T::Currency::minimum_balance() { + value += ledger.active; + ledger.active = Zero::zero(); + } + + // Note: bonding for extra era to allow for accounting + let era = Self::get_current_era() + T::BondingDuration::get(); + log::info!("Era for the unbond: {:?}", era); + + if let Some(mut chunk) = + ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) + { + // To keep the chunk count down, we only keep one chunk per era. Since + // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will + // be the last one. + chunk.value = chunk.value.defensive_saturating_add(value) + } else { + ledger + .unlocking + .try_push(UnlockChunk { value, era }) + .map_err(|_| Error::::NoMoreChunks)?; + }; + + Self::update_ledger(&controller, &ledger); + + Self::deposit_event(Event::::Unbonded(ledger.stash, value)); + } + Ok(()) + } + + + /// Remove any unlocked chunks from the `unlocking` queue from our management. + /// + /// This essentially frees up that balance to be used by the stash account to do + /// whatever it wants. + /// + /// The dispatch origin for this call must be _Signed_ by the controller. + /// + /// Emits `Withdrawn`. + /// + /// See also [`Call::unbond`]. + #[pallet::weight(10_000)] + pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { + let controller = ensure_signed(origin)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let (stash, old_total) = (ledger.stash.clone(), ledger.total); + let current_era = Self::get_current_era(); + ledger = ledger.consolidate_unlocked(current_era); + log::info!("Current era: {:?}", current_era); + + if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { + log::info!("Killing stash"); + // This account must have called `unbond()` with some value that caused the active + // portion to fall below existential deposit + will have no more unlocking chunks + // left. We can now safely remove all accounts-related information. + Self::kill_stash(&stash)?; + } else { + log::info!("Updating ledger"); + // This was the consequence of a partial unbond. just update the ledger and move on. + Self::update_ledger(&controller, &ledger); + }; + + log::info!("Current total: {:?}", ledger.total); + log::info!("Old total: {:?}", old_total); + + // `old_total` should never be less than the new total because + // `consolidate_unlocked` strictly subtracts balance. + if ledger.total < old_total { + log::info!("Preparing for transfer"); + // Already checked that this won't overflow by entry condition. + let value = old_total - ledger.total; + + let account_id = Self::account_id(); + + T::Currency::transfer( + &account_id, + &stash, + value, + ExistenceRequirement::KeepAlive, + )?; + Self::deposit_event(Event::::Withdrawn(stash, value)); + } + + Ok(()) + } + } + + impl Pallet { + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account() + } + /// Update the ledger for a controller. + /// + /// This will also deposit the funds to pallet. + fn update_ledger_and_deposit( + stash: &T::AccountId, + controller: &T::AccountId, + ledger: &AccountsLedger>, + ) -> DispatchResult { + let account_id = Self::account_id(); + + T::Currency::transfer( + stash, + &account_id, + ledger.total, + ExistenceRequirement::KeepAlive, + )?; + >::insert(controller, ledger); + + Ok(()) + } + + /// Update the ledger for a controller. + fn update_ledger( + controller: &T::AccountId, + ledger: &AccountsLedger>, + ) { + >::insert(controller, ledger); + } + + /// Remove all associated data of a stash account from the accounts system. + /// + /// Assumes storage is upgraded before calling. + /// + /// This is called: + /// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance. + fn kill_stash(stash: &T::AccountId) -> DispatchResult { + let controller = >::get(stash).ok_or(Error::::NotStash)?; + + >::remove(stash); + >::remove(&controller); + + frame_system::Pallet::::dec_consumers(stash); + + Ok(()) + } + + // Get the current era. + fn get_current_era() -> EraIndex { + ((T::TimeProvider::now().as_millis() - TIME_START_MS) / ERA_DURATION_MS) + .try_into() + .unwrap() + } + } +} diff --git a/frame/ddc-staking/Cargo.toml b/frame/ddc-staking/Cargo.toml index 0e4beac59b0f9..97c02a71551a5 100644 --- a/frame/ddc-staking/Cargo.toml +++ b/frame/ddc-staking/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/ddc-staking/src/benchmarking.rs b/frame/ddc-staking/src/benchmarking.rs index 85b22a0832a33..a15f6f34728e0 100644 --- a/frame/ddc-staking/src/benchmarking.rs +++ b/frame/ddc-staking/src/benchmarking.rs @@ -78,7 +78,7 @@ benchmarks! { let controller = create_funded_user::("controller", USER_SEED, 100); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); - let amount = T::Currency::minimum_balance() * 10u32.into(); + let amount = T::Currency::minimum_balance() * 10u32.into(); whitelist_account!(stash); }: _(RawOrigin::Signed(stash.clone()), controller_lookup, amount) verify { @@ -90,9 +90,9 @@ benchmarks! { // clean up any existing state. clear_storages_and_edges::(); - let origin_balance = MinStorageBond::::get().max(T::Currency::minimum_balance()); + let origin_balance = MinStorageBond::::get().max(T::Currency::minimum_balance()); - let scenario = AccountsScenario::::new(origin_balance)?; + let scenario = AccountsScenario::::new(origin_balance)?; // Original benchmark staking code (/frame/staking/src/benchmarking.rs) let max_additional = BalanceOf::::try_from(u128::MAX).map_err(|_| "balance expected to be a u128").unwrap() - origin_balance; @@ -117,7 +117,7 @@ benchmarks! { clear_storages_and_edges::(); let total_issuance = T::Currency::total_issuance(); - + // Constant taken from original benchmark staking code (/frame/staking/src/benchmarking.rs) let origin_balance = BalanceOf::::try_from(952_994_955_240_703u128) .map_err(|_| "balance expected to be a u128") @@ -127,7 +127,7 @@ benchmarks! { let stash = scenario.origin_stash1.clone(); let controller = scenario.origin_controller1.clone(); // unbond half of initial balance - let amount = origin_balance / 2u32.into(); + let amount = origin_balance / 2u32.into(); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; @@ -143,7 +143,7 @@ benchmarks! { let (stash, controller) = create_stash_controller::(0, 100)?; let amount = T::Currency::minimum_balance() * 5u32.into(); // Half of total DddcStaking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; - CurrentEra::::put(EraIndex::max_value()); + CurrentEra::::put(EraIndex::max_value()); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_total: BalanceOf = ledger.total; whitelist_account!(controller); diff --git a/frame/ddc-staking/src/lib.rs b/frame/ddc-staking/src/lib.rs index a8c3535c62357..ced37c72a13c0 100644 --- a/frame/ddc-staking/src/lib.rs +++ b/frame/ddc-staking/src/lib.rs @@ -14,22 +14,30 @@ use codec::{Decode, Encode, HasCompact}; use frame_support::{ parameter_types, - traits::{Currency, DefensiveSaturating, LockIdentifier, WithdrawReasons}, - BoundedVec, + traits::{ + Currency, DefensiveSaturating, ExistenceRequirement, LockIdentifier, WithdrawReasons, + }, + BoundedVec, PalletId, }; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, CheckedSub, Saturating, Zero}, - RuntimeDebug, + traits::{AccountIdConversion, AtLeast32BitUnsigned, CheckedSub, Saturating, Zero}, + Perbill, RuntimeDebug, }; use sp_staking::EraIndex; -use sp_std::prelude::*; +use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + prelude::*, +}; pub use pallet::*; const DDC_STAKING_ID: LockIdentifier = *b"ddcstake"; // DDC maintainer's stake +/// Counter for the number of "reward" points earned by a given staker. +pub type RewardPoint = u64; + /// The balance type of this pallet. pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -39,6 +47,21 @@ parameter_types! { pub MaxUnlockingChunks: u32 = 32; } +/// Reward points of an era. Used to split era total payout between stakers. +#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct EraRewardPoints { + /// Total number of points. Equals the sum of reward points for each staker. + pub total: RewardPoint, + /// The reward points earned by a given staker. + pub individual: BTreeMap, +} + +impl Default for EraRewardPoints { + fn default() -> Self { + EraRewardPoints { total: Default::default(), individual: BTreeMap::new() } + } +} + /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct UnlockChunk { @@ -134,6 +157,8 @@ pub mod pallet { /// Number of eras that staked funds must remain bonded for. #[pallet::constant] type BondingDuration: Get; + /// To derive an account for withdrawing CDN rewards. + type StakersPayoutSource: Get; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -178,6 +203,14 @@ pub mod pallet { #[pallet::getter(fn current_era)] pub type CurrentEra = StorageValue<_, EraIndex>; + /// The reward each CDN participant earned in the era. + /// + /// See also [`pallet_staking::ErasRewardPoints`]. + #[pallet::storage] + #[pallet::getter(fn eras_edges_reward_points)] + pub type ErasEdgesRewardPoints = + StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { @@ -217,6 +250,8 @@ pub mod pallet { // An account already declared a desire to participate in the network with a certain role // and to take another role it should call `chill` first. AlreadyInRole, + /// Two or more occurrences of a staker account in rewards points list. + DuplicateRewardPoints, } #[pallet::call] @@ -504,9 +539,96 @@ pub mod pallet { } Ok(()) } + + /// Pay out all the stakers for a single era. + #[pallet::weight(100_000)] + pub fn payout_stakers(origin: OriginFor, era: EraIndex) -> DispatchResult { + ensure_signed(origin)?; + Self::do_payout_stakers(era) + } + + /// Set reward points for CDN participants at the given era. + /// + /// The dispatch origin for this call must be _Signed_ by the validator. + /// + /// `stakers_points` is a vector of (stash account ID, reward points) pairs. The rewards + /// distribution will be based on total reward points, with each CDN participant receiving a + /// proportionate reward based on their individual reward points. + /// + /// See also [`ErasEdgesRewardPoints`]. + #[pallet::weight(100_000)] + pub fn set_era_reward_points( + origin: OriginFor, + era: EraIndex, + stakers_points: Vec<(T::AccountId, u64)>, + ) -> DispatchResult { + ensure_signed(origin)?; + + // ToDo: ensure origin is a validator eligible to set rewards + + // Check that a staker mentioned only once, fail with an error otherwise. + let unique_stakers_count = + stakers_points.iter().map(|(staker, _)| staker).collect::>().len(); + if unique_stakers_count != stakers_points.len() { + Err(Error::::DuplicateRewardPoints)? + } + + // ToDo: check that all accounts had an active stake at the era + + Self::reward_by_ids(era, stakers_points); + + Ok(()) + } } impl Pallet { + pub(super) fn do_payout_stakers(era: EraIndex) -> DispatchResult { + // ToDo: check that the era is finished + // ToDo: check reward points are set + + // An account we withdraw the funds from and the amount of funds to withdraw. + let payout_source_account: T::AccountId = T::StakersPayoutSource::get().into_account(); + let payout_budget = T::Currency::free_balance(&payout_source_account); + let era_reward_points: EraRewardPoints = + >::get(&era); + log::debug!( + "Will payout to DDC stakers for era {:?} from account {:?} with total budget {:?} \ + , there are {:?} stakers earned {:?} reward points", + era, + payout_source_account, + payout_budget, + era_reward_points.individual.len(), + era_reward_points.total, + ); + + // Transfer a part of the budget to each CDN participant rewarded this era. + for (stash, points) in era_reward_points.individual { + let part = Perbill::from_rational(points, era_reward_points.total); + let reward: BalanceOf = part * payout_budget; + log::debug!( + "Rewarding {:?} with {:?} points, its part is {:?}, reward size {:?}, balance \ + on payout source account {:?}", + stash, + points, + part, + reward, + T::Currency::free_balance(&payout_source_account) + ); + T::Currency::transfer( + &payout_source_account, + &stash, + reward, + ExistenceRequirement::AllowDeath, + )?; // ToDo: all success or noop + } + log::debug!( + "Balance left on payout source account {:?}", + T::Currency::free_balance(&payout_source_account), + ); + + Ok(()) + } + /// Update the ledger for a controller. /// /// This will also update the stash lock. @@ -610,5 +732,18 @@ pub mod pallet { outcome } + + /// Add reward points to CDN participants using their stash account ID. + pub fn reward_by_ids( + era: EraIndex, + stakers_points: impl IntoIterator, + ) { + >::mutate(era, |era_rewards| { + for (staker, points) in stakers_points.into_iter() { + *era_rewards.individual.entry(staker).or_default() += points; + era_rewards.total += points; + } + }); + } } } diff --git a/frame/ddc-validator/Cargo.toml b/frame/ddc-validator/Cargo.toml new file mode 100644 index 0000000000000..7e2d7f563281a --- /dev/null +++ b/frame/ddc-validator/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-ddc-validator" +version = "0.1.0" +edition = "2021" + +[dependencies] +array-bytes = "6.0.0" +alt_serde = { version = "1", default-features = false, features = ["derive"] } +base64 = { version = "0.21.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } +log = { version = "0.4.17", default-features = false } +lite-json = { version = "0.2.0", default-features = false } +pallet-contracts = { version = '4.0.0-dev', default-features = false, path = "../contracts" } +pallet-ddc-staking = { version = "0.1.0", default-features = false, path = "../ddc-staking" } +pallet-session = { version = "4.0.0-dev", path = "../session", default-features = false } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../staking" } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", optional = true } +serde_json = { version = "1", default-features = false, git = "https://github.com/Cerebellum-Network/json", branch = "no-std-cere", features = ["alloc"] } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-keystore = { version = "0.12.0", default-features = false, path = "../../primitives/keystore", optional = true } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "frame-election-provider-support/std", + "lite-json/std", + "pallet-contracts/std", + "pallet-ddc-staking/std", + "pallet-session/std", + "pallet-staking/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-keystore", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", +] + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-randomness-collective-flip = { version = "4.0.0-dev", path = "../randomness-collective-flip" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } diff --git a/frame/ddc-validator/README.md b/frame/ddc-validator/README.md new file mode 100644 index 0000000000000..3a21d9d6cc2b5 --- /dev/null +++ b/frame/ddc-validator/README.md @@ -0,0 +1 @@ +# DDC Validator diff --git a/frame/ddc-validator/src/dac.rs b/frame/ddc-validator/src/dac.rs new file mode 100644 index 0000000000000..9f4b325bb53dc --- /dev/null +++ b/frame/ddc-validator/src/dac.rs @@ -0,0 +1,614 @@ +//! A module with Data Activity Capture (DAC) interaction. + +use crate::{utils, DacTotalAggregates, ValidationDecision}; +use alloc::{format, string::String}; // ToDo: remove String usage +use alt_serde::{de::DeserializeOwned, Deserialize, Serialize}; +use codec::{Decode, Encode}; +use lite_json::json::JsonValue; +use log::info; +use serde_json::Value; +use sp_runtime::{ + generic::Era, + offchain::{ + http, + http::{Method, Request}, + Duration, + }, +}; +use sp_staking::EraIndex; +pub use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + prelude::*, +}; + +pub type TimestampInSec = u64; +pub const HTTP_TIMEOUT_MS: u64 = 30_000; +pub const FAILED_CONTENT_CONSUMER_THRESHOLD: TimestampInSec = 100; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct RedisFtAggregate { + #[serde(rename = "FT.AGGREGATE")] + pub ft_aggregate: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(crate = "alt_serde")] +#[serde(untagged)] +pub enum FtAggregate { + Length(u32), + Node(Vec), +} + +#[derive(Clone, Debug, Encode, Decode, scale_info::TypeInfo, PartialEq)] +pub struct BytesSent { + pub node_public_key: String, + pub era: EraIndex, + pub sum: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct FileRequestWrapper { + #[serde(rename = "JSON.GET")] + json: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct FileRequests { + requests: Requests, +} + +pub type Requests = BTreeMap; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct FileRequest { + file_request_id: String, + file_info: FileInfo, + bucket_id: u64, + timestamp: u64, + chunks: BTreeMap, + user_public_key: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct Chunk { + log: Log, + cid: String, + ack: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct Ack { + bytes_received: u64, + user_timestamp: u64, + nonce: String, + node_public_key: String, + user_public_key: String, + signature: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct Log { + #[serde(rename = "type")] + log_type: u64, + session_id: String, + user_public_key: String, + era: u64, + user_address: String, + bytes_sent: u64, + timestamp: u64, + node_public_key: String, + signature: String, + bucket_id: u64, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct FileInfo { + #[serde(rename = "chunkCids")] + chunk_cids: Vec, + + #[serde(rename = "requestedChunkCids")] + requested_chunk_cids: Vec, +} + +type EdgeId = String; +type ValidatorId = String; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(crate = "alt_serde")] +pub(crate) struct EdgesToResults(BTreeMap>); + +#[derive(Debug, Deserialize, Serialize)] +#[serde(crate = "alt_serde")] +pub(crate) struct Wrapper { + #[serde(rename = "HGET")] + decisions: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(crate = "alt_serde")] +pub(crate) struct ValidationResult { + validator_id: ValidatorId, + edge_id: EdgeId, + result: bool, + received: u64, + sent: u64, + era: EraIndex, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(crate = "alt_serde")] +pub(crate) struct FinalDecision { + result: bool, + edge_id: EdgeId, + era: EraIndex, + received: u64, + sent: u64, + results_logs: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(crate = "alt_serde")] +pub(crate) struct FinalDecisions(BTreeMap); + +impl BytesSent { + pub fn new(aggregate: RedisFtAggregate) -> BytesSent { + let data = aggregate.ft_aggregate[1].clone(); + + match data { + FtAggregate::Node(node) => + return BytesSent { + node_public_key: node[1].clone(), + era: node[3].clone().parse::().expect("era must be convertible u32") + as EraIndex, + sum: node[5].parse::().expect("bytesSentSum must be convertible to u32"), + }, + FtAggregate::Length(_) => panic!("[DAC Validator] Not a Node"), + } + } + + pub fn get_all(aggregation: RedisFtAggregate) -> Vec { + let mut res: Vec = vec![]; + for i in 1..aggregation.ft_aggregate.len() { + let data = aggregation.ft_aggregate[i].clone(); + match data { + FtAggregate::Node(node) => { + let node = BytesSent { + node_public_key: node[1].clone(), + era: node[3].clone().parse::().expect("era must be convertible u32") + as EraIndex, + sum: node[5] + .parse::() + .expect("bytesSentSum must be convertible to u32"), + }; + + res.push(node); + }, + FtAggregate::Length(_) => panic!("[DAC Validator] Not a Node"), + } + } + + return res + } +} + +#[derive(Clone, Debug, Encode, Decode, scale_info::TypeInfo, PartialEq)] +pub struct BytesReceived { + pub node_public_key: String, + pub era: EraIndex, + pub sum: u32, +} + +impl BytesReceived { + pub fn new(aggregate: RedisFtAggregate) -> BytesReceived { + let data = aggregate.ft_aggregate[1].clone(); + + match data { + FtAggregate::Node(node) => + return BytesReceived { + node_public_key: node[1].clone(), + era: node[3].clone().parse::().expect("era must be convertible u32") + as EraIndex, + sum: node[5] + .parse::() + .expect("bytesReceivedSum must be convertible to u32"), + }, + FtAggregate::Length(_) => panic!("[DAC Validator] Not a Node"), + } + } + + pub fn get_all(aggregation: RedisFtAggregate) -> Vec { + let mut res: Vec = vec![]; + for i in 1..aggregation.ft_aggregate.len() { + let data = aggregation.ft_aggregate[i].clone(); + match data { + FtAggregate::Node(node) => { + let node = BytesReceived { + node_public_key: node[1].clone(), + era: node[3].clone().parse::().expect("era must be convertible u32") + as EraIndex, + sum: node[5] + .parse::() + .expect("bytesReceivedSum must be convertible to u32"), + }; + + res.push(node); + }, + FtAggregate::Length(_) => panic!("[DAC Validator] Not a Node"), + } + } + + return res + } +} + +fn get_timestamps_with_ack(file_requests: &Requests) -> Vec { + let mut timestamps: Vec = Vec::new(); + + for (_, file_request) in file_requests { + for (_, chunk) in &file_request.chunks { + if let Some(ack) = &chunk.ack { + timestamps.push(chunk.log.timestamp); + } + } + } + + timestamps.sort(); + + timestamps +} + +pub fn get_served_bytes_sum(file_requests: &Requests) -> (u64, u64) { + let ack_timestamps = get_timestamps_with_ack(file_requests); + let mut total_bytes_received = 0u64; + let mut total_bytes_sent = 0u64; + + for (_, file_request) in file_requests { + for (_, chunk) in &file_request.chunks { + total_bytes_sent += chunk.log.bytes_sent; + + if let Some(ack) = &chunk.ack { + total_bytes_received += ack.bytes_received; + } else { + total_bytes_received += get_proved_delivered_bytes(chunk, &ack_timestamps); + } + } + } + + (total_bytes_sent, total_bytes_received) +} + +fn get_proved_delivered_bytes(chunk: &Chunk, ack_timestamps: &Vec) -> u64 { + let log_timestamp = chunk.log.timestamp; + let neighbors = get_closer_neighbors(log_timestamp, &ack_timestamps); + let is_proved = + is_lies_within_threshold(log_timestamp, neighbors, FAILED_CONTENT_CONSUMER_THRESHOLD); + + if is_proved { + return chunk.log.bytes_sent + } else { + 0 + } +} + +fn get_closer_neighbors( + timestamp: TimestampInSec, + timestamps: &Vec, +) -> (TimestampInSec, TimestampInSec) { + let mut before = 0; + let mut after = TimestampInSec::MAX; + for ts in timestamps { + if ts < ×tamp { + before = before.max(*ts); + } else if ts > ×tamp { + after = after.min(*ts); + } + } + + (before, after) +} + +fn is_lies_within_threshold( + timestamp: TimestampInSec, + borders: (TimestampInSec, TimestampInSec), + threshold: TimestampInSec, +) -> bool { + let left_distance = timestamp - borders.0; + let right_distance = borders.1 - timestamp; + + if left_distance < threshold || right_distance < threshold { + return true + } + + false +} + +fn get_file_request_url(data_provider_url: &String) -> String { + let res = format!("{}/JSON.GET/testddc:dac:data", data_provider_url); + + res +} + +pub(crate) fn fetch_file_request(url: &String) -> Requests { + log::info!("fetch_file_request | url: {:?}", url); + let response: FileRequestWrapper = http_get_json(&url).unwrap(); + let value: Value = serde_json::from_str(response.json.as_str()).unwrap(); + let map: Requests = serde_json::from_value(value).unwrap(); + + log::info!("response.json: {:?}", response.json); + + map +} + +pub(crate) fn fetch_data( + data_provider_url: &String, + era: EraIndex, + cdn_node: &T::AccountId, +) -> (BytesSent, BytesReceived) { + log::info!("[DAC Validator] DAC Validator is running. Current era is {}", era); + // Todo: handle the error + let bytes_sent_query = get_bytes_sent_query_url(data_provider_url, era); + let bytes_sent_res: RedisFtAggregate = http_get_json(&bytes_sent_query).unwrap(); + log::info!("[DAC Validator] Bytes sent sum is fetched: {:?}", bytes_sent_res); + let bytes_sent = BytesSent::new(bytes_sent_res); + + // Todo: handle the error + let bytes_received_query = get_bytes_received_query_url(data_provider_url, era); + let bytes_received_res: RedisFtAggregate = http_get_json(&bytes_received_query).unwrap(); + log::info!("[DAC Validator] Bytes received sum is fetched:: {:?}", bytes_received_res); + let bytes_received = BytesReceived::new(bytes_received_res); + + (bytes_sent, bytes_received) +} + +pub(crate) fn fetch_data1( + data_provider_url: &String, + era: EraIndex, +) -> (Vec, Vec) { + log::info!("[DAC Validator] DAC Validator is running. Current era is {}", era); + // Todo: handle the error + let bytes_sent_query = get_bytes_sent_query_url(data_provider_url, era); + let bytes_sent_res: RedisFtAggregate = http_get_json(&bytes_sent_query).unwrap(); + log::info!("[DAC Validator] Bytes sent sum is fetched: {:?}", bytes_sent_res); + let bytes_sent = BytesSent::get_all(bytes_sent_res); + + // Todo: handle the error + let bytes_received_query = get_bytes_received_query_url(data_provider_url, era); + let bytes_received_res: RedisFtAggregate = http_get_json(&bytes_received_query).unwrap(); + log::info!("[DAC Validator] Bytes received sum is fetched:: {:?}", bytes_received_res); + let bytes_received = BytesReceived::get_all(bytes_received_res); + + (bytes_sent, bytes_received) +} + +pub(crate) fn fetch_data2( + data_provider_url: &String, + era: EraIndex, +) -> (String, Vec, String, Vec) { + let bytes_sent_query = get_bytes_sent_query_url(data_provider_url, era); + let bytes_sent_res: RedisFtAggregate = http_get_json(&bytes_sent_query).unwrap(); + let bytes_sent = BytesSent::get_all(bytes_sent_res); + + let bytes_received_query = get_bytes_received_query_url(data_provider_url, era); + let bytes_received_res: RedisFtAggregate = http_get_json(&bytes_received_query).unwrap(); + let bytes_received = BytesReceived::get_all(bytes_received_res); + + (bytes_sent_query, bytes_sent, bytes_received_query, bytes_received) +} + +fn get_bytes_received_query_url(data_provider_url: &String, era: EraIndex) -> String { + format!("{}/FT.AGGREGATE/ddc:dac:searchCommonIndex/@era:[{}%20{}]/GROUPBY/2/@nodePublicKey/@era/REDUCE/SUM/1/@bytesReceived/AS/bytesReceivedSum", data_provider_url, era, era) +} + +pub(crate) fn http_get_json(url: &str) -> crate::ResultStr { + let body = http_get_request(url).map_err(|err| { + log::error!("[DAC Validator] Error while getting {}: {:?}", url, err); + "HTTP GET error" + })?; + + let parsed = serde_json::from_slice(&body).map_err(|err| { + log::warn!("[DAC Validator] Error while parsing JSON from {}: {:?}", url, err); + "HTTP JSON parse error" + }); + + parsed +} + +fn http_get_request(http_url: &str) -> Result, http::Error> { + // log::info!("[DAC Validator] Sending request to: {:?}", http_url); + + // Initiate an external HTTP GET request. This is using high-level wrappers from + // `sp_runtime`. + let request = http::Request::get(http_url); + + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(HTTP_TIMEOUT_MS)); + + let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + + let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + + if response.code != 200 { + log::warn!("[DAC Validator] http_get_request unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } + + // Next we fully read the response body and collect it to a vector of bytes. + Ok(response.body().collect::>()) +} + +fn filter_data( + s: &Vec, + r: &Vec, + a: &T::AccountId, +) -> (BytesSent, BytesReceived) { + let ac = utils::account_to_string::(a.clone()); + + let filtered_s = &*s.into_iter().find(|bs| bs.node_public_key == ac).unwrap(); + let filtered_r = &*r.into_iter().find(|br| br.node_public_key == ac).unwrap(); + + (filtered_s.clone(), filtered_r.clone()) +} + +fn get_bytes_sent_query_url(data_provider_url: &String, era: EraIndex) -> String { + format!("{}/FT.AGGREGATE/ddc:dac:searchCommonIndex/@era:[{}%20{}]/GROUPBY/2/@nodePublicKey/@era/REDUCE/SUM/1/@bytesSent/AS/bytesSentSum", data_provider_url, era, era) +} + +pub(crate) fn fetch_aggregates( + data_provider_url: &String, + era: EraIndex, +) -> Result { + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(HTTP_TIMEOUT_MS)); + let url = + format!("{}/JSON.GET/ddc:dac:aggregation:nodes:{}?type=query", data_provider_url, era); + let request = http::Request::get(url.as_str()); + let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } + let body = response.body().collect::>(); + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + log::warn!("No UTF-8 body"); + http::Error::Unknown + })?; + let json = lite_json::parse_json(body_str).map_err(|_| { + log::warn!("No JSON body"); + http::Error::Unknown + })?; + Ok(json) +} + +pub(crate) fn make_http_put(url: &String, payload: &String) -> Result<(), http::Error> { + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(HTTP_TIMEOUT_MS)); + + let request = http::Request::new(url.as_str()) + .method(Method::Put) + .body(vec![payload.as_bytes()]); + + let pending_req = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + let response = pending_req.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } + + let body = response.body().collect::>(); + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + log::warn!("No UTF-8 body"); + http::Error::Unknown + })?; + + let json = lite_json::parse_json(body_str).map_err(|_| { + log::warn!("No JSON body"); + http::Error::Unknown + })?; + + Ok(()) +} + +pub(crate) fn fetch_validators_decisions( + data_provider_url: &String, + era: EraIndex, +) -> Result { + let url = format!("{}/HGET/mock:ddc:dac:decisions_to_eras/{}", data_provider_url, era); + let wrapper: Wrapper = http_get_json(&url).unwrap(); + + Ok(wrapper) +} + +pub(crate) fn post_final_decision( + data_provider_url: &String, + era: EraIndex, + decision: FinalDecision, +) -> Result<(), http::Error> { + let url = format!("{}/HSET/mock:ddc:dac:final_decision_to_era/{}", data_provider_url, era); + + let payload_str: String = serde_json::to_string(&decision).unwrap(); + let res = make_http_put(&url, &payload_str); + + res +} + +pub(crate) fn get_final_decision(decisions: Vec) -> ValidationDecision { + let common_decisions = find_largest_group(decisions).unwrap(); + let decision_example = common_decisions.get(0).unwrap(); + + let serialized_decisions = serde_json::to_string(&common_decisions).unwrap(); + + let final_decision = ValidationDecision { + edge: decision_example.edge.clone(), + result: decision_example.result, + payload: utils::hash(&serialized_decisions), + totals: DacTotalAggregates { + received: decision_example.totals.received, + sent: decision_example.totals.sent, + failed_by_client: 0, + failure_rate: 0, + }, + }; + + final_decision +} + +pub(crate) fn fetch_validation_results( + data_provider_url: &String, + era: EraIndex, + edge: &String, +) -> Result, http::Error> { + let wrapper = fetch_validators_decisions(data_provider_url, 5 as EraIndex).unwrap(); // Era is mocked for now + let mut edges: EdgesToResults = serde_json::from_str(wrapper.decisions.as_str()).unwrap(); + let results = edges.0.remove(edge).unwrap(); + + Ok(results) +} + +fn find_largest_group(decisions: Vec) -> Option> { + let mut groups: Vec> = Vec::new(); + let half = decisions.len() / 2; + + for decision in decisions { + let mut found_group = false; + + for group in &mut groups { + if group.iter().all(|x| { + x.result == decision.result && + x.totals.received == decision.totals.received && + x.totals.sent == decision.totals.sent + }) { + group.push(decision.clone()); + found_group = true; + break; + } + } + + if !found_group { + groups.push(vec![decision]); + } + } + + let largest_group = groups.into_iter().max_by_key(|group| group.len()).unwrap_or(Vec::new()); + + if largest_group.len() > half { + Some(largest_group) + } else { + None + } +} + diff --git a/frame/ddc-validator/src/lib.rs b/frame/ddc-validator/src/lib.rs new file mode 100644 index 0000000000000..7440512a791ef --- /dev/null +++ b/frame/ddc-validator/src/lib.rs @@ -0,0 +1,784 @@ +//! # DDC Validator pallet +//! +//! The DDC Validator pallet defines storage item to store validation results and implements OCW +//! (off-chain worker) to produce these results using the data from Data Activity Capture (DAC). +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! - [`Hooks`] +//! +//! ## Notes +//! +//! - Era definition in this pallet is different than in the `pallet-staking`. Check DAC +//! documentation for `era` definition used in this pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod dac; +mod payments; +mod shm; +mod utils; +mod validation; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub use alloc::{format, string::String}; +pub use alt_serde::{de::DeserializeOwned, Deserialize, Serialize}; +pub use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; +pub use core::fmt::Debug; +pub use frame_support::{ + decl_event, decl_module, decl_storage, + dispatch::DispatchResult, + pallet_prelude::*, + parameter_types, storage, + traits::{Currency, Randomness, UnixTime}, + weights::Weight, + BoundedVec, RuntimeDebug, +}; +pub use frame_system::{ + ensure_signed, + offchain::{AppCrypto, CreateSignedTransaction, SendSignedTransaction, Signer, SigningTypes}, + pallet_prelude::*, +}; +pub use lite_json::json::JsonValue; +pub use pallet::*; +pub use pallet_ddc_staking::{self as ddc_staking}; +pub use pallet_session as session; +pub use pallet_staking::{self as staking}; +pub use scale_info::TypeInfo; +pub use serde_json::Value; +pub use sp_core::crypto::{AccountId32, KeyTypeId, UncheckedFrom}; +pub use sp_io::crypto::sr25519_public_keys; +pub use sp_runtime::offchain::{http, storage::StorageValueRef, Duration, Timestamp}; +pub use sp_staking::EraIndex; +pub use sp_std::{collections::btree_map::BTreeMap, prelude::*}; + +extern crate alloc; + +/// The balance type of this pallet. +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +type ResultStr = Result; + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"dacv"); + +pub const TIME_START_MS: u128 = 1_672_531_200_000; +// pub const ERA_DURATION_MS: u128 = 120_000; +pub const ERA_DURATION_MS: u128 = 30_000; +pub const ERA_IN_BLOCKS: u8 = 20; + +/// Webdis in experimental cluster connected to Redis in dev. +// pub const DEFAULT_DATA_PROVIDER_URL: &str = "https://dev-dac-redis.network-dev.aws.cere.io"; +pub const DEFAULT_DATA_PROVIDER_URL: &str = "http://161.35.140.182:7379"; +pub const DATA_PROVIDER_URL_KEY: &[u8; 32] = b"ddc-validator::data-provider-url"; +pub const QUORUM_SIZE: usize = 1; + +/// Aggregated values from DAC that describe CDN node's activity during a certain era. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, Deserialize)] +#[serde(crate = "alt_serde")] +pub struct DacTotalAggregates { + /// Total bytes received by the client. + pub received: u64, + /// Total bytes sent by the CDN node. + pub sent: u64, + /// Total bytes sent by the CDN node to the client which interrupts the connection. + pub failed_by_client: u64, + /// ToDo: explain. + pub failure_rate: u64, +} + +/// Final DAC Validation decision. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Serialize, Deserialize)] +#[serde(crate = "alt_serde")] +pub struct ValidationDecision { + /// CDN node public key. + pub edge: String, + /// Validation result. + pub result: bool, + /// A hash of the data used to produce validation result. + pub payload: [u8; 32], + /// Values aggregated from the payload. + pub totals: DacTotalAggregates, +} + +pub mod crypto { + use super::KEY_TYPE; + use frame_system::offchain::AppCrypto; + use sp_core::sr25519::Signature as Sr25519Signature; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + }; + app_crypto!(sr25519, KEY_TYPE); + + use sp_runtime::{MultiSignature, MultiSigner}; + + pub struct TestAuthId; + + impl AppCrypto<::Signer, Sr25519Signature> for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } + + impl AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } +} + +#[frame_support::pallet] +pub mod pallet { + use log::info; + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + frame_system::Config + + pallet_contracts::Config + + pallet_session::Config::AccountId> + + pallet_staking::Config + + ddc_staking::Config + + CreateSignedTransaction> + where + ::AccountId: AsRef<[u8]> + UncheckedFrom, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// Something that provides randomness in the runtime. Required by the tasks assignment + /// procedure. + type Randomness: Randomness; + + /// A dispatchable call. + type Call: From>; + + type AuthorityId: AppCrypto; + type TimeProvider: UnixTime; + + /// Number of validators expected to produce an individual validation decision to form a + /// consensus. Tasks assignment procedure use this value to determine the number of + /// validators are getting the same task. Must be an odd number. + #[pallet::constant] + type DdcValidatorsQuorumSize: Get; + + type ValidatorsMax: Get; + + /// Proof-of-Delivery parameter specifies an allowed deviation between bytes sent and bytes + /// received. The deviation is expressed as a percentage. For example, if the value is 10, + /// then the difference between bytes sent and bytes received is allowed to be up to 10%. + /// The value must be in range [0, 100]. + #[pallet::constant] + type ValidationThreshold: Get; + } + + #[pallet::storage] + #[pallet::getter(fn assignments)] + pub(super) type Assignments = + StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, Vec>; + + /// A signal to start a process on all the validators. + #[pallet::storage] + #[pallet::getter(fn signal)] + pub(super) type Signal = StorageValue<_, bool>; + + /// The map from the era and CDN participant stash key to the validation decision related. + #[pallet::storage] + #[pallet::getter(fn validation_decisions)] + pub type ValidationDecisions = + StorageDoubleMap<_, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, ValidationDecision>; + + /// The last era for which the tasks assignment produced. + #[pallet::storage] + #[pallet::getter(fn last_managed_era)] + pub type LastManagedEra = StorageValue<_, EraIndex>; + + #[pallet::error] + pub enum Error { + // TBA + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event + where + ::AccountId: AsRef<[u8]> + UncheckedFrom, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, {} + + #[pallet::hooks] + impl Hooks> for Pallet + where + ::AccountId: AsRef<[u8]> + UncheckedFrom, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + fn on_initialize(block_number: T::BlockNumber) -> Weight { + if block_number <= 1u32.into() { + return 0 + } + + Signal::::set(Some(false)); + + let era = Self::get_current_era(); + log::info!("current era: {:?}", era); + + if let Some(last_managed_era) = >::get() { + log::info!("last_managed_era: {:?}", last_managed_era); + if last_managed_era >= era { + return 0 + } + } + >::put(era); + + Self::assign(QUORUM_SIZE); + + 0 + } + + fn offchain_worker(block_number: T::BlockNumber) { + // Skip if not a validator. + if !sp_io::offchain::is_validator() { + return + } + + let current_era = Self::get_current_era(); + let last_managed_era = Self::last_managed_era().unwrap_or(0); + let data_provider_url = Self::get_data_provider_url(); + log::info!("[DAC Validator] Data provider URL: {:?}", &data_provider_url); + + // `If` commented for testing purposes + // if current_era > last_managed_era { + Self::validate_edges(); + //} + + // Print the number of broken sessions per CDN node. + // let aggregates_value = dac::fetch_aggregates(&data_provider_url, 77436).unwrap(); // 77436 is for a mock data + // let aggregates_obj = aggregates_value.as_object().unwrap(); + // aggregates_obj + // .into_iter() + // .for_each(|(cdn_node_pubkey, cdn_node_aggregates_value)| { + // // iterate over aggregates for each node + // let cdn_node_aggregates_obj = cdn_node_aggregates_value.as_object().unwrap(); + // // Extract `nodeInterruptedSessions` field + // let (_, cdn_node_interrupted_sessions_value) = cdn_node_aggregates_obj + // .into_iter() + // .find(|(key, _)| key.iter().copied().eq("nodeInterruptedSessions".chars())) + // .unwrap(); + // let cdn_node_interrupted_sessions_obj = + // cdn_node_interrupted_sessions_value.as_object().unwrap(); + // // Prepare CDN pubkey without heap allocated string + // let cdn_node_pubkey_vecu8: Vec = + // cdn_node_pubkey.iter().map(|c| *c as u8).collect(); + // let cdn_node_pubkey_str = + // sp_std::str::from_utf8(&cdn_node_pubkey_vecu8).unwrap(); + // log::info!( + // "Broken sessions per CDN node | Node {}: {} sessions broken", + // cdn_node_pubkey_str, + // cdn_node_interrupted_sessions_obj.len(), /* count sessions broken by the + // * node */ + // ); + // }); + + // Wait for signal. + let signal = Signal::::get().unwrap_or(false); + if !signal { + log::info!("🔎 DAC Validator is idle at block {:?}, waiting for a signal, signal state is {:?}", block_number, signal); + return + } + + // Read from DAC. + let (sent_query, sent, received_query, received) = + dac::fetch_data2(&data_provider_url, current_era - 1); + log::info!( + "🔎 DAC Validator is fetching data from DAC, current era: {:?}, bytes sent query: {:?}, bytes sent response: {:?}, bytes received query: {:?}, bytes received response: {:?}", + current_era, + sent_query, + sent, + received_query, + received, + ); + + // Create intermediate validation decisions + // ======================================== + + // All validators validate all CDN nodes. + let edges: Vec = >::iter_keys().collect(); + for edge in edges.iter() { + // Get string type CDN node pubkey + let edge_pubkey: String = utils::account_to_string::(edge.clone()); + + // Get bytes sent and received for the CDN node + let node_sent: &dac::BytesSent = match sent + .iter() + .find(|bytes_sent| bytes_sent.node_public_key == edge_pubkey) + { + Some(node_sent) => node_sent, + None => { + log::warn!("No logs to validate {:?}", edge); + continue + }, + }; + let client_received: &dac::BytesReceived = match received + .iter() + .find(|bytes_received| bytes_received.node_public_key == edge_pubkey) + { + Some(client_received) => client_received, + None => { + log::warn!("No acks to validate {:?}", edge); + continue + }, + }; + + // Proof-of-delivery validation + let validation_result = Self::validate(node_sent, client_received); + + // Prepare an intermediate validation decision + let validation_decision = ValidationDecision { + edge: utils::account_to_string::(edge.clone()), + result: validation_result, + payload: [0u8; 32], // ToDo: put a hash of the validated data here + totals: DacTotalAggregates { + sent: node_sent.sum as u64, + received: client_received.sum as u64, + failed_by_client: 0, // ToDo + failure_rate: 0, // ToDo + }, + }; + + // Encode validation decision to base64 + let validation_decision_serialized: Vec = validation_decision.encode(); + let validation_decision_base64 = + shm::base64_encode(&validation_decision_serialized); + log::info!( + "Intermediate validation decision for CDN node {:?}: , base64 encoded: {:?}", + validation_decision, + validation_decision_base64, + ); + + // Prepare values to publish validation decision and publish it + let validator_id_string = String::from("validator1"); // ToDo: get validator ID + let edge_id_string = utils::account_to_string::(edge.clone()); + let validation_decision_base64_string = + validation_decision_base64.iter().cloned().collect::(); + let response = shm::share_intermediate_validation_result( + &data_provider_url, + current_era - 1, + &validator_id_string, + &edge_id_string, + validation_result, + &validation_decision_base64_string, + ); + match response { + Ok(response) => log::info!("Shared memory response: {:?}", response.to_string()), + Err(e) => { + log::error!("Shared memory error: {:?}", e); + continue + }, + } + } + log::info!( + "Intermediate validation results published for {} CDN nodes in era {:?}", + edges.len(), + current_era - 1 + ); + + // Set CDN nodes' reward points + // ============================ + + // Let's use a mock data until we have a real final validation decisions for all the CDN + // nodes. + let mock_final_validation_decisions: Vec<(T::AccountId, ValidationDecision)> = vec![ + ( + utils::string_to_account::( + "0xd4160f567d7265b9de2c7cbf1a5c931e5b3195efb2224f8706bfb53ea6eaacd1".into(), + ), + ValidationDecision { + edge: "test".into(), + result: true, + payload: [0u8; 32], + totals: DacTotalAggregates { + sent: 100, + received: 100, + failed_by_client: 0, + failure_rate: 0, + }, + }, + ), + ( + utils::string_to_account::( + "0xa2d14e71b52e5695e72c0567926bc68b68bda74df5c1ccf1d4ba612c153ff66b".into(), + ), + ValidationDecision { + edge: "test".into(), + result: true, + payload: [0u8; 32], + totals: DacTotalAggregates { + sent: 200, + received: 200, + failed_by_client: 0, + failure_rate: 0, + }, + }, + ), + ]; + + // Calculate CDN nodes reward points from validation decision aggregates + let cdn_nodes_reward_points: Vec<(T::AccountId, u64)> = mock_final_validation_decisions + .into_iter() + .filter(|(_, validation_decision)| validation_decision.result) // skip misbehaving + .map(|(cdn_node, validation_decision)| { + // ToDo: should we use `sent` or `received` or anything else as a reward point? + (cdn_node, validation_decision.totals.sent) + }) + .collect(); + + // Store CDN node reward points on-chain + let signer: Signer = Signer::<_, _>::any_account(); + if !signer.can_sign() { + log::warn!("No local accounts available to set era reward points. Consider adding one via `author_insertKey` RPC."); + return + } + // ToDo: replace local call by a call from `ddc-staking` pallet + let _tx_res = signer.send_signed_transaction(|_account| Call::set_era_reward_points { + era: current_era - 1, + stakers_points: cdn_nodes_reward_points.clone(), + }); + } + } + + #[pallet::call] + impl Pallet + where + ::AccountId: AsRef<[u8]> + UncheckedFrom, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + /// Run a process at the same time on all the validators. + #[pallet::weight(10_000)] + pub fn send_signal(origin: OriginFor) -> DispatchResult { + ensure_signed(origin)?; + + Signal::::set(Some(true)); + + Ok(()) + } + + /// Set validation decision for a given CDN node in an era. + #[pallet::weight(10_000)] + pub fn set_validation_decision( + origin: OriginFor, + era: EraIndex, + cdn_node: T::AccountId, + validation_decision: ValidationDecision, + ) -> DispatchResult { + ensure_signed(origin)?; + + // ToDo: check if origin is a validator. + // ToDo: check if the era is current - 1. + // ToDo: check if the validation decision is not set yet. + // ToDo: check cdn_node is known to ddc-staking. + + ValidationDecisions::::insert(era, cdn_node, validation_decision); + + // ToDo: emit event. + + Ok(()) + } + + /// Set reward points for CDN participants at the given era. + /// + /// ToDo: remove it when the off-chain worker will be able to set reward points using the + /// same call defined in `pallet-ddc-staking`. + /// + /// `stakers_points` is a vector of (stash account ID, reward points) pairs. The rewards + /// distribution will be based on total reward points, with each CDN participant receiving a + /// proportionate reward based on their individual reward points. + /// + /// See also [`pallet_ddc_staking::ErasEdgesRewardPoints`]. + #[pallet::weight(100_000)] + pub fn set_era_reward_points( + origin: OriginFor, + era: EraIndex, + stakers_points: Vec<(T::AccountId, u64)>, + ) -> DispatchResult { + ensure_signed(origin)?; + + >::mutate(era, |era_rewards| { + for (staker, points) in stakers_points.into_iter() { + *era_rewards.individual.entry(staker).or_default() += points; + era_rewards.total += points; + } + }); + + Ok(()) + } + } + + impl Pallet + where + ::AccountId: AsRef<[u8]> + UncheckedFrom, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + fn get_data_provider_url() -> String { + let url_ref = sp_io::offchain::local_storage_get( + sp_core::offchain::StorageKind::PERSISTENT, + DATA_PROVIDER_URL_KEY, + ); + + match url_ref { + Some(url) => + String::from_utf8(url).expect("Data provider URL should be valid UTF-8 string"), + None => String::from(DEFAULT_DATA_PROVIDER_URL), + } + } + + fn get_mock_data_url() -> String { + let data_url = Self::get_data_provider_url(); + let mock_url = "/JSON.GET/testddc:dac:data"; + let url = format!("{}{}", data_url, mock_url); + + url + } + + fn get_signer() -> ResultStr> { + let signer = Signer::<_, _>::any_account(); + if !signer.can_sign() { + return Err("[DAC Validator] No local accounts available. Consider adding one via `author_insertKey` RPC."); + } + + Ok(signer) + } + + // Get the current era; Shall we start era count from 0 or from 1? + fn get_current_era() -> EraIndex { + ((T::TimeProvider::now().as_millis() - TIME_START_MS) / ERA_DURATION_MS) + .try_into() + .unwrap() + } + + fn validate(bytes_sent: &dac::BytesSent, bytes_received: &dac::BytesReceived) -> bool { + let percentage_difference = 1f32 - (bytes_received.sum as f32 / bytes_sent.sum as f32); + + return if percentage_difference > 0.0 && + (T::ValidationThreshold::get() as f32 - percentage_difference) > 0.0 + { + true + } else { + false + } + } + + fn is_valid(bytes_sent: u64, bytes_received: u64) -> bool { + let percentage_difference = 1f32 - (bytes_received as f32 / bytes_sent as f32); + + return if percentage_difference > 0.0 && + (T::ValidationThreshold::get() as f32 - percentage_difference) > 0.0 + { + true + } else { + false + } + } + + fn shuffle(mut list: Vec) -> Vec { + let len = list.len(); + for i in 1..len { + let random_index = Self::choose(len as u32).unwrap() as usize; + list.swap(i, random_index) + } + + list + } + + fn split(list: Vec, segment_len: usize) -> Vec> { + list.chunks(segment_len).map(|chunk| chunk.to_vec()).collect() + } + + fn assign(quorum_size: usize) { + let validators: Vec = >::iter_keys().collect(); + let edges: Vec = >::iter_keys().collect(); + + if edges.len() == 0 { + return + } + + let shuffled_validators = Self::shuffle(validators); + let shuffled_edges = Self::shuffle(edges); + + let validators_keys: Vec = shuffled_validators + .iter() + .map(|v| utils::account_to_string::(v.clone())) + .collect(); + + let quorums = Self::split(validators_keys, quorum_size); + let edges_groups = Self::split(shuffled_edges, quorums.len()); + + let era = Self::get_current_era(); + + for (i, quorum) in quorums.iter().enumerate() { + let edges_group = &edges_groups[i]; + for validator in quorum { + Assignments::::insert( + era, + utils::string_to_account::(validator.clone()), + edges_group, + ); + } + } + } + + /// Randomly choose a number in range `[0, total)`. + /// Returns `None` for zero input. + /// Modification of `choose_ticket` from `pallet-lottery` version `4.0.0-dev`. + fn choose(total: u32) -> Option { + if total == 0 { + return None + } + let mut random_number = Self::generate_random_number(0); + + // Best effort attempt to remove bias from modulus operator. + for i in 1..128 { + if random_number < u32::MAX - u32::MAX % total { + break + } + + random_number = Self::generate_random_number(i); + } + + Some(random_number % total) + } + + /// Generate a random number from a given seed. + /// Note that there is potential bias introduced by using modulus operator. + /// You should call this function with different seed values until the random + /// number lies within `u32::MAX - u32::MAX % n`. + /// Modification of `generate_random_number` from `pallet-lottery` version `4.0.0-dev`. + fn generate_random_number(seed: u32) -> u32 { + let (random_seed, _) = + ::Randomness::random(&(b"ddc-validator", seed).encode()); + let random_number = ::decode(&mut random_seed.as_ref()) + .expect("secure hashes should always be bigger than u32; qed"); + + random_number + } + + fn find_validators_from_quorum(validator_id: &T::AccountId, era: &EraIndex) -> Vec { + let validator_edges = Self::assignments(era, &validator_id).unwrap(); + let mut quorum_members: Vec = Vec::new(); + + >::iter_prefix(era).for_each(|(candidate_id, edges)| { + if validator_edges == edges { + let candidate_id_str = utils::account_to_string::(candidate_id); + quorum_members.push(candidate_id_str); + } + }); + + quorum_members + } + + fn get_public_key() -> Option { + match sr25519_public_keys(KEY_TYPE).first() { + Some(pubkey) => Some(T::AccountId::decode(&mut &pubkey.encode()[..]).unwrap()), + None => None + } + } + + fn validate_edges() { + let current_era = Self::get_current_era(); + let mock_data_url = Self::get_mock_data_url(); + let data_provider_url = Self::get_data_provider_url(); + + // let signer = Self::get_signer().unwrap(); + // let validator = signer.get_any_account().unwrap().id; + let validator = Self::get_public_key().unwrap(); + + info!("validator: {:?}", validator); + + let assigned_edges = Self::assignments(current_era - 1, validator.clone()).unwrap(); + + info!("assigned_edges: {:?}", assigned_edges); + + for assigned_edge in assigned_edges.iter() { + let file_request = dac::fetch_file_request(&mock_data_url); + let (bytes_sent, bytes_received) = dac::get_served_bytes_sum(&file_request); + let is_valid = Self::is_valid(bytes_sent, bytes_received); + + info!("bytes_sent, bytes_received: {:?}, {:?}", bytes_sent, bytes_received); + + let payload = serde_json::to_string(&file_request).unwrap(); + let decision = ValidationDecision { + edge: utils::account_to_string::(assigned_edge.clone()), + result: is_valid, + payload: utils::hash(&payload), + totals: DacTotalAggregates { + received: bytes_received, + sent: bytes_sent, + failed_by_client: 0, + failure_rate: 0, + } + }; + + info!("decision: {:?}", decision); + + let serialized_decision = serde_json::to_string(&decision).unwrap(); + let encoded_decision = shm::base64_encode(&serialized_decision.as_bytes().to_vec()); + let validator_str = utils::account_to_string::(validator.clone()); + let edge_str = utils::account_to_string::(assigned_edge.clone()); + + let encoded_decision_str = encoded_decision.iter().cloned().collect::(); + + let response = shm::share_intermediate_validation_result( + &data_provider_url, + current_era - 1, + &validator_str, + &edge_str, + is_valid, + &encoded_decision_str, + ); + + if let Err(res) = response.clone() { + log::error!("share_intermediate_validation_result request failed: {:?}", res); + } + + if let Ok(res) = response.clone() { + info!("shm res: {:?}", res.to_string()); + } + + if let Ok(res) = response { + let edge = utils::account_to_string::(assigned_edge.clone()); + let prev_era = (current_era - 1) as EraIndex; + let quorum = Self::find_validators_from_quorum(&validator, &prev_era); + let validations_res = shm::get_intermediate_decisions(&data_provider_url, &edge_str, &prev_era, quorum); + + log::info!("get_intermediate_decisions result: {:?}", validations_res); + + if validations_res.len() == QUORUM_SIZE { + let final_res = dac::get_final_decision(validations_res); + + let signer = Self::get_signer().unwrap(); + + let tx_res = signer.send_signed_transaction(|_acct| Call::set_validation_decision { + era: current_era, + cdn_node: utils::string_to_account::(edge.clone()), + validation_decision: final_res.clone(), + }); + + log::info!("final_res: {:?}", final_res); + } + } + } + } + } +} diff --git a/frame/ddc-validator/src/mock.rs b/frame/ddc-validator/src/mock.rs new file mode 100644 index 0000000000000..cfbd37992bf4d --- /dev/null +++ b/frame/ddc-validator/src/mock.rs @@ -0,0 +1,311 @@ +use crate::{self as pallet_ddc_validator, *}; +use frame_election_provider_support::{onchain, SequentialPhragmen}; +use frame_support::{ + parameter_types, + traits::{ConstU16, ConstU64, Currency, Everything, Nothing, U128CurrencyToVote}, + weights::Weight, +}; +use frame_system::{offchain::SendTransactionTypes, EnsureRoot}; +use pallet_contracts as contracts; +use pallet_session::ShouldEndSession; +use sp_core::H256; +use sp_runtime::{ + curve, + curve::PiecewiseLinear, + generic, impl_opaque_keys, + testing::{Header, TestXt, UintAuthorityId}, + traits::{ + BlakeTwo256, Convert, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify, + }, + MultiSignature, Perbill, +}; +use sp_staking::SessionIndex; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type Balance = u128; +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +pub type BlockNumber = u32; +pub type Moment = u64; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Contracts: contracts, + Timestamp: pallet_timestamp, + Session: pallet_session, + Staking: pallet_staking, + DdcStaking: pallet_ddc_staking, + RandomnessCollectiveFlip: pallet_randomness_collective_flip, + DdcValidator: pallet_ddc_validator, + } +); + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Call = Call; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + // u64; // sp_core::sr25519::Public; + type Lookup = IdentityLookup; + type Header = generic::Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const SignedClaimHandicap: BlockNumber = 2; + pub const TombstoneDeposit: Balance = 16; + pub const StorageSizeOffset: u32 = 8; + pub const RentByteFee: Balance = 4; + pub const RentDepositOffset: Balance = 10_000; + pub const SurchargeReward: Balance = 150; + pub const MaxDepth: u32 = 100; + pub const MaxValueSize: u32 = 16_384; + pub Schedule: pallet_contracts::Schedule = Default::default(); +} + +use contracts::Config as contractsConfig; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub struct TestWeightToFee; +impl Convert for TestWeightToFee { + fn convert(weight: u64) -> u128 { + weight as u128 + } +} + +impl contracts::Config for Test { + type Time = Timestamp; + type Randomness = RandomnessCollectiveFlip; + type Currency = Balances; + type Event = Event; + type CallStack = [pallet_contracts::Frame; 31]; + type WeightPrice = TestWeightToFee; //pallet_transaction_payment::Module; + type WeightInfo = (); + type ChainExtension = (); + type DeletionQueueDepth = (); + type DeletionWeightLimit = (); + type Schedule = Schedule; + type Call = Call; + type CallFilter = Nothing; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type AddressGenerator = pallet_contracts::DefaultAddressGenerator; +} + +parameter_types! { + pub const TransactionByteFee: u64 = 0; + pub const DepositPerItem: Balance = 0; + pub const DepositPerByte: Balance = 0; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} + +impl pallet_timestamp::Config for Test { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +impl pallet_randomness_collective_flip::Config for Test {} + +pub struct TestShouldEndSession; +impl ShouldEndSession for TestShouldEndSession { + fn should_end_session(now: u32) -> bool { + now % 10 == 0 // every 10 blocks + } +} + +impl_opaque_keys! { + pub struct MockSessionKeys { + pub dummy: UintAuthorityId, + } +} + +impl From for MockSessionKeys { + fn from(dummy: UintAuthorityId) -> Self { + Self { dummy } + } +} + +impl pallet_session::Config for Test { + type Event = Event; + type ValidatorId = AccountId; + type ValidatorIdOf = (); + type ShouldEndSession = TestShouldEndSession; + type NextSessionRotation = (); + type SessionManager = (); + type SessionHandler = pallet_session::TestSessionHandler; + type Keys = MockSessionKeys; + type WeightInfo = (); +} + +impl pallet_session::historical::Config for Test { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +pallet_staking_reward_curve::build! { + const REWARD_CURVE: PiecewiseLinear<'static> = curve!( + min_inflation: 0_000_100, + max_inflation: 0_050_000, + ideal_stake: 0_200_000, + falloff: 0_050_000, + max_piece_count: 100, + test_precision: 0_050_000, + ); +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; + type DataProvider = Staking; + type WeightInfo = frame_election_provider_support::weights::SubstrateWeight; +} + +parameter_types! { + pub const SessionsPerEra: sp_staking::SessionIndex = 6; + pub const BondingDuration: sp_staking::EraIndex = 3; + pub const SlashDeferDuration: sp_staking::EraIndex = 2; + pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + pub const MaxNominatorRewardedPerValidator: u32 = 256; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); + pub OffchainRepeat: BlockNumber = 5; +} + +impl pallet_staking::Config for Test { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type UnixTime = Timestamp; + type CurrencyToVote = U128CurrencyToVote; + type RewardRemainder = (); + type Event = Event; + type Slash = (); // send the slashed funds to the treasury. + type Reward = (); // rewards are minted from the void + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SlashDeferDuration = SlashDeferDuration; + type SlashCancelOrigin = frame_system::EnsureRoot; + type SessionInterface = Self; + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = Session; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; + type ElectionProvider = onchain::UnboundedExecution; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type WeightInfo = pallet_staking::weights::SubstrateWeight; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type CurrencyBalance = Balance; + type OnStakerSlash = (); +} + +impl pallet_ddc_staking::Config for Test { + type BondingDuration = BondingDuration; + type Currency = Balances; + type Event = Event; + type WeightInfo = pallet_ddc_staking::weights::SubstrateWeight; +} + +parameter_types! { + pub const DdcValidatorsQuorumSize: u32 = 3; + pub const ValidationThreshold: u32 = 5; +} + +impl pallet_ddc_validator::Config for Test { + type DdcValidatorsQuorumSize = DdcValidatorsQuorumSize; + type Event = Event; + type Randomness = RandomnessCollectiveFlip; + type Call = Call; + type AuthorityId = pallet_ddc_validator::crypto::TestAuthId; + type TimeProvider = pallet_timestamp::Pallet; + type ValidationThreshold = ValidationThreshold; + type ValidatorsMax = (); +} + +impl SendTransactionTypes for Test +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = Extrinsic; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const MaxLocks: u32 = 10; +} + +impl pallet_balances::Config for Test { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = (); +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::default().build_storage::().unwrap().into() +} + +pub type Extrinsic = TestXt; + +impl SigningTypes for Test { + type Public = ::Signer; + type Signature = Signature; +} + +impl CreateSignedTransaction for Test +where + Call: From, +{ + fn create_transaction>( + call: Call, + _public: ::Signer, + _account: AccountId, + nonce: u64, + ) -> Option<(Call, ::SignaturePayload)> { + Some((call, (nonce, ()))) + } +} diff --git a/frame/ddc-validator/src/payments.rs b/frame/ddc-validator/src/payments.rs new file mode 100644 index 0000000000000..a1a26ade5908d --- /dev/null +++ b/frame/ddc-validator/src/payments.rs @@ -0,0 +1 @@ +//! Payments module to calculate and store a withdrawal per content owner and a payout per CDN node. diff --git a/frame/ddc-validator/src/shm.rs b/frame/ddc-validator/src/shm.rs new file mode 100644 index 0000000000000..1fe38e1cca750 --- /dev/null +++ b/frame/ddc-validator/src/shm.rs @@ -0,0 +1,156 @@ +//! Validators' "shared memory" module. +//! +//! It implements a step of the DAC and Validation sequence when validators share their intermediate +//! validation results with each other. The design of the "shared memory" is expected to become like +//! transactions pool or peers list in the future, but for now it works on the centralized Redis +//! server which we maintain for DAC DataModel. + +use alloc::{format, string::String}; +pub use sp_std::{collections::btree_map::BTreeMap}; +// ToDo: remove String usage +use base64::prelude::*; +use lite_json::json::JsonValue; +use sp_runtime::offchain::{http, Duration}; +use sp_staking::EraIndex; +use sp_std::prelude::*; +use crate::{dac, utils, ValidationDecision}; +use alt_serde::{de::DeserializeOwned, Deserialize, Serialize}; +use log::info; + +const HTTP_TIMEOUT_MS: u64 = 30_000; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(crate = "alt_serde")] +#[serde(rename_all = "camelCase")] +pub struct IntermediateDecisionsWrapper { + #[serde(rename = "JSON.GET")] + json: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(crate = "alt_serde")] +pub(crate) struct IntermediateDecisions { + validators_to_decisions: BTreeMap, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(crate = "alt_serde")] +struct IntermediateDecision { + result: bool, + data: String, +} + +pub fn base64_decode(input: &String) -> Vec { + let mut buf = Vec::with_capacity(392); // ToDo: calculate capacity + buf.resize(392, 0); + BASE64_STANDARD.decode_slice(input, &mut buf).unwrap(); // ToDo: handle error + buf.iter().map(|&char| char as u8).collect() +} + +/// Encodes a vector of bytes into a vector of characters using base64 encoding. +pub fn base64_encode(input: &Vec) -> Vec { + let mut buf = Vec::with_capacity(392); // ToDo: calculate capacity + buf.resize(392, 0); + BASE64_STANDARD.encode_slice(input, &mut buf).unwrap(); // ToDo: handle error + buf.iter().map(|&byte| byte as char).collect() +} + +/// Publish intermediate validation result to redis. +pub fn share_intermediate_validation_result( + shared_memory_webdis_url: &String, + era: EraIndex, + validator: &String, + cdn_node: &String, + validation_result: bool, + validation_decision_encoded: &String, +) -> Result { + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(HTTP_TIMEOUT_MS)); + let validation_decision_string = String::from(validation_decision_encoded); + let json = serde_json::json!({ + "result": validation_result, + "data": validation_decision_string, + }); + let json_str = serde_json::to_string(&json).unwrap(); + let unescaped_json = utils::unescape(&json_str); + let url_encoded_json = utils::url_encode(&unescaped_json); + + log::info!("json_str: {:?}", json_str); + + let url = format!( + "{}/FCALL/save_validation_result_by_node/1/{}:{}:{}/{}", + shared_memory_webdis_url, + validator, + cdn_node, + era, + url_encoded_json, + ); + + log::info!("share_intermediate_validation_result url: {:?}", url); + let request = http::Request::get(url.as_str()); + let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } + let body = response.body().collect::>(); + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + log::warn!("No UTF-8 body"); + http::Error::Unknown + })?; + + log::info!("body_str: {:?}", body_str); + + let json = lite_json::parse_json(body_str).map_err(|_| { + log::warn!("No JSON body"); + http::Error::Unknown + })?; + Ok(json) +} + +pub(crate) fn get_intermediate_decisions(data_provider_url: &String, edge: &str, era: &EraIndex, quorum: Vec) -> Vec { + let url = format!("{}/JSON.GET/ddc:dac:shared:nodes:{}", data_provider_url, era); + + let response: IntermediateDecisionsWrapper = dac::http_get_json(url.as_str()).unwrap(); + let mut edges_to_validators_decisions: BTreeMap> = serde_json::from_str(&response.json).unwrap(); + let decisions_for_edge = IntermediateDecisions { + validators_to_decisions: edges_to_validators_decisions.remove(edge).unwrap() + }; + + let quorum_decisions = find_quorum_decisions(decisions_for_edge, quorum); + let decoded_decisions = decode_intermediate_decisions(quorum_decisions); + + decoded_decisions +} + +pub(crate) fn decode_intermediate_decisions(decisions: IntermediateDecisions) -> Vec { + let mut decoded_decisions: Vec = Vec::new(); + + for (_, decision) in decisions.validators_to_decisions.iter() { + let data = base64_decode(&decision.data); + + let data_str = String::from_utf8_lossy(&data); + let data_trimmed = data_str.trim_end_matches('\0'); + + info!("data_str: {:?}", data_trimmed); + + let decoded_decision: ValidationDecision = serde_json::from_str(&data_trimmed).unwrap(); + + decoded_decisions.push(decoded_decision); + } + + decoded_decisions +} + +pub(crate) fn find_quorum_decisions(all_decisions: IntermediateDecisions, quorum: Vec) -> IntermediateDecisions { + let mut quorum_decisions: BTreeMap = BTreeMap::new(); + for (validator_id, decision) in all_decisions.validators_to_decisions.iter() { + if quorum.contains(validator_id) { + quorum_decisions.insert(validator_id.clone(), decision.clone()); + } + } + + IntermediateDecisions { + validators_to_decisions: quorum_decisions + } +} diff --git a/frame/ddc-validator/src/tests.rs b/frame/ddc-validator/src/tests.rs new file mode 100644 index 0000000000000..33c9b10712091 --- /dev/null +++ b/frame/ddc-validator/src/tests.rs @@ -0,0 +1,11 @@ +use crate::mock::*; +use frame_support::assert_ok; +use sp_core::crypto::AccountId32; +use sp_runtime::DispatchResult; + +#[test] +fn save_validated_data_works() { + new_test_ext().execute_with(|| { + assert_ok!(DispatchResult::Ok(())); + }); +} diff --git a/frame/ddc-validator/src/utils.rs b/frame/ddc-validator/src/utils.rs new file mode 100644 index 0000000000000..36dc4492a3eb4 --- /dev/null +++ b/frame/ddc-validator/src/utils.rs @@ -0,0 +1,60 @@ +use crate::dac::ValidationResult; +use alloc::{format, string::String}; +use codec::{Decode, Encode}; +use sp_core::crypto::AccountId32; +use sp_io::hashing::blake2_256; +pub use sp_std::prelude::*; + +pub fn account_to_string(account: T::AccountId) -> String { + let to32 = T::AccountId::encode(&account); + let pub_key_str = array_bytes::bytes2hex("", to32); + + pub_key_str +} + +pub fn string_to_account(pub_key_str: String) -> T::AccountId { + let acc32: sp_core::crypto::AccountId32 = + array_bytes::hex2array::<_, 32>(pub_key_str).unwrap().into(); + let mut to32 = AccountId32::as_ref(&acc32); + let address: T::AccountId = T::AccountId::decode(&mut to32).unwrap(); + address +} + +pub(crate) fn hash(data: &String) -> [u8; 32] { + let hash = blake2_256(data.as_bytes()); + let mut result = [0u8; 32]; + result.copy_from_slice(&hash); + + result +} + +pub(crate) fn url_encode(input: &str) -> String { + let mut encoded = String::new(); + + for byte in input.bytes() { + match byte { + // Unreserved characters (alphanumeric and -_.~) + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + encoded.push(byte as char); + } + _ => { + encoded.push('%'); + encoded.push_str(&format!("{:02X}", byte)); + } + } + } + + encoded +} + +pub(crate) fn unescape(json: &str) -> String { + let mut result = String::new(); + + for ch in json.chars() { + if ch != '\\' { + result.push(ch); + } + } + + result +} \ No newline at end of file diff --git a/frame/ddc-validator/src/validation.rs b/frame/ddc-validator/src/validation.rs new file mode 100644 index 0000000000000..017d90782db54 --- /dev/null +++ b/frame/ddc-validator/src/validation.rs @@ -0,0 +1 @@ +//! DAC Validation implementation.