From 7c9ddc42675463fdf986b045d3c442396c475baf Mon Sep 17 00:00:00 2001 From: anvie Date: Wed, 7 Sep 2022 15:18:26 +0700 Subject: [PATCH] remove client/beefy and clean up code --- Cargo.lock | 315 ++---- client/beefy/Cargo.toml | 52 - client/beefy/rpc/Cargo.toml | 32 - client/beefy/rpc/src/lib.rs | 300 ------ client/beefy/rpc/src/notification.rs | 39 - client/beefy/src/error.rs | 35 - client/beefy/src/gossip.rs | 462 --------- client/beefy/src/import.rs | 201 ---- client/beefy/src/justification.rs | 188 ---- client/beefy/src/keystore.rs | 378 -------- client/beefy/src/lib.rs | 273 ------ client/beefy/src/metrics.rs | 107 --- client/beefy/src/notification.rs | 55 -- client/beefy/src/round.rs | 444 --------- client/beefy/src/tests.rs | 815 ---------------- client/beefy/src/worker.rs | 1328 -------------------------- test-utils/runtime/Cargo.toml | 2 - 17 files changed, 54 insertions(+), 4972 deletions(-) delete mode 100644 client/beefy/Cargo.toml delete mode 100644 client/beefy/rpc/Cargo.toml delete mode 100644 client/beefy/rpc/src/lib.rs delete mode 100644 client/beefy/rpc/src/notification.rs delete mode 100644 client/beefy/src/error.rs delete mode 100644 client/beefy/src/gossip.rs delete mode 100644 client/beefy/src/import.rs delete mode 100644 client/beefy/src/justification.rs delete mode 100644 client/beefy/src/keystore.rs delete mode 100644 client/beefy/src/lib.rs delete mode 100644 client/beefy/src/metrics.rs delete mode 100644 client/beefy/src/notification.rs delete mode 100644 client/beefy/src/round.rs delete mode 100644 client/beefy/src/tests.rs delete mode 100644 client/beefy/src/worker.rs diff --git a/Cargo.lock b/Cargo.lock index 9625c38f4..1882a21ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "opaque-debug 0.3.0", @@ -261,7 +261,7 @@ checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", "blocking", - "cfg-if 1.0.0", + "cfg-if", "event-listener", "futures-lite", "libc", @@ -395,7 +395,7 @@ checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object 0.29.0", @@ -439,102 +439,6 @@ dependencies = [ "serde", ] -[[package]] -name = "beefy-gadget" -version = "4.0.0-dev" -dependencies = [ - "async-trait", - "beefy-primitives", - "fnv", - "futures", - "futures-timer", - "hex", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "sc-block-builder", - "sc-chain-spec", - "sc-client-api", - "sc-consensus", - "sc-finality-grandpa", - "sc-keystore", - "sc-network", - "sc-network-gossip", - "sc-network-test", - "sc-utils", - "serde", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-finality-grandpa", - "sp-keyring", - "sp-keystore", - "sp-mmr-primitives", - "sp-runtime", - "sp-tracing", - "strum", - "substrate-prometheus-endpoint", - "substrate-test-runtime-client", - "tempfile", - "thiserror", - "tokio", - "wasm-timer", -] - -[[package]] -name = "beefy-gadget-rpc" -version = "4.0.0-dev" -dependencies = [ - "beefy-gadget", - "beefy-primitives", - "futures", - "jsonrpsee", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "sc-rpc", - "sc-utils", - "serde", - "serde_json", - "sp-core", - "sp-runtime", - "substrate-test-runtime-client", - "thiserror", - "tokio", -] - -[[package]] -name = "beefy-merkle-tree" -version = "4.0.0-dev" -dependencies = [ - "beefy-primitives", - "env_logger", - "hex", - "hex-literal", - "log", - "sp-api", - "tiny-keccak", -] - -[[package]] -name = "beefy-primitives" -version = "4.0.0-dev" -dependencies = [ - "hex", - "hex-literal", - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-application-crypto", - "sp-core", - "sp-keystore", - "sp-runtime", - "sp-std", -] - [[package]] name = "bimap" version = "0.6.2" @@ -637,7 +541,7 @@ dependencies = [ "arrayref", "arrayvec 0.7.2", "cc", - "cfg-if 1.0.0", + "cfg-if", "constant_time_eq", ] @@ -851,12 +755,6 @@ dependencies = [ "nom", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -869,7 +767,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cipher", "cpufeatures", "zeroize", @@ -939,15 +837,6 @@ dependencies = [ "generic-array 0.14.6", ] -[[package]] -name = "ckb-merkle-mountain-range" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f061f97d64fd1822664bdfb722f7ae5469a97b77567390f7442be5b5dc82a5b" -dependencies = [ - "cfg-if 0.1.10", -] - [[package]] name = "clang-sys" version = "1.3.3" @@ -1091,7 +980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "libc", "scopeguard", "windows-sys 0.33.0", @@ -1103,7 +992,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1276,7 +1165,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1323,7 +1212,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -1333,7 +1222,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] @@ -1345,7 +1234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg", - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", "memoffset", "once_cell", @@ -1358,7 +1247,7 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", ] @@ -1637,7 +1526,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "dirs-sys-next", ] @@ -1838,7 +1727,7 @@ version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -2045,7 +1934,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "windows-sys 0.36.1", @@ -2285,7 +2174,7 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "parity-scale-codec", "scale-info", "serde", @@ -2668,7 +2557,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -2681,7 +2570,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] @@ -3116,7 +3005,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -3353,7 +3242,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", "sec1", @@ -3476,7 +3365,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "winapi", ] @@ -4147,7 +4036,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "value-bag", ] @@ -4579,7 +4468,7 @@ checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "memoffset", ] @@ -4591,7 +4480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "libc", ] @@ -4703,7 +4592,6 @@ dependencies = [ "node-primitives", "pallet-contracts-rpc", "pallet-did-rpc", - "pallet-mmr-rpc", "pallet-transaction-payment-rpc", "sc-chain-spec", "sc-client-api", @@ -4785,7 +4673,7 @@ dependencies = [ [[package]] name = "nuchain-node" -version = "2.0.4" +version = "2.0.5" dependencies = [ "assert_cmd", "async-std", @@ -4912,7 +4800,6 @@ dependencies = [ "pallet-liquidity", "pallet-membership", "pallet-migration", - "pallet-mmr", "pallet-multisig", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", @@ -5110,7 +4997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" dependencies = [ "bitflags", - "cfg-if 1.0.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -5389,48 +5276,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-beefy" -version = "4.0.0-dev" -dependencies = [ - "beefy-primitives", - "frame-support", - "frame-system", - "pallet-session", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", - "sp-std", -] - -[[package]] -name = "pallet-beefy-mmr" -version = "4.0.0-dev" -dependencies = [ - "beefy-merkle-tree", - "beefy-primitives", - "frame-support", - "frame-system", - "hex", - "hex-literal", - "log", - "pallet-beefy", - "pallet-mmr", - "pallet-session", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-staking", - "sp-std", -] - [[package]] name = "pallet-bounties" version = "4.0.0-dev" @@ -5937,41 +5782,6 @@ dependencies = [ "sp-version", ] -[[package]] -name = "pallet-mmr" -version = "4.0.0-dev" -dependencies = [ - "ckb-merkle-mountain-range", - "env_logger", - "frame-benchmarking", - "frame-support", - "frame-system", - "hex-literal", - "itertools", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-mmr-primitives", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-mmr-rpc" -version = "3.0.0" -dependencies = [ - "jsonrpsee", - "parity-scale-codec", - "serde", - "serde_json", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-mmr-primitives", - "sp-runtime", -] - [[package]] name = "pallet-multisig" version = "4.0.0-dev" @@ -6699,7 +6509,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "hashbrown 0.12.3", "impl-trait-for-tuples", "parity-util-mem-derive", @@ -6768,7 +6578,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "instant", "libc", "redox_syscall", @@ -6782,7 +6592,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -6982,7 +6792,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "log", "wepoll-ffi", @@ -7006,7 +6816,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "opaque-debug 0.3.0", "universal-hash", @@ -7126,7 +6936,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cface98dfa6d645ea4c789839f176e4b072265d085bfcc48eaa8d137f58d3c39" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fnv", "lazy_static", "memchr", @@ -7174,7 +6984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" dependencies = [ "bytes", - "cfg-if 1.0.0", + "cfg-if", "cmake", "heck", "itertools", @@ -8388,7 +8198,7 @@ dependencies = [ name = "sc-executor-wasmtime" version = "0.10.0-dev" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "log", "once_cell", @@ -9113,7 +8923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" dependencies = [ "bitvec", - "cfg-if 1.0.0", + "cfg-if", "derive_more", "parity-scale-codec", "scale-info-derive", @@ -9355,7 +9165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -9367,7 +9177,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.3", ] @@ -9391,7 +9201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -9403,7 +9213,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.3", ] @@ -9994,21 +9804,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "sp-mmr-primitives" -version = "4.0.0-dev" -dependencies = [ - "hex-literal", - "log", - "parity-scale-codec", - "serde", - "sp-api", - "sp-core", - "sp-debug-derive", - "sp-runtime", - "sp-std", -] - [[package]] name = "sp-npos-elections" version = "4.0.0-dev" @@ -10602,9 +10397,7 @@ dependencies = [ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ - "beefy-merkle-tree", - "beefy-primitives", - "cfg-if 1.0.0", + "cfg-if", "frame-support", "frame-system", "frame-system-rpc-runtime-api", @@ -10793,7 +10586,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "libc", "redox_syscall", @@ -11063,7 +10856,7 @@ version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "log", "pin-project-lite 0.2.9", "tracing-attributes", @@ -11200,7 +10993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" dependencies = [ "async-trait", - "cfg-if 1.0.0", + "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", @@ -11223,7 +11016,7 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "futures-util", "ipconfig", "lazy_static", @@ -11294,7 +11087,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "digest 0.10.3", "rand 0.8.5", "static_assertions", @@ -11500,7 +11293,7 @@ version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -11525,7 +11318,7 @@ version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -11610,7 +11403,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "indexmap", "js-sys", "loupe", @@ -11743,7 +11536,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "enum-iterator", "enumset", "leb128", @@ -11769,7 +11562,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "enumset", "leb128", "loupe", @@ -11835,7 +11628,7 @@ checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" dependencies = [ "backtrace", "cc", - "cfg-if 1.0.0", + "cfg-if", "corosensei", "enum-iterator", "indexmap", @@ -11905,7 +11698,7 @@ dependencies = [ "anyhow", "backtrace", "bincode", - "cfg-if 1.0.0", + "cfg-if", "indexmap", "lazy_static", "libc", @@ -11998,7 +11791,7 @@ dependencies = [ "addr2line", "anyhow", "bincode", - "cfg-if 1.0.0", + "cfg-if", "cpp_demangle", "gimli", "log", @@ -12035,7 +11828,7 @@ dependencies = [ "anyhow", "backtrace", "cc", - "cfg-if 1.0.0", + "cfg-if", "indexmap", "libc", "log", diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml deleted file mode 100644 index e21942095..000000000 --- a/client/beefy/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "beefy-gadget" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -repository = "https://github.com/paritytech/substrate" -description = "BEEFY Client gadget for substrate" -homepage = "https://substrate.io" - -[dependencies] -async-trait = "0.1.50" -codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -fnv = "1.0.6" -futures = "0.3" -futures-timer = "3.0.1" -hex = "0.4.2" -log = "0.4" -parking_lot = "0.12.0" -thiserror = "1.0" -wasm-timer = "0.2.5" -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } -prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } -sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } - -[dev-dependencies] -serde = "1.0.136" -strum = { version = "0.24.1", features = ["derive"] } -tempfile = "3.1.0" -tokio = "1.17.0" -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sc-network-test = { version = "0.8.0", path = "../network/test" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml deleted file mode 100644 index 46ee7640d..000000000 --- a/client/beefy/rpc/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "beefy-gadget-rpc" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -repository = "https://github.com/paritytech/substrate" -description = "RPC for the BEEFY Client gadget for substrate" -homepage = "https://substrate.io" - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -futures = "0.3.21" -jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } -log = "0.4" -parking_lot = "0.12.0" -serde = { version = "1.0.136", features = ["derive"] } -thiserror = "1.0" -beefy-gadget = { version = "4.0.0-dev", path = "../." } -beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } -sc-utils = { version = "4.0.0-dev", path = "../../utils" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } - -[dev-dependencies] -serde_json = "1.0.79" -sc-rpc = { version = "4.0.0-dev", features = [ - "test-helpers", -], path = "../../rpc" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -tokio = { version = "1.17.0", features = ["macros"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs deleted file mode 100644 index 3be182ceb..000000000 --- a/client/beefy/rpc/src/lib.rs +++ /dev/null @@ -1,300 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! RPC API for BEEFY. - -#![warn(missing_docs)] - -use parking_lot::RwLock; -use std::sync::Arc; - -use sc_rpc::SubscriptionTaskExecutor; -use sp_runtime::traits::Block as BlockT; - -use futures::{task::SpawnError, FutureExt, StreamExt}; -use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError, RpcResult}, - proc_macros::rpc, - types::{error::CallError, ErrorObject, SubscriptionResult}, - SubscriptionSink, -}; -use log::warn; - -use beefy_gadget::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}; - -mod notification; - -#[derive(Debug, thiserror::Error)] -/// Top-level error type for the RPC handler -pub enum Error { - /// The BEEFY RPC endpoint is not ready. - #[error("BEEFY RPC endpoint not ready")] - EndpointNotReady, - /// The BEEFY RPC background task failed to spawn. - #[error("BEEFY RPC background task failed to spawn")] - RpcTaskFailure(#[from] SpawnError), -} - -/// The error codes returned by jsonrpc. -pub enum ErrorCode { - /// Returned when BEEFY RPC endpoint is not ready. - NotReady = 1, - /// Returned on BEEFY RPC background task failure. - TaskFailure = 2, -} - -impl From for ErrorCode { - fn from(error: Error) -> Self { - match error { - Error::EndpointNotReady => ErrorCode::NotReady, - Error::RpcTaskFailure(_) => ErrorCode::TaskFailure, - } - } -} - -impl From for JsonRpseeError { - fn from(error: Error) -> Self { - let message = error.to_string(); - let code = ErrorCode::from(error); - JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( - code as i32, - message, - None::<()>, - ))) - } -} - -// Provides RPC methods for interacting with BEEFY. -#[rpc(client, server)] -pub trait BeefyApi { - /// Returns the block most recently finalized by BEEFY, alongside side its justification. - #[subscription( - name = "beefy_subscribeJustifications" => "beefy_justifications", - unsubscribe = "beefy_unsubscribeJustifications", - item = Notification, - )] - fn subscribe_justifications(&self); - - /// Returns hash of the latest BEEFY finalized block as seen by this client. - /// - /// The latest BEEFY block might not be available if the BEEFY gadget is not running - /// in the network or if the client is still initializing or syncing with the network. - /// In such case an error would be returned. - #[method(name = "beefy_getFinalizedHead")] - async fn latest_finalized(&self) -> RpcResult; -} - -/// Implements the BeefyApi RPC trait for interacting with BEEFY. -pub struct Beefy { - finality_proof_stream: BeefyVersionedFinalityProofStream, - beefy_best_block: Arc>>, - executor: SubscriptionTaskExecutor, -} - -impl Beefy -where - Block: BlockT, -{ - /// Creates a new Beefy Rpc handler instance. - pub fn new( - finality_proof_stream: BeefyVersionedFinalityProofStream, - best_block_stream: BeefyBestBlockStream, - executor: SubscriptionTaskExecutor, - ) -> Result { - let beefy_best_block = Arc::new(RwLock::new(None)); - - let stream = best_block_stream.subscribe(); - let closure_clone = beefy_best_block.clone(); - let future = stream.for_each(move |best_beefy| { - let async_clone = closure_clone.clone(); - async move { *async_clone.write() = Some(best_beefy) } - }); - - executor.spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed()); - Ok(Self { finality_proof_stream, beefy_best_block, executor }) - } -} - -#[async_trait] -impl BeefyApiServer - for Beefy -where - Block: BlockT, -{ - fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { - let stream = self - .finality_proof_stream - .subscribe() - .map(|vfp| notification::EncodedVersionedFinalityProof::new::(vfp)); - - let fut = async move { - sink.pipe_from_stream(stream).await; - }; - - self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); - Ok(()) - } - - async fn latest_finalized(&self) -> RpcResult { - self.beefy_best_block - .read() - .as_ref() - .cloned() - .ok_or(Error::EndpointNotReady) - .map_err(Into::into) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use beefy_gadget::{ - justification::BeefyVersionedFinalityProof, - notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofSender}, - }; - use beefy_primitives::{known_payload_ids, Payload, SignedCommitment}; - use codec::{Decode, Encode}; - use jsonrpsee::{types::EmptyParams, RpcModule}; - use sp_runtime::traits::{BlakeTwo256, Hash}; - use substrate_test_runtime_client::runtime::Block; - - fn setup_io_handler() -> (RpcModule>, BeefyVersionedFinalityProofSender) { - let (_, stream) = BeefyBestBlockStream::::channel(); - setup_io_handler_with_best_block_stream(stream) - } - - fn setup_io_handler_with_best_block_stream( - best_block_stream: BeefyBestBlockStream, - ) -> (RpcModule>, BeefyVersionedFinalityProofSender) { - let (finality_proof_sender, finality_proof_stream) = - BeefyVersionedFinalityProofStream::::channel(); - - let handler = - Beefy::new(finality_proof_stream, best_block_stream, sc_rpc::testing::test_executor()) - .expect("Setting up the BEEFY RPC handler works"); - - (handler.into_rpc(), finality_proof_sender) - } - - #[tokio::test] - async fn uninitialized_rpc_handler() { - let (rpc, _) = setup_io_handler(); - let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); - let (response, _) = rpc.raw_json_request(&request).await.unwrap(); - - assert_eq!(expected_response, response.result); - } - - #[tokio::test] - async fn latest_finalized_rpc() { - let (sender, stream) = BeefyBestBlockStream::::channel(); - let (io, _) = setup_io_handler_with_best_block_stream(stream); - - let hash = BlakeTwo256::hash(b"42"); - let r: Result<(), ()> = sender.notify(|| Ok(hash)); - r.unwrap(); - - // Verify RPC `beefy_getFinalizedHead` returns expected hash. - let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let expected = "{\ - \"jsonrpc\":\"2.0\",\ - \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ - \"id\":1\ - }" - .to_string(); - let not_ready = "{\ - \"jsonrpc\":\"2.0\",\ - \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ - \"id\":1\ - }" - .to_string(); - - let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); - while std::time::Instant::now() < deadline { - let (response, _) = io.raw_json_request(request).await.expect("RPC requests work"); - if response.result != not_ready { - assert_eq!(response.result, expected); - // Success - return - } - std::thread::sleep(std::time::Duration::from_millis(50)) - } - - panic!( - "Deadline reached while waiting for best BEEFY block to update. Perhaps the background task is broken?" - ); - } - - #[tokio::test] - async fn subscribe_and_unsubscribe_with_wrong_id() { - let (rpc, _) = setup_io_handler(); - // Subscribe call. - let _sub = rpc - .subscribe("beefy_subscribeJustifications", EmptyParams::new()) - .await - .unwrap(); - - // Unsubscribe with wrong ID - let (response, _) = rpc - .raw_json_request( - r#"{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":["FOO"],"id":1}"#, - ) - .await - .unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - - assert_eq!(response.result, expected); - } - - fn create_finality_proof() -> BeefyVersionedFinalityProof { - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); - BeefyVersionedFinalityProof::::V1(SignedCommitment { - commitment: beefy_primitives::Commitment { - payload, - block_number: 5, - validator_set_id: 0, - }, - signatures: vec![], - }) - } - - #[tokio::test] - async fn subscribe_and_listen_to_one_justification() { - let (rpc, finality_proof_sender) = setup_io_handler(); - - // Subscribe - let mut sub = rpc - .subscribe("beefy_subscribeJustifications", EmptyParams::new()) - .await - .unwrap(); - - // Notify with finality_proof - let finality_proof = create_finality_proof(); - let r: Result<(), ()> = finality_proof_sender.notify(|| Ok(finality_proof.clone())); - r.unwrap(); - - // Inspect what we received - let (bytes, recv_sub_id) = sub.next::().await.unwrap().unwrap(); - let recv_finality_proof: BeefyVersionedFinalityProof = - Decode::decode(&mut &bytes[..]).unwrap(); - assert_eq!(&recv_sub_id, sub.subscription_id()); - assert_eq!(recv_finality_proof, finality_proof); - } -} diff --git a/client/beefy/rpc/src/notification.rs b/client/beefy/rpc/src/notification.rs deleted file mode 100644 index a81542564..000000000 --- a/client/beefy/rpc/src/notification.rs +++ /dev/null @@ -1,39 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use codec::Encode; -use serde::{Deserialize, Serialize}; - -use sp_runtime::traits::Block as BlockT; - -/// An encoded finality proof proving that the given header has been finalized. -/// The given bytes should be the SCALE-encoded representation of a -/// `beefy_primitives::VersionedFinalityProof`. -#[derive(Clone, Serialize, Deserialize)] -pub struct EncodedVersionedFinalityProof(sp_core::Bytes); - -impl EncodedVersionedFinalityProof { - pub fn new( - finality_proof: beefy_gadget::justification::BeefyVersionedFinalityProof, - ) -> Self - where - Block: BlockT, - { - EncodedVersionedFinalityProof(finality_proof.encode().into()) - } -} diff --git a/client/beefy/src/error.rs b/client/beefy/src/error.rs deleted file mode 100644 index dd5fd649d..000000000 --- a/client/beefy/src/error.rs +++ /dev/null @@ -1,35 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! BEEFY gadget specific errors -//! -//! Used for BEEFY gadget interal error handling only - -use std::fmt::Debug; - -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum Error { - #[error("Backend: {0}")] - Backend(String), - #[error("Keystore error: {0}")] - Keystore(String), - #[error("Signature error: {0}")] - Signature(String), - #[error("Session uninitialized")] - UninitSession, -} diff --git a/client/beefy/src/gossip.rs b/client/beefy/src/gossip.rs deleted file mode 100644 index 02d5efe9e..000000000 --- a/client/beefy/src/gossip.rs +++ /dev/null @@ -1,462 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::{collections::BTreeMap, time::Duration}; - -use sc_network::PeerId; -use sc_network_gossip::{MessageIntent, ValidationResult, Validator, ValidatorContext}; -use sp_core::hashing::twox_64; -use sp_runtime::traits::{Block, Hash, Header, NumberFor}; - -use codec::{Decode, Encode}; -use log::{debug, trace}; -use parking_lot::{Mutex, RwLock}; -use wasm_timer::Instant; - -use beefy_primitives::{ - crypto::{Public, Signature}, - VoteMessage, -}; - -use crate::keystore::BeefyKeystore; - -// Timeout for rebroadcasting messages. -const REBROADCAST_AFTER: Duration = Duration::from_secs(60 * 5); - -/// Gossip engine messages topic -pub(crate) fn topic() -> B::Hash -where - B: Block, -{ - <::Hashing as Hash>::hash(b"beefy") -} - -/// A type that represents hash of the message. -pub type MessageHash = [u8; 8]; - -struct KnownVotes { - last_done: Option>, - live: BTreeMap, fnv::FnvHashSet>, -} - -impl KnownVotes { - pub fn new() -> Self { - Self { last_done: None, live: BTreeMap::new() } - } - - /// Create new round votes set if not already present. - fn insert(&mut self, round: NumberFor) { - self.live.entry(round).or_default(); - } - - /// Remove `round` and older from live set, update `last_done` accordingly. - fn conclude(&mut self, round: NumberFor) { - self.live.retain(|&number, _| number > round); - self.last_done = self.last_done.max(Some(round)); - } - - /// Return true if `round` is newer than previously concluded rounds. - /// - /// Latest concluded round is still considered alive to allow proper gossiping for it. - fn is_live(&self, round: &NumberFor) -> bool { - Some(*round) >= self.last_done - } - - /// Add new _known_ `hash` to the round's known votes. - fn add_known(&mut self, round: &NumberFor, hash: MessageHash) { - self.live.get_mut(round).map(|known| known.insert(hash)); - } - - /// Check if `hash` is already part of round's known votes. - fn is_known(&self, round: &NumberFor, hash: &MessageHash) -> bool { - self.live.get(round).map(|known| known.contains(hash)).unwrap_or(false) - } -} - -/// BEEFY gossip validator -/// -/// Validate BEEFY gossip messages and limit the number of live BEEFY voting rounds. -/// -/// Allows messages for 'rounds >= last concluded' to flow, everything else gets -/// rejected/expired. -/// -///All messaging is handled in a single BEEFY global topic. -pub(crate) struct GossipValidator -where - B: Block, -{ - topic: B::Hash, - known_votes: RwLock>, - next_rebroadcast: Mutex, -} - -impl GossipValidator -where - B: Block, -{ - pub fn new() -> GossipValidator { - GossipValidator { - topic: topic::(), - known_votes: RwLock::new(KnownVotes::new()), - next_rebroadcast: Mutex::new(Instant::now() + REBROADCAST_AFTER), - } - } - - /// Note a voting round. - /// - /// Noting round will start a live `round`. - pub(crate) fn note_round(&self, round: NumberFor) { - debug!(target: "beefy", "🥩 About to note gossip round #{}", round); - self.known_votes.write().insert(round); - } - - /// Conclude a voting round. - /// - /// This can be called once round is complete so we stop gossiping for it. - pub(crate) fn conclude_round(&self, round: NumberFor) { - debug!(target: "beefy", "🥩 About to drop gossip round #{}", round); - self.known_votes.write().conclude(round); - } -} - -impl Validator for GossipValidator -where - B: Block, -{ - fn validate( - &self, - _context: &mut dyn ValidatorContext, - sender: &PeerId, - mut data: &[u8], - ) -> ValidationResult { - if let Ok(msg) = VoteMessage::, Public, Signature>::decode(&mut data) { - let msg_hash = twox_64(data); - let round = msg.commitment.block_number; - - // Verify general usefulness of the message. - // We are going to discard old votes right away (without verification) - // Also we keep track of already received votes to avoid verifying duplicates. - { - let known_votes = self.known_votes.read(); - - if !known_votes.is_live(&round) { - return ValidationResult::Discard - } - - if known_votes.is_known(&round, &msg_hash) { - return ValidationResult::ProcessAndKeep(self.topic) - } - } - - if BeefyKeystore::verify(&msg.id, &msg.signature, &msg.commitment.encode()) { - self.known_votes.write().add_known(&round, msg_hash); - return ValidationResult::ProcessAndKeep(self.topic) - } else { - // TODO: report peer - debug!(target: "beefy", "🥩 Bad signature on message: {:?}, from: {:?}", msg, sender); - } - } - - ValidationResult::Discard - } - - fn message_expired<'a>(&'a self) -> Box bool + 'a> { - let known_votes = self.known_votes.read(); - Box::new(move |_topic, mut data| { - let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { - Ok(vote) => vote, - Err(_) => return true, - }; - - let round = msg.commitment.block_number; - let expired = !known_votes.is_live(&round); - - trace!(target: "beefy", "🥩 Message for round #{} expired: {}", round, expired); - - expired - }) - } - - fn message_allowed<'a>( - &'a self, - ) -> Box bool + 'a> { - let do_rebroadcast = { - let now = Instant::now(); - let mut next_rebroadcast = self.next_rebroadcast.lock(); - if now >= *next_rebroadcast { - *next_rebroadcast = now + REBROADCAST_AFTER; - true - } else { - false - } - }; - - let known_votes = self.known_votes.read(); - Box::new(move |_who, intent, _topic, mut data| { - if let MessageIntent::PeriodicRebroadcast = intent { - return do_rebroadcast - } - - let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { - Ok(vote) => vote, - Err(_) => return false, - }; - - let round = msg.commitment.block_number; - let allowed = known_votes.is_live(&round); - - trace!(target: "beefy", "🥩 Message for round #{} allowed: {}", round, allowed); - - allowed - }) - } -} - -#[cfg(test)] -mod tests { - use sc_keystore::LocalKeystore; - use sc_network_test::Block; - use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; - - use crate::keystore::{tests::Keyring, BeefyKeystore}; - use beefy_primitives::{ - crypto::Signature, known_payload_ids, Commitment, MmrRootHash, Payload, VoteMessage, - KEY_TYPE, - }; - - use super::*; - - #[test] - fn known_votes_insert_remove() { - let mut kv = KnownVotes::::new(); - - kv.insert(1); - kv.insert(1); - kv.insert(2); - assert_eq!(kv.live.len(), 2); - - let mut kv = KnownVotes::::new(); - kv.insert(1); - kv.insert(2); - kv.insert(3); - - assert!(kv.last_done.is_none()); - kv.conclude(2); - assert_eq!(kv.live.len(), 1); - assert!(!kv.live.contains_key(&2)); - assert_eq!(kv.last_done, Some(2)); - - kv.conclude(1); - assert_eq!(kv.last_done, Some(2)); - - kv.conclude(3); - assert_eq!(kv.last_done, Some(3)); - assert!(kv.live.is_empty()); - } - - #[test] - fn note_and_drop_round_works() { - let gv = GossipValidator::::new(); - - gv.note_round(1u64); - - assert!(gv.known_votes.read().is_live(&1u64)); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - assert_eq!(gv.known_votes.read().live.len(), 4); - - gv.conclude_round(7u64); - - let votes = gv.known_votes.read(); - - // rounds 1 and 3 are outdated, don't gossip anymore - assert!(!votes.is_live(&1u64)); - assert!(!votes.is_live(&3u64)); - // latest concluded round is still gossiped - assert!(votes.is_live(&7u64)); - // round 10 is alive and in-progress - assert!(votes.is_live(&10u64)); - } - - #[test] - fn note_same_round_twice() { - let gv = GossipValidator::::new(); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - assert_eq!(gv.known_votes.read().live.len(), 3); - - // note round #7 again -> should not change anything - gv.note_round(7u64); - - let votes = gv.known_votes.read(); - - assert_eq!(votes.live.len(), 3); - - assert!(votes.is_live(&3u64)); - assert!(votes.is_live(&7u64)); - assert!(votes.is_live(&10u64)); - } - - struct TestContext; - impl ValidatorContext for TestContext { - fn broadcast_topic(&mut self, _topic: B::Hash, _force: bool) { - todo!() - } - - fn broadcast_message(&mut self, _topic: B::Hash, _message: Vec, _force: bool) { - todo!() - } - - fn send_message(&mut self, _who: &sc_network::PeerId, _message: Vec) { - todo!() - } - - fn send_topic(&mut self, _who: &sc_network::PeerId, _topic: B::Hash, _force: bool) { - todo!() - } - } - - fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { - let store: SyncCryptoStorePtr = std::sync::Arc::new(LocalKeystore::in_memory()); - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&who.to_seed())).unwrap(); - let beefy_keystore: BeefyKeystore = Some(store).into(); - - beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() - } - - fn dummy_vote(block_number: u64) -> VoteMessage { - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, MmrRootHash::default().encode()); - let commitment = Commitment { payload, block_number, validator_set_id: 0 }; - let signature = sign_commitment(&Keyring::Alice, &commitment); - - VoteMessage { commitment, id: Keyring::Alice.public(), signature } - } - - #[test] - fn should_avoid_verifying_signatures_twice() { - let gv = GossipValidator::::new(); - let sender = sc_network::PeerId::random(); - let mut context = TestContext; - - let vote = dummy_vote(3); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - // first time the cache should be populated - let res = gv.validate(&mut context, &sender, &vote.encode()); - - assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); - assert_eq!( - gv.known_votes.read().live.get(&vote.commitment.block_number).map(|x| x.len()), - Some(1) - ); - - // second time we should hit the cache - let res = gv.validate(&mut context, &sender, &vote.encode()); - - assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); - - // next we should quickly reject if the round is not live - gv.conclude_round(7_u64); - - assert!(!gv.known_votes.read().is_live(&vote.commitment.block_number)); - - let res = gv.validate(&mut context, &sender, &vote.encode()); - - assert!(matches!(res, ValidationResult::Discard)); - } - - #[test] - fn messages_allowed_and_expired() { - let gv = GossipValidator::::new(); - let sender = sc_network::PeerId::random(); - let topic = Default::default(); - let intent = MessageIntent::Broadcast; - - // note round 2 and 3, then conclude 2 - gv.note_round(2u64); - gv.note_round(3u64); - gv.conclude_round(2u64); - let mut allowed = gv.message_allowed(); - let mut expired = gv.message_expired(); - - // check bad vote format - assert!(!allowed(&sender, intent, &topic, &mut [0u8; 16])); - assert!(expired(topic, &mut [0u8; 16])); - - // inactive round 1 -> expired - let vote = dummy_vote(1); - let mut encoded_vote = vote.encode(); - assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(expired(topic, &mut encoded_vote)); - - // active round 2 -> !expired - concluded but still gossiped - let vote = dummy_vote(2); - let mut encoded_vote = vote.encode(); - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(!expired(topic, &mut encoded_vote)); - - // in progress round 3 -> !expired - let vote = dummy_vote(3); - let mut encoded_vote = vote.encode(); - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(!expired(topic, &mut encoded_vote)); - - // unseen round 4 -> !expired - let vote = dummy_vote(3); - let mut encoded_vote = vote.encode(); - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(!expired(topic, &mut encoded_vote)); - } - - #[test] - fn messages_rebroadcast() { - let gv = GossipValidator::::new(); - let sender = sc_network::PeerId::random(); - let topic = Default::default(); - - let vote = dummy_vote(1); - let mut encoded_vote = vote.encode(); - - // re-broadcasting only allowed at `REBROADCAST_AFTER` intervals - let intent = MessageIntent::PeriodicRebroadcast; - let mut allowed = gv.message_allowed(); - - // rebroadcast not allowed so soon after GossipValidator creation - assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); - - // hack the inner deadline to be `now` - *gv.next_rebroadcast.lock() = Instant::now(); - - // still not allowed on old `allowed` closure result - assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); - - // renew closure result - let mut allowed = gv.message_allowed(); - // rebroadcast should be allowed now - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - } -} diff --git a/client/beefy/src/import.rs b/client/beefy/src/import.rs deleted file mode 100644 index 129484199..000000000 --- a/client/beefy/src/import.rs +++ /dev/null @@ -1,201 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use beefy_primitives::{BeefyApi, BEEFY_ENGINE_ID}; -use codec::Encode; -use log::error; -use std::{collections::HashMap, sync::Arc}; - -use sp_api::{ProvideRuntimeApi, TransactionFor}; -use sp_blockchain::{well_known_cache_keys, HeaderBackend}; -use sp_consensus::Error as ConsensusError; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, - EncodedJustification, -}; - -use sc_client_api::backend::Backend; -use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; - -use crate::{ - justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, - notification::BeefyVersionedFinalityProofSender, -}; - -/// A block-import handler for BEEFY. -/// -/// This scans each imported block for BEEFY justifications and verifies them. -/// Wraps a `inner: BlockImport` and ultimately defers to it. -/// -/// When using BEEFY, the block import worker should be using this block import object. -pub struct BeefyBlockImport { - backend: Arc, - runtime: Arc, - inner: I, - justification_sender: BeefyVersionedFinalityProofSender, -} - -impl Clone for BeefyBlockImport { - fn clone(&self) -> Self { - BeefyBlockImport { - backend: self.backend.clone(), - runtime: self.runtime.clone(), - inner: self.inner.clone(), - justification_sender: self.justification_sender.clone(), - } - } -} - -impl BeefyBlockImport { - /// Create a new BeefyBlockImport. - pub fn new( - backend: Arc, - runtime: Arc, - inner: I, - justification_sender: BeefyVersionedFinalityProofSender, - ) -> BeefyBlockImport { - BeefyBlockImport { backend, runtime, inner, justification_sender } - } -} - -impl BeefyBlockImport -where - Block: BlockT, - BE: Backend, - Runtime: ProvideRuntimeApi, - Runtime::Api: BeefyApi + Send + Sync, -{ - fn decode_and_verify( - &self, - encoded: &EncodedJustification, - number: NumberFor, - hash: ::Hash, - ) -> Result, ConsensusError> { - let block_id = BlockId::hash(hash); - let validator_set = self - .runtime - .runtime_api() - .validator_set(&block_id) - .map_err(|e| ConsensusError::ClientImport(e.to_string()))? - .ok_or_else(|| ConsensusError::ClientImport("Unknown validator set".to_string()))?; - - decode_and_verify_finality_proof::(&encoded[..], number, &validator_set) - } - - /// Import BEEFY justification: Send it to worker for processing and also append it to backend. - /// - /// This function assumes: - /// - `justification` is verified and valid, - /// - the block referred by `justification` has been imported _and_ finalized. - fn import_beefy_justification_unchecked( - &self, - number: NumberFor, - justification: BeefyVersionedFinalityProof, - ) { - // Append the justification to the block in the backend. - if let Err(e) = self.backend.append_justification( - BlockId::Number(number), - (BEEFY_ENGINE_ID, justification.encode()), - ) { - error!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, justification); - } - // Send the justification to the BEEFY voter for processing. - self.justification_sender - .notify(|| Ok::<_, ()>(justification)) - .expect("forwards closure result; the closure always returns Ok; qed."); - } -} - -#[async_trait::async_trait] -impl BlockImport for BeefyBlockImport -where - Block: BlockT, - BE: Backend, - I: BlockImport< - Block, - Error = ConsensusError, - Transaction = sp_api::TransactionFor, - > + Send - + Sync, - Runtime: ProvideRuntimeApi + Send + Sync, - Runtime::Api: BeefyApi, -{ - type Error = ConsensusError; - type Transaction = TransactionFor; - - async fn import_block( - &mut self, - mut block: BlockImportParams, - new_cache: HashMap>, - ) -> Result { - let hash = block.post_hash(); - let number = *block.header.number(); - - let beefy_proof = block - .justifications - .as_mut() - .and_then(|just| { - let decoded = just - .get(BEEFY_ENGINE_ID) - .map(|encoded| self.decode_and_verify(encoded, number, hash)); - // Remove BEEFY justification from the list before giving to `inner`; - // we will append it to backend ourselves at the end if all goes well. - just.remove(BEEFY_ENGINE_ID); - decoded - }) - .transpose() - .unwrap_or(None); - - // Run inner block import. - let inner_import_result = self.inner.import_block(block, new_cache).await?; - - match (beefy_proof, &inner_import_result) { - (Some(proof), ImportResult::Imported(_)) => { - let status = self.backend.blockchain().info(); - if number <= status.finalized_number && - Some(hash) == - self.backend - .blockchain() - .hash(number) - .map_err(|e| ConsensusError::ClientImport(e.to_string()))? - { - // The proof is valid and the block is imported and final, we can import. - self.import_beefy_justification_unchecked(number, proof); - } else { - error!( - target: "beefy", - "🥩 Cannot import justification: {:?} for, not yet final, block number {:?}", - proof, - number, - ); - } - }, - _ => (), - } - - Ok(inner_import_result) - } - - async fn check_block( - &mut self, - block: BlockCheckParams, - ) -> Result { - self.inner.check_block(block).await - } -} diff --git a/client/beefy/src/justification.rs b/client/beefy/src/justification.rs deleted file mode 100644 index d9be18593..000000000 --- a/client/beefy/src/justification.rs +++ /dev/null @@ -1,188 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use crate::keystore::BeefyKeystore; -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - ValidatorSet, VersionedFinalityProof, -}; -use codec::{Decode, Encode}; -use sp_consensus::Error as ConsensusError; -use sp_runtime::traits::{Block as BlockT, NumberFor}; - -/// A finality proof with matching BEEFY authorities' signatures. -pub type BeefyVersionedFinalityProof = - beefy_primitives::VersionedFinalityProof, Signature>; - -/// Decode and verify a Beefy FinalityProof. -pub(crate) fn decode_and_verify_finality_proof( - encoded: &[u8], - target_number: NumberFor, - validator_set: &ValidatorSet, -) -> Result, ConsensusError> { - let proof = >::decode(&mut &*encoded) - .map_err(|_| ConsensusError::InvalidJustification)?; - verify_with_validator_set::(target_number, validator_set, &proof).map(|_| proof) -} - -/// Verify the Beefy finality proof against the validator set at the block it was generated. -fn verify_with_validator_set( - target_number: NumberFor, - validator_set: &ValidatorSet, - proof: &BeefyVersionedFinalityProof, -) -> Result<(), ConsensusError> { - match proof { - VersionedFinalityProof::V1(signed_commitment) => { - if signed_commitment.signatures.len() != validator_set.len() || - signed_commitment.commitment.validator_set_id != validator_set.id() || - signed_commitment.commitment.block_number != target_number - { - return Err(ConsensusError::InvalidJustification) - } - - // Arrangement of signatures in the commitment should be in the same order - // as validators for that set. - let message = signed_commitment.commitment.encode(); - let valid_signatures = validator_set - .validators() - .into_iter() - .zip(signed_commitment.signatures.iter()) - .filter(|(id, signature)| { - signature - .as_ref() - .map(|sig| BeefyKeystore::verify(id, sig, &message[..])) - .unwrap_or(false) - }) - .count(); - if valid_signatures >= crate::round::threshold(validator_set.len()) { - Ok(()) - } else { - Err(ConsensusError::InvalidJustification) - } - }, - } -} - -#[cfg(test)] -pub(crate) mod tests { - use beefy_primitives::{ - known_payload_ids, Commitment, Payload, SignedCommitment, VersionedFinalityProof, - }; - use substrate_test_runtime_client::runtime::Block; - - use super::*; - use crate::{keystore::tests::Keyring, tests::make_beefy_ids}; - - pub(crate) fn new_finality_proof( - block_num: NumberFor, - validator_set: &ValidatorSet, - keys: &[Keyring], - ) -> BeefyVersionedFinalityProof { - let commitment = Commitment { - payload: Payload::new(known_payload_ids::MMR_ROOT_ID, vec![]), - block_number: block_num, - validator_set_id: validator_set.id(), - }; - let message = commitment.encode(); - let signatures = keys.iter().map(|key| Some(key.sign(&message))).collect(); - VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }) - } - - #[test] - fn should_verify_with_validator_set() { - let keys = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - - // build valid justification - let block_num = 42; - let proof = new_finality_proof(block_num, &validator_set, keys); - - let good_proof = proof.clone().into(); - // should verify successfully - verify_with_validator_set::(block_num, &validator_set, &good_proof).unwrap(); - - // wrong block number -> should fail verification - let good_proof = proof.clone().into(); - match verify_with_validator_set::(block_num + 1, &validator_set, &good_proof) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), - }; - - // wrong validator set id -> should fail verification - let good_proof = proof.clone().into(); - let other = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - match verify_with_validator_set::(block_num, &other, &good_proof) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), - }; - - // wrong signatures length -> should fail verification - let mut bad_proof = proof.clone(); - // change length of signatures - let bad_signed_commitment = match bad_proof { - VersionedFinalityProof::V1(ref mut sc) => sc, - }; - bad_signed_commitment.signatures.pop().flatten().unwrap(); - match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), - }; - - // not enough signatures -> should fail verification - let mut bad_proof = proof.clone(); - let bad_signed_commitment = match bad_proof { - VersionedFinalityProof::V1(ref mut sc) => sc, - }; - // remove a signature (but same length) - *bad_signed_commitment.signatures.first_mut().unwrap() = None; - match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), - }; - - // not enough _correct_ signatures -> should fail verification - let mut bad_proof = proof.clone(); - let bad_signed_commitment = match bad_proof { - VersionedFinalityProof::V1(ref mut sc) => sc, - }; - // change a signature to a different key - *bad_signed_commitment.signatures.first_mut().unwrap() = - Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); - match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), - }; - } - - #[test] - fn should_decode_and_verify_finality_proof() { - let keys = &[Keyring::Alice, Keyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let block_num = 1; - - // build valid justification - let proof = new_finality_proof(block_num, &validator_set, keys); - let versioned_proof: BeefyVersionedFinalityProof = proof.into(); - let encoded = versioned_proof.encode(); - - // should successfully decode and verify - let verified = - decode_and_verify_finality_proof::(&encoded, block_num, &validator_set).unwrap(); - assert_eq!(verified, versioned_proof); - } -} diff --git a/client/beefy/src/keystore.rs b/client/beefy/src/keystore.rs deleted file mode 100644 index b0259a420..000000000 --- a/client/beefy/src/keystore.rs +++ /dev/null @@ -1,378 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use sp_application_crypto::RuntimeAppPublic; -use sp_core::keccak_256; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; - -use log::warn; - -use beefy_primitives::{ - crypto::{Public, Signature}, - KEY_TYPE, -}; - -use crate::error; - -/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a -/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize -/// common cryptographic functionality. -pub(crate) struct BeefyKeystore(Option); - -impl BeefyKeystore { - /// Check if the keystore contains a private key for one of the public keys - /// contained in `keys`. A public key with a matching private key is known - /// as a local authority id. - /// - /// Return the public key for which we also do have a private key. If no - /// matching private key is found, `None` will be returned. - pub fn authority_id(&self, keys: &[Public]) -> Option { - let store = self.0.clone()?; - - // we do check for multiple private keys as a key store sanity check. - let public: Vec = keys - .iter() - .filter(|k| SyncCryptoStore::has_keys(&*store, &[(k.to_raw_vec(), KEY_TYPE)])) - .cloned() - .collect(); - - if public.len() > 1 { - warn!(target: "beefy", "🥩 Multiple private keys found for: {:?} ({})", public, public.len()); - } - - public.get(0).cloned() - } - - /// Sign `message` with the `public` key. - /// - /// Note that `message` usually will be pre-hashed before being signed. - /// - /// Return the message signature or an error in case of failure. - pub fn sign(&self, public: &Public, message: &[u8]) -> Result { - let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - - let msg = keccak_256(message); - let public = public.as_ref(); - - let sig = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, public, &msg) - .map_err(|e| error::Error::Keystore(e.to_string()))? - .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; - - // check that `sig` has the expected result type - let sig = sig.clone().try_into().map_err(|_| { - error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)) - })?; - - Ok(sig) - } - - /// Returns a vector of [`beefy_primitives::crypto::Public`] keys which are currently supported - /// (i.e. found in the keystore). - pub fn public_keys(&self) -> Result, error::Error> { - let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - - let pk: Vec = SyncCryptoStore::ecdsa_public_keys(&*store, KEY_TYPE) - .drain(..) - .map(Public::from) - .collect(); - - Ok(pk) - } - - /// Use the `public` key to verify that `sig` is a valid signature for `message`. - /// - /// Return `true` if the signature is authentic, `false` otherwise. - pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { - let msg = keccak_256(message); - let sig = sig.as_ref(); - let public = public.as_ref(); - - sp_core::ecdsa::Pair::verify_prehashed(sig, &msg, public) - } -} - -impl From> for BeefyKeystore { - fn from(store: Option) -> BeefyKeystore { - BeefyKeystore(store) - } -} - -#[cfg(test)] -pub mod tests { - use std::sync::Arc; - - use sc_keystore::LocalKeystore; - use sp_core::{ecdsa, keccak_256, Pair}; - use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; - - use beefy_primitives::{crypto, KEY_TYPE}; - - use super::BeefyKeystore; - use crate::error::Error; - - /// Set of test accounts using [`beefy_primitives::crypto`] types. - #[allow(missing_docs)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumIter)] - pub(crate) enum Keyring { - Alice, - Bob, - Charlie, - Dave, - Eve, - Ferdie, - One, - Two, - } - - impl Keyring { - /// Sign `msg`. - pub fn sign(self, msg: &[u8]) -> crypto::Signature { - let msg = keccak_256(msg); - ecdsa::Pair::from(self).sign_prehashed(&msg).into() - } - - /// Return key pair. - pub fn pair(self) -> crypto::Pair { - ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() - } - - /// Return public key. - pub fn public(self) -> crypto::Public { - self.pair().public() - } - - /// Return seed string. - pub fn to_seed(self) -> String { - format!("//{}", self) - } - } - - impl From for crypto::Pair { - fn from(k: Keyring) -> Self { - k.pair() - } - } - - impl From for ecdsa::Pair { - fn from(k: Keyring) -> Self { - k.pair().into() - } - } - - fn keystore() -> SyncCryptoStorePtr { - Arc::new(LocalKeystore::in_memory()) - } - - #[test] - fn verify_should_work() { - let msg = keccak_256(b"I am Alice!"); - let sig = Keyring::Alice.sign(b"I am Alice!"); - - assert!(ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Alice.public().into(), - )); - - // different public key -> fail - assert!(!ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Bob.public().into(), - )); - - let msg = keccak_256(b"I am not Alice!"); - - // different msg -> fail - assert!( - !ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),) - ); - } - - #[test] - fn pair_works() { - let want = crypto::Pair::from_string("//Alice", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Alice.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//Bob", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Bob.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//Charlie", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Charlie.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//Dave", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Dave.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//Eve", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Eve.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//Ferdie", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Ferdie.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//One", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::One.pair().to_raw_vec(); - assert_eq!(want, got); - - let want = crypto::Pair::from_string("//Two", None).expect("Pair failed").to_raw_vec(); - let got = Keyring::Two.pair().to_raw_vec(); - assert_eq!(want, got); - } - - #[test] - fn authority_id_works() { - let store = keystore(); - - let alice: crypto::Public = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); - - let bob = Keyring::Bob.public(); - let charlie = Keyring::Charlie.public(); - - let store: BeefyKeystore = Some(store).into(); - - let mut keys = vec![bob, charlie]; - - let id = store.authority_id(keys.as_slice()); - assert!(id.is_none()); - - keys.push(alice.clone()); - - let id = store.authority_id(keys.as_slice()).unwrap(); - assert_eq!(id, alice); - } - - #[test] - fn sign_works() { - let store = keystore(); - - let alice: crypto::Public = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); - - let store: BeefyKeystore = Some(store).into(); - - let msg = b"are you involved or commited?"; - - let sig1 = store.sign(&alice, msg).unwrap(); - let sig2 = Keyring::Alice.sign(msg); - - assert_eq!(sig1, sig2); - } - - #[test] - fn sign_error() { - let store = keystore(); - - let _ = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Bob.to_seed())) - .ok() - .unwrap(); - - let store: BeefyKeystore = Some(store).into(); - - let alice = Keyring::Alice.public(); - - let msg = b"are you involved or commited?"; - let sig = store.sign(&alice, msg).err().unwrap(); - let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string()); - - assert_eq!(sig, err); - } - - #[test] - fn sign_no_keystore() { - let store: BeefyKeystore = None.into(); - - let alice = Keyring::Alice.public(); - let msg = b"are you involved or commited"; - - let sig = store.sign(&alice, msg).err().unwrap(); - let err = Error::Keystore("no Keystore".to_string()); - assert_eq!(sig, err); - } - - #[test] - fn verify_works() { - let store = keystore(); - - let alice: crypto::Public = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); - - let store: BeefyKeystore = Some(store).into(); - - // `msg` and `sig` match - let msg = b"are you involved or commited?"; - let sig = store.sign(&alice, msg).unwrap(); - assert!(BeefyKeystore::verify(&alice, &sig, msg)); - - // `msg and `sig` don't match - let msg = b"you are just involved"; - assert!(!BeefyKeystore::verify(&alice, &sig, msg)); - } - - // Note that we use keys with and without a seed for this test. - #[test] - fn public_keys_works() { - const TEST_TYPE: sp_application_crypto::KeyTypeId = - sp_application_crypto::KeyTypeId(*b"test"); - - let store = keystore(); - - let add_key = |key_type, seed: Option<&str>| { - SyncCryptoStore::ecdsa_generate_new(&*store, key_type, seed).unwrap() - }; - - // test keys - let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); - let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str())); - - let _ = add_key(TEST_TYPE, None); - let _ = add_key(TEST_TYPE, None); - - // BEEFY keys - let _ = add_key(KEY_TYPE, Some(Keyring::Dave.to_seed().as_str())); - let _ = add_key(KEY_TYPE, Some(Keyring::Eve.to_seed().as_str())); - - let key1: crypto::Public = add_key(KEY_TYPE, None).into(); - let key2: crypto::Public = add_key(KEY_TYPE, None).into(); - - let store: BeefyKeystore = Some(store).into(); - - let keys = store.public_keys().ok().unwrap(); - - assert!(keys.len() == 4); - assert!(keys.contains(&Keyring::Dave.public())); - assert!(keys.contains(&Keyring::Eve.public())); - assert!(keys.contains(&key1)); - assert!(keys.contains(&key2)); - } -} diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs deleted file mode 100644 index bdf44e056..000000000 --- a/client/beefy/src/lib.rs +++ /dev/null @@ -1,273 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use beefy_primitives::{BeefyApi, MmrRootHash}; -use prometheus::Registry; -use sc_client_api::{Backend, BlockchainEvents, Finalizer}; -use sc_consensus::BlockImport; -use sc_network_gossip::Network as GossipNetwork; -use sp_api::ProvideRuntimeApi; -use sp_blockchain::HeaderBackend; -use sp_consensus::{Error as ConsensusError, SyncOracle}; -use sp_keystore::SyncCryptoStorePtr; -use sp_mmr_primitives::MmrApi; -use sp_runtime::traits::Block; -use std::sync::Arc; - -mod error; -mod gossip; -mod keystore; -mod metrics; -mod round; -mod worker; - -pub mod import; -pub mod justification; -pub mod notification; - -#[cfg(test)] -mod tests; - -use crate::{ - import::BeefyBlockImport, - notification::{ - BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender, - BeefyVersionedFinalityProofStream, - }, -}; - -pub use beefy_protocol_name::standard_name as protocol_standard_name; - -pub(crate) mod beefy_protocol_name { - use sc_chain_spec::ChainSpec; - - const NAME: &str = "/beefy/1"; - /// Old names for the notifications protocol, used for backward compatibility. - pub(crate) const LEGACY_NAMES: [&str; 1] = ["/paritytech/beefy/1"]; - - /// Name of the notifications protocol used by BEEFY. - /// - /// Must be registered towards the networking in order for BEEFY to properly function. - pub fn standard_name>( - genesis_hash: &Hash, - chain_spec: &Box, - ) -> std::borrow::Cow<'static, str> { - let chain_prefix = match chain_spec.fork_id() { - Some(fork_id) => format!("/{}/{}", hex::encode(genesis_hash), fork_id), - None => format!("/{}", hex::encode(genesis_hash)), - }; - format!("{}{}", chain_prefix, NAME).into() - } -} - -/// Returns the configuration value to put in -/// [`sc_network::config::NetworkConfiguration::extra_sets`]. -/// For standard protocol name see [`beefy_protocol_name::standard_name`]. -pub fn beefy_peers_set_config( - protocol_name: std::borrow::Cow<'static, str>, -) -> sc_network::config::NonDefaultSetConfig { - let mut cfg = sc_network::config::NonDefaultSetConfig::new(protocol_name, 1024 * 1024); - - cfg.allow_non_reserved(25, 25); - cfg.add_fallback_names(beefy_protocol_name::LEGACY_NAMES.iter().map(|&n| n.into()).collect()); - cfg -} - -/// A convenience BEEFY client trait that defines all the type bounds a BEEFY client -/// has to satisfy. Ideally that should actually be a trait alias. Unfortunately as -/// of today, Rust does not allow a type alias to be used as a trait bound. Tracking -/// issue is . -pub trait Client: - BlockchainEvents + HeaderBackend + Finalizer + Send + Sync -where - B: Block, - BE: Backend, -{ - // empty -} - -impl Client for T -where - B: Block, - BE: Backend, - T: BlockchainEvents - + HeaderBackend - + Finalizer - + ProvideRuntimeApi - + Send - + Sync, -{ - // empty -} - -/// Links between the block importer, the background voter and the RPC layer, -/// to be used by the voter. -#[derive(Clone)] -pub struct BeefyVoterLinks { - // BlockImport -> Voter links - /// Stream of BEEFY signed commitments from block import to voter. - pub from_block_import_justif_stream: BeefyVersionedFinalityProofStream, - - // Voter -> RPC links - /// Sends BEEFY signed commitments from voter to RPC. - pub to_rpc_justif_sender: BeefyVersionedFinalityProofSender, - /// Sends BEEFY best block hashes from voter to RPC. - pub to_rpc_best_block_sender: BeefyBestBlockSender, -} - -/// Links used by the BEEFY RPC layer, from the BEEFY background voter. -#[derive(Clone)] -pub struct BeefyRPCLinks { - /// Stream of signed commitments coming from the voter. - pub from_voter_justif_stream: BeefyVersionedFinalityProofStream, - /// Stream of BEEFY best block hashes coming from the voter. - pub from_voter_best_beefy_stream: BeefyBestBlockStream, -} - -/// Make block importer and link half necessary to tie the background voter to it. -pub fn beefy_block_import_and_links( - wrapped_block_import: I, - backend: Arc, - runtime: Arc, -) -> (BeefyBlockImport, BeefyVoterLinks, BeefyRPCLinks) -where - B: Block, - BE: Backend, - I: BlockImport> - + Send - + Sync, - RuntimeApi: ProvideRuntimeApi + Send + Sync, - RuntimeApi::Api: BeefyApi, -{ - // Voter -> RPC links - let (to_rpc_justif_sender, from_voter_justif_stream) = - notification::BeefyVersionedFinalityProofStream::::channel(); - let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = - notification::BeefyBestBlockStream::::channel(); - - // BlockImport -> Voter links - let (to_voter_justif_sender, from_block_import_justif_stream) = - notification::BeefyVersionedFinalityProofStream::::channel(); - - // BlockImport - let import = - BeefyBlockImport::new(backend, runtime, wrapped_block_import, to_voter_justif_sender); - let voter_links = BeefyVoterLinks { - from_block_import_justif_stream, - to_rpc_justif_sender, - to_rpc_best_block_sender, - }; - let rpc_links = BeefyRPCLinks { from_voter_best_beefy_stream, from_voter_justif_stream }; - - (import, voter_links, rpc_links) -} - -/// BEEFY gadget initialization parameters. -pub struct BeefyParams -where - B: Block, - BE: Backend, - C: Client, - R: ProvideRuntimeApi, - R::Api: BeefyApi + MmrApi, - N: GossipNetwork + Clone + SyncOracle + Send + Sync + 'static, -{ - /// BEEFY client - pub client: Arc, - /// Client Backend - pub backend: Arc, - /// Runtime Api Provider - pub runtime: Arc, - /// Local key store - pub key_store: Option, - /// Gossip network - pub network: N, - /// Minimal delta between blocks, BEEFY should vote for - pub min_block_delta: u32, - /// Prometheus metric registry - pub prometheus_registry: Option, - /// Chain specific GRANDPA protocol name. See [`beefy_protocol_name::standard_name`]. - pub protocol_name: std::borrow::Cow<'static, str>, - /// Links between the block importer, the background voter and the RPC layer. - pub links: BeefyVoterLinks, -} - -/// Start the BEEFY gadget. -/// -/// This is a thin shim around running and awaiting a BEEFY worker. -pub async fn start_beefy_gadget(beefy_params: BeefyParams) -where - B: Block, - BE: Backend, - C: Client, - R: ProvideRuntimeApi, - R::Api: BeefyApi + MmrApi, - N: GossipNetwork + Clone + SyncOracle + Send + Sync + 'static, -{ - let BeefyParams { - client, - backend, - runtime, - key_store, - network, - min_block_delta, - prometheus_registry, - protocol_name, - links, - } = beefy_params; - - let sync_oracle = network.clone(); - let gossip_validator = Arc::new(gossip::GossipValidator::new()); - let gossip_engine = sc_network_gossip::GossipEngine::new( - network, - protocol_name, - gossip_validator.clone(), - None, - ); - - let metrics = - prometheus_registry.as_ref().map(metrics::Metrics::register).and_then( - |result| match result { - Ok(metrics) => { - log::debug!(target: "beefy", "🥩 Registered metrics"); - Some(metrics) - }, - Err(err) => { - log::debug!(target: "beefy", "🥩 Failed to register metrics: {:?}", err); - None - }, - }, - ); - - let worker_params = worker::WorkerParams { - client, - backend, - runtime, - sync_oracle, - key_store: key_store.into(), - gossip_engine, - gossip_validator, - links, - metrics, - min_block_delta, - }; - - let worker = worker::BeefyWorker::<_, _, _, _, _>::new(worker_params); - - worker.run().await -} diff --git a/client/beefy/src/metrics.rs b/client/beefy/src/metrics.rs deleted file mode 100644 index 71e34e24c..000000000 --- a/client/beefy/src/metrics.rs +++ /dev/null @@ -1,107 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! BEEFY Prometheus metrics definition - -use prometheus::{register, Counter, Gauge, PrometheusError, Registry, U64}; - -/// BEEFY metrics exposed through Prometheus -pub(crate) struct Metrics { - /// Current active validator set id - pub beefy_validator_set_id: Gauge, - /// Total number of votes sent by this node - pub beefy_votes_sent: Counter, - /// Most recent concluded voting round - pub beefy_round_concluded: Gauge, - /// Best block finalized by BEEFY - pub beefy_best_block: Gauge, - /// Next block BEEFY should vote on - pub beefy_should_vote_on: Gauge, - /// Number of sessions with lagging signed commitment on mandatory block - pub beefy_lagging_sessions: Counter, -} - -impl Metrics { - pub(crate) fn register(registry: &Registry) -> Result { - Ok(Self { - beefy_validator_set_id: register( - Gauge::new( - "substrate_beefy_validator_set_id", - "Current BEEFY active validator set id.", - )?, - registry, - )?, - beefy_votes_sent: register( - Counter::new("substrate_beefy_votes_sent", "Number of votes sent by this node")?, - registry, - )?, - beefy_round_concluded: register( - Gauge::new( - "substrate_beefy_round_concluded", - "Voting round, that has been concluded", - )?, - registry, - )?, - beefy_best_block: register( - Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?, - registry, - )?, - beefy_should_vote_on: register( - Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?, - registry, - )?, - beefy_lagging_sessions: register( - Counter::new( - "substrate_beefy_lagging_sessions", - "Number of sessions with lagging signed commitment on mandatory block", - )?, - registry, - )?, - }) - } -} - -// Note: we use the `format` macro to convert an expr into a `u64`. This will fail, -// if expr does not derive `Display`. -#[macro_export] -macro_rules! metric_set { - ($self:ident, $m:ident, $v:expr) => {{ - let val: u64 = format!("{}", $v).parse().unwrap(); - - if let Some(metrics) = $self.metrics.as_ref() { - metrics.$m.set(val); - } - }}; -} - -#[macro_export] -macro_rules! metric_inc { - ($self:ident, $m:ident) => {{ - if let Some(metrics) = $self.metrics.as_ref() { - metrics.$m.inc(); - } - }}; -} - -#[cfg(test)] -#[macro_export] -macro_rules! metric_get { - ($self:ident, $m:ident) => {{ - $self.metrics.as_ref().map(|metrics| metrics.$m.clone()) - }}; -} diff --git a/client/beefy/src/notification.rs b/client/beefy/src/notification.rs deleted file mode 100644 index c673115e4..000000000 --- a/client/beefy/src/notification.rs +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use sc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr}; -use sp_runtime::traits::Block as BlockT; - -use crate::justification::BeefyVersionedFinalityProof; - -/// The sending half of the notifications channel(s) used to send -/// notifications about best BEEFY block from the gadget side. -pub type BeefyBestBlockSender = NotificationSender<::Hash>; - -/// The receiving half of a notifications channel used to receive -/// notifications about best BEEFY blocks determined on the gadget side. -pub type BeefyBestBlockStream = - NotificationStream<::Hash, BeefyBestBlockTracingKey>; - -/// The sending half of the notifications channel(s) used to send notifications -/// about versioned finality proof generated at the end of a BEEFY round. -pub type BeefyVersionedFinalityProofSender = - NotificationSender>; - -/// The receiving half of a notifications channel used to receive notifications -/// about versioned finality proof generated at the end of a BEEFY round. -pub type BeefyVersionedFinalityProofStream = - NotificationStream, BeefyVersionedFinalityProofTracingKey>; - -/// Provides tracing key for BEEFY best block stream. -#[derive(Clone)] -pub struct BeefyBestBlockTracingKey; -impl TracingKeyStr for BeefyBestBlockTracingKey { - const TRACING_KEY: &'static str = "mpsc_beefy_best_block_notification_stream"; -} - -/// Provides tracing key for BEEFY versioned finality proof stream. -#[derive(Clone)] -pub struct BeefyVersionedFinalityProofTracingKey; -impl TracingKeyStr for BeefyVersionedFinalityProofTracingKey { - const TRACING_KEY: &'static str = "mpsc_beefy_versioned_finality_proof_notification_stream"; -} diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs deleted file mode 100644 index ebd85c8de..000000000 --- a/client/beefy/src/round.rs +++ /dev/null @@ -1,444 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::{ - collections::{BTreeMap, HashMap}, - hash::Hash, -}; - -use log::{debug, trace}; - -use beefy_primitives::{ - crypto::{Public, Signature}, - ValidatorSet, ValidatorSetId, -}; -use sp_runtime::traits::{Block, NumberFor}; - -/// Tracks for each round which validators have voted/signed and -/// whether the local `self` validator has voted/signed. -/// -/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). -#[derive(Default)] -struct RoundTracker { - self_vote: bool, - votes: HashMap, -} - -impl RoundTracker { - fn add_vote(&mut self, vote: (Public, Signature), self_vote: bool) -> bool { - if self.votes.contains_key(&vote.0) { - return false - } - - self.self_vote = self.self_vote || self_vote; - self.votes.insert(vote.0, vote.1); - true - } - - fn has_self_vote(&self) -> bool { - self.self_vote - } - - fn is_done(&self, threshold: usize) -> bool { - self.votes.len() >= threshold - } -} - -/// Minimum size of `authorities` subset that produced valid signatures for a block to finalize. -pub fn threshold(authorities: usize) -> usize { - let faulty = authorities.saturating_sub(1) / 3; - authorities - faulty -} - -/// Keeps track of all voting rounds (block numbers) within a session. -/// Only round numbers > `best_done` are of interest, all others are considered stale. -/// -/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). -pub(crate) struct Rounds { - rounds: BTreeMap<(Payload, NumberFor), RoundTracker>, - session_start: NumberFor, - validator_set: ValidatorSet, - mandatory_done: bool, - best_done: Option>, -} - -impl Rounds -where - P: Ord + Hash + Clone, - B: Block, -{ - pub(crate) fn new(session_start: NumberFor, validator_set: ValidatorSet) -> Self { - Rounds { - rounds: BTreeMap::new(), - session_start, - validator_set, - mandatory_done: false, - best_done: None, - } - } - - pub(crate) fn validator_set_id(&self) -> ValidatorSetId { - self.validator_set.id() - } - - pub(crate) fn validators(&self) -> &[Public] { - self.validator_set.validators() - } - - pub(crate) fn session_start(&self) -> NumberFor { - self.session_start - } - - pub(crate) fn mandatory_done(&self) -> bool { - self.mandatory_done - } - - pub(crate) fn should_self_vote(&self, round: &(P, NumberFor)) -> bool { - Some(round.1.clone()) > self.best_done && - self.rounds.get(round).map(|tracker| !tracker.has_self_vote()).unwrap_or(true) - } - - pub(crate) fn add_vote( - &mut self, - round: &(P, NumberFor), - vote: (Public, Signature), - self_vote: bool, - ) -> bool { - let num = round.1; - if num < self.session_start || Some(num) <= self.best_done { - debug!(target: "beefy", "🥩 received vote for old stale round {:?}, ignoring", num); - false - } else if !self.validators().iter().any(|id| vote.0 == *id) { - debug!( - target: "beefy", - "🥩 received vote {:?} from validator that is not in the validator set, ignoring", - vote - ); - false - } else { - self.rounds.entry(round.clone()).or_default().add_vote(vote, self_vote) - } - } - - pub(crate) fn try_conclude( - &mut self, - round: &(P, NumberFor), - ) -> Option>> { - let done = self - .rounds - .get(round) - .map(|tracker| tracker.is_done(threshold(self.validator_set.len()))) - .unwrap_or(false); - trace!(target: "beefy", "🥩 Round #{} done: {}", round.1, done); - - if done { - // remove this and older (now stale) rounds - let signatures = self.rounds.remove(round)?.votes; - self.rounds.retain(|&(_, number), _| number > round.1); - self.mandatory_done = self.mandatory_done || round.1 == self.session_start; - self.best_done = self.best_done.max(Some(round.1)); - debug!(target: "beefy", "🥩 Concluded round #{}", round.1); - - Some( - self.validators() - .iter() - .map(|authority_id| signatures.get(authority_id).cloned()) - .collect(), - ) - } else { - None - } - } - - #[cfg(test)] - pub(crate) fn test_set_mandatory_done(&mut self, done: bool) { - self.mandatory_done = done; - } -} - -#[cfg(test)] -mod tests { - use sc_network_test::Block; - use sp_core::H256; - - use beefy_primitives::{crypto::Public, ValidatorSet}; - - use super::{threshold, RoundTracker, Rounds}; - use crate::keystore::tests::Keyring; - - #[test] - fn round_tracker() { - let mut rt = RoundTracker::default(); - let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); - let threshold = 2; - - // self vote not added yet - assert!(!rt.has_self_vote()); - - // adding new vote allowed - assert!(rt.add_vote(bob_vote.clone(), false)); - // adding existing vote not allowed - assert!(!rt.add_vote(bob_vote, false)); - - // self vote still not added yet - assert!(!rt.has_self_vote()); - - // vote is not done - assert!(!rt.is_done(threshold)); - - let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); - // adding new vote (self vote this time) allowed - assert!(rt.add_vote(alice_vote, true)); - - // self vote registered - assert!(rt.has_self_vote()); - // vote is now done - assert!(rt.is_done(threshold)); - } - - #[test] - fn vote_threshold() { - assert_eq!(threshold(1), 1); - assert_eq!(threshold(2), 2); - assert_eq!(threshold(3), 3); - assert_eq!(threshold(4), 3); - assert_eq!(threshold(100), 67); - assert_eq!(threshold(300), 201); - } - - #[test] - fn new_rounds() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - 42, - ) - .unwrap(); - - let session_start = 1u64.into(); - let rounds = Rounds::::new(session_start, validators); - - assert_eq!(42, rounds.validator_set_id()); - assert_eq!(1, rounds.session_start()); - assert_eq!( - &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - rounds.validators() - ); - } - - #[test] - fn add_and_conclude_votes() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![ - Keyring::Alice.public(), - Keyring::Bob.public(), - Keyring::Charlie.public(), - Keyring::Eve.public(), - ], - Default::default(), - ) - .unwrap(); - let round = (H256::from_low_u64_le(1), 1); - - let session_start = 1u64.into(); - let mut rounds = Rounds::::new(session_start, validators); - - // no self vote yet, should self vote - assert!(rounds.should_self_vote(&round)); - - // add 1st good vote - assert!(rounds.add_vote( - &round, - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - true - )); - // round not concluded - assert!(rounds.try_conclude(&round).is_none()); - // self vote already present, should not self vote - assert!(!rounds.should_self_vote(&round)); - - // double voting not allowed - assert!(!rounds.add_vote( - &round, - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - true - )); - - // invalid vote (Dave is not a validator) - assert!(!rounds.add_vote( - &round, - (Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")), - false - )); - assert!(rounds.try_conclude(&round).is_none()); - - // add 2nd good vote - assert!(rounds.add_vote( - &round, - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), - false - )); - // round not concluded - assert!(rounds.try_conclude(&round).is_none()); - - // add 3rd good vote - assert!(rounds.add_vote( - &round, - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), - false - )); - // round concluded - assert!(rounds.try_conclude(&round).is_some()); - - // Eve is a validator, but round was concluded, adding vote disallowed - assert!(!rounds.add_vote( - &round, - (Keyring::Eve.public(), Keyring::Eve.sign(b"I am committed")), - false - )); - } - - #[test] - fn old_rounds_not_accepted() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - 42, - ) - .unwrap(); - let alice = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); - - let session_start = 10u64.into(); - let mut rounds = Rounds::::new(session_start, validators); - - let mut vote = (H256::from_low_u64_le(1), 9); - // add vote for previous session, should fail - assert!(!rounds.add_vote(&vote, alice.clone(), true)); - // no votes present - assert!(rounds.rounds.is_empty()); - - // simulate 11 was concluded - rounds.best_done = Some(11); - // add votes for current session, but already concluded rounds, should fail - vote.1 = 10; - assert!(!rounds.add_vote(&vote, alice.clone(), true)); - vote.1 = 11; - assert!(!rounds.add_vote(&vote, alice.clone(), true)); - // no votes present - assert!(rounds.rounds.is_empty()); - - // add good vote - vote.1 = 12; - assert!(rounds.add_vote(&vote, alice, true)); - // good vote present - assert_eq!(rounds.rounds.len(), 1); - } - - #[test] - fn multiple_rounds() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![ - Keyring::Alice.public(), - Keyring::Bob.public(), - Keyring::Charlie.public(), - Keyring::Dave.public(), - ], - Default::default(), - ) - .unwrap(); - - let session_start = 1u64.into(); - let mut rounds = Rounds::::new(session_start, validators); - - // round 1 - assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - true, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), - false, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), - false, - )); - - // round 2 - assert!(rounds.add_vote( - &(H256::from_low_u64_le(2), 2), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")), - true, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(2), 2), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")), - false, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(2), 2), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am again committed")), - false, - )); - - // round 3 - assert!(rounds.add_vote( - &(H256::from_low_u64_le(3), 3), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")), - true, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(3), 3), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")), - false, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(3), 3), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am still committed")), - false, - )); - assert_eq!(3, rounds.rounds.len()); - - // conclude unknown round - assert!(rounds.try_conclude(&(H256::from_low_u64_le(5), 5)).is_none()); - assert_eq!(3, rounds.rounds.len()); - - // conclude round 2 - let signatures = rounds.try_conclude(&(H256::from_low_u64_le(2), 2)).unwrap(); - assert_eq!(1, rounds.rounds.len()); - - assert_eq!( - signatures, - vec![ - Some(Keyring::Alice.sign(b"I am again committed")), - Some(Keyring::Bob.sign(b"I am again committed")), - Some(Keyring::Charlie.sign(b"I am again committed")), - None - ] - ); - } -} diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs deleted file mode 100644 index 134339009..000000000 --- a/client/beefy/src/tests.rs +++ /dev/null @@ -1,815 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Tests and test helpers for BEEFY. - -use futures::{future, stream::FuturesUnordered, Future, StreamExt}; -use parking_lot::Mutex; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, sync::Arc, task::Poll}; -use tokio::{runtime::Runtime, time::Duration}; - -use sc_chain_spec::{ChainSpec, GenericChainSpec}; -use sc_client_api::HeaderBackend; -use sc_consensus::{ - BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, - ImportedAux, -}; -use sc_keystore::LocalKeystore; -use sc_network_test::{ - Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, - TestNetFactory, -}; -use sc_utils::notification::NotificationReceiver; - -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, VersionedFinalityProof, BEEFY_ENGINE_ID, - KEY_TYPE as BeefyKeyType, -}; -use sp_mmr_primitives::{ - BatchProof, EncodableOpaqueLeaf, Error as MmrError, LeafIndex, MmrApi, Proof, -}; - -use sp_api::{ApiRef, ProvideRuntimeApi}; -use sp_consensus::BlockOrigin; -use sp_core::H256; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use sp_runtime::{ - codec::Encode, - generic::BlockId, - traits::{Header as HeaderT, NumberFor}, - BuildStorage, DigestItem, Justifications, Storage, -}; - -use substrate_test_runtime_client::{runtime::Header, ClientExt}; - -use crate::{ - beefy_block_import_and_links, beefy_protocol_name, justification::*, - keystore::tests::Keyring as BeefyKeyring, BeefyRPCLinks, BeefyVoterLinks, -}; - -pub(crate) const BEEFY_PROTOCOL_NAME: &'static str = "/beefy/1"; -const GOOD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0xbf); -const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42); - -type BeefyBlockImport = crate::BeefyBlockImport< - Block, - substrate_test_runtime_client::Backend, - two_validators::TestApi, - BlockImportAdapter>, ->; - -pub(crate) type BeefyValidatorSet = ValidatorSet; -pub(crate) type BeefyPeer = Peer; - -#[derive(Debug, Serialize, Deserialize)] -struct Genesis(std::collections::BTreeMap); -impl BuildStorage for Genesis { - fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { - storage - .top - .extend(self.0.iter().map(|(a, b)| (a.clone().into_bytes(), b.clone().into_bytes()))); - Ok(()) - } -} - -#[test] -fn beefy_protocol_name() { - let chain_spec = GenericChainSpec::::from_json_bytes( - &include_bytes!("../../chain-spec/res/chain_spec.json")[..], - ) - .unwrap() - .cloned_box(); - - // Create protocol name using random genesis hash. - let genesis_hash = H256::random(); - let expected = format!("/{}/beefy/1", hex::encode(genesis_hash)); - let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec); - assert_eq!(proto_name.to_string(), expected); - - // Create protocol name using hardcoded genesis hash. Verify exact representation. - let genesis_hash = [ - 50, 4, 60, 123, 58, 106, 216, 246, 194, 188, 139, 193, 33, 212, 202, 171, 9, 55, 123, 94, - 8, 43, 12, 251, 187, 57, 173, 19, 188, 74, 205, 147, - ]; - let expected = - "/32043c7b3a6ad8f6c2bc8bc121d4caab09377b5e082b0cfbbb39ad13bc4acd93/beefy/1".to_string(); - let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec); - assert_eq!(proto_name.to_string(), expected); -} - -#[derive(Default)] -pub(crate) struct PeerData { - pub(crate) beefy_rpc_links: Mutex>>, - pub(crate) beefy_voter_links: Mutex>>, -} - -#[derive(Default)] -pub(crate) struct BeefyTestNet { - peers: Vec, -} - -impl BeefyTestNet { - pub(crate) fn new(n_authority: usize, n_full: usize) -> Self { - let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority + n_full) }; - for _ in 0..n_authority { - net.add_authority_peer(); - } - for _ in 0..n_full { - net.add_full_peer(); - } - net - } - - pub(crate) fn add_authority_peer(&mut self) { - self.add_full_peer_with_config(FullPeerConfig { - notifications_protocols: vec![BEEFY_PROTOCOL_NAME.into()], - is_authority: true, - ..Default::default() - }) - } - - pub(crate) fn generate_blocks( - &mut self, - count: usize, - session_length: u64, - validator_set: &BeefyValidatorSet, - include_mmr_digest: bool, - ) { - self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; - - if include_mmr_digest { - let block_num = *block.header.number(); - let num_byte = block_num.to_le_bytes().into_iter().next().unwrap(); - let mmr_root = MmrRootHash::repeat_byte(num_byte); - add_mmr_digest(&mut block.header, mmr_root); - } - - if *block.header.number() % session_length == 0 { - add_auth_change_digest(&mut block.header, validator_set.clone()); - } - - block - }); - } -} - -impl TestNetFactory for BeefyTestNet { - type Verifier = PassThroughVerifier; - type BlockImport = BeefyBlockImport; - type PeerData = PeerData; - - fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier { - PassThroughVerifier::new(false) // use non-instant finality. - } - - fn make_block_import( - &self, - client: PeersClient, - ) -> ( - BlockImportAdapter, - Option>, - Self::PeerData, - ) { - let inner = BlockImportAdapter::new(client.clone()); - let (block_import, voter_links, rpc_links) = beefy_block_import_and_links( - inner, - client.as_backend(), - Arc::new(two_validators::TestApi {}), - ); - let peer_data = PeerData { - beefy_rpc_links: Mutex::new(Some(rpc_links)), - beefy_voter_links: Mutex::new(Some(voter_links)), - }; - (BlockImportAdapter::new(block_import), None, peer_data) - } - - fn peer(&mut self, i: usize) -> &mut BeefyPeer { - &mut self.peers[i] - } - - fn peers(&self) -> &Vec { - &self.peers - } - - fn mut_peers)>(&mut self, closure: F) { - closure(&mut self.peers); - } - - fn add_full_peer(&mut self) { - self.add_full_peer_with_config(FullPeerConfig { - notifications_protocols: vec![BEEFY_PROTOCOL_NAME.into()], - is_authority: false, - ..Default::default() - }) - } -} - -macro_rules! create_test_api { - ( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => { - pub(crate) mod $api_name { - use super::*; - - #[derive(Clone, Default)] - pub(crate) struct TestApi {} - - // compiler gets confused and warns us about unused inner - #[allow(dead_code)] - pub(crate) struct RuntimeApi { - inner: TestApi, - } - - impl ProvideRuntimeApi for TestApi { - type Api = RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { - RuntimeApi { inner: self.clone() }.into() - } - } - sp_api::mock_impl_runtime_apis! { - impl BeefyApi for RuntimeApi { - fn validator_set() -> Option { - BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0) - } - } - - impl MmrApi for RuntimeApi { - fn generate_proof(_leaf_index: LeafIndex) - -> Result<(EncodableOpaqueLeaf, Proof), MmrError> { - unimplemented!() - } - - fn verify_proof(_leaf: EncodableOpaqueLeaf, _proof: Proof) - -> Result<(), MmrError> { - unimplemented!() - } - - fn verify_proof_stateless( - _root: MmrRootHash, - _leaf: EncodableOpaqueLeaf, - _proof: Proof - ) -> Result<(), MmrError> { - unimplemented!() - } - - fn mmr_root() -> Result { - Ok($mmr_root) - } - - fn generate_batch_proof(_leaf_indices: Vec) -> Result<(Vec, BatchProof), MmrError> { - unimplemented!() - } - - fn verify_batch_proof(_leaves: Vec, _proof: BatchProof) -> Result<(), MmrError> { - unimplemented!() - } - - fn verify_batch_proof_stateless( - _root: MmrRootHash, - _leaves: Vec, - _proof: BatchProof - ) -> Result<(), MmrError> { - unimplemented!() - } - } - } - } - }; -} - -create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob); -create_test_api!( - four_validators, - mmr_root: GOOD_MMR_ROOT, - BeefyKeyring::Alice, - BeefyKeyring::Bob, - BeefyKeyring::Charlie, - BeefyKeyring::Dave -); -create_test_api!( - bad_four_validators, - mmr_root: BAD_MMR_ROOT, - BeefyKeyring::Alice, - BeefyKeyring::Bob, - BeefyKeyring::Charlie, - BeefyKeyring::Dave -); - -fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) { - header.digest_mut().push(DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::::MmrRoot(mmr_hash).encode(), - )); -} - -fn add_auth_change_digest(header: &mut Header, new_auth_set: BeefyValidatorSet) { - header.digest_mut().push(DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::::AuthoritiesChange(new_auth_set).encode(), - )); -} - -pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { - keys.iter().map(|key| key.clone().public().into()).collect() -} - -pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStorePtr { - let keystore = Arc::new(LocalKeystore::in_memory()); - SyncCryptoStore::ecdsa_generate_new(&*keystore, BeefyKeyType, Some(&authority.to_seed())) - .expect("Creates authority key"); - keystore -} - -// Spawns beefy voters. Returns a future to spawn on the runtime. -fn initialize_beefy( - net: &mut BeefyTestNet, - peers: Vec<(usize, &BeefyKeyring, Arc)>, - min_block_delta: u32, -) -> impl Future -where - API: ProvideRuntimeApi + Default + Sync + Send, - API::Api: BeefyApi + MmrApi, -{ - let voters = FuturesUnordered::new(); - - for (peer_id, key, api) in peers.into_iter() { - let peer = &net.peers[peer_id]; - - let keystore = create_beefy_keystore(*key); - - let (_, _, peer_data) = net.make_block_import(peer.client().clone()); - let PeerData { beefy_rpc_links, beefy_voter_links } = peer_data; - - let beefy_voter_links = beefy_voter_links.lock().take(); - *peer.data.beefy_rpc_links.lock() = beefy_rpc_links.lock().take(); - *peer.data.beefy_voter_links.lock() = beefy_voter_links.clone(); - - let beefy_params = crate::BeefyParams { - client: peer.client().as_client(), - backend: peer.client().as_backend(), - runtime: api.clone(), - key_store: Some(keystore), - network: peer.network_service().clone(), - links: beefy_voter_links.unwrap(), - min_block_delta, - prometheus_registry: None, - protocol_name: BEEFY_PROTOCOL_NAME.into(), - }; - let gadget = crate::start_beefy_gadget::<_, _, _, _, _>(beefy_params); - - fn assert_send(_: &T) {} - assert_send(&gadget); - voters.push(gadget); - } - - voters.for_each(|_| async move {}) -} - -fn block_until(future: impl Future + Unpin, net: &Arc>, runtime: &mut Runtime) { - let drive_to_completion = futures::future::poll_fn(|cx| { - net.lock().poll(cx); - Poll::<()>::Pending - }); - runtime.block_on(future::select(future, drive_to_completion)); -} - -fn run_for(duration: Duration, net: &Arc>, runtime: &mut Runtime) { - let sleep = runtime.spawn(async move { tokio::time::sleep(duration).await }); - block_until(sleep, net, runtime); -} - -pub(crate) fn get_beefy_streams( - net: &mut BeefyTestNet, - peers: &[BeefyKeyring], -) -> (Vec>, Vec>>) -{ - let mut best_block_streams = Vec::new(); - let mut versioned_finality_proof_streams = Vec::new(); - for peer_id in 0..peers.len() { - let beefy_rpc_links = net.peer(peer_id).data.beefy_rpc_links.lock().clone().unwrap(); - let BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream } = - beefy_rpc_links; - best_block_streams.push(from_voter_best_beefy_stream.subscribe()); - versioned_finality_proof_streams.push(from_voter_justif_stream.subscribe()); - } - (best_block_streams, versioned_finality_proof_streams) -} - -fn wait_for_best_beefy_blocks( - streams: Vec>, - net: &Arc>, - runtime: &mut Runtime, - expected_beefy_blocks: &[u64], -) { - let mut wait_for = Vec::new(); - let len = expected_beefy_blocks.len(); - streams.into_iter().enumerate().for_each(|(i, stream)| { - let mut expected = expected_beefy_blocks.iter(); - wait_for.push(Box::pin(stream.take(len).for_each(move |best_beefy_hash| { - let expected = expected.next(); - async move { - let block_id = BlockId::hash(best_beefy_hash); - let header = - net.lock().peer(i).client().as_client().expect_header(block_id).unwrap(); - let best_beefy = *header.number(); - - assert_eq!(expected, Some(best_beefy).as_ref()); - } - }))); - }); - let wait_for = futures::future::join_all(wait_for); - block_until(wait_for, net, runtime); -} - -fn wait_for_beefy_signed_commitments( - streams: Vec>>, - net: &Arc>, - runtime: &mut Runtime, - expected_commitment_block_nums: &[u64], -) { - let mut wait_for = Vec::new(); - let len = expected_commitment_block_nums.len(); - streams.into_iter().for_each(|stream| { - let mut expected = expected_commitment_block_nums.iter(); - wait_for.push(Box::pin(stream.take(len).for_each(move |versioned_finality_proof| { - let expected = expected.next(); - async move { - let signed_commitment = match versioned_finality_proof { - beefy_primitives::VersionedFinalityProof::V1(sc) => sc, - }; - let commitment_block_num = signed_commitment.commitment.block_number; - assert_eq!(expected, Some(commitment_block_num).as_ref()); - // TODO: also verify commitment payload, validator set id, and signatures. - } - }))); - }); - let wait_for = futures::future::join_all(wait_for); - block_until(wait_for, net, runtime); -} - -fn streams_empty_after_timeout( - streams: Vec>, - net: &Arc>, - runtime: &mut Runtime, - timeout: Option, -) where - T: std::fmt::Debug, - T: std::cmp::PartialEq, -{ - if let Some(timeout) = timeout { - run_for(timeout, net, runtime); - } - streams.into_iter().for_each(|mut stream| { - runtime.block_on(future::poll_fn(move |cx| { - assert_eq!(stream.poll_next_unpin(cx), Poll::Pending); - Poll::Ready(()) - })); - }); -} - -fn finalize_block_and_wait_for_beefy( - net: &Arc>, - peers: &[BeefyKeyring], - runtime: &mut Runtime, - finalize_targets: &[u64], - expected_beefy: &[u64], -) { - let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); - - for block in finalize_targets { - let finalize = BlockId::number(*block); - for i in 0..peers.len() { - net.lock().peer(i).client().as_client().finalize_block(finalize, None).unwrap(); - } - } - - if expected_beefy.is_empty() { - // run for quarter second then verify no new best beefy block available - let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, runtime, None); - } else { - // run until expected beefy blocks are received - wait_for_best_beefy_blocks(best_blocks, &net, runtime, expected_beefy); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, runtime, expected_beefy); - } -} - -#[test] -fn beefy_finalizing_blocks() { - sp_tracing::try_init_simple(); - - let mut runtime = Runtime::new().unwrap(); - let peers = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); - let session_len = 10; - let min_block_delta = 4; - - let mut net = BeefyTestNet::new(2, 0); - - let api = Arc::new(two_validators::TestApi {}); - let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); - - // push 42 blocks including `AuthorityChange` digests every 10 blocks. - net.generate_blocks(42, session_len, &validator_set, true); - net.block_until_sync(); - - let net = Arc::new(Mutex::new(net)); - - // Minimum BEEFY block delta is 4. - - // finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule. - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[5], &[1, 5]); - - // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[10]); - - // GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule) - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[18], &[14, 18]); - - // GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[20], &[20]); - - // GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4 - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[21], &[]); -} - -#[test] -fn lagging_validators() { - sp_tracing::try_init_simple(); - - let mut runtime = Runtime::new().unwrap(); - let peers = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); - let session_len = 30; - let min_block_delta = 1; - - let mut net = BeefyTestNet::new(2, 0); - let api = Arc::new(two_validators::TestApi {}); - let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); - - // push 62 blocks including `AuthorityChange` digests every 30 blocks. - net.generate_blocks(62, session_len, &validator_set, true); - net.block_until_sync(); - - let net = Arc::new(Mutex::new(net)); - - // finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from - // diff-power-of-two rule. - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[15], &[1, 9, 13, 14, 15]); - - // Alice finalizes #25, Bob lags behind - let finalize = BlockId::number(25); - let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); - net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); - // verify nothing gets finalized by BEEFY - let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); - - // Bob catches up and also finalizes #25 - let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); - net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); - // expected beefy finalizes block #17 from diff-power-of-two - wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[23, 24, 25]); - - // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[30, 32], &[30, 31, 32]); - - // Verify that session-boundary votes get buffered by client and only processed once - // session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session - // validator set). - - // Alice finalizes session-boundary mandatory block #60, Bob lags behind - let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); - let finalize = BlockId::number(60); - net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); - // verify nothing gets finalized by BEEFY - let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); - - // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) - let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); - net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); - // verify beefy skips intermediary votes, and successfully finalizes mandatory block #40 - wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[60]); -} - -#[test] -fn correct_beefy_payload() { - sp_tracing::try_init_simple(); - - let mut runtime = Runtime::new().unwrap(); - let peers = - &[BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; - let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); - let session_len = 20; - let min_block_delta = 2; - - let mut net = BeefyTestNet::new(4, 0); - - // Alice, Bob, Charlie will vote on good payloads - let good_api = Arc::new(four_validators::TestApi {}); - let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie] - .iter() - .enumerate() - .map(|(id, key)| (id, key, good_api.clone())) - .collect(); - runtime.spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); - - // Dave will vote on bad mmr roots - let bad_api = Arc::new(bad_four_validators::TestApi {}); - let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)]; - runtime.spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); - - // push 10 blocks - net.generate_blocks(12, session_len, &validator_set, false); - net.block_until_sync(); - - let net = Arc::new(Mutex::new(net)); - // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. - finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[1, 9]); - - let (best_blocks, versioned_finality_proof) = - get_beefy_streams(&mut net.lock(), &[BeefyKeyring::Alice]); - - // now 2 good validators and 1 bad one are voting - net.lock() - .peer(0) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); - net.lock() - .peer(1) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); - net.lock() - .peer(3) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); - - // verify consensus is _not_ reached - let timeout = Some(Duration::from_millis(250)); - streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); - - // 3rd good validator catches up and votes as well - let (best_blocks, versioned_finality_proof) = - get_beefy_streams(&mut net.lock(), &[BeefyKeyring::Alice]); - net.lock() - .peer(2) - .client() - .as_client() - .finalize_block(BlockId::number(11), None) - .unwrap(); - - // verify consensus is reached - wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); - wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[11]); -} - -#[test] -fn beefy_importing_blocks() { - use futures::{executor::block_on, future::poll_fn, task::Poll}; - use sc_block_builder::BlockBuilderProvider; - use sc_client_api::BlockBackend; - - sp_tracing::try_init_simple(); - - let mut net = BeefyTestNet::new(2, 0); - - let client = net.peer(0).client().clone(); - let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); - let PeerData { beefy_rpc_links: _, beefy_voter_links } = peer_data; - let justif_stream = beefy_voter_links.lock().take().unwrap().from_block_import_justif_stream; - - let params = |block: Block, justifications: Option| { - let mut import = BlockImportParams::new(BlockOrigin::File, block.header); - import.justifications = justifications; - import.body = Some(block.extrinsics); - import.finalized = true; - import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - import - }; - - let full_client = client.as_client(); - let parent_id = BlockId::Number(0); - let block_id = BlockId::Number(1); - let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); - let block = builder.build().unwrap().block; - - // Import without justifications. - let mut justif_recv = justif_stream.subscribe(); - assert_eq!( - block_on(block_import.import_block(params(block.clone(), None), HashMap::new())).unwrap(), - ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }), - ); - assert_eq!( - block_on(block_import.import_block(params(block, None), HashMap::new())).unwrap(), - ImportResult::AlreadyInChain - ); - // Verify no justifications present: - { - // none in backend, - assert!(full_client.justifications(&block_id).unwrap().is_none()); - // and none sent to BEEFY worker. - block_on(poll_fn(move |cx| { - assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); - Poll::Ready(()) - })); - } - - // Import with valid justification. - let parent_id = BlockId::Number(1); - let block_num = 2; - let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); - let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); - let encoded = versioned_proof.encode(); - let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); - - let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); - let block = builder.build().unwrap().block; - let mut justif_recv = justif_stream.subscribe(); - assert_eq!( - block_on(block_import.import_block(params(block, justif), HashMap::new())).unwrap(), - ImportResult::Imported(ImportedAux { - bad_justification: false, - is_new_best: true, - ..Default::default() - }), - ); - // Verify justification successfully imported: - { - // available in backend, - assert!(full_client.justifications(&BlockId::Number(block_num)).unwrap().is_some()); - // and also sent to BEEFY worker. - block_on(poll_fn(move |cx| { - match justif_recv.poll_next_unpin(cx) { - Poll::Ready(Some(_justification)) => (), - v => panic!("unexpected value: {:?}", v), - } - Poll::Ready(()) - })); - } - - // Import with invalid justification (incorrect validator set). - let parent_id = BlockId::Number(2); - let block_num = 3; - let keys = &[BeefyKeyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); - let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); - let encoded = versioned_proof.encode(); - let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); - - let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); - let block = builder.build().unwrap().block; - let mut justif_recv = justif_stream.subscribe(); - assert_eq!( - block_on(block_import.import_block(params(block, justif), HashMap::new())).unwrap(), - ImportResult::Imported(ImportedAux { - // Still `false` because we don't want to fail import on bad BEEFY justifications. - bad_justification: false, - is_new_best: true, - ..Default::default() - }), - ); - // Verify bad justifications was not imported: - { - // none in backend, - assert!(full_client.justifications(&block_id).unwrap().is_none()); - // and none sent to BEEFY worker. - block_on(poll_fn(move |cx| { - assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); - Poll::Ready(()) - })); - } -} diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs deleted file mode 100644 index 2c4985c0e..000000000 --- a/client/beefy/src/worker.rs +++ /dev/null @@ -1,1328 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::{ - collections::{BTreeMap, BTreeSet, VecDeque}, - fmt::Debug, - marker::PhantomData, - sync::Arc, -}; - -use codec::{Codec, Decode, Encode}; -use futures::StreamExt; -use log::{debug, error, info, log_enabled, trace, warn}; - -use sc_client_api::{Backend, FinalityNotification}; -use sc_network_gossip::GossipEngine; - -use sp_api::{BlockId, ProvideRuntimeApi}; -use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; -use sp_consensus::SyncOracle; -use sp_mmr_primitives::MmrApi; -use sp_runtime::{ - generic::OpaqueDigestItemId, - traits::{Block, Header, NumberFor}, - SaturatedConversion, -}; - -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - known_payload_ids, BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, - ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, -}; - -use crate::{ - error::Error, - gossip::{topic, GossipValidator}, - justification::BeefyVersionedFinalityProof, - keystore::BeefyKeystore, - metric_inc, metric_set, - metrics::Metrics, - round::Rounds, - BeefyVoterLinks, Client, -}; - -enum RoundAction { - Drop, - Process, - Enqueue, -} - -/// Responsible for the voting strategy. -/// It chooses which incoming votes to accept and which votes to generate. -struct VoterOracle { - /// Queue of known sessions. Keeps track of voting rounds (block numbers) within each session. - /// - /// There are three voter states coresponding to three queue states: - /// 1. voter uninitialized: queue empty, - /// 2. up-to-date - all mandatory blocks leading up to current GRANDPA finalized: - /// queue has ONE element, the 'current session' where `mandatory_done == true`, - /// 3. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`. - /// In this state, everytime a session gets its mandatory block BEEFY finalized, it's - /// popped off the queue, eventually getting to state `2. up-to-date`. - sessions: VecDeque>, - /// Min delta in block numbers between two blocks, BEEFY should vote on. - min_block_delta: u32, -} - -impl VoterOracle { - pub fn new(min_block_delta: u32) -> Self { - Self { - sessions: VecDeque::new(), - // Always target at least one block better than current best beefy. - min_block_delta: min_block_delta.max(1), - } - } - - /// Return mutable reference to rounds pertaining to first session in the queue. - /// Voting will always happen at the head of the queue. - pub fn rounds_mut(&mut self) -> Option<&mut Rounds> { - self.sessions.front_mut() - } - - /// Add new observed session to the Oracle. - pub fn add_session(&mut self, rounds: Rounds) { - self.sessions.push_back(rounds); - self.try_prune(); - } - - /// Prune the queue to keep the Oracle in one of the expected three states. - /// - /// Call this function on each BEEFY finality, - /// or at the very least on each BEEFY mandatory block finality. - pub fn try_prune(&mut self) { - if self.sessions.len() > 1 { - // when there's multiple sessions, only keep the `!mandatory_done()` ones. - self.sessions.retain(|s| !s.mandatory_done()) - } - } - - /// Return `(A, B)` tuple representing inclusive [A, B] interval of votes to accept. - pub fn accepted_interval( - &self, - best_grandpa: NumberFor, - ) -> Result<(NumberFor, NumberFor), Error> { - let rounds = self.sessions.front().ok_or(Error::UninitSession)?; - - if rounds.mandatory_done() { - // There's only one session active and its mandatory is done. - // Accept any GRANDPA finalized vote. - Ok((rounds.session_start(), best_grandpa.into())) - } else { - // There's at least one session with mandatory not done. - // Only accept votes for the mandatory block in the front of queue. - Ok((rounds.session_start(), rounds.session_start())) - } - } - - /// Utility function to quickly decide what to do for each round. - pub fn triage_round( - &self, - round: NumberFor, - best_grandpa: NumberFor, - ) -> Result { - let (start, end) = self.accepted_interval(best_grandpa)?; - if start <= round && round <= end { - Ok(RoundAction::Process) - } else if round > end { - Ok(RoundAction::Enqueue) - } else { - Ok(RoundAction::Drop) - } - } - - /// Return `Some(number)` if we should be voting on block `number`, - /// return `None` if there is no block we should vote on. - pub fn voting_target( - &self, - best_beefy: Option>, - best_grandpa: NumberFor, - ) -> Option> { - let rounds = if let Some(r) = self.sessions.front() { - r - } else { - debug!(target: "beefy", "🥩 No voting round started"); - return None - }; - - // `target` is guaranteed > `best_beefy` since `min_block_delta` is at least `1`. - let target = - vote_target(best_grandpa, best_beefy, rounds.session_start(), self.min_block_delta); - trace!( - target: "beefy", - "🥩 best beefy: #{:?}, best finalized: #{:?}, current_vote_target: {:?}", - best_beefy, - best_grandpa, - target - ); - target - } -} - -pub(crate) struct WorkerParams { - pub client: Arc, - pub backend: Arc, - pub runtime: Arc, - pub sync_oracle: SO, - pub key_store: BeefyKeystore, - pub gossip_engine: GossipEngine, - pub gossip_validator: Arc>, - pub links: BeefyVoterLinks, - pub metrics: Option, - pub min_block_delta: u32, -} - -/// A BEEFY worker plays the BEEFY protocol -pub(crate) struct BeefyWorker { - // utilities - client: Arc, - backend: Arc, - runtime: Arc, - sync_oracle: SO, - key_store: BeefyKeystore, - gossip_engine: GossipEngine, - gossip_validator: Arc>, - - // channels - /// Links between the block importer, the background voter and the RPC layer. - links: BeefyVoterLinks, - - // voter state - /// BEEFY client metrics. - metrics: Option, - /// Best block we received a GRANDPA finality for. - best_grandpa_block_header: ::Header, - /// Best block a BEEFY voting round has been concluded for. - best_beefy_block: Option>, - /// Buffer holding votes for future processing. - pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, - /// Buffer holding justifications for future processing. - pending_justifications: BTreeMap, Vec>>, - /// Chooses which incoming votes to accept and which votes to generate. - voting_oracle: VoterOracle, -} - -impl BeefyWorker -where - B: Block + Codec, - BE: Backend, - C: Client, - R: ProvideRuntimeApi, - R::Api: BeefyApi + MmrApi, - SO: SyncOracle + Send + Sync + Clone + 'static, -{ - /// Return a new BEEFY worker instance. - /// - /// Note that a BEEFY worker is only fully functional if a corresponding - /// BEEFY pallet has been deployed on-chain. - /// - /// The BEEFY pallet is needed in order to keep track of the BEEFY authority set. - pub(crate) fn new(worker_params: WorkerParams) -> Self { - let WorkerParams { - client, - backend, - runtime, - key_store, - sync_oracle, - gossip_engine, - gossip_validator, - links, - metrics, - min_block_delta, - } = worker_params; - - let last_finalized_header = client - .expect_header(BlockId::number(client.info().finalized_number)) - .expect("latest block always has header available; qed."); - - BeefyWorker { - client: client.clone(), - backend, - runtime, - sync_oracle, - key_store, - gossip_engine, - gossip_validator, - links, - metrics, - best_grandpa_block_header: last_finalized_header, - best_beefy_block: None, - pending_votes: BTreeMap::new(), - pending_justifications: BTreeMap::new(), - voting_oracle: VoterOracle::new(min_block_delta), - } - } - - /// Simple wrapper that gets MMR root from header digests or from client state. - fn get_mmr_root_digest(&self, header: &B::Header) -> Option { - find_mmr_root_digest::(header).or_else(|| { - self.runtime - .runtime_api() - .mmr_root(&BlockId::hash(header.hash())) - .ok() - .and_then(|r| r.ok()) - }) - } - - /// Verify `active` validator set for `block` against the key store - /// - /// We want to make sure that we have _at least one_ key in our keystore that - /// is part of the validator set, that's because if there are no local keys - /// then we can't perform our job as a validator. - /// - /// Note that for a non-authority node there will be no keystore, and we will - /// return an error and don't check. The error can usually be ignored. - fn verify_validator_set( - &self, - block: &NumberFor, - active: &ValidatorSet, - ) -> Result<(), Error> { - let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); - - let public_keys = self.key_store.public_keys()?; - let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); - - if store.intersection(&active).count() == 0 { - let msg = "no authority public key found in store".to_string(); - debug!(target: "beefy", "🥩 for block {:?} {}", block, msg); - Err(Error::Keystore(msg)) - } else { - Ok(()) - } - } - - /// Handle session changes by starting new voting round for mandatory blocks. - fn init_session_at( - &mut self, - validator_set: ValidatorSet, - new_session_start: NumberFor, - ) { - debug!(target: "beefy", "🥩 New active validator set: {:?}", validator_set); - metric_set!(self, beefy_validator_set_id, validator_set.id()); - - // BEEFY should produce the mandatory block of each session. - if let Some(active_session) = self.voting_oracle.rounds_mut() { - if !active_session.mandatory_done() { - debug!( - target: "beefy", "🥩 New session {} while active session {} is still lagging.", - validator_set.id(), - active_session.validator_set_id(), - ); - metric_inc!(self, beefy_lagging_sessions); - } - } - - if log_enabled!(target: "beefy", log::Level::Debug) { - // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(&new_session_start, &validator_set); - } - - let id = validator_set.id(); - self.voting_oracle.add_session(Rounds::new(new_session_start, validator_set)); - info!(target: "beefy", "🥩 New Rounds for validator set id: {:?} with session_start {:?}", id, new_session_start); - } - - fn handle_finality_notification(&mut self, notification: &FinalityNotification) { - debug!(target: "beefy", "🥩 Finality notification: {:?}", notification); - let header = ¬ification.header; - - if *header.number() > *self.best_grandpa_block_header.number() { - // update best GRANDPA finalized block we have seen - self.best_grandpa_block_header = header.clone(); - - // Check for and enqueue potential new session. - if let Some(new_validator_set) = find_authorities_change::(header) { - self.init_session_at(new_validator_set, *header.number()); - // TODO: when adding SYNC protocol, fire up a request for justification for this - // mandatory block here. - } - } - } - - /// Based on [VoterOracle] this vote is either processed here or enqueued for later. - fn triage_incoming_vote( - &mut self, - vote: VoteMessage, AuthorityId, Signature>, - ) -> Result<(), Error> { - let block_num = vote.commitment.block_number; - let best_grandpa = *self.best_grandpa_block_header.number(); - match self.voting_oracle.triage_round(block_num, best_grandpa)? { - RoundAction::Process => self.handle_vote( - (vote.commitment.payload, vote.commitment.block_number), - (vote.id, vote.signature), - false, - )?, - RoundAction::Enqueue => { - debug!(target: "beefy", "🥩 Buffer vote for round: {:?}.", block_num); - self.pending_votes.entry(block_num).or_default().push(vote) - }, - RoundAction::Drop => (), - }; - Ok(()) - } - - /// Based on [VoterOracle] this justification is either processed here or enqueued for later. - /// - /// Expects `justification` to be valid. - fn triage_incoming_justif( - &mut self, - justification: BeefyVersionedFinalityProof, - ) -> Result<(), Error> { - let signed_commitment = match justification { - VersionedFinalityProof::V1(ref sc) => sc, - }; - let block_num = signed_commitment.commitment.block_number; - let best_grandpa = *self.best_grandpa_block_header.number(); - match self.voting_oracle.triage_round(block_num, best_grandpa)? { - RoundAction::Process => self.finalize(justification), - RoundAction::Enqueue => { - debug!(target: "beefy", "🥩 Buffer justification for round: {:?}.", block_num); - self.pending_justifications.entry(block_num).or_default().push(justification) - }, - RoundAction::Drop => (), - }; - Ok(()) - } - - fn handle_vote( - &mut self, - round: (Payload, NumberFor), - vote: (AuthorityId, Signature), - self_vote: bool, - ) -> Result<(), Error> { - self.gossip_validator.note_round(round.1); - - let rounds = self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?; - - if rounds.add_vote(&round, vote, self_vote) { - if let Some(signatures) = rounds.try_conclude(&round) { - self.gossip_validator.conclude_round(round.1); - - let block_num = round.1; - let commitment = Commitment { - payload: round.0, - block_number: block_num, - validator_set_id: rounds.validator_set_id(), - }; - - let finality_proof = - VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }); - - metric_set!(self, beefy_round_concluded, block_num); - - info!(target: "beefy", "🥩 Round #{} concluded, finality_proof: {:?}.", round.1, finality_proof); - - if let Err(e) = self.backend.append_justification( - BlockId::Number(block_num), - (BEEFY_ENGINE_ID, finality_proof.clone().encode()), - ) { - debug!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, finality_proof); - } - - // We created the `finality_proof` and know to be valid. - self.finalize(finality_proof); - } - } - Ok(()) - } - - /// Provide BEEFY finality for block based on `finality_proof`: - /// 1. Prune irrelevant past sessions from the oracle, - /// 2. Set BEEFY best block, - /// 3. Send best block hash and `finality_proof` to RPC worker. - /// - /// Expects `finality proof` to be valid. - fn finalize(&mut self, finality_proof: BeefyVersionedFinalityProof) { - // Prune any now "finalized" sessions from queue. - self.voting_oracle.try_prune(); - let signed_commitment = match finality_proof { - VersionedFinalityProof::V1(ref sc) => sc, - }; - let block_num = signed_commitment.commitment.block_number; - if Some(block_num) > self.best_beefy_block { - // Set new best BEEFY block number. - self.best_beefy_block = Some(block_num); - metric_set!(self, beefy_best_block, block_num); - - self.client.hash(block_num).ok().flatten().map(|hash| { - self.links - .to_rpc_best_block_sender - .notify(|| Ok::<_, ()>(hash)) - .expect("forwards closure result; the closure always returns Ok; qed.") - }); - - self.links - .to_rpc_justif_sender - .notify(|| Ok::<_, ()>(finality_proof)) - .expect("forwards closure result; the closure always returns Ok; qed."); - } else { - debug!(target: "beefy", "🥩 Can't set best beefy to older: {}", block_num); - } - } - - /// Handle previously buffered justifications and votes that now land in the voting interval. - fn try_pending_justif_and_votes(&mut self) -> Result<(), Error> { - let best_grandpa = *self.best_grandpa_block_header.number(); - let _ph = PhantomData::::default(); - - fn to_process_for( - pending: &mut BTreeMap, Vec>, - (start, end): (NumberFor, NumberFor), - _: PhantomData, - ) -> BTreeMap, Vec> { - // These are still pending. - let still_pending = pending.split_off(&end.saturating_add(1u32.into())); - // These can be processed. - let to_handle = pending.split_off(&start); - // The rest can be dropped. - *pending = still_pending; - // Return ones to process. - to_handle - } - - // Process pending justifications. - let interval = self.voting_oracle.accepted_interval(best_grandpa)?; - if !self.pending_justifications.is_empty() { - let justifs_to_handle = to_process_for(&mut self.pending_justifications, interval, _ph); - for (num, justifications) in justifs_to_handle.into_iter() { - debug!(target: "beefy", "🥩 Handle buffered justifications for: {:?}.", num); - for justif in justifications.into_iter() { - self.finalize(justif); - } - } - } - - // Process pending votes. - let interval = self.voting_oracle.accepted_interval(best_grandpa)?; - if !self.pending_votes.is_empty() { - let votes_to_handle = to_process_for(&mut self.pending_votes, interval, _ph); - for (num, votes) in votes_to_handle.into_iter() { - debug!(target: "beefy", "🥩 Handle buffered votes for: {:?}.", num); - for v in votes.into_iter() { - if let Err(err) = self.handle_vote( - (v.commitment.payload, v.commitment.block_number), - (v.id, v.signature), - false, - ) { - error!(target: "beefy", "🥩 Error handling buffered vote: {}", err); - }; - } - } - } - Ok(()) - } - - /// Decide if should vote, then vote.. or don't.. - fn try_to_vote(&mut self) -> Result<(), Error> { - // Vote if there's now a new vote target. - if let Some(target) = self - .voting_oracle - .voting_target(self.best_beefy_block, *self.best_grandpa_block_header.number()) - { - metric_set!(self, beefy_should_vote_on, target); - self.do_vote(target)?; - } - Ok(()) - } - - /// Create and gossip Signed Commitment for block number `target_number`. - /// - /// Also handle this self vote by calling `self.handle_vote()` for it. - fn do_vote(&mut self, target_number: NumberFor) -> Result<(), Error> { - debug!(target: "beefy", "🥩 Try voting on {}", target_number); - - // Most of the time we get here, `target` is actually `best_grandpa`, - // avoid asking `client` for header in that case. - let target_header = if target_number == *self.best_grandpa_block_header.number() { - self.best_grandpa_block_header.clone() - } else { - self.client.expect_header(BlockId::Number(target_number)).map_err(|err| { - let err_msg = format!( - "Couldn't get header for block #{:?} (error: {:?}), skipping vote..", - target_number, err - ); - Error::Backend(err_msg) - })? - }; - let target_hash = target_header.hash(); - - let mmr_root = if let Some(hash) = self.get_mmr_root_digest(&target_header) { - hash - } else { - warn!(target: "beefy", "🥩 No MMR root digest found for: {:?}", target_hash); - return Ok(()) - }; - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, mmr_root.encode()); - - let rounds = self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?; - if !rounds.should_self_vote(&(payload.clone(), target_number)) { - debug!(target: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); - return Ok(()) - } - let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); - - let authority_id = if let Some(id) = self.key_store.authority_id(validators) { - debug!(target: "beefy", "🥩 Local authority id: {:?}", id); - id - } else { - debug!(target: "beefy", "🥩 Missing validator id - can't vote for: {:?}", target_hash); - return Ok(()) - }; - - let commitment = Commitment { payload, block_number: target_number, validator_set_id }; - let encoded_commitment = commitment.encode(); - - let signature = match self.key_store.sign(&authority_id, &encoded_commitment) { - Ok(sig) => sig, - Err(err) => { - warn!(target: "beefy", "🥩 Error signing commitment: {:?}", err); - return Ok(()) - }, - }; - - trace!( - target: "beefy", - "🥩 Produced signature using {:?}, is_valid: {:?}", - authority_id, - BeefyKeystore::verify(&authority_id, &signature, &encoded_commitment) - ); - - let message = VoteMessage { commitment, id: authority_id, signature }; - - let encoded_message = message.encode(); - - metric_inc!(self, beefy_votes_sent); - - debug!(target: "beefy", "🥩 Sent vote message: {:?}", message); - - if let Err(err) = self.handle_vote( - (message.commitment.payload, message.commitment.block_number), - (message.id, message.signature), - true, - ) { - error!(target: "beefy", "🥩 Error handling self vote: {}", err); - } - - self.gossip_engine.gossip_message(topic::(), encoded_message, false); - - Ok(()) - } - - /// Wait for BEEFY runtime pallet to be available. - async fn wait_for_runtime_pallet(&mut self) { - let mut gossip_engine = &mut self.gossip_engine; - let mut finality_stream = self.client.finality_notification_stream().fuse(); - loop { - futures::select! { - notif = finality_stream.next() => { - let notif = match notif { - Some(notif) => notif, - None => break - }; - let at = BlockId::hash(notif.header.hash()); - if let Some(active) = self.runtime.runtime_api().validator_set(&at).ok().flatten() { - if active.id() == GENESIS_AUTHORITY_SET_ID { - // When starting from genesis, there is no session boundary digest. - // Just initialize `rounds` to Block #1 as BEEFY mandatory block. - self.init_session_at(active, 1u32.into()); - } - // In all other cases, we just go without `rounds` initialized, meaning the - // worker won't vote until it witnesses a session change. - // Once we'll implement 'initial sync' (catch-up), the worker will be able to - // start voting right away. - self.handle_finality_notification(¬if); - if let Err(err) = self.try_to_vote() { - debug!(target: "beefy", "🥩 {}", err); - } - break - } else { - trace!(target: "beefy", "🥩 Finality notification: {:?}", notif); - debug!(target: "beefy", "🥩 Waiting for BEEFY pallet to become available..."); - } - }, - _ = gossip_engine => { - break - } - } - } - } - - /// Main loop for BEEFY worker. - /// - /// Wait for BEEFY runtime pallet to be available, then start the main async loop - /// which is driven by finality notifications and gossiped votes. - pub(crate) async fn run(mut self) { - info!(target: "beefy", "🥩 run BEEFY worker, best grandpa: #{:?}.", self.best_grandpa_block_header.number()); - self.wait_for_runtime_pallet().await; - - let mut finality_notifications = self.client.finality_notification_stream().fuse(); - let mut votes = Box::pin( - self.gossip_engine - .messages_for(topic::()) - .filter_map(|notification| async move { - trace!(target: "beefy", "🥩 Got vote message: {:?}", notification); - - VoteMessage::, AuthorityId, Signature>::decode( - &mut ¬ification.message[..], - ) - .ok() - }) - .fuse(), - ); - let mut block_import_justif = self.links.from_block_import_justif_stream.subscribe().fuse(); - - loop { - let mut gossip_engine = &mut self.gossip_engine; - // Wait for, and handle external events. - // The branches below only change 'state', actual voting happen afterwards, - // based on the new resulting 'state'. - futures::select_biased! { - notification = finality_notifications.next() => { - if let Some(notification) = notification { - self.handle_finality_notification(¬ification); - } else { - return; - } - }, - // TODO: when adding SYNC protocol, join the on-demand justifications stream to - // this one, and handle them both here. - justif = block_import_justif.next() => { - if let Some(justif) = justif { - // Block import justifications have already been verified to be valid - // by `BeefyBlockImport`. - if let Err(err) = self.triage_incoming_justif(justif) { - debug!(target: "beefy", "🥩 {}", err); - } - } else { - return; - } - }, - vote = votes.next() => { - if let Some(vote) = vote { - // Votes have already been verified to be valid by the gossip validator. - if let Err(err) = self.triage_incoming_vote(vote) { - debug!(target: "beefy", "🥩 {}", err); - } - } else { - return; - } - }, - _ = gossip_engine => { - error!(target: "beefy", "🥩 Gossip engine has terminated."); - return; - } - } - - // Don't bother acting on 'state' changes during major sync. - if !self.sync_oracle.is_major_syncing() { - // Handle pending justifications and/or votes for now GRANDPA finalized blocks. - if let Err(err) = self.try_pending_justif_and_votes() { - debug!(target: "beefy", "🥩 {}", err); - } - - // There were external events, 'state' is changed, author a vote if needed/possible. - if let Err(err) = self.try_to_vote() { - debug!(target: "beefy", "🥩 {}", err); - } - } - } - } -} - -/// Extract the MMR root hash from a digest in the given header, if it exists. -fn find_mmr_root_digest(header: &B::Header) -> Option -where - B: Block, -{ - let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); - - let filter = |log: ConsensusLog| match log { - ConsensusLog::MmrRoot(root) => Some(root), - _ => None, - }; - header.digest().convert_first(|l| l.try_to(id).and_then(filter)) -} - -/// Scan the `header` digest log for a BEEFY validator set change. Return either the new -/// validator set or `None` in case no validator set change has been signaled. -fn find_authorities_change(header: &B::Header) -> Option> -where - B: Block, -{ - let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); - - let filter = |log: ConsensusLog| match log { - ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), - _ => None, - }; - header.digest().convert_first(|l| l.try_to(id).and_then(filter)) -} - -/// Calculate next block number to vote on. -/// -/// Return `None` if there is no voteable target yet. -fn vote_target( - best_grandpa: N, - best_beefy: Option, - session_start: N, - min_delta: u32, -) -> Option -where - N: AtLeast32Bit + Copy + Debug, -{ - // if the mandatory block (session_start) does not have a beefy justification yet, - // we vote on it - let target = match best_beefy { - None => { - debug!( - target: "beefy", - "🥩 vote target - mandatory block: #{:?}", - session_start, - ); - session_start - }, - Some(bbb) if bbb < session_start => { - debug!( - target: "beefy", - "🥩 vote target - mandatory block: #{:?}", - session_start, - ); - session_start - }, - Some(bbb) => { - let diff = best_grandpa.saturating_sub(bbb) + 1u32.into(); - let diff = diff.saturated_into::() / 2; - let target = bbb + min_delta.max(diff.next_power_of_two()).into(); - - debug!( - target: "beefy", - "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", - diff, - diff.next_power_of_two(), - target, - ); - - target - }, - }; - - // Don't vote for targets until they've been finalized - // (`target` can be > `best_grandpa` when `min_delta` is big enough). - if target > best_grandpa { - None - } else { - Some(target) - } -} - -#[cfg(test)] -pub(crate) mod tests { - use super::*; - use crate::{ - keystore::tests::Keyring, - notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, - tests::{ - create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, - BeefyPeer, BeefyTestNet, BEEFY_PROTOCOL_NAME, - }, - BeefyRPCLinks, - }; - - use futures::{executor::block_on, future::poll_fn, task::Poll}; - - use sc_client_api::HeaderBackend; - use sc_network::NetworkService; - use sc_network_test::{PeersFullClient, TestNetFactory}; - use sp_api::HeaderT; - use substrate_test_runtime_client::{ - runtime::{Block, Digest, DigestItem, Header, H256}, - Backend, - }; - - fn create_beefy_worker( - peer: &BeefyPeer, - key: &Keyring, - min_block_delta: u32, - ) -> BeefyWorker>> { - let keystore = create_beefy_keystore(*key); - - let (to_rpc_justif_sender, from_voter_justif_stream) = - BeefyVersionedFinalityProofStream::::channel(); - let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = - BeefyBestBlockStream::::channel(); - let (_, from_block_import_justif_stream) = - BeefyVersionedFinalityProofStream::::channel(); - - let beefy_rpc_links = - BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream }; - *peer.data.beefy_rpc_links.lock() = Some(beefy_rpc_links); - - let links = BeefyVoterLinks { - from_block_import_justif_stream, - to_rpc_justif_sender, - to_rpc_best_block_sender, - }; - - let api = Arc::new(TestApi {}); - let network = peer.network_service().clone(); - let sync_oracle = network.clone(); - let gossip_validator = Arc::new(crate::gossip::GossipValidator::new()); - let gossip_engine = - GossipEngine::new(network, BEEFY_PROTOCOL_NAME, gossip_validator.clone(), None); - let worker_params = crate::worker::WorkerParams { - client: peer.client().as_client(), - backend: peer.client().as_backend(), - runtime: api, - key_store: Some(keystore).into(), - links, - gossip_engine, - gossip_validator, - min_block_delta, - metrics: None, - sync_oracle, - }; - BeefyWorker::<_, _, _, _, _>::new(worker_params) - } - - #[test] - fn vote_on_min_block_delta() { - let t = vote_target(1u32, Some(1), 1, 4); - assert_eq!(None, t); - let t = vote_target(2u32, Some(1), 1, 4); - assert_eq!(None, t); - let t = vote_target(4u32, Some(2), 1, 4); - assert_eq!(None, t); - let t = vote_target(6u32, Some(2), 1, 4); - assert_eq!(Some(6), t); - - let t = vote_target(9u32, Some(4), 1, 4); - assert_eq!(Some(8), t); - - let t = vote_target(10u32, Some(10), 1, 8); - assert_eq!(None, t); - let t = vote_target(12u32, Some(10), 1, 8); - assert_eq!(None, t); - let t = vote_target(18u32, Some(10), 1, 8); - assert_eq!(Some(18), t); - } - - #[test] - fn vote_on_power_of_two() { - let t = vote_target(1008u32, Some(1000), 1, 4); - assert_eq!(Some(1004), t); - - let t = vote_target(1016u32, Some(1000), 1, 4); - assert_eq!(Some(1008), t); - - let t = vote_target(1032u32, Some(1000), 1, 4); - assert_eq!(Some(1016), t); - - let t = vote_target(1064u32, Some(1000), 1, 4); - assert_eq!(Some(1032), t); - - let t = vote_target(1128u32, Some(1000), 1, 4); - assert_eq!(Some(1064), t); - - let t = vote_target(1256u32, Some(1000), 1, 4); - assert_eq!(Some(1128), t); - - let t = vote_target(1512u32, Some(1000), 1, 4); - assert_eq!(Some(1256), t); - - let t = vote_target(1024u32, Some(1), 1, 4); - assert_eq!(Some(513), t); - } - - #[test] - fn vote_on_target_block() { - let t = vote_target(1008u32, Some(1002), 1, 4); - assert_eq!(Some(1006), t); - let t = vote_target(1010u32, Some(1002), 1, 4); - assert_eq!(Some(1006), t); - - let t = vote_target(1016u32, Some(1006), 1, 4); - assert_eq!(Some(1014), t); - let t = vote_target(1022u32, Some(1006), 1, 4); - assert_eq!(Some(1014), t); - - let t = vote_target(1032u32, Some(1012), 1, 4); - assert_eq!(Some(1028), t); - let t = vote_target(1044u32, Some(1012), 1, 4); - assert_eq!(Some(1028), t); - - let t = vote_target(1064u32, Some(1014), 1, 4); - assert_eq!(Some(1046), t); - let t = vote_target(1078u32, Some(1014), 1, 4); - assert_eq!(Some(1046), t); - - let t = vote_target(1128u32, Some(1008), 1, 4); - assert_eq!(Some(1072), t); - let t = vote_target(1136u32, Some(1008), 1, 4); - assert_eq!(Some(1072), t); - } - - #[test] - fn vote_on_mandatory_block() { - let t = vote_target(1008u32, Some(1002), 1004, 4); - assert_eq!(Some(1004), t); - let t = vote_target(1016u32, Some(1006), 1007, 4); - assert_eq!(Some(1007), t); - let t = vote_target(1064u32, Some(1014), 1063, 4); - assert_eq!(Some(1063), t); - let t = vote_target(1320u32, Some(1012), 1234, 4); - assert_eq!(Some(1234), t); - - let t = vote_target(1128u32, Some(1008), 1008, 4); - assert_eq!(Some(1072), t); - } - - #[test] - fn should_vote_target() { - let mut oracle = VoterOracle::::new(1); - - // rounds not initialized -> should vote: `None` - assert_eq!(oracle.voting_target(None, 1), None); - - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - - oracle.add_session(Rounds::new(1, validator_set.clone())); - - // under min delta - oracle.min_block_delta = 4; - assert_eq!(oracle.voting_target(Some(1), 1), None); - assert_eq!(oracle.voting_target(Some(2), 5), None); - - // vote on min delta - assert_eq!(oracle.voting_target(Some(4), 9), Some(8)); - oracle.min_block_delta = 8; - assert_eq!(oracle.voting_target(Some(10), 18), Some(18)); - - // vote on power of two - oracle.min_block_delta = 1; - assert_eq!(oracle.voting_target(Some(1000), 1008), Some(1004)); - assert_eq!(oracle.voting_target(Some(1000), 1016), Some(1008)); - - // nothing new to vote on - assert_eq!(oracle.voting_target(Some(1000), 1000), None); - - // vote on mandatory - oracle.sessions.clear(); - oracle.add_session(Rounds::new(1000, validator_set.clone())); - assert_eq!(oracle.voting_target(None, 1008), Some(1000)); - oracle.sessions.clear(); - oracle.add_session(Rounds::new(1001, validator_set.clone())); - assert_eq!(oracle.voting_target(Some(1000), 1008), Some(1001)); - } - - #[test] - fn test_oracle_accepted_interval() { - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - - let mut oracle = VoterOracle::::new(1); - - // rounds not initialized -> should accept votes: `None` - assert!(oracle.accepted_interval(1).is_err()); - - let session_one = 1; - oracle.add_session(Rounds::new(session_one, validator_set.clone())); - // mandatory not done, only accept mandatory - for i in 0..15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); - } - - // add more sessions, nothing changes - let session_two = 11; - let session_three = 21; - oracle.add_session(Rounds::new(session_two, validator_set.clone())); - oracle.add_session(Rounds::new(session_three, validator_set.clone())); - // mandatory not done, should accept mandatory for session_one - for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); - } - - // simulate finish mandatory for session one, prune oracle - oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); - oracle.try_prune(); - // session_one pruned, should accept mandatory for session_two - for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_two, session_two))); - } - - // simulate finish mandatory for session two, prune oracle - oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); - oracle.try_prune(); - // session_two pruned, should accept mandatory for session_three - for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_three, session_three))); - } - - // simulate finish mandatory for session three - oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); - // verify all other blocks in this session are now open to voting - for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); - } - // pruning does nothing in this case - oracle.try_prune(); - for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); - } - - // adding new session automatically prunes "finalized" previous session - let session_four = 31; - oracle.add_session(Rounds::new(session_four, validator_set.clone())); - assert_eq!(oracle.sessions.front().unwrap().session_start(), session_four); - assert_eq!(oracle.accepted_interval(session_four + 10), Ok((session_four, session_four))); - } - - #[test] - fn extract_authorities_change_digest() { - let mut header = Header::new( - 1u32.into(), - Default::default(), - Default::default(), - Default::default(), - Digest::default(), - ); - - // verify empty digest shows nothing - assert!(find_authorities_change::(&header).is_none()); - - let peers = &[Keyring::One, Keyring::Two]; - let id = 42; - let validator_set = ValidatorSet::new(make_beefy_ids(peers), id).unwrap(); - header.digest_mut().push(DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::::AuthoritiesChange(validator_set.clone()).encode(), - )); - - // verify validator set is correctly extracted from digest - let extracted = find_authorities_change::(&header); - assert_eq!(extracted, Some(validator_set)); - } - - #[test] - fn extract_mmr_root_digest() { - let mut header = Header::new( - 1u32.into(), - Default::default(), - Default::default(), - Default::default(), - Digest::default(), - ); - - // verify empty digest shows nothing - assert!(find_mmr_root_digest::(&header).is_none()); - - let mmr_root_hash = H256::random(); - header.digest_mut().push(DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::::MmrRoot(mmr_root_hash.clone()).encode(), - )); - - // verify validator set is correctly extracted from digest - let extracted = find_mmr_root_digest::(&header); - assert_eq!(extracted, Some(mmr_root_hash)); - } - - #[test] - fn keystore_vs_validator_set() { - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut net = BeefyTestNet::new(1, 0); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - // keystore doesn't contain other keys than validators' - assert_eq!(worker.verify_validator_set(&1, &validator_set), Ok(())); - - // unknown `Bob` key - let keys = &[Keyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let err_msg = "no authority public key found in store".to_string(); - let expected = Err(Error::Keystore(err_msg)); - assert_eq!(worker.verify_validator_set(&1, &validator_set), expected); - - // worker has no keystore - worker.key_store = None.into(); - let expected_err = Err(Error::Keystore("no Keystore".into())); - assert_eq!(worker.verify_validator_set(&1, &validator_set), expected_err); - } - - #[test] - fn test_finalize() { - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut net = BeefyTestNet::new(1, 0); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - let (mut best_block_streams, mut finality_proofs) = get_beefy_streams(&mut net, keys); - let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - let mut finality_proof = finality_proofs.drain(..).next().unwrap(); - - let create_finality_proof = |block_num: NumberFor| { - let commitment = Commitment { - payload: Payload::new(known_payload_ids::MMR_ROOT_ID, vec![]), - block_number: block_num, - validator_set_id: validator_set.id(), - }; - VersionedFinalityProof::V1(SignedCommitment { commitment, signatures: vec![None] }) - }; - - // no 'best beefy block' or finality proofs - assert_eq!(worker.best_beefy_block, None); - block_on(poll_fn(move |cx| { - assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); - assert_eq!(finality_proof.poll_next_unpin(cx), Poll::Pending); - Poll::Ready(()) - })); - - // unknown hash for block #1 - let (mut best_block_streams, mut finality_proofs) = get_beefy_streams(&mut net, keys); - let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - let mut finality_proof = finality_proofs.drain(..).next().unwrap(); - let justif = create_finality_proof(1); - worker.finalize(justif.clone()); - assert_eq!(worker.best_beefy_block, Some(1)); - block_on(poll_fn(move |cx| { - assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); - match finality_proof.poll_next_unpin(cx) { - // expect justification - Poll::Ready(Some(received)) => assert_eq!(received, justif), - v => panic!("unexpected value: {:?}", v), - } - Poll::Ready(()) - })); - - // generate 2 blocks, try again expect success - let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); - let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - net.generate_blocks(2, 10, &validator_set, false); - - let justif = create_finality_proof(2); - worker.finalize(justif); - assert_eq!(worker.best_beefy_block, Some(2)); - block_on(poll_fn(move |cx| { - match best_block_stream.poll_next_unpin(cx) { - // expect Some(hash-of-block-2) - Poll::Ready(Some(hash)) => { - let block_num = net.peer(0).client().as_client().number(hash).unwrap(); - assert_eq!(block_num, Some(2)); - }, - v => panic!("unexpected value: {:?}", v), - } - Poll::Ready(()) - })); - } - - #[test] - fn should_init_session() { - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut net = BeefyTestNet::new(1, 0); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - assert!(worker.voting_oracle.sessions.is_empty()); - - worker.init_session_at(validator_set.clone(), 1); - let worker_rounds = worker.voting_oracle.rounds_mut().unwrap(); - assert_eq!(worker_rounds.session_start(), 1); - assert_eq!(worker_rounds.validators(), validator_set.validators()); - assert_eq!(worker_rounds.validator_set_id(), validator_set.id()); - - // new validator set - let keys = &[Keyring::Bob]; - let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - - worker.init_session_at(new_validator_set.clone(), 11); - // Since mandatory is not done for old rounds, we still get those. - let rounds = worker.voting_oracle.rounds_mut().unwrap(); - assert_eq!(rounds.validator_set_id(), validator_set.id()); - // Let's finalize mandatory. - rounds.test_set_mandatory_done(true); - worker.voting_oracle.try_prune(); - // Now we should get the next round. - let rounds = worker.voting_oracle.rounds_mut().unwrap(); - // Expect new values. - assert_eq!(rounds.session_start(), 11); - assert_eq!(rounds.validators(), new_validator_set.validators()); - assert_eq!(rounds.validator_set_id(), new_validator_set.id()); - } - - #[test] - fn should_triage_votes_and_process_later() { - let keys = &[Keyring::Alice, Keyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut net = BeefyTestNet::new(1, 0); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - fn new_vote( - block_number: NumberFor, - ) -> VoteMessage, AuthorityId, Signature> { - let commitment = Commitment { - payload: Payload::new(*b"BF", vec![]), - block_number, - validator_set_id: 0, - }; - VoteMessage { - commitment, - id: Keyring::Alice.public(), - signature: Keyring::Alice.sign(b"I am committed"), - } - } - - // best grandpa is 20 - let best_grandpa_header = Header::new( - 20u32.into(), - Default::default(), - Default::default(), - Default::default(), - Digest::default(), - ); - - worker.voting_oracle.add_session(Rounds::new(10, validator_set.clone())); - worker.best_grandpa_block_header = best_grandpa_header; - - // triage votes for blocks 10..13 - worker.triage_incoming_vote(new_vote(10)).unwrap(); - worker.triage_incoming_vote(new_vote(11)).unwrap(); - worker.triage_incoming_vote(new_vote(12)).unwrap(); - // triage votes for blocks 20..23 - worker.triage_incoming_vote(new_vote(20)).unwrap(); - worker.triage_incoming_vote(new_vote(21)).unwrap(); - worker.triage_incoming_vote(new_vote(22)).unwrap(); - - // vote for 10 should have been handled, while the rest buffered for later processing - let mut votes = worker.pending_votes.values(); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 11); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 12); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 20); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); - assert!(votes.next().is_none()); - - // simulate mandatory done, and retry buffered votes - worker.voting_oracle.rounds_mut().unwrap().test_set_mandatory_done(true); - worker.try_pending_justif_and_votes().unwrap(); - // all blocks <= grandpa finalized should have been handled, rest still buffered - let mut votes = worker.pending_votes.values(); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); - } -} diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 739be9ec8..bed473065 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -13,7 +13,6 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } @@ -66,7 +65,6 @@ default = [ "std", ] std = [ - "beefy-primitives/std", "sp-application-crypto/std", "sp-consensus-aura/std", "sp-consensus-babe/std",