diff --git a/Cargo.lock b/Cargo.lock index 3d2949d7..eb4880b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,14 +137,9 @@ dependencies = [ [[package]] name = "array-bytes" -version = "4.1.0" -source = "git+https://github.com/hack-ink/array-bytes?rev=994cd29b66bd2ab5c8c15f0b15a1618d4bb2d94c#994cd29b66bd2ab5c8c15f0b15a1618d4bb2d94c" - -[[package]] -name = "array-init" -version = "2.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" +checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" [[package]] name = "arrayref" @@ -274,7 +269,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -1911,7 +1906,7 @@ dependencies = [ "fixed-hash", "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "scale-info", "tiny-keccak", ] @@ -1945,7 +1940,7 @@ dependencies = [ "fixed-hash", "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "primitive-types", "scale-info", "uint", @@ -3240,15 +3235,6 @@ dependencies = [ "rlp", ] -[[package]] -name = "impl-serde" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" -dependencies = [ - "serde", -] - [[package]] name = "impl-serde" version = "0.4.0" @@ -3286,12 +3272,6 @@ dependencies = [ "serde", ] -[[package]] -name = "indexmap-nostd" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" - [[package]] name = "indicatif" version = "0.16.2" @@ -3304,184 +3284,6 @@ dependencies = [ "regex", ] -[[package]] -name = "ink_allocator" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9588a59a0e8997c0b2153cd11b5aaa77c06a0537a6b18f3811d1f1aa098b12" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ink_engine" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487c3b390b7feb0620496b0cd38683433c7d7e6946b1caabda51e1f23eb24b30" -dependencies = [ - "blake2", - "derive_more", - "parity-scale-codec", - "rand 0.8.5", - "secp256k1", - "sha2 0.10.6", - "sha3", -] - -[[package]] -name = "ink_env" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a891d34301a3dbb1c7b7424c49ae184282b163491c54f9acd17fcbe14a80447b" -dependencies = [ - "arrayref", - "blake2", - "cfg-if", - "derive_more", - "ink_allocator", - "ink_engine", - "ink_metadata", - "ink_prelude", - "ink_primitives", - "num-traits", - "parity-scale-codec", - "paste", - "rand 0.8.5", - "rlibc", - "scale-info", - "secp256k1", - "sha2 0.10.6", - "sha3", - "static_assertions", -] - -[[package]] -name = "ink_lang" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca26e374e0f89c82cf5dabb4309ef3c76a01659ad95186f4e84455c5f4621a0" -dependencies = [ - "derive_more", - "ink_env", - "ink_lang_macro", - "ink_prelude", - "ink_primitives", - "ink_storage", - "parity-scale-codec", -] - -[[package]] -name = "ink_lang_codegen" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fe57826726d89c84fe0b1fafe0dee328f58c8e927be40f0290f04602aacc45c" -dependencies = [ - "blake2", - "derive_more", - "either", - "heck", - "impl-serde 0.3.2", - "ink_lang_ir", - "itertools", - "parity-scale-codec", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ink_lang_ir" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47d16b2a5340df90f11b2ec2242b37907f5c8396dbbc72c52ec9f2b1a8c90c8" -dependencies = [ - "blake2", - "either", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ink_lang_macro" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b858be42ac6cde2c15ce6d7fa75cef59b64a3baf37f7105f39208f2b84dadb" -dependencies = [ - "ink_lang_codegen", - "ink_lang_ir", - "ink_primitives", - "parity-scale-codec", - "proc-macro2", - "syn", -] - -[[package]] -name = "ink_metadata" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74913aaed5751f5615af4631b7559328b8ed56c9cb821b89e14af0706176e849" -dependencies = [ - "derive_more", - "impl-serde 0.3.2", - "ink_prelude", - "ink_primitives", - "scale-info", - "serde", -] - -[[package]] -name = "ink_prelude" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f031e6b8495594a7288b089bf4122e76c26b994959d1b2b693bdfe846b14c0e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ink_primitives" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12cf42dce81d060401c7cec95a392ad6d3c2f18661fa3083f619ce135133c33" -dependencies = [ - "cfg-if", - "ink_prelude", - "parity-scale-codec", - "scale-info", -] - -[[package]] -name = "ink_storage" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0a98b6acbd79eedf44720412437d713e7195d1407822604de5885b0ee6c7e1" -dependencies = [ - "array-init", - "cfg-if", - "derive_more", - "ink_env", - "ink_metadata", - "ink_prelude", - "ink_primitives", - "ink_storage_derive", - "parity-scale-codec", - "scale-info", -] - -[[package]] -name = "ink_storage_derive" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babf1d8903dc9219ad8e8aa181eddb919d9794aad1da23ccdce770925b7de2ba" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "inout" version = "0.1.3" @@ -5102,70 +4904,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-contracts" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#6dc38b0ba11dff62c1042ab0fa21d0b55a64bf6e" -dependencies = [ - "bitflags", - "frame-benchmarking", - "frame-support", - "frame-system", - "impl-trait-for-tuples", - "log", - "pallet-contracts-primitives", - "pallet-contracts-proc-macro", - "parity-scale-codec", - "rand 0.8.5", - "scale-info", - "serde", - "smallvec", - "sp-api", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", - "wasm-instrument 0.4.0", - "wasmi 0.20.0", - "wasmparser-nostd", -] - -[[package]] -name = "pallet-contracts-primitives" -version = "7.0.0" -source = "git+https://github.com/serai-dex/substrate#6dc38b0ba11dff62c1042ab0fa21d0b55a64bf6e" -dependencies = [ - "bitflags", - "parity-scale-codec", - "sp-runtime", - "sp-std", - "sp-weights", -] - -[[package]] -name = "pallet-contracts-proc-macro" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#6dc38b0ba11dff62c1042ab0fa21d0b55a64bf6e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pallet-randomness-collective-flip" -version = "4.0.0-dev" -source = "git+https://github.com/serai-dex/substrate#6dc38b0ba11dff62c1042ab0fa21d0b55a64bf6e" -dependencies = [ - "frame-support", - "frame-system", - "parity-scale-codec", - "safe-mix", - "scale-info", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-session" version = "4.0.0-dev" @@ -5500,7 +5238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -5717,7 +5455,7 @@ dependencies = [ "fixed-hash", "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "scale-info", "uint", ] @@ -6211,7 +5949,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin 0.5.2", + "spin", "untrusted", "web-sys", "winapi", @@ -6226,12 +5964,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "rlibc" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" - [[package]] name = "rlp" version = "0.5.2" @@ -6317,15 +6049,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -6419,15 +6142,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" -[[package]] -name = "safe-mix" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3d055a2582e6b00ed7a31c1524040aa391092bf636328350813f3a0605215c" -dependencies = [ - "rustc_version 0.2.3", -] - [[package]] name = "salsa20" version = "0.10.2" @@ -6663,7 +6377,7 @@ dependencies = [ "sp-version", "sp-wasm-interface", "tracing", - "wasmi 0.13.2", + "wasmi", ] [[package]] @@ -6675,8 +6389,8 @@ dependencies = [ "sp-maybe-compressed-blob", "sp-wasm-interface", "thiserror", - "wasm-instrument 0.3.0", - "wasmi 0.13.2", + "wasm-instrument", + "wasmi", ] [[package]] @@ -6689,7 +6403,7 @@ dependencies = [ "sc-executor-common", "sp-runtime-interface", "sp-wasm-interface", - "wasmi 0.13.2", + "wasmi", ] [[package]] @@ -7484,15 +7198,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.16" @@ -7514,30 +7219,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7" -[[package]] -name = "serai-extension" -version = "0.1.0" -dependencies = [ - "ink_env", - "ink_lang", - "parity-scale-codec", -] - -[[package]] -name = "serai-multisig" -version = "0.1.0" -dependencies = [ - "ink_env", - "ink_lang", - "ink_metadata", - "ink_primitives", - "ink_storage", - "lazy_static", - "parity-scale-codec", - "scale-info", - "serai-extension", -] - [[package]] name = "serai-node" version = "0.1.0" @@ -7568,6 +7249,7 @@ dependencies = [ "sc-tendermint", "sc-transaction-pool", "sc-transaction-pool-api", + "serai-primitives", "serai-runtime", "sp-api", "sp-application-crypto", @@ -7583,6 +7265,18 @@ dependencies = [ "sp-timestamp", "substrate-build-script-utils", "substrate-frame-rpc-system", + "validator-sets-pallet", +] + +[[package]] +name = "serai-primitives" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-std", ] [[package]] @@ -7617,9 +7311,6 @@ dependencies = [ "frame-system-rpc-runtime-api", "hex-literal", "pallet-balances", - "pallet-contracts", - "pallet-contracts-primitives", - "pallet-randomness-collective-flip", "pallet-session", "pallet-tendermint", "pallet-timestamp", @@ -7640,6 +7331,7 @@ dependencies = [ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", + "validator-sets-pallet", ] [[package]] @@ -7865,7 +7557,7 @@ dependencies = [ "curve25519-dalek 4.0.0-pre.5", "rand_core 0.6.4", "ring", - "rustc_version 0.4.0", + "rustc_version", "sha2 0.10.6", "subtle", ] @@ -8032,7 +7724,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde 0.4.0", + "impl-serde", "lazy_static", "libsecp256k1", "log", @@ -8058,7 +7750,7 @@ dependencies = [ "substrate-bip39", "thiserror", "tiny-bip39", - "wasmi 0.13.2", + "wasmi", "zeroize", ] @@ -8353,7 +8045,7 @@ name = "sp-storage" version = "7.0.0" source = "git+https://github.com/serai-dex/substrate#6dc38b0ba11dff62c1042ab0fa21d0b55a64bf6e" dependencies = [ - "impl-serde 0.4.0", + "impl-serde", "parity-scale-codec", "ref-cast", "serde", @@ -8451,7 +8143,7 @@ name = "sp-version" version = "5.0.0" source = "git+https://github.com/serai-dex/substrate#6dc38b0ba11dff62c1042ab0fa21d0b55a64bf6e" dependencies = [ - "impl-serde 0.4.0", + "impl-serde", "parity-scale-codec", "parity-wasm", "scale-info", @@ -8483,7 +8175,7 @@ dependencies = [ "log", "parity-scale-codec", "sp-std", - "wasmi 0.13.2", + "wasmi", "wasmtime", ] @@ -8509,12 +8201,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" - [[package]] name = "spki" version = "0.6.0" @@ -9423,6 +9109,29 @@ dependencies = [ "serde", ] +[[package]] +name = "validator-sets-pallet" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serai-primitives", + "validator-sets-primitives", +] + +[[package]] +name = "validator-sets-primitives" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-std", +] + [[package]] name = "valuable" version = "0.1.0" @@ -9567,15 +9276,6 @@ dependencies = [ "parity-wasm", ] -[[package]] -name = "wasm-instrument" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" -dependencies = [ - "parity-wasm", -] - [[package]] name = "wasm-opt" version = "0.110.2" @@ -9640,19 +9340,7 @@ checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" dependencies = [ "parity-wasm", "wasmi-validation", - "wasmi_core 0.2.1", -] - -[[package]] -name = "wasmi" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01bf50edb2ea9d922aa75a7bf3c15e26a6c9e2d18c56e862b49737a582901729" -dependencies = [ - "spin 0.9.4", - "wasmi_arena", - "wasmi_core 0.5.0", - "wasmparser-nostd", + "wasmi_core", ] [[package]] @@ -9664,12 +9352,6 @@ dependencies = [ "parity-wasm", ] -[[package]] -name = "wasmi_arena" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ea379cbb0b41f3a9f0bf7b47036d036aae7f43383d8cc487d4deccf40dee0a" - [[package]] name = "wasmi_core" version = "0.2.1" @@ -9683,17 +9365,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "wasmi_core" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5bf998ab792be85e20e771fe14182b4295571ad1d4f89d3da521c1bef5f597a" -dependencies = [ - "downcast-rs", - "libm 0.2.6", - "num-traits", -] - [[package]] name = "wasmparser" version = "0.89.1" @@ -9703,15 +9374,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "wasmparser-nostd" -version = "0.91.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c37f310b5a62bfd5ae7c0f1d8e6f98af16a5d6d84ba764e9c36439ec14e318b" -dependencies = [ - "indexmap-nostd", -] - [[package]] name = "wasmtime" version = "1.0.2" @@ -10130,7 +9792,7 @@ dependencies = [ "futures", "js-sys", "pharos", - "rustc_version 0.4.0", + "rustc_version", "send_wrapper", "thiserror", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index df9adc65..7cdec0c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,11 @@ members = [ "processor", + "substrate/serai/primitives", + + "substrate/validator-sets/primitives", + "substrate/validator-sets/pallet", + "substrate/tendermint/machine", "substrate/tendermint/primitives", "substrate/tendermint/client", @@ -29,9 +34,6 @@ members = [ "substrate/runtime", "substrate/node", - - "contracts/extension", - "contracts/multisig", ] # Always compile Monero (and a variety of dependencies) with optimizations due @@ -53,8 +55,3 @@ monero-serai = { opt-level = 3 } [profile.release] panic = "unwind" - -[patch.crates-io] -# array-bytes 4.1.0 is GPL-3.0. -# array-bytes git, which has no code changes, includes a dual-license under Apache-2.0. -array-bytes = { git = "https://github.com/hack-ink/array-bytes", rev = "994cd29b66bd2ab5c8c15f0b15a1618d4bb2d94c" } diff --git a/contracts/extension/Cargo.toml b/contracts/extension/Cargo.toml deleted file mode 100644 index 9272659e..00000000 --- a/contracts/extension/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "serai-extension" -version = "0.1.0" -description = "An ink! extension for exposing Serai to ink" -license = "AGPL-3.0-only" -repository = "https://github.com/serai-dex/serai/tree/develop/contracts/extension" -authors = ["Luke Parker "] -edition = "2021" -publish = false - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } - -ink_env = { version = "3", default-features = false } -ink_lang = { version = "3", default-features = false } - -[features] -default = ["std"] -std = ["ink_env/std"] -ink-as-dependency = [] diff --git a/contracts/extension/src/lib.rs b/contracts/extension/src/lib.rs deleted file mode 100644 index 1c5a34bc..00000000 --- a/contracts/extension/src/lib.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -use ink_lang as ink; -use ink_env::{Environment, DefaultEnvironment, AccountId}; - -pub type Curve = u16; -pub type Coin = u32; -pub type GlobalValidatorSetId = u32; -pub type ValidatorSetIndex = u8; -pub type Key = Vec; - -#[ink::chain_extension] -pub trait SeraiExtension { - type ErrorCode = (); - - /// Returns the ID for the current global validator set. - #[ink(extension = 0, handle_status = false, returns_result = false)] - fn global_validator_set_id() -> GlobalValidatorSetId; - - /// Returns the amount of active validator sets within the global validator set. - #[ink(extension = 1, handle_status = false, returns_result = false)] - fn validator_sets() -> u8; - - /// Returns the amount of key shares used within the specified validator set. - #[ink(extension = 2, handle_status = false, returns_result = false)] - fn validator_set_shares(set: ValidatorSetIndex) -> u16; - - /// Returns the validator set the specified account is in, along with their amount of shares in - /// that validator set, if they are in a current validator - #[ink(extension = 3, handle_status = false, returns_result = false)] - fn active_validator(account: &AccountId) -> Option<(ValidatorSetIndex, u16)>; -} - -pub struct SeraiEnvironment; -impl Environment for SeraiEnvironment { - const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; - - type AccountId = ::AccountId; - type Balance = ::Balance; - type Hash = ::Hash; - type BlockNumber = ::BlockNumber; - type Timestamp = ::Timestamp; - - type ChainExtension = SeraiExtension; -} - -pub fn test_validators() -> Vec { - vec![ - AccountId::from([1; 32]), - AccountId::from([2; 32]), - AccountId::from([3; 32]), - AccountId::from([4; 32]), - AccountId::from([5; 32]), - ] -} - -pub fn test_register() { - struct ExtensionId; - impl ink_env::test::ChainExtension for ExtensionId { - fn func_id(&self) -> u32 { - 0 - } - - fn call(&mut self, _: &[u8], output: &mut Vec) -> u32 { - // Non-0 global validator set ID - scale::Encode::encode_to(&1u32, output); - 0 - } - } - ink_env::test::register_chain_extension(ExtensionId); - - struct ExtensionSets; - impl ink_env::test::ChainExtension for ExtensionSets { - fn func_id(&self) -> u32 { - 1 - } - - fn call(&mut self, _: &[u8], output: &mut Vec) -> u32 { - // 1 validator set - scale::Encode::encode_to(&1u8, output); - 0 - } - } - ink_env::test::register_chain_extension(ExtensionSets); - - struct ExtensionShares; - impl ink_env::test::ChainExtension for ExtensionShares { - fn func_id(&self) -> u32 { - 2 - } - - fn call(&mut self, _: &[u8], output: &mut Vec) -> u32 { - // 1 key share per validator - scale::Encode::encode_to(&u16::try_from(test_validators().len()).unwrap(), output); - 0 - } - } - ink_env::test::register_chain_extension(ExtensionShares); - - struct ExtensionActive; - impl ink_env::test::ChainExtension for ExtensionActive { - fn func_id(&self) -> u32 { - 3 - } - - fn call(&mut self, input: &[u8], output: &mut Vec) -> u32 { - use scale::Decode; - let potential = AccountId::decode(&mut &input[1 ..]).unwrap(); // TODO: Why is this [1 ..]? - - let mut presence = false; - for validator in test_validators() { - if potential == validator { - presence = true; - } - } - // Validator set 0, 1 key share - scale::Encode::encode_to(&Some((0u8, 1u16)).filter(|_| presence), output); - 0 - } - } - ink_env::test::register_chain_extension(ExtensionActive); -} diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml deleted file mode 100644 index 6fb4d08b..00000000 --- a/contracts/multisig/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "serai-multisig" -version = "0.1.0" -description = "An ink! tracker for Serai's current multisig" -license = "AGPL-3.0-only" -repository = "https://github.com/serai-dex/serai/tree/develop/contracts/multisig" -authors = ["Luke Parker "] -edition = "2021" -publish = false - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[lib] -name = "serai_multisig" -path = "lib.rs" -crate-type = ["cdylib"] - -[dependencies] -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } -scale-info = { version = "2", default-features = false, features = ["derive"], optional = true } - -ink_primitives = { version = "3", default-features = false } -ink_metadata = { version = "3", default-features = false, features = ["derive"], optional = true } -ink_env = { version = "3", default-features = false } -ink_storage = { version = "3", default-features = false } -ink_lang = { version = "3", default-features = false } - -serai-extension = { path = "../extension", default-features = false } - -[dev-dependencies] -lazy_static = "1" - -[features] -default = ["std"] -std = [ - "scale/std", - "scale-info/std", - - "ink_primitives/std", - "ink_metadata/std", - "ink_env/std", - "ink_storage/std", - - "serai-extension/std", -] -ink-as-dependency = [] diff --git a/contracts/multisig/lib.rs b/contracts/multisig/lib.rs deleted file mode 100644 index 62167a42..00000000 --- a/contracts/multisig/lib.rs +++ /dev/null @@ -1,356 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -use ink_lang as ink; - -use serai_extension::{Curve, GlobalValidatorSetId, ValidatorSetIndex, Key}; - -type KeysHash = [u8; 32]; - -#[allow(clippy::all)] -#[ink::contract(env = serai_extension::SeraiEnvironment)] -mod multisig { - use scale::Encode; - - use ink_storage::{traits::SpreadAllocate, Mapping}; - use ink_env::{hash::Blake2x256, hash_encoded}; - - use super::*; - - /// A contract which tracks the current multisig keys. - /// Mapping of each validator set to their multisigs. - #[ink(storage)] - #[derive(SpreadAllocate)] - pub struct Multisig { - /// Global validator set ID under which this multisig was updated. - /// Used to track if the multisig has been updated to the latest instantiation of a validator - /// set or not. - /// May be behind, and still healthy, if a validator set didn't change despite the global - /// validator set doing so. - updated_at: Mapping, - /// Mapping from a curve's index to the multisig's current public key for it, if it has one. - // This is a mapping due to ink's eager loading. Considering we're right now only considering - // Secp256k1 and Ed25519, it may be notably more efficient to use a Vec here. - // In practice, we're likely discussing up to 7 curves in total, so it may always be better to - // simply use a Vec here, especially since it'd be Vec>. - keys: Mapping<(ValidatorSetIndex, Curve), Key>, - /// Validator + Keys -> Voted already or not. - /// Prevents voting multiple times on the same set of keys. - voted: Mapping<(AccountId, KeysHash), ()>, - /// Global Validator Set ID + Validator + Keys -> Vote Count. - /// Including the GVSID locks it to a specific time period, preventing a validator from joining - /// a set, voting on old keys, and then moving their bond to a new account to vote again. - votes: Mapping<(GlobalValidatorSetId, ValidatorSetIndex, KeysHash), u16>, - } - - /// Event emitted when a new set of multisig keys is voted on. - #[ink(event)] - pub struct Vote { - /// Validator who issued the vote. - #[ink(topic)] - validator: AccountId, - /// Global validator set ID under which keys are being generated. - #[ink(topic)] - global_validator_set: GlobalValidatorSetId, - /// Validator set for which keys are being generated. - #[ink(topic)] - validator_set: ValidatorSetIndex, - /// Hash of the keys voted on. - #[ink(topic)] - hash: KeysHash, - /// Keys voted on. Only present in the first event for a given set of keys. - keys: Option>>, - } - - /// Event emitted when the new keys are fully generated for a validator set, having been fully - /// voted on. - #[ink(event)] - pub struct KeyGen { - #[ink(topic)] - global_validator_set: GlobalValidatorSetId, - #[ink(topic)] - validator_set: ValidatorSetIndex, - #[ink(topic)] - hash: KeysHash, - } - - /// The Multisig error types. - #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] - #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] - pub enum Error { - /// Returned if a validator set hasn't had keys registered for it yet. - NonExistentValidatorSet, - /// Returned if a validator set and curve index doesn't have a key registered for it. - NonExistentKey, - /// Returned if a curve index doesn't exist. - NonExistentCurve, - /// Returned if a non-validator is voting. - NotValidator, - /// Returned if this validator set already generated keys. - AlreadyGeneratedKeys, - /// Returned if this validator has already voted for these keys. - AlreadyVoted, - } - - /// The Multisig result type. - pub type Result = core::result::Result; - - impl Multisig { - /// Deploys the Multisig contract. - #[ink(constructor)] - pub fn new() -> Self { - ink_lang::utils::initialize_contract(|_| {}) - } - - /// Global validator set ID under which a validator set updated their multisig. - #[ink(message)] - pub fn updated_at(&self, validator_set: ValidatorSetIndex) -> Result { - self.updated_at.get(validator_set).ok_or(Error::NonExistentValidatorSet) - } - - /// Returns the key currently in-use for a given validator set and curve. - /// This is then bound to a given chain by applying a network-specific additive offset, as done - /// by the processor. Each chain then has its own way of receiving funds to these keys, leaving - /// this not for usage by wallets, nor the processor which is expected to track events for this - /// information. This is really solely for debugging purposes. - #[ink(message)] - pub fn key(&self, validator_set: ValidatorSetIndex, curve: Curve) -> Result { - self.keys.get((validator_set, curve)).ok_or(Error::NonExistentKey) - } - - // TODO: voted - // TODO: votes - - fn hash(value: &T) -> KeysHash { - let mut output = KeysHash::default(); - hash_encoded::(value, &mut output); - output - } - - /// Vote for a given set of keys. - #[ink(message)] - pub fn vote(&mut self, keys: Vec>) -> Result<()> { - if keys.len() > 256 { - Err(Error::NonExistentCurve)?; - } - - // Make sure they're a valid validator. - let validator = self.env().caller(); - let active_validator = self.env().extension().active_validator(&validator); - if active_validator.is_none() { - Err(Error::NotValidator)?; - } - let (validator_set, shares) = active_validator.unwrap(); - - // Prevent a validator set from generating keys multiple times. Only the first-voted-in keys - // should be acknowledged. - let global_validator_set = self.env().extension().global_validator_set_id(); - if self.updated_at.get(validator_set) == Some(global_validator_set) { - Err(Error::AlreadyGeneratedKeys)?; - } - - // Prevent a validator from voting on keys multiple times. - let keys_hash = Self::hash(&keys); - if self.voted.get((validator, keys_hash)).is_some() { - Err(Error::AlreadyVoted)?; - } - self.voted.insert((validator, keys_hash), &()); - - let votes = - if let Some(votes) = self.votes.get((global_validator_set, validator_set, keys_hash)) { - self.env().emit_event(Vote { - validator, - global_validator_set, - validator_set, - hash: keys_hash, - keys: None, - }); - votes + shares - } else { - self.env().emit_event(Vote { - validator, - global_validator_set, - validator_set, - hash: keys_hash, - keys: Some(keys.clone()), - }); - shares - }; - // We could skip writing this if we've reached consensus, yet best to keep our ducks in a row - self.votes.insert((global_validator_set, validator_set, keys_hash), &votes); - - // If we've reached consensus, action this. - if votes == self.env().extension().validator_set_shares(validator_set) { - self.updated_at.insert(validator_set, &global_validator_set); - for (k, key) in keys.iter().enumerate() { - if let Some(key) = key { - self.keys.insert((validator_set, Curve::try_from(k).unwrap()), key); - } - } - self.env().emit_event(KeyGen { global_validator_set, validator_set, hash: keys_hash }); - } - - Ok(()) - } - } - - #[cfg(test)] - mod tests { - use lazy_static::lazy_static; - - use ink_env::{ - hash::{CryptoHash, Blake2x256}, - AccountId, - topics::PrefixedValue, - }; - use ink_lang as ink; - - use serai_extension::{test_validators, test_register}; - - use super::*; - - type Event = ::Type; - - lazy_static! { - static ref EXPECTED_GLOBAL_VALIDATOR_SET: GlobalValidatorSetId = 1; - static ref EXPECTED_VALIDATOR_SET: ValidatorSetIndex = 0; - static ref KEYS: Vec> = vec![Some(vec![0, 1]), Some(vec![2, 3])]; - static ref EXPECTED_HASH: KeysHash = { - let mut hash = KeysHash::default(); - ink_env::hash_encoded::(&*KEYS, &mut hash); - hash - }; - } - - fn hash_prefixed(prefixed: PrefixedValue) -> [u8; 32] { - let encoded = prefixed.encode(); - let mut hash = KeysHash::default(); - if encoded.len() < 32 { - hash[.. encoded.len()].copy_from_slice(&encoded); - } else { - Blake2x256::hash(&encoded, &mut hash); - } - hash - } - - fn assert_vote( - event: &ink_env::test::EmittedEvent, - expected_validator: AccountId, - expected_keys: Option<()>, - ) { - let decoded_event = ::decode(&mut &event.data[..]) - .expect("encountered invalid contract event data buffer"); - - if let Event::Vote(Vote { - validator, - global_validator_set, - validator_set, - hash, - keys: actual_keys, - }) = decoded_event - { - assert_eq!(validator, expected_validator); - assert_eq!(global_validator_set, *EXPECTED_GLOBAL_VALIDATOR_SET); - assert_eq!(validator_set, *EXPECTED_VALIDATOR_SET); - assert_eq!(hash, *EXPECTED_HASH); - assert_eq!(actual_keys.as_ref(), expected_keys.map(|_| &*KEYS)); - } else { - panic!("invalid Vote event") - } - - let expected_topics = vec![ - hash_prefixed(PrefixedValue { prefix: b"", value: b"Multisig::Vote" }), - hash_prefixed(PrefixedValue { - prefix: b"Multisig::Vote::validator", - value: &expected_validator, - }), - hash_prefixed(PrefixedValue { - prefix: b"Multisig::Vote::global_validator_set", - value: &*EXPECTED_GLOBAL_VALIDATOR_SET, - }), - hash_prefixed(PrefixedValue { - prefix: b"Multisig::Vote::validator_set", - value: &*EXPECTED_VALIDATOR_SET, - }), - hash_prefixed(PrefixedValue { prefix: b"Multisig::Vote::hash", value: &*EXPECTED_HASH }), - ]; - - for (n, (actual_topic, expected_topic)) in - event.topics.iter().zip(expected_topics).enumerate() - { - assert_eq!(actual_topic, &expected_topic, "encountered invalid topic at {}", n); - } - } - - fn assert_key_gen(event: &ink_env::test::EmittedEvent) { - let decoded_event = ::decode(&mut &event.data[..]) - .expect("encountered invalid contract event data buffer"); - - if let Event::KeyGen(KeyGen { global_validator_set, validator_set, hash }) = decoded_event { - assert_eq!(global_validator_set, *EXPECTED_GLOBAL_VALIDATOR_SET); - assert_eq!(validator_set, *EXPECTED_VALIDATOR_SET); - assert_eq!(hash, *EXPECTED_HASH); - } else { - panic!("invalid KeyGen event") - } - - let expected_topics = vec![ - hash_prefixed(PrefixedValue { prefix: b"", value: b"Multisig::KeyGen" }), - hash_prefixed(PrefixedValue { - prefix: b"Multisig::KeyGen::global_validator_set", - value: &*EXPECTED_GLOBAL_VALIDATOR_SET, - }), - hash_prefixed(PrefixedValue { - prefix: b"Multisig::KeyGen::validator_set", - value: &*EXPECTED_VALIDATOR_SET, - }), - hash_prefixed(PrefixedValue { prefix: b"Multisig::KeyGen::hash", value: &*EXPECTED_HASH }), - ]; - - for (n, (actual_topic, expected_topic)) in - event.topics.iter().zip(expected_topics).enumerate() - { - assert_eq!(actual_topic, &expected_topic, "encountered invalid topic at {}", n); - } - } - - /// The default constructor does its job. - #[ink::test] - fn new() { - let multisig = Multisig::new(); - assert_eq!(multisig.updated_at(0), Err(Error::NonExistentValidatorSet)); - } - - /// Non-existent keys error accordingly. - #[ink::test] - fn non_existent_key() { - assert_eq!(Multisig::new().key(0, 0), Err(Error::NonExistentKey)); - } - - #[ink::test] - fn success() { - test_register(); - let mut multisig = Multisig::new(); - - // Test voting on keys works without issue, emitting the keys for the first vote - let mut emitted_events = vec![]; - for (i, validator) in test_validators().iter().enumerate() { - ink_env::test::set_caller::(*validator); - multisig.vote(KEYS.clone()).unwrap(); - - emitted_events = ink_env::test::recorded_events().collect::>(); - // If this is the last validator, it should also trigger a keygen event, hence the + 1 - assert_eq!(emitted_events.len(), (i + 1) + (i / (test_validators().len() - 1))); - assert_vote( - &emitted_events[i], - *validator, - // Only the first event for this hash should have the keys - Some(()).filter(|_| i == 0), - ); - } - - // Since this should have key gen'd, verify that - assert_eq!(multisig.updated_at(0).unwrap(), *EXPECTED_GLOBAL_VALIDATOR_SET); - assert_key_gen(&emitted_events[test_validators().len()]); - } - } -} diff --git a/deny.toml b/deny.toml index 0909db18..ca7751f0 100644 --- a/deny.toml +++ b/deny.toml @@ -42,8 +42,10 @@ exceptions = [ { allow = ["AGPL-3.0"], name = "ethereum-serai" }, { allow = ["AGPL-3.0"], name = "serai-processor" }, - { allow = ["AGPL-3.0"], name = "serai-extension" }, - { allow = ["AGPL-3.0"], name = "serai-multisig" }, + { allow = ["AGPL-3.0"], name = "serai-primitives" }, + + { allow = ["AGPL-3.0"], name = "validator-sets-primitives" }, + { allow = ["AGPL-3.0"], name = "validator-sets-pallet" }, { allow = ["AGPL-3.0"], name = "sp-tendermint" }, { allow = ["AGPL-3.0"], name = "pallet-tendermint" }, diff --git a/docs/Serai.md b/docs/Serai.md index 683bc897..d2e21d2e 100644 --- a/docs/Serai.md +++ b/docs/Serai.md @@ -5,29 +5,10 @@ for various connected networks, offering secure decentralized custody of foreign assets to applications built on it. Serai is exemplified by Serai DEX, an automated-market-maker (AMM) decentralized -exchange, allowing swapping BTC, ETH, USDC, DAI, and XMR. It is the premier +exchange, allowing swapping Bitcoin, Ether, DAI, and Monero. It is the premier application of Serai. ### Substrate Serai is based on [Substrate](https://docs.substrate.io), a blockchain framework offering a robust infrastructure. - -### Smart Contracts - -Serai offers WASM-based smart contracts. All applications are built over these -contracts, enabling composable interactions within a mutual layer. These -contracts are primarily written in [ink!](https://ink.substrate.io/), a -framework for building contracts in Rust. - -Initially, smart contract deployment will not be enabled. Solely Serai DEX will -be available, due to the variety of economic considerations around securing the -multisig. Serai may expand in the future with more explicitly added -applications, each with tailored economic models, or may enable arbitrary -contract deployment. At this time, we solely plan for Serai DEX's availabiliy. - -### Application Calls - -Applications, such as Serai DEX, may be called via calling their relevant smart -contracts. At a low level, this is done via specifying the address of the -contract being interacted with, along with SCALE-encoded calldata. diff --git a/docs/protocol/Constants.md b/docs/protocol/Constants.md index 2cbe45b9..88c09f4e 100644 --- a/docs/protocol/Constants.md +++ b/docs/protocol/Constants.md @@ -5,41 +5,30 @@ These are the list of types used to represent various properties within the protocol. -| Alias | Shorthand | Type | -|-------------------------|-----------|----------| -| Amount | Amount | u64 | -| Curve | Curve | u16 | -| Coin | Coin | u32 | -| Global Validator Set ID | GVSID | u32 | -| Validator Set Index | VS | u8 | -| Key | Key | Vec\ | - -### Curves - -Integer IDs for various curves. It should be noted some curves may be the same, -yet have distinct IDs due to having different basepoints, and accordingly -different keys. For such cases, the processor is expected to create one secret -per curve, and then use DLEq proofs to port keys to other basepoints as needed. - -| Curve | ID | -|-----------|----| -| Secp256k1 | 0 | -| Ed25519 | 1 | +| Alias | Type | +|------------------------|--------------------------------| +| Amount | u64 | +| Coin | u32 | +| Session | u32 | +| Validator Set Index | u16 | +| Validator Set Instance | (Session, Validator Set Index) | +| Key | Vec\ | ### Networks -Every network connected to Serai operates over a specific curve. While the -processor generates keys for curves, these keys are bound to specific networks -via an additive offset created by hashing the network's name (among other -things). The network's key is used for all coins on that network. +Every network connected to Serai operates over a specific curve. The processor +generates a distinct set of keys per network. Beyond the key-generation itself +being isolated, the generated keys are further bound to their respective +networks via an additive offset created by hashing the network's name (among +other properties). The network's key is used for all coins on that network. Networks are not acknowledged by the Serai network, solely by the processor. -| Network | Curve | -|----------|-------| -| Bitcoin | 0 | -| Ethereum | 0 | -| Monero | 1 | +| Network | Curve | +|----------|-----------| +| Bitcoin | Secp256k1 | +| Ethereum | Secp256k1 | +| Monero | Ed25519 | ### Coins @@ -48,7 +37,6 @@ Coins exist over a network and have a distinct integer ID. | Coin | Network | ID | |----------|----------|----| | Bitcoin | Bitcoin | 0 | -| Ethereum | Ethereum | 1 | -| USDC | Ethereum | 2 | -| DAI | Ethereum | 3 | -| Monero | Monero | 4 | +| Ether | Ethereum | 1 | +| DAI | Ethereum | 2 | +| Monero | Monero | 3 | diff --git a/docs/protocol/Multisig.md b/docs/protocol/Multisig.md deleted file mode 100644 index 7a572a98..00000000 --- a/docs/protocol/Multisig.md +++ /dev/null @@ -1,35 +0,0 @@ -# Multisig - -Multisigs are confirmed on-chain by the `Multisig` contract. While the processor -does create the multisig, and sign for it, making it irrelevant to the chain, -confirming it on-chain solves the question of if the multisig was successfully -created or not. If each processor simply asked all other processors for -confirmation, votes lost to the network would create an inconsistent view. This -is a form of the Byzantine Generals Problem, which can be resolved by placing -votes within a BFT system. - -Confirmation requires all participants confirm the new set of keys. While this -isn't BFT, despite the voting process being BFT, it avoids the scenario where -only t (where t is the BFT threshold, as used in the t-of-n multisig) -successfully generated shares, actually creating a t-of-t multisig in practice, -which is not BFT. This does mean a single node can delay a churn, which is -expected to be handled via a combination of slashing, and if necessary, removal. - -Validators are allowed to vote multiple times across sets of keys, with the -first set to be confirmed becoming the set of keys for that validator set. These -keys remain valid for the validator set until it is changed. If a validator set -remains consistent despite the global validator set updating, their keys carry. -If a validator set adds a new member, and then loses them, their historical keys -are not reused. - -Once new keys are confirmed for a given validator set, they become tracked and -the recommended set of keys for incoming funds. The old keys are still eligible -to receive funds for a provided grace period, requiring the current validator -set to track both sets of keys. The old keys are also still used to handle all -outgoing payments as well, until the end of the grace period, at which point -they're no longer eligible to receive funds and they forward all of their funds -to the new set of keys. - -### `vote(keys: Vec>)` - -Lets a validator vote on a set of keys for their validator set. diff --git a/docs/protocol/Staking.md b/docs/protocol/Staking.md new file mode 100644 index 00000000..f84fb035 --- /dev/null +++ b/docs/protocol/Staking.md @@ -0,0 +1,19 @@ +# Staking + +Serai's staking pallet offers a DPoS system. All stake which enters the system +is delegated somewhere. Delegates can then bond their stake to different +validator sets, justifying their inclusion and providing financial security. + +Delegators may transfer stake whenever, so long as that stake isn't actively +bonded. Delegators may also unstake whenever, so long as the prior condition +is still met. + +### Stake (message) + + - `delegate` (Address): Address to delegate the newly added stake to. + - `amount` (Amount): Amount to stake and delegate. + +### Unstake (message) + + - `delegate` (Address): Address the stake is currently delegated to. + - `amount` (Amount): Amount to unstake. diff --git a/docs/protocol/Validator Sets.md b/docs/protocol/Validator Sets.md index d89510cd..eb6abd7c 100644 --- a/docs/protocol/Validator Sets.md +++ b/docs/protocol/Validator Sets.md @@ -2,29 +2,80 @@ Validator Sets are defined at the protocol level, with the following parameters: - - `index` (VS): Validator set index, a global key atomically increasing -from 0. - - `bond` (Amount): Amount of bond per key-share of this validator set. - - `coins` (Vec\): Coins managed by this validator set. + - `bond` (Amount): Amount of bond per key-share. + - `coins` (Vec\): List of coins within this set. + - `participants` (Vec\): List of participants within this set. -At launch, there will solely be validator set 0, managing Bitcoin, Ethereum, -USDC, DAI, and Monero. +Validator Sets are referred to by `ValidatorSetIndex` yet have their data +accessible via `ValidatorSetInstance`. -### Multisig Management - -Every validator set is expected to form a t-of-n multisig, where n is the amount -of key shares in the validator set and t is `n / 3 * 2 + 1`, per curve required -by its coins. This multisig is secure to hold funds up to 67% of the validator -set's bond value. If funds exceed that threshold, there's more value in the -multisig than in the supermajority of bond that must be put forth to control it. +At launch, there will solely be Validator Set 0, managing Bitcoin, Ether, DAI, +and Monero. ### Participation in the BFT process -All validator sets participate in the BFT process. Specifically, a block -containing `Oraclization`s for a coin must be approved by the BFT majority of -the validator set responsible for it, along with the BFT majority of the network -by bond. +All Validator Sets participate in the BFT process described under +[Consensus](./Consensus.md). Specifically, a block containing In Instructions +for a coin must be approved by the BFT majority of the Validator Set responsible +for it, along with the BFT majority of the network by bond. -At this time, `Oraclization`s for a coin are only expected to be included when a -validator from the validator set managing the coin is the producer of the block +At this time, In Instructions for a coin are only expected to be included when a +validator from the Validator Set managing the coin is the producer of the block in question. + +Since there is currently only one Validator Set, the aforementioned BFT +conditions collapse to simply the BFT majority by bond. Ensuring BFT majority +per responsible Validator Set is accordingly unimplemented for now. + +### Multisig + +Every Validator Set is expected to form a `t`-of-`n` multisig, where `n` is the +amount of key shares in the Validator Set and `t` is `n * 2 / 3 + 1`, for each +of its networks. This multisig is secure to hold coins up to 67% of the +Validator Set's bonded value. If the coins exceed that threshold, there's more +value in the multisig than in the supermajority of bond that must be put forth +to control it. Accordingly, it'd be no longer financially secure, and it MUST +reject newly added coins which would cross that threshold. + +### Multisig Creation + +Multisigs are created by processors, communicating via their Coordinators. +They're then confirmed on chain via the `validator-sets` pallet. This is done by +having 100% of participants agree on the resulting group key. While this isn't +fault tolerant, a malicious actor who forces a `t`-of-`n` multisig to be +`t`-of-`n-1` reduces the fault tolerance of the multisig which is a greater +issue. If a node does prevent multisig creation, other validators should issue +slashes for it/remove it from the Validator Set entirely. + +Due to the fact multiple key generations may occur to account for +faulty/malicious nodes, voting on multiple keys for a single coin is allowed, +with the first key to be confirmed becoming the key for that coin. + +Placing it on chain also solves the question of if the multisig was successfully +created or not. Processors cannot simply ask each other if they succeeded +without creating an instance of the Byzantine Generals Problem. Placing results +within a Byzantine Fault Tolerant system resolves this. + +### Multisig Lifetime + +The keys for a Validator Set remain valid until its participants change. If a +Validator Set adds a new member, and then they leave, the set's historical keys +are not reused. + +### Multisig Handoffs + +Once new keys are confirmed for a given Validator Set, they become tracked and +the recommended set of keys for incoming coins. The old keys are still eligible +to receive coins for a provided grace period, requiring the current Validator +Set to track both sets of keys. The old keys are also prioritized for handling +outbound transfers, until the end of the grace period, at which point they're +no longer eligible to receive coins and they forward all of their coins to the +new set of keys. It is only then that validators in the previous instance of the +set, yet not the current instance, may unbond their stake. + +### Vote (message) + + - `coin` (Coin): Coin whose key is being voted for. + - `key` (Key): Key being voted on. + +Once a key is voted on by every member, it's adopted as detailed above. diff --git a/docs/protocol/Validators.md b/docs/protocol/Validators.md deleted file mode 100644 index c750a1a9..00000000 --- a/docs/protocol/Validators.md +++ /dev/null @@ -1,44 +0,0 @@ -# Validators - -### Register (message) - - - `validator` (signer): Address which will be the validator on Substrate. - - `manager` (signer): Address which will manage this validator. - - `set` (VS): Validator set being joined. - -Marks `validator` as a validator candidate for the specified validator set, -enabling delegation. - -### Delegate (message) - - - `delegator` (signer): Address delegating funds to `validator`. - - `validator` (address): Registered validator being delegated to. - - `amount` (Amount): Amount of funds being delegated to `validator`. - -Delegated funds will be removed from `delegator`'s wallet and moved to -`validator`'s bond. `amount` must be a multiple of the validator set's bond, and -`delegator` must be `validator`'s manager. - -### Undelegate (message) - - - `delegator` (signer): Address removing delegated funds from `validator`. - - `validator` (address): Registered validator no longer being delegated to. - - `amount` (Amount): Amount of funds no longer being delegated to -`validator`. - -`delegator` must be `validator`'s manager, and `amount` must be a multiple of -the validator set's bond. `validator` is scheduled to lose an according amount -of key shares at the next churn, and once they do, the specified amount will be -moved from `validator`'s bond to `delegator`'s wallet. - -`validator`'s bond must be at least the validator set's bond after the -undelegation. - -### Resign (message) - - - `manager` (signer): Manager of `validator`. - - `validator` (address): Validator being removed from the pool/candidacy. - -If `validator` is active, they will be removed at the next churn. If they are -solely a candidate, they will no longer be eligible for delegations. All bond is -refunded after their removal. diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index 6f12d087..f83eb24e 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -59,6 +59,10 @@ sc-rpc-api = { git = "https://github.com/serai-dex/substrate" } substrate-frame-rpc-system = { git = "https://github.com/serai-dex/substrate" } pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate" } +serai-primitives = { path = "../serai/primitives" } + +validator-sets-pallet = { path = "../validator-sets/pallet" } + sp-tendermint = { path = "../tendermint/primitives" } pallet-tendermint = { path = "../tendermint/pallet", default-features = false } serai-runtime = { path = "../runtime" } @@ -73,5 +77,5 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-benchmarking-cli/runtime-benchmarks", - "serai-runtime/runtime-benchmarks" + "serai-runtime/runtime-benchmarks", ] diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 1d45002f..bb7dd0e7 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -1,11 +1,12 @@ +use sp_core::{Pair as PairTrait, sr25519::Pair}; use sc_service::ChainType; -use sp_core::{Pair as PairTrait, sr25519::Pair}; +use serai_primitives::{Amount, COIN, Coin}; use pallet_tendermint::crypto::Public; use serai_runtime::{ WASM_BINARY, AccountId, opaque::SessionKeys, GenesisConfig, SystemConfig, BalancesConfig, - SessionConfig, + ValidatorSetsConfig, SessionConfig, }; pub type ChainSpec = sc_service::GenericChainSpec; @@ -34,6 +35,12 @@ fn testnet_genesis( balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), }, transaction_payment: Default::default(), + + validator_sets: ValidatorSetsConfig { + bond: Amount(1_000_000) * COIN, + coins: Coin(4), + participants: validators.iter().map(|name| account_id_from_name(name)).collect(), + }, session: SessionConfig { keys: validators.iter().map(|name| session_key(*name)).collect() }, } } diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index bfa4b172..7960b012 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -37,13 +37,10 @@ frame-executive = { git = "https://github.com/serai-dex/substrate", default-feat frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true } pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false } -pallet-randomness-collective-flip = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-balances = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false } -pallet-contracts-primitives = { git = "https://github.com/serai-dex/substrate", default-features = false } -pallet-contracts = { git = "https://github.com/serai-dex/substrate", default-features = false } - +validator-sets-pallet = { path = "../validator-sets/pallet", default-features = false } pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-tendermint = { path = "../tendermint/pallet", default-features = false } @@ -77,13 +74,10 @@ std = [ "frame-executive/std", "pallet-timestamp/std", - "pallet-randomness-collective-flip/std", "pallet-balances/std", "pallet-transaction-payment/std", - "pallet-contracts/std", - "pallet-contracts-primitives/std", - + "validator-sets-pallet/std", "pallet-session/std", "pallet-tendermint/std", diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 2b397ab0..4afc9d47 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -18,7 +18,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use frame_support::{ - traits::{ConstBool, ConstU8, ConstU32, ConstU64}, + traits::{ConstU8, ConstU32, ConstU64}, weights::{ constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND}, IdentityFee, Weight, @@ -130,7 +130,6 @@ parameter_types! { .get(DispatchClass::Normal) .max_total .unwrap_or(BlockWeights::get().max_block); - pub Schedule: pallet_contracts::Schedule = Default::default(); } impl frame_system::Config for Runtime { @@ -163,8 +162,6 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } -impl pallet_randomness_collective_flip::Config for Runtime {} - impl pallet_timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = (); @@ -193,34 +190,6 @@ impl pallet_transaction_payment::Config for Runtime { type FeeMultiplierUpdate = (); } -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = RandomnessCollectiveFlip; - type Currency = Balances; - type RuntimeEvent = RuntimeEvent; - type RuntimeCall = RuntimeCall; - - type CallFilter = frame_support::traits::Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type CallStack = [pallet_contracts::Frame; 31]; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = (); - type DeletionQueueDepth = DeletionQueueDepth; - type DeletionWeightLimit = DeletionWeightLimit; - type Schedule = Schedule; - type AddressGenerator = pallet_contracts::DefaultAddressGenerator; - - type MaxCodeLen = ConstU32<{ 128 * 1024 }>; - type MaxStorageKeyLen = ConstU32<128>; - - type UnsafeUnstableInterface = ConstBool; - type MaxDebugBufferLen = ConstU32<255>; -} - -impl pallet_tendermint::Config for Runtime {} - const SESSION_LENGTH: BlockNumber = 5 * DAYS; type Sessions = PeriodicSessions, ConstU32<{ SESSION_LENGTH }>>; @@ -231,6 +200,10 @@ impl Convert> for IdentityValidatorIdOf { } } +impl validator_sets_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + impl pallet_session::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ValidatorId = AccountId; @@ -243,6 +216,8 @@ impl pallet_session::Config for Runtime { type WeightInfo = pallet_session::weights::SubstrateWeight; } +impl pallet_tendermint::Config for Runtime {} + pub type Address = AccountId; pub type Header = generic::Header; pub type Block = generic::Block; @@ -274,11 +249,11 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic { System: frame_system, - RandomnessCollectiveFlip: pallet_randomness_collective_flip, Timestamp: pallet_timestamp, Balances: pallet_balances, TransactionPayment: pallet_transaction_payment, - Contracts: pallet_contracts, + + ValidatorSets: validator_sets_pallet, Session: pallet_session, Tendermint: pallet_tendermint, } diff --git a/substrate/serai/primitives/Cargo.toml b/substrate/serai/primitives/Cargo.toml new file mode 100644 index 00000000..24231788 --- /dev/null +++ b/substrate/serai/primitives/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "serai-primitives" +version = "0.1.0" +description = "Primitives for the Serai blockchain" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/serai/primitives" +authors = ["Luke Parker "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +serde = { version = "1.0", features = ["derive"], optional = true } + +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } + +[features] +std = ["scale/std", "scale-info/std", "serde", "sp-core/std", "sp-std/std"] +default = ["std"] diff --git a/contracts/extension/LICENSE b/substrate/serai/primitives/LICENSE similarity index 100% rename from contracts/extension/LICENSE rename to substrate/serai/primitives/LICENSE diff --git a/substrate/serai/primitives/src/lib.rs b/substrate/serai/primitives/src/lib.rs new file mode 100644 index 00000000..829942d3 --- /dev/null +++ b/substrate/serai/primitives/src/lib.rs @@ -0,0 +1,36 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use core::ops::{Add, Mul}; + +use scale::{Encode, Decode, MaxEncodedLen}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; + +/// The type used for amounts. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Amount(pub u64); + +impl Add for Amount { + type Output = Amount; + fn add(self, other: Amount) -> Amount { + Amount(self.0 + other.0) + } +} + +impl Mul for Amount { + type Output = Amount; + fn mul(self, other: Amount) -> Amount { + Amount(self.0 * other.0) + } +} + +/// One whole coin with eight decimals. +#[allow(clippy::inconsistent_digit_grouping)] +pub const COIN: Amount = Amount(1_000_000_00); + +/// The type used to identify coins. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Coin(pub u32); diff --git a/substrate/validator-sets/pallet/Cargo.toml b/substrate/validator-sets/pallet/Cargo.toml new file mode 100644 index 00000000..e5f423e4 --- /dev/null +++ b/substrate/validator-sets/pallet/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "validator-sets-pallet" +version = "0.1.0" +description = "Validator sets pallet" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/validator-sets/pallet" +authors = ["Luke Parker "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } + +serai-primitives = { path = "../../serai/primitives", default-features = false } +validator-sets-primitives = { path = "../primitives", default-features = false } + +[features] +std = [ + "scale/std", + "scale-info/std", + + "frame-system/std", + "frame-support/std", + + "serai-primitives/std", + "validator-sets-primitives/std", +] + +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", +] + +default = ["std"] diff --git a/contracts/multisig/LICENSE b/substrate/validator-sets/pallet/LICENSE similarity index 100% rename from contracts/multisig/LICENSE rename to substrate/validator-sets/pallet/LICENSE diff --git a/substrate/validator-sets/pallet/src/lib.rs b/substrate/validator-sets/pallet/src/lib.rs new file mode 100644 index 00000000..7ac8a700 --- /dev/null +++ b/substrate/validator-sets/pallet/src/lib.rs @@ -0,0 +1,207 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[frame_support::pallet] +pub mod pallet { + use scale::{Encode, Decode}; + use scale_info::TypeInfo; + + use frame_system::pallet_prelude::*; + use frame_support::pallet_prelude::*; + + use serai_primitives::*; + use validator_sets_primitives::*; + + #[pallet::config] + pub trait Config: frame_system::Config + TypeInfo { + type RuntimeEvent: IsType<::RuntimeEvent> + From>; + } + + #[pallet::genesis_config] + #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen)] + pub struct GenesisConfig { + /// Bond requirement to join the initial validator set. + /// Every participant at genesis will automatically be assumed to have this much bond. + /// This bond cannot be withdrawn however as there's no stake behind it. + pub bond: Amount, + /// Amount of coins to spawn the network with in the initial validator set. + pub coins: Coin, + /// List of participants to place in the genesis set. + pub participants: Vec, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { bond: Amount(1), coins: Coin(0), participants: vec![] } + } + } + + // Max of 16 coins per validator set + // At launch, we'll have BTC, ETH, DAI, and XMR + // In the future, these will be split into separate validator sets, so we're already not + // planning expansion beyond just a few coins per validator set + // The only case which really makes sense for multiple coins in a validator set is: + // 1) The coins are small, easy to run, and make no sense to be in their own set + // In this case, it's still hard to ask validators to run 16 different nodes + // 2) The coins are all on the same network yet there's no DEX on-chain + // In these cases, it'd be hard to find and justify 16 different coins from that single chain + // This could probably be just 8, yet 16 is a hedge for the unforseen + // If necessary, this can be increased with a fork + type MaxCoinsPerSet = ConstU32<16>; + + // Support keys up to 96 bytes (BLS12-381 G2) + const MAX_KEY_LEN: u32 = 96; + type MaxKeyLen = ConstU32; + + #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] + pub struct ValidatorSet { + bond: Amount, + coins: BoundedVec, + + // Participant and their amount bonded to this set + // Limit each set to 100 participants for now + participants: BoundedVec<(T::AccountId, Amount), ConstU32<100>>, + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + /// The details of a validator set instance. + #[pallet::storage] + #[pallet::getter(fn validator_set)] + pub type ValidatorSets = + StorageMap<_, Twox64Concat, ValidatorSetInstance, ValidatorSet, OptionQuery>; + + type Key = BoundedVec; + + /// The key for a given validator set instance coin. + #[pallet::storage] + #[pallet::getter(fn key)] + pub type Keys = + StorageMap<_, Twox64Concat, (ValidatorSetInstance, Coin), Key, OptionQuery>; + + /// If an account has voted for a specific key or not. Prevents them from voting multiple times. + #[pallet::storage] + #[pallet::getter(fn voted)] + pub type Voted = StorageMap<_, Blake2_128Concat, (T::AccountId, Key), (), OptionQuery>; + + /// How many times a key has been voted for. Once consensus is reached, the keys will be adopted. + #[pallet::storage] + #[pallet::getter(fn vote_count)] + pub type VoteCount = + StorageMap<_, Blake2_128Concat, (ValidatorSetInstance, Coin, Key), u16, ValueQuery>; + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + let mut coins = Vec::new(); + for coin in 0 .. self.coins.0 { + coins.push(Coin(coin)); + } + + let mut participants = Vec::new(); + for participant in self.participants.clone() { + participants.push((participant, self.bond)); + } + + ValidatorSets::::set( + ValidatorSetInstance(Session(0), ValidatorSetIndex(0)), + Some(ValidatorSet { + bond: self.bond, + coins: BoundedVec::try_from(coins).unwrap(), + participants: BoundedVec::try_from(participants).unwrap(), + }), + ); + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Vote { + voter: T::AccountId, + instance: ValidatorSetInstance, + coin: Coin, + key: Key, + // Amount of votes the key now has + votes: u16, + }, + KeyGen { + instance: ValidatorSetInstance, + coin: Coin, + key: Key, + }, + } + + #[pallet::error] + pub enum Error { + /// Validator Set doesn't exist. + NonExistentValidatorSet, + /// Non-validator is voting. + NotValidator, + /// Validator Set already generated keys. + AlreadyGeneratedKeys, + /// Vvalidator has already voted for these keys. + AlreadyVoted, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(0)] // TODO + pub fn vote( + origin: OriginFor, + index: ValidatorSetIndex, + coin: Coin, + key: Key, + ) -> DispatchResult { + let signer = ensure_signed(origin)?; + // TODO: Do we need to check the key is within the length bounds? + // The docs suggest the BoundedVec will create/write, yet not read, which could be an issue + // if it can be passed in + + // TODO: Get session + let session: Session = Session(0); + + // Confirm a key hasn't been set for this set instance + let instance = ValidatorSetInstance(session, index); + if Keys::::get((instance, coin)).is_some() { + Err(Error::::AlreadyGeneratedKeys)?; + } + + // Confirm the signer is a validator in the set + let set = ValidatorSets::::get(instance).ok_or(Error::::NonExistentValidatorSet)?; + + if set.participants.iter().any(|participant| participant.0 == signer) { + Err(Error::::NotValidator)?; + } + + // Confirm this signer hasn't already voted for these keys + if Voted::::get((&signer, &key)).is_some() { + Err(Error::::AlreadyVoted)?; + } + Voted::::set((&signer, &key), Some(())); + + // Add their vote + let votes = VoteCount::::mutate((instance, coin, &key), |value| { + *value += 1; + *value + }); + + Self::deposit_event(Event::Vote { voter: signer, instance, coin, key: key.clone(), votes }); + + // If we've reached consensus, set the key + if usize::try_from(votes).unwrap() == set.participants.len() { + Keys::::set((instance, coin), Some(key.clone())); + Self::deposit_event(Event::KeyGen { instance, coin, key }); + } + + Ok(()) + } + } + + // TODO: Support session rotation +} + +pub use pallet::*; diff --git a/substrate/validator-sets/primitives/Cargo.toml b/substrate/validator-sets/primitives/Cargo.toml new file mode 100644 index 00000000..7ddca4ad --- /dev/null +++ b/substrate/validator-sets/primitives/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "validator-sets-primitives" +version = "0.1.0" +description = "Primitives for validator sets" +license = "AGPL-3.0-only" +repository = "https://github.com/serai-dex/serai/tree/develop/substrate/validator-sets/primitives" +authors = ["Luke Parker "] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } + +serde = { version = "1.0", features = ["derive"], optional = true } + +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false } + +[features] +std = ["scale/std", "scale-info/std", "serde", "sp-core/std", "sp-std/std"] +default = ["std"] diff --git a/substrate/validator-sets/primitives/LICENSE b/substrate/validator-sets/primitives/LICENSE new file mode 100644 index 00000000..d6e1814a --- /dev/null +++ b/substrate/validator-sets/primitives/LICENSE @@ -0,0 +1,15 @@ +AGPL-3.0-only license + +Copyright (c) 2022 Luke Parker + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License Version 3 as +published by the Free Software Foundation. + +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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . diff --git a/substrate/validator-sets/primitives/src/lib.rs b/substrate/validator-sets/primitives/src/lib.rs new file mode 100644 index 00000000..642d2800 --- /dev/null +++ b/substrate/validator-sets/primitives/src/lib.rs @@ -0,0 +1,21 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use scale::{Encode, Decode, MaxEncodedLen}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; + +/// The type used to identify a specific session of validators. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Session(pub u32); + +/// The type used to identify a validator set. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ValidatorSetIndex(pub u16); + +/// The type used to identify a specific validator set during a specific session. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ValidatorSetInstance(pub Session, pub ValidatorSetIndex);