mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-01 07:47:57 +00:00
Mixnet integration (#1346)
See #1345, <https://github.com/paritytech/substrate/pull/14207>. This adds all the necessary mixnet components, and puts them together in the "kitchen-sink" node/runtime. The components added are: - A pallet (`frame/mixnet`). This is responsible for determining the current mixnet session and phase, and the mixnodes to use in each session. It provides a function that validators can call to register a mixnode for the next session. The logic of this pallet is very similar to that of the `im-online` pallet. - A service (`client/mixnet`). This implements the core mixnet logic, building on the `mixnet` crate. The service communicates with other nodes using notifications sent over the "mixnet" protocol. - An RPC interface. This currently only supports sending transactions over the mixnet. --------- Co-authored-by: David Emett <dave@sp4m.net> Co-authored-by: Javier Viola <javier@parity.io>
This commit is contained in:
Generated
+241
-70
@@ -116,7 +116,7 @@ dependencies = [
|
||||
"cipher 0.3.0",
|
||||
"ctr 0.8.0",
|
||||
"ghash 0.4.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -130,7 +130,7 @@ dependencies = [
|
||||
"cipher 0.4.4",
|
||||
"ctr 0.9.2",
|
||||
"ghash 0.5.0",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -571,6 +571,12 @@ dependencies = [
|
||||
"sha3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "array-bytes"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6"
|
||||
|
||||
[[package]]
|
||||
name = "array-bytes"
|
||||
version = "6.1.0"
|
||||
@@ -1276,7 +1282,7 @@ dependencies = [
|
||||
name = "binary-merkle-tree"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"env_logger 0.9.3",
|
||||
"hash-db",
|
||||
"log",
|
||||
@@ -1353,6 +1359,18 @@ dependencies = [
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330"
|
||||
dependencies = [
|
||||
"byte-tools",
|
||||
"crypto-mac 0.7.0",
|
||||
"digest 0.8.1",
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
@@ -2180,6 +2198,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "c2-chacha"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d27dae93fe7b1e0424dc57179ac396908c26b035a87234809f5c4dfd1b47dc80"
|
||||
dependencies = [
|
||||
"cipher 0.2.5",
|
||||
"ppv-lite86",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.6"
|
||||
@@ -2236,7 +2264,7 @@ checksum = "5aca1a8fbc20b50ac9673ff014abfb2b5f4085ee1a850d408f14a159c5853ac7"
|
||||
dependencies = [
|
||||
"aead 0.3.2",
|
||||
"cipher 0.2.5",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2269,6 +2297,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chacha"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"keystream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.8.2"
|
||||
@@ -3134,7 +3172,7 @@ checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3146,7 +3184,7 @@ checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3161,6 +3199,16 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
"subtle 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.8.0"
|
||||
@@ -3168,7 +3216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3178,7 +3226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3741,7 +3789,7 @@ dependencies = [
|
||||
name = "cumulus-relay-chain-minimal-node"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-trait",
|
||||
"cumulus-primitives-core",
|
||||
"cumulus-relay-chain-interface",
|
||||
@@ -3968,7 +4016,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.8.1",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3981,7 +4029,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3998,7 +4046,7 @@ dependencies = [
|
||||
"fiat-crypto",
|
||||
"platforms",
|
||||
"rustc_version 0.4.0",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -4313,7 +4361,7 @@ dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4582,7 +4630,7 @@ dependencies = [
|
||||
"pkcs8 0.9.0",
|
||||
"rand_core 0.6.4",
|
||||
"sec1 0.3.0",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -4601,7 +4649,7 @@ dependencies = [
|
||||
"pkcs8 0.10.2",
|
||||
"rand_core 0.6.4",
|
||||
"sec1 0.7.3",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -4801,7 +4849,7 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f86a749cf851891866c10515ef6c299b5c69661465e9c3bbe7e07a2b77fb0f7"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"fs-err",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4902,7 +4950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4912,7 +4960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5065,7 +5113,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
||||
name = "frame-benchmarking"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"frame-support",
|
||||
"frame-support-procedural",
|
||||
"frame-system",
|
||||
@@ -5093,7 +5141,7 @@ name = "frame-benchmarking-cli"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"chrono",
|
||||
"clap 4.4.6",
|
||||
"comfy-table",
|
||||
@@ -5204,7 +5252,7 @@ dependencies = [
|
||||
name = "frame-executive"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"frame-try-runtime",
|
||||
@@ -5261,7 +5309,7 @@ name = "frame-support"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"aquamarine",
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"bitflags 1.3.2",
|
||||
"docify",
|
||||
@@ -5791,7 +5839,7 @@ checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
|
||||
dependencies = [
|
||||
"ff 0.12.1",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5802,7 +5850,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||
dependencies = [
|
||||
"ff 0.13.0",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5888,6 +5936,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
@@ -6663,6 +6720,12 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keystream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28"
|
||||
|
||||
[[package]]
|
||||
name = "kitchensink-runtime"
|
||||
version = "3.0.0-dev"
|
||||
@@ -6711,6 +6774,7 @@ dependencies = [
|
||||
"pallet-lottery",
|
||||
"pallet-membership",
|
||||
"pallet-message-queue",
|
||||
"pallet-mixnet",
|
||||
"pallet-mmr",
|
||||
"pallet-multisig",
|
||||
"pallet-nft-fractionalization",
|
||||
@@ -6764,6 +6828,7 @@ dependencies = [
|
||||
"sp-genesis-builder",
|
||||
"sp-inherents",
|
||||
"sp-io",
|
||||
"sp-mixnet",
|
||||
"sp-offchain",
|
||||
"sp-runtime",
|
||||
"sp-session",
|
||||
@@ -7366,7 +7431,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
"digest 0.9.0",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7449,6 +7514,18 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"blake2 0.8.1",
|
||||
"chacha",
|
||||
"keystream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lite-json"
|
||||
version = "0.2.0"
|
||||
@@ -7779,6 +7856,31 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mixnet"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daa3eb39495d8e2e2947a1d862852c90cc6a4a8845f8b41c8829cb9fcc047f4a"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.7.4",
|
||||
"bitflags 1.3.2",
|
||||
"blake2 0.10.6",
|
||||
"c2-chacha",
|
||||
"curve25519-dalek 4.0.0",
|
||||
"either",
|
||||
"hashlink",
|
||||
"lioness",
|
||||
"log",
|
||||
"parking_lot 0.12.1",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_distr",
|
||||
"subtle 2.4.1",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mmr-gadget"
|
||||
version = "4.0.0-dev"
|
||||
@@ -8080,7 +8182,7 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65"
|
||||
name = "node-bench"
|
||||
version = "0.9.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"clap 4.4.6",
|
||||
"derive_more",
|
||||
"fs_extra",
|
||||
@@ -8116,7 +8218,7 @@ dependencies = [
|
||||
name = "node-cli"
|
||||
version = "3.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_cmd",
|
||||
"clap 4.4.6",
|
||||
"clap_complete",
|
||||
@@ -8157,6 +8259,7 @@ dependencies = [
|
||||
"sc-consensus-slots",
|
||||
"sc-executor",
|
||||
"sc-keystore",
|
||||
"sc-mixnet",
|
||||
"sc-network",
|
||||
"sc-network-common",
|
||||
"sc-network-statement",
|
||||
@@ -8186,6 +8289,7 @@ dependencies = [
|
||||
"sp-io",
|
||||
"sp-keyring",
|
||||
"sp-keystore",
|
||||
"sp-mixnet",
|
||||
"sp-runtime",
|
||||
"sp-statement-store",
|
||||
"sp-timestamp",
|
||||
@@ -8277,6 +8381,7 @@ dependencies = [
|
||||
"sc-consensus-babe-rpc",
|
||||
"sc-consensus-grandpa",
|
||||
"sc-consensus-grandpa-rpc",
|
||||
"sc-mixnet",
|
||||
"sc-rpc",
|
||||
"sc-rpc-api",
|
||||
"sc-rpc-spec-v2",
|
||||
@@ -8723,7 +8828,7 @@ dependencies = [
|
||||
name = "pallet-alliance"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
@@ -9026,7 +9131,7 @@ dependencies = [
|
||||
name = "pallet-beefy-mmr"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"binary-merkle-tree",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
@@ -9249,7 +9354,7 @@ dependencies = [
|
||||
name = "pallet-contracts"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"bitflags 1.3.2",
|
||||
"env_logger 0.9.3",
|
||||
@@ -9583,7 +9688,7 @@ dependencies = [
|
||||
name = "pallet-glutton"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
@@ -9751,11 +9856,30 @@ dependencies = [
|
||||
"sp-weights",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-mixnet"
|
||||
version = "0.1.0-dev"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"log",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serde",
|
||||
"sp-application-crypto",
|
||||
"sp-arithmetic",
|
||||
"sp-io",
|
||||
"sp-mixnet",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-mmr"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"env_logger 0.9.3",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
@@ -10553,7 +10677,7 @@ dependencies = [
|
||||
name = "pallet-transaction-storage"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
@@ -10946,7 +11070,7 @@ version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78f19d20a0d2cc52327a88d131fa1c4ea81ea4a04714aedcfeca2dd410049cf8"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"crc32fast",
|
||||
"fs2",
|
||||
"hex",
|
||||
@@ -13787,7 +13911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac 0.12.1",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -13800,7 +13924,7 @@ dependencies = [
|
||||
"ark-poly",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"common",
|
||||
"fflonk",
|
||||
"merlin 3.0.0",
|
||||
@@ -14420,7 +14544,7 @@ dependencies = [
|
||||
name = "sc-cli"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"chrono",
|
||||
"clap 4.4.6",
|
||||
"fdlimit",
|
||||
@@ -14436,6 +14560,7 @@ dependencies = [
|
||||
"sc-client-api",
|
||||
"sc-client-db",
|
||||
"sc-keystore",
|
||||
"sc-mixnet",
|
||||
"sc-network",
|
||||
"sc-service",
|
||||
"sc-telemetry",
|
||||
@@ -14490,7 +14615,7 @@ dependencies = [
|
||||
name = "sc-client-db"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"criterion 0.4.0",
|
||||
"hash-db",
|
||||
"kitchensink-runtime",
|
||||
@@ -14655,7 +14780,7 @@ dependencies = [
|
||||
name = "sc-consensus-beefy"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
"fnv",
|
||||
@@ -14731,7 +14856,7 @@ name = "sc-consensus-grandpa"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"dyn-clone",
|
||||
@@ -14886,7 +15011,7 @@ dependencies = [
|
||||
name = "sc-executor"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"criterion 0.4.0",
|
||||
"env_logger 0.9.3",
|
||||
@@ -14973,7 +15098,7 @@ dependencies = [
|
||||
name = "sc-keystore"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"parking_lot 0.12.1",
|
||||
"serde_json",
|
||||
"sp-application-crypto",
|
||||
@@ -14983,11 +15108,38 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sc-mixnet"
|
||||
version = "0.1.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes 4.2.0",
|
||||
"arrayvec 0.7.4",
|
||||
"blake2 0.10.6",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"libp2p-identity",
|
||||
"log",
|
||||
"mixnet",
|
||||
"multiaddr",
|
||||
"parity-scale-codec",
|
||||
"parking_lot 0.12.1",
|
||||
"sc-client-api",
|
||||
"sc-network",
|
||||
"sc-transaction-pool-api",
|
||||
"sp-api",
|
||||
"sp-consensus",
|
||||
"sp-core",
|
||||
"sp-keystore",
|
||||
"sp-mixnet",
|
||||
"sp-runtime",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sc-network"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
@@ -15102,7 +15254,7 @@ dependencies = [
|
||||
name = "sc-network-light"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-channel",
|
||||
"futures",
|
||||
"libp2p-identity",
|
||||
@@ -15122,7 +15274,7 @@ dependencies = [
|
||||
name = "sc-network-statement"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-channel",
|
||||
"futures",
|
||||
"libp2p",
|
||||
@@ -15139,7 +15291,7 @@ dependencies = [
|
||||
name = "sc-network-sync"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
"fork-tree",
|
||||
@@ -15209,7 +15361,7 @@ dependencies = [
|
||||
name = "sc-network-transactions"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"futures",
|
||||
"libp2p",
|
||||
"log",
|
||||
@@ -15226,7 +15378,7 @@ dependencies = [
|
||||
name = "sc-offchain"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures",
|
||||
@@ -15286,6 +15438,7 @@ dependencies = [
|
||||
"sc-block-builder",
|
||||
"sc-chain-spec",
|
||||
"sc-client-api",
|
||||
"sc-mixnet",
|
||||
"sc-network",
|
||||
"sc-network-common",
|
||||
"sc-rpc-api",
|
||||
@@ -15317,6 +15470,7 @@ dependencies = [
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"sc-chain-spec",
|
||||
"sc-mixnet",
|
||||
"sc-transaction-pool-api",
|
||||
"scale-info",
|
||||
"serde",
|
||||
@@ -15346,7 +15500,7 @@ dependencies = [
|
||||
name = "sc-rpc-spec-v2"
|
||||
version = "0.10.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"futures",
|
||||
"futures-util",
|
||||
@@ -15459,7 +15613,7 @@ dependencies = [
|
||||
name = "sc-service-test"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-channel",
|
||||
"fdlimit",
|
||||
"futures",
|
||||
@@ -15632,7 +15786,7 @@ dependencies = [
|
||||
name = "sc-transaction-pool"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"criterion 0.4.0",
|
||||
@@ -15752,7 +15906,7 @@ dependencies = [
|
||||
"rand 0.7.3",
|
||||
"rand_core 0.5.1",
|
||||
"sha2 0.8.2",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -15826,7 +15980,7 @@ dependencies = [
|
||||
"der 0.6.1",
|
||||
"generic-array 0.14.7",
|
||||
"pkcs8 0.9.0",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -15840,7 +15994,7 @@ dependencies = [
|
||||
"der 0.7.8",
|
||||
"generic-array 0.14.7",
|
||||
"pkcs8 0.10.2",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -16405,14 +16559,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155"
|
||||
dependencies = [
|
||||
"aes-gcm 0.9.4",
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"chacha20poly1305",
|
||||
"curve25519-dalek 4.0.0",
|
||||
"rand_core 0.6.4",
|
||||
"ring 0.16.20",
|
||||
"rustc_version 0.4.0",
|
||||
"sha2 0.10.7",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16479,7 +16633,7 @@ version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"assert_matches",
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"expander 2.0.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -16747,7 +16901,7 @@ dependencies = [
|
||||
name = "sp-consensus-beefy"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"lazy_static",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
@@ -16821,10 +16975,10 @@ dependencies = [
|
||||
name = "sp-core"
|
||||
version = "21.0.0"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"bandersnatch_vrfs",
|
||||
"bitflags 1.3.2",
|
||||
"blake2",
|
||||
"blake2 0.10.6",
|
||||
"bounded-collections",
|
||||
"bs58 0.5.0",
|
||||
"criterion 0.4.0",
|
||||
@@ -17029,11 +17183,22 @@ dependencies = [
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp-mixnet"
|
||||
version = "0.1.0-dev"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-api",
|
||||
"sp-application-crypto",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp-mmr-primitives"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"ckb-merkle-mountain-range",
|
||||
"log",
|
||||
"parity-scale-codec",
|
||||
@@ -17231,7 +17396,7 @@ dependencies = [
|
||||
name = "sp-state-machine"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"assert_matches",
|
||||
"hash-db",
|
||||
"log",
|
||||
@@ -17353,7 +17518,7 @@ name = "sp-trie"
|
||||
version = "22.0.0"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"criterion 0.4.0",
|
||||
"hash-db",
|
||||
"hashbrown 0.13.2",
|
||||
@@ -17659,7 +17824,7 @@ dependencies = [
|
||||
"md-5",
|
||||
"rand 0.8.5",
|
||||
"ring 0.16.20",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
@@ -17825,7 +17990,7 @@ dependencies = [
|
||||
name = "substrate-test-client"
|
||||
version = "2.0.1"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"async-trait",
|
||||
"futures",
|
||||
"parity-scale-codec",
|
||||
@@ -17850,7 +18015,7 @@ dependencies = [
|
||||
name = "substrate-test-runtime"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"array-bytes",
|
||||
"array-bytes 6.1.0",
|
||||
"frame-executive",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
@@ -17964,6 +18129,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
@@ -19055,7 +19226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -19065,7 +19236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -19814,7 +19985,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"sha2 0.10.7",
|
||||
"signature 1.6.4",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"webpki 0.21.4",
|
||||
@@ -19908,7 +20079,7 @@ dependencies = [
|
||||
"rtcp",
|
||||
"rtp",
|
||||
"sha-1 0.9.8",
|
||||
"subtle",
|
||||
"subtle 2.4.1",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"webrtc-util",
|
||||
|
||||
@@ -217,6 +217,7 @@ members = [
|
||||
"substrate/client/keystore",
|
||||
"substrate/client/merkle-mountain-range",
|
||||
"substrate/client/merkle-mountain-range/rpc",
|
||||
"substrate/client/mixnet",
|
||||
"substrate/client/network-gossip",
|
||||
"substrate/client/network",
|
||||
"substrate/client/network/bitswap",
|
||||
@@ -298,6 +299,7 @@ members = [
|
||||
"substrate/frame/membership",
|
||||
"substrate/frame/merkle-mountain-range",
|
||||
"substrate/frame/message-queue",
|
||||
"substrate/frame/mixnet",
|
||||
"substrate/frame/multisig",
|
||||
"substrate/frame/nft-fractionalization",
|
||||
"substrate/frame/nfts",
|
||||
@@ -394,6 +396,7 @@ members = [
|
||||
"substrate/primitives/maybe-compressed-blob",
|
||||
"substrate/primitives/merkle-mountain-range",
|
||||
"substrate/primitives/metadata-ir",
|
||||
"substrate/primitives/mixnet",
|
||||
"substrate/primitives/npos-elections",
|
||||
"substrate/primitives/npos-elections/fuzzer",
|
||||
"substrate/primitives/offchain",
|
||||
|
||||
@@ -60,6 +60,7 @@ sp-keystore = { path = "../../../primitives/keystore" }
|
||||
sp-consensus = { path = "../../../primitives/consensus/common" }
|
||||
sp-transaction-storage-proof = { path = "../../../primitives/transaction-storage-proof" }
|
||||
sp-io = { path = "../../../primitives/io" }
|
||||
sp-mixnet = { path = "../../../primitives/mixnet" }
|
||||
sp-statement-store = { path = "../../../primitives/statement-store" }
|
||||
|
||||
# client dependencies
|
||||
@@ -82,6 +83,7 @@ sc-service = { path = "../../../client/service", default-features = false}
|
||||
sc-telemetry = { path = "../../../client/telemetry" }
|
||||
sc-executor = { path = "../../../client/executor" }
|
||||
sc-authority-discovery = { path = "../../../client/authority-discovery" }
|
||||
sc-mixnet = { path = "../../../client/mixnet" }
|
||||
sc-sync-state-rpc = { path = "../../../client/sync-state-rpc" }
|
||||
sc-sysinfo = { path = "../../../client/sysinfo" }
|
||||
sc-storage-monitor = { path = "../../../client/storage-monitor" }
|
||||
|
||||
@@ -100,7 +100,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
|
||||
wasm_runtime_overrides: None,
|
||||
};
|
||||
|
||||
node_cli::service::new_full_base(config, false, |_, _| ())
|
||||
node_cli::service::new_full_base(config, None, false, |_, _| ())
|
||||
.expect("creating a full node doesn't fail")
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
|
||||
wasm_runtime_overrides: None,
|
||||
};
|
||||
|
||||
node_cli::service::new_full_base(config, false, |_, _| ()).expect("Creates node")
|
||||
node_cli::service::new_full_base(config, None, false, |_, _| ()).expect("Creates node")
|
||||
}
|
||||
|
||||
fn create_accounts(num: usize) -> Vec<sr25519::Pair> {
|
||||
|
||||
@@ -33,6 +33,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||
use sp_consensus_babe::AuthorityId as BabeId;
|
||||
use sp_core::{crypto::UncheckedInto, sr25519, Pair, Public};
|
||||
use sp_mixnet::types::AuthorityId as MixnetId;
|
||||
use sp_runtime::{
|
||||
traits::{IdentifyAccount, Verify},
|
||||
Perbill,
|
||||
@@ -72,8 +73,9 @@ fn session_keys(
|
||||
babe: BabeId,
|
||||
im_online: ImOnlineId,
|
||||
authority_discovery: AuthorityDiscoveryId,
|
||||
mixnet: MixnetId,
|
||||
) -> SessionKeys {
|
||||
SessionKeys { grandpa, babe, im_online, authority_discovery }
|
||||
SessionKeys { grandpa, babe, im_online, authority_discovery, mixnet }
|
||||
}
|
||||
|
||||
fn staging_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
@@ -93,6 +95,7 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
BabeId,
|
||||
ImOnlineId,
|
||||
AuthorityDiscoveryId,
|
||||
MixnetId,
|
||||
)> = vec![
|
||||
(
|
||||
// 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy
|
||||
@@ -111,6 +114,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
|
||||
array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106")
|
||||
.unchecked_into(),
|
||||
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
|
||||
array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106")
|
||||
.unchecked_into(),
|
||||
),
|
||||
(
|
||||
// 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2
|
||||
@@ -129,6 +135,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
|
||||
array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e")
|
||||
.unchecked_into(),
|
||||
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
|
||||
array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e")
|
||||
.unchecked_into(),
|
||||
),
|
||||
(
|
||||
// 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp
|
||||
@@ -147,6 +156,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
|
||||
array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a")
|
||||
.unchecked_into(),
|
||||
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
|
||||
array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a")
|
||||
.unchecked_into(),
|
||||
),
|
||||
(
|
||||
// 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9
|
||||
@@ -165,6 +177,9 @@ fn staging_testnet_config_genesis() -> RuntimeGenesisConfig {
|
||||
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
|
||||
array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378")
|
||||
.unchecked_into(),
|
||||
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
|
||||
array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378")
|
||||
.unchecked_into(),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -217,7 +232,7 @@ where
|
||||
/// Helper function to generate stash, controller and session key from seed.
|
||||
pub fn authority_keys_from_seed(
|
||||
seed: &str,
|
||||
) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId) {
|
||||
) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, MixnetId) {
|
||||
(
|
||||
get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed)),
|
||||
get_account_id_from_seed::<sr25519::Public>(seed),
|
||||
@@ -225,6 +240,7 @@ pub fn authority_keys_from_seed(
|
||||
get_from_seed::<BabeId>(seed),
|
||||
get_from_seed::<ImOnlineId>(seed),
|
||||
get_from_seed::<AuthorityDiscoveryId>(seed),
|
||||
get_from_seed::<MixnetId>(seed),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -237,6 +253,7 @@ pub fn testnet_genesis(
|
||||
BabeId,
|
||||
ImOnlineId,
|
||||
AuthorityDiscoveryId,
|
||||
MixnetId,
|
||||
)>,
|
||||
initial_nominators: Vec<AccountId>,
|
||||
root_key: AccountId,
|
||||
@@ -306,7 +323,13 @@ pub fn testnet_genesis(
|
||||
(
|
||||
x.0.clone(),
|
||||
x.0.clone(),
|
||||
session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()),
|
||||
session_keys(
|
||||
x.2.clone(),
|
||||
x.3.clone(),
|
||||
x.4.clone(),
|
||||
x.5.clone(),
|
||||
x.6.clone(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
@@ -367,6 +390,7 @@ pub fn testnet_genesis(
|
||||
..Default::default()
|
||||
},
|
||||
glutton: Default::default(),
|
||||
mixnet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +499,7 @@ pub(crate) mod tests {
|
||||
|
||||
sc_service_test::connectivity(integration_test_config_with_two_authorities(), |config| {
|
||||
let NewFullBase { task_manager, client, network, sync, transaction_pool, .. } =
|
||||
new_full_base(config, false, |_, _| ())?;
|
||||
new_full_base(config, None, false, |_, _| ())?;
|
||||
Ok(sc_service_test::TestNetComponents::new(
|
||||
task_manager,
|
||||
client,
|
||||
|
||||
@@ -27,6 +27,10 @@ pub struct Cli {
|
||||
#[clap(flatten)]
|
||||
pub run: sc_cli::RunCmd,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[clap(flatten)]
|
||||
pub mixnet_params: sc_cli::MixnetParams,
|
||||
|
||||
/// Disable automatic hardware benchmarks.
|
||||
///
|
||||
/// By default these benchmarks are automatically ran at startup and measure
|
||||
|
||||
@@ -111,7 +111,7 @@ pub fn run() -> Result<()> {
|
||||
},
|
||||
BenchmarkCmd::Block(cmd) => {
|
||||
// ensure that we keep the task manager alive
|
||||
let partial = new_partial(&config)?;
|
||||
let partial = new_partial(&config, None)?;
|
||||
cmd.run(partial.client)
|
||||
},
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
@@ -122,7 +122,7 @@ pub fn run() -> Result<()> {
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
BenchmarkCmd::Storage(cmd) => {
|
||||
// ensure that we keep the task manager alive
|
||||
let partial = new_partial(&config)?;
|
||||
let partial = new_partial(&config, None)?;
|
||||
let db = partial.backend.expose_db();
|
||||
let storage = partial.backend.expose_storage();
|
||||
|
||||
@@ -130,7 +130,7 @@ pub fn run() -> Result<()> {
|
||||
},
|
||||
BenchmarkCmd::Overhead(cmd) => {
|
||||
// ensure that we keep the task manager alive
|
||||
let partial = new_partial(&config)?;
|
||||
let partial = new_partial(&config, None)?;
|
||||
let ext_builder = RemarkBuilder::new(partial.client.clone());
|
||||
|
||||
cmd.run(
|
||||
@@ -143,7 +143,7 @@ pub fn run() -> Result<()> {
|
||||
},
|
||||
BenchmarkCmd::Extrinsic(cmd) => {
|
||||
// ensure that we keep the task manager alive
|
||||
let partial = service::new_partial(&config)?;
|
||||
let partial = service::new_partial(&config, None)?;
|
||||
// Register the *Remark* and *TKA* builders.
|
||||
let ext_factory = ExtrinsicFactory(vec![
|
||||
Box::new(RemarkBuilder::new(partial.client.clone())),
|
||||
@@ -178,21 +178,21 @@ pub fn run() -> Result<()> {
|
||||
let runner = cli.create_runner(cmd)?;
|
||||
runner.async_run(|config| {
|
||||
let PartialComponents { client, task_manager, import_queue, .. } =
|
||||
new_partial(&config)?;
|
||||
new_partial(&config, None)?;
|
||||
Ok((cmd.run(client, import_queue), task_manager))
|
||||
})
|
||||
},
|
||||
Some(Subcommand::ExportBlocks(cmd)) => {
|
||||
let runner = cli.create_runner(cmd)?;
|
||||
runner.async_run(|config| {
|
||||
let PartialComponents { client, task_manager, .. } = new_partial(&config)?;
|
||||
let PartialComponents { client, task_manager, .. } = new_partial(&config, None)?;
|
||||
Ok((cmd.run(client, config.database), task_manager))
|
||||
})
|
||||
},
|
||||
Some(Subcommand::ExportState(cmd)) => {
|
||||
let runner = cli.create_runner(cmd)?;
|
||||
runner.async_run(|config| {
|
||||
let PartialComponents { client, task_manager, .. } = new_partial(&config)?;
|
||||
let PartialComponents { client, task_manager, .. } = new_partial(&config, None)?;
|
||||
Ok((cmd.run(client, config.chain_spec), task_manager))
|
||||
})
|
||||
},
|
||||
@@ -200,7 +200,7 @@ pub fn run() -> Result<()> {
|
||||
let runner = cli.create_runner(cmd)?;
|
||||
runner.async_run(|config| {
|
||||
let PartialComponents { client, task_manager, import_queue, .. } =
|
||||
new_partial(&config)?;
|
||||
new_partial(&config, None)?;
|
||||
Ok((cmd.run(client, import_queue), task_manager))
|
||||
})
|
||||
},
|
||||
@@ -211,7 +211,8 @@ pub fn run() -> Result<()> {
|
||||
Some(Subcommand::Revert(cmd)) => {
|
||||
let runner = cli.create_runner(cmd)?;
|
||||
runner.async_run(|config| {
|
||||
let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?;
|
||||
let PartialComponents { client, task_manager, backend, .. } =
|
||||
new_partial(&config, None)?;
|
||||
let aux_revert = Box::new(|client: Arc<FullClient>, backend, blocks| {
|
||||
sc_consensus_babe::revert(client.clone(), backend, blocks)?;
|
||||
grandpa::revert(client, blocks)?;
|
||||
|
||||
@@ -134,6 +134,7 @@ pub fn create_extrinsic(
|
||||
/// Creates a new partial node.
|
||||
pub fn new_partial(
|
||||
config: &Configuration,
|
||||
mixnet_config: Option<&sc_mixnet::Config>,
|
||||
) -> Result<
|
||||
sc_service::PartialComponents<
|
||||
FullClient,
|
||||
@@ -154,6 +155,7 @@ pub fn new_partial(
|
||||
grandpa::SharedVoterState,
|
||||
Option<Telemetry>,
|
||||
Arc<StatementStore>,
|
||||
Option<sc_mixnet::ApiBackend>,
|
||||
),
|
||||
>,
|
||||
ServiceError,
|
||||
@@ -246,6 +248,8 @@ pub fn new_partial(
|
||||
)
|
||||
.map_err(|e| ServiceError::Other(format!("Statement store error: {:?}", e)))?;
|
||||
|
||||
let (mixnet_api, mixnet_api_backend) = mixnet_config.map(sc_mixnet::Api::new).unzip();
|
||||
|
||||
let (rpc_extensions_builder, rpc_setup) = {
|
||||
let (_, grandpa_link, _) = &import_setup;
|
||||
|
||||
@@ -287,6 +291,7 @@ pub fn new_partial(
|
||||
},
|
||||
statement_store: rpc_statement_store.clone(),
|
||||
backend: rpc_backend.clone(),
|
||||
mixnet_api: mixnet_api.as_ref().cloned(),
|
||||
};
|
||||
|
||||
node_rpc::create_full(deps).map_err(Into::into)
|
||||
@@ -303,7 +308,14 @@ pub fn new_partial(
|
||||
select_chain,
|
||||
import_queue,
|
||||
transaction_pool,
|
||||
other: (rpc_extensions_builder, import_setup, rpc_setup, telemetry, statement_store),
|
||||
other: (
|
||||
rpc_extensions_builder,
|
||||
import_setup,
|
||||
rpc_setup,
|
||||
telemetry,
|
||||
statement_store,
|
||||
mixnet_api_backend,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -326,6 +338,7 @@ pub struct NewFullBase {
|
||||
/// Creates a full service from the configuration.
|
||||
pub fn new_full_base(
|
||||
config: Configuration,
|
||||
mixnet_config: Option<sc_mixnet::Config>,
|
||||
disable_hardware_benchmarks: bool,
|
||||
with_startup_data: impl FnOnce(
|
||||
&sc_consensus_babe::BabeBlockImport<Block, FullClient, FullGrandpaBlockImport>,
|
||||
@@ -347,31 +360,36 @@ pub fn new_full_base(
|
||||
keystore_container,
|
||||
select_chain,
|
||||
transaction_pool,
|
||||
other: (rpc_builder, import_setup, rpc_setup, mut telemetry, statement_store),
|
||||
} = new_partial(&config)?;
|
||||
other:
|
||||
(rpc_builder, import_setup, rpc_setup, mut telemetry, statement_store, mixnet_api_backend),
|
||||
} = new_partial(&config, mixnet_config.as_ref())?;
|
||||
|
||||
let shared_voter_state = rpc_setup;
|
||||
let auth_disc_publish_non_global_ips = config.network.allow_non_globals_in_dht;
|
||||
let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network);
|
||||
|
||||
let grandpa_protocol_name = grandpa::protocol_standard_name(
|
||||
&client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"),
|
||||
&config.chain_spec,
|
||||
);
|
||||
let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed");
|
||||
|
||||
let grandpa_protocol_name = grandpa::protocol_standard_name(&genesis_hash, &config.chain_spec);
|
||||
net_config.add_notification_protocol(grandpa::grandpa_peers_set_config(
|
||||
grandpa_protocol_name.clone(),
|
||||
));
|
||||
|
||||
let statement_handler_proto = sc_network_statement::StatementHandlerPrototype::new(
|
||||
client
|
||||
.block_hash(0u32.into())
|
||||
.ok()
|
||||
.flatten()
|
||||
.expect("Genesis block exists; qed"),
|
||||
genesis_hash,
|
||||
config.chain_spec.fork_id(),
|
||||
);
|
||||
net_config.add_notification_protocol(statement_handler_proto.set_config());
|
||||
|
||||
let mixnet_protocol_name =
|
||||
sc_mixnet::protocol_name(genesis_hash.as_ref(), config.chain_spec.fork_id());
|
||||
if let Some(mixnet_config) = &mixnet_config {
|
||||
net_config.add_notification_protocol(sc_mixnet::peers_set_config(
|
||||
mixnet_protocol_name.clone(),
|
||||
mixnet_config,
|
||||
));
|
||||
}
|
||||
|
||||
let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new(
|
||||
backend.clone(),
|
||||
import_setup.1.shared_authority_set().clone(),
|
||||
@@ -391,6 +409,20 @@ pub fn new_full_base(
|
||||
block_relay: None,
|
||||
})?;
|
||||
|
||||
if let Some(mixnet_config) = mixnet_config {
|
||||
let mixnet = sc_mixnet::run(
|
||||
mixnet_config,
|
||||
mixnet_api_backend.expect("Mixnet API backend created if mixnet enabled"),
|
||||
client.clone(),
|
||||
sync_service.clone(),
|
||||
network.clone(),
|
||||
mixnet_protocol_name,
|
||||
transaction_pool.clone(),
|
||||
Some(keystore_container.keystore()),
|
||||
);
|
||||
task_manager.spawn_handle().spawn("mixnet", None, mixnet);
|
||||
}
|
||||
|
||||
let role = config.role.clone();
|
||||
let force_authoring = config.force_authoring;
|
||||
let backoff_authoring_blocks =
|
||||
@@ -546,7 +578,7 @@ pub fn new_full_base(
|
||||
// and vote data availability than the observer. The observer has not
|
||||
// been tested extensively yet and having most nodes in a network run it
|
||||
// could lead to finality stalls.
|
||||
let grandpa_config = grandpa::GrandpaParams {
|
||||
let grandpa_params = grandpa::GrandpaParams {
|
||||
config: grandpa_config,
|
||||
link: grandpa_link,
|
||||
network: network.clone(),
|
||||
@@ -563,7 +595,7 @@ pub fn new_full_base(
|
||||
task_manager.spawn_essential_handle().spawn_blocking(
|
||||
"grandpa-voter",
|
||||
None,
|
||||
grandpa::run_grandpa_voter(grandpa_config)?,
|
||||
grandpa::run_grandpa_voter(grandpa_params)?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -623,8 +655,9 @@ pub fn new_full_base(
|
||||
|
||||
/// Builds a new service for a full client.
|
||||
pub fn new_full(config: Configuration, cli: Cli) -> Result<TaskManager, ServiceError> {
|
||||
let mixnet_config = cli.mixnet_params.config(config.role.is_authority());
|
||||
let database_source = config.database.clone();
|
||||
let task_manager = new_full_base(config, cli.no_hardware_benchmarks, |_, _| ())
|
||||
let task_manager = new_full_base(config, mixnet_config, cli.no_hardware_benchmarks, |_, _| ())
|
||||
.map(|NewFullBase { task_manager, .. }| task_manager)?;
|
||||
|
||||
sc_storage_monitor::StorageMonitorService::try_spawn(
|
||||
@@ -702,6 +735,7 @@ mod tests {
|
||||
let NewFullBase { task_manager, client, network, sync, transaction_pool, .. } =
|
||||
new_full_base(
|
||||
config,
|
||||
None,
|
||||
false,
|
||||
|block_import: &sc_consensus_babe::BabeBlockImport<Block, _, _>,
|
||||
babe_link: &sc_consensus_babe::BabeLink<Block>| {
|
||||
@@ -876,7 +910,7 @@ mod tests {
|
||||
crate::chain_spec::tests::integration_test_config_with_two_authorities(),
|
||||
|config| {
|
||||
let NewFullBase { task_manager, client, network, sync, transaction_pool, .. } =
|
||||
new_full_base(config, false, |_, _| ())?;
|
||||
new_full_base(config, None, false, |_, _| ())?;
|
||||
Ok(sc_service_test::TestNetComponents::new(
|
||||
task_manager,
|
||||
client,
|
||||
|
||||
@@ -23,6 +23,7 @@ sc-consensus-babe = { path = "../../../client/consensus/babe" }
|
||||
sc-consensus-babe-rpc = { path = "../../../client/consensus/babe/rpc" }
|
||||
sc-consensus-grandpa = { path = "../../../client/consensus/grandpa" }
|
||||
sc-consensus-grandpa-rpc = { path = "../../../client/consensus/grandpa/rpc" }
|
||||
sc-mixnet = { path = "../../../client/mixnet" }
|
||||
sc-rpc = { path = "../../../client/rpc" }
|
||||
sc-rpc-api = { path = "../../../client/rpc-api" }
|
||||
sc-rpc-spec-v2 = { path = "../../../client/rpc-spec-v2" }
|
||||
|
||||
@@ -92,6 +92,8 @@ pub struct FullDeps<C, P, SC, B> {
|
||||
pub statement_store: Arc<dyn sp_statement_store::StatementStore>,
|
||||
/// The backend used by the node.
|
||||
pub backend: Arc<B>,
|
||||
/// Mixnet API.
|
||||
pub mixnet_api: Option<sc_mixnet::Api>,
|
||||
}
|
||||
|
||||
/// Instantiate all Full RPC extensions.
|
||||
@@ -106,6 +108,7 @@ pub fn create_full<C, P, SC, B>(
|
||||
grandpa,
|
||||
statement_store,
|
||||
backend,
|
||||
mixnet_api,
|
||||
}: FullDeps<C, P, SC, B>,
|
||||
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
|
||||
where
|
||||
@@ -133,6 +136,7 @@ where
|
||||
use sc_consensus_grandpa_rpc::{Grandpa, GrandpaApiServer};
|
||||
use sc_rpc::{
|
||||
dev::{Dev, DevApiServer},
|
||||
mixnet::MixnetApiServer,
|
||||
statement::StatementApiServer,
|
||||
};
|
||||
use sc_rpc_spec_v2::chain_spec::{ChainSpec, ChainSpecApiServer};
|
||||
@@ -196,5 +200,10 @@ where
|
||||
sc_rpc::statement::StatementStore::new(statement_store, deny_unsafe).into_rpc();
|
||||
io.merge(statement_store)?;
|
||||
|
||||
if let Some(mixnet_api) = mixnet_api {
|
||||
let mixnet = sc_rpc::mixnet::Mixnet::new(mixnet_api).into_rpc();
|
||||
io.merge(mixnet)?;
|
||||
}
|
||||
|
||||
Ok(io)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ sp-block-builder = { path = "../../../primitives/block-builder", default-feature
|
||||
sp-genesis-builder = { version = "0.1.0-dev", default-features = false, path = "../../../primitives/genesis-builder" }
|
||||
sp-inherents = { path = "../../../primitives/inherents", default-features = false}
|
||||
node-primitives = { path = "../primitives", default-features = false}
|
||||
sp-mixnet = { path = "../../../primitives/mixnet", default-features = false }
|
||||
sp-offchain = { path = "../../../primitives/offchain", default-features = false}
|
||||
sp-core = { path = "../../../primitives/core", default-features = false}
|
||||
sp-std = { path = "../../../primitives/std", default-features = false}
|
||||
@@ -88,6 +89,7 @@ pallet-identity = { path = "../../../frame/identity", default-features = false}
|
||||
pallet-lottery = { path = "../../../frame/lottery", default-features = false}
|
||||
pallet-membership = { path = "../../../frame/membership", default-features = false}
|
||||
pallet-message-queue = { path = "../../../frame/message-queue", default-features = false}
|
||||
pallet-mixnet = { path = "../../../frame/mixnet", default-features = false }
|
||||
pallet-mmr = { path = "../../../frame/merkle-mountain-range", default-features = false}
|
||||
pallet-multisig = { path = "../../../frame/multisig", default-features = false}
|
||||
pallet-nfts = { path = "../../../frame/nfts", default-features = false}
|
||||
@@ -185,6 +187,7 @@ std = [
|
||||
"pallet-lottery/std",
|
||||
"pallet-membership/std",
|
||||
"pallet-message-queue/std",
|
||||
"pallet-mixnet/std",
|
||||
"pallet-mmr/std",
|
||||
"pallet-multisig/std",
|
||||
"pallet-nft-fractionalization/std",
|
||||
@@ -235,6 +238,7 @@ std = [
|
||||
"sp-genesis-builder/std",
|
||||
"sp-inherents/std",
|
||||
"sp-io/std",
|
||||
"sp-mixnet/std",
|
||||
"sp-offchain/std",
|
||||
"sp-runtime/std",
|
||||
"sp-session/std",
|
||||
@@ -281,6 +285,7 @@ runtime-benchmarks = [
|
||||
"pallet-lottery/runtime-benchmarks",
|
||||
"pallet-membership/runtime-benchmarks",
|
||||
"pallet-message-queue/runtime-benchmarks",
|
||||
"pallet-mixnet/runtime-benchmarks",
|
||||
"pallet-mmr/runtime-benchmarks",
|
||||
"pallet-multisig/runtime-benchmarks",
|
||||
"pallet-nft-fractionalization/runtime-benchmarks",
|
||||
@@ -354,6 +359,7 @@ try-runtime = [
|
||||
"pallet-lottery/try-runtime",
|
||||
"pallet-membership/try-runtime",
|
||||
"pallet-message-queue/try-runtime",
|
||||
"pallet-mixnet/try-runtime",
|
||||
"pallet-mmr/try-runtime",
|
||||
"pallet-multisig/try-runtime",
|
||||
"pallet-nft-fractionalization/try-runtime",
|
||||
|
||||
@@ -589,6 +589,7 @@ impl_opaque_keys! {
|
||||
pub babe: Babe,
|
||||
pub im_online: ImOnline,
|
||||
pub authority_discovery: AuthorityDiscovery,
|
||||
pub mixnet: Mixnet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1048,7 +1049,7 @@ impl pallet_democracy::Config for Runtime {
|
||||
pallet_collective::EnsureProportionAtLeast<AccountId, TechnicalCollective, 2, 3>;
|
||||
type InstantOrigin =
|
||||
pallet_collective::EnsureProportionAtLeast<AccountId, TechnicalCollective, 1, 1>;
|
||||
type InstantAllowed = frame_support::traits::ConstBool<true>;
|
||||
type InstantAllowed = ConstBool<true>;
|
||||
type FastTrackVotingPeriod = FastTrackVotingPeriod;
|
||||
// To cancel a proposal which has been passed, 2/3 of the council must agree to it.
|
||||
type CancellationOrigin =
|
||||
@@ -2028,6 +2029,29 @@ impl pallet_broker::Config for Runtime {
|
||||
type PriceAdapter = pallet_broker::Linear;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MixnetNumCoverToCurrentBlocks: BlockNumber = 3;
|
||||
pub const MixnetNumRequestsToCurrentBlocks: BlockNumber = 3;
|
||||
pub const MixnetNumCoverToPrevBlocks: BlockNumber = 3;
|
||||
pub const MixnetNumRegisterStartSlackBlocks: BlockNumber = 3;
|
||||
pub const MixnetNumRegisterEndSlackBlocks: BlockNumber = 3;
|
||||
pub const MixnetRegistrationPriority: TransactionPriority = ImOnlineUnsignedPriority::get() - 1;
|
||||
}
|
||||
|
||||
impl pallet_mixnet::Config for Runtime {
|
||||
type MaxAuthorities = MaxAuthorities;
|
||||
type MaxExternalAddressSize = ConstU32<128>;
|
||||
type MaxExternalAddressesPerMixnode = ConstU32<16>;
|
||||
type NextSessionRotation = Babe;
|
||||
type NumCoverToCurrentBlocks = MixnetNumCoverToCurrentBlocks;
|
||||
type NumRequestsToCurrentBlocks = MixnetNumRequestsToCurrentBlocks;
|
||||
type NumCoverToPrevBlocks = MixnetNumCoverToPrevBlocks;
|
||||
type NumRegisterStartSlackBlocks = MixnetNumRegisterStartSlackBlocks;
|
||||
type NumRegisterEndSlackBlocks = MixnetNumRegisterEndSlackBlocks;
|
||||
type RegistrationPriority = MixnetRegistrationPriority;
|
||||
type MinMixnodes = ConstU32<7>; // Low to allow small testing networks
|
||||
}
|
||||
|
||||
construct_runtime!(
|
||||
pub struct Runtime
|
||||
{
|
||||
@@ -2104,6 +2128,7 @@ construct_runtime!(
|
||||
SafeMode: pallet_safe_mode,
|
||||
Statement: pallet_statement,
|
||||
Broker: pallet_broker,
|
||||
Mixnet: pallet_mixnet,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2663,6 +2688,24 @@ impl_runtime_apis! {
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_mixnet::runtime_api::MixnetApi<Block> for Runtime {
|
||||
fn session_status() -> sp_mixnet::types::SessionStatus {
|
||||
Mixnet::session_status()
|
||||
}
|
||||
|
||||
fn prev_mixnodes() -> Result<Vec<sp_mixnet::types::Mixnode>, sp_mixnet::types::MixnodesErr> {
|
||||
Mixnet::prev_mixnodes()
|
||||
}
|
||||
|
||||
fn current_mixnodes() -> Result<Vec<sp_mixnet::types::Mixnode>, sp_mixnet::types::MixnodesErr> {
|
||||
Mixnet::current_mixnodes()
|
||||
}
|
||||
|
||||
fn maybe_register(session_index: sp_mixnet::types::SessionIndex, mixnode: sp_mixnet::types::Mixnode) -> bool {
|
||||
Mixnet::maybe_register(session_index, mixnode)
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_session::SessionKeys<Block> for Runtime {
|
||||
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
|
||||
SessionKeys::generate(seed)
|
||||
|
||||
@@ -109,5 +109,6 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec<AccountId>) -> Run
|
||||
trash_data_count: Default::default(),
|
||||
..Default::default()
|
||||
},
|
||||
mixnet: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ pub fn to_session_keys(
|
||||
babe: sr25519_keyring.to_owned().public().into(),
|
||||
im_online: sr25519_keyring.to_owned().public().into(),
|
||||
authority_discovery: sr25519_keyring.to_owned().public().into(),
|
||||
mixnet: sr25519_keyring.to_owned().public().into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ pub fn generate_authority_keys_and_store(
|
||||
.map_err(|err| err.to_string())?
|
||||
.into();
|
||||
|
||||
let (_, _, grandpa, babe, im_online, authority_discovery) =
|
||||
let (_, _, grandpa, babe, im_online, authority_discovery, mixnet) =
|
||||
chain_spec::authority_keys_from_seed(seed);
|
||||
|
||||
let insert_key = |key_type, public| {
|
||||
@@ -198,6 +198,8 @@ pub fn generate_authority_keys_and_store(
|
||||
sp_core::crypto::key_types::AUTHORITY_DISCOVERY,
|
||||
authority_discovery.as_slice(),
|
||||
)?;
|
||||
|
||||
insert_key(sp_core::crypto::key_types::MIXNET, mixnet.as_slice())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -33,6 +33,7 @@ tokio = { version = "1.22.0", features = ["signal", "rt-multi-thread", "parking_
|
||||
sc-client-api = { path = "../api" }
|
||||
sc-client-db = { path = "../db", default-features = false}
|
||||
sc-keystore = { path = "../keystore" }
|
||||
sc-mixnet = { path = "../mixnet" }
|
||||
sc-network = { path = "../network" }
|
||||
sc-service = { path = "../service", default-features = false}
|
||||
sc-telemetry = { path = "../telemetry" }
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use clap::Args;
|
||||
use sp_core::H256;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn parse_kx_secret(s: &str) -> Result<sc_mixnet::KxSecret, String> {
|
||||
H256::from_str(s).map(H256::to_fixed_bytes).map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
/// Parameters used to create the mixnet configuration.
|
||||
#[derive(Debug, Clone, Args)]
|
||||
pub struct MixnetParams {
|
||||
/// Enable the mixnet service.
|
||||
///
|
||||
/// This will make the mixnet RPC methods available. If the node is running as a validator, it
|
||||
/// will also attempt to register and operate as a mixnode.
|
||||
#[arg(long)]
|
||||
pub mixnet: bool,
|
||||
|
||||
/// The mixnet key-exchange secret to use in session 0.
|
||||
///
|
||||
/// Should be 64 hex characters, giving a 32-byte secret.
|
||||
///
|
||||
/// WARNING: Secrets provided as command-line arguments are easily exposed. Use of this option
|
||||
/// should be limited to development and testing.
|
||||
#[arg(long, value_name = "SECRET", value_parser = parse_kx_secret)]
|
||||
pub mixnet_session_0_kx_secret: Option<sc_mixnet::KxSecret>,
|
||||
}
|
||||
|
||||
impl MixnetParams {
|
||||
/// Returns the mixnet configuration, or `None` if the mixnet is disabled.
|
||||
pub fn config(&self, is_authority: bool) -> Option<sc_mixnet::Config> {
|
||||
self.mixnet.then(|| {
|
||||
let mut config = sc_mixnet::Config {
|
||||
core: sc_mixnet::CoreConfig {
|
||||
session_0_kx_secret: self.mixnet_session_0_kx_secret,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
if !is_authority {
|
||||
// Only authorities can be mixnodes; don't attempt to register
|
||||
config.substrate.register = false;
|
||||
// Only mixnodes need to allow connections from non-mixnodes
|
||||
config.substrate.num_gateway_slots = 0;
|
||||
}
|
||||
config
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ mod database_params;
|
||||
mod import_params;
|
||||
mod keystore_params;
|
||||
mod message_params;
|
||||
mod mixnet_params;
|
||||
mod network_params;
|
||||
mod node_key_params;
|
||||
mod offchain_worker_params;
|
||||
@@ -39,9 +40,10 @@ use sp_runtime::{
|
||||
use std::{fmt::Debug, str::FromStr};
|
||||
|
||||
pub use crate::params::{
|
||||
database_params::*, import_params::*, keystore_params::*, message_params::*, network_params::*,
|
||||
node_key_params::*, offchain_worker_params::*, prometheus_params::*, pruning_params::*,
|
||||
runtime_params::*, shared_params::*, telemetry_params::*, transaction_pool_params::*,
|
||||
database_params::*, import_params::*, keystore_params::*, message_params::*, mixnet_params::*,
|
||||
network_params::*, node_key_params::*, offchain_worker_params::*, prometheus_params::*,
|
||||
pruning_params::*, runtime_params::*, shared_params::*, telemetry_params::*,
|
||||
transaction_pool_params::*,
|
||||
};
|
||||
|
||||
/// Parse Ss58AddressFormat
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
[package]
|
||||
description = "Substrate mixnet service"
|
||||
name = "sc-mixnet"
|
||||
version = "0.1.0-dev"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
array-bytes = "4.1"
|
||||
arrayvec = "0.7.2"
|
||||
blake2 = "0.10.4"
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
|
||||
futures = "0.3.25"
|
||||
futures-timer = "3.0.2"
|
||||
libp2p-identity = { version = "0.1.3", features = ["peerid"] }
|
||||
log = "0.4.17"
|
||||
mixnet = "0.7.0"
|
||||
multiaddr = "0.17.1"
|
||||
parking_lot = "0.12.1"
|
||||
sc-client-api = { path = "../api" }
|
||||
sc-network = { path = "../network" }
|
||||
sc-transaction-pool-api = { path = "../transaction-pool/api" }
|
||||
sp-api = { path = "../../primitives/api" }
|
||||
sp-consensus = { path = "../../primitives/consensus/common" }
|
||||
sp-core = { path = "../../primitives/core" }
|
||||
sp-keystore = { path = "../../primitives/keystore" }
|
||||
sp-mixnet = { path = "../../primitives/mixnet" }
|
||||
sp-runtime = { path = "../../primitives/runtime" }
|
||||
thiserror = "1.0"
|
||||
@@ -0,0 +1,3 @@
|
||||
Substrate mixnet service.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -0,0 +1,69 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{config::Config, error::Error, request::Request};
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
SinkExt,
|
||||
};
|
||||
use sp_core::Bytes;
|
||||
use std::future::Future;
|
||||
|
||||
/// The other end of an [`Api`]. This should be passed to [`run`](super::run::run).
|
||||
pub struct ApiBackend {
|
||||
pub(super) request_receiver: mpsc::Receiver<Request>,
|
||||
}
|
||||
|
||||
/// Interface to the mixnet service.
|
||||
#[derive(Clone)]
|
||||
pub struct Api {
|
||||
request_sender: mpsc::Sender<Request>,
|
||||
}
|
||||
|
||||
impl Api {
|
||||
/// Create a new `Api`. The [`ApiBackend`] should be passed to [`run`](super::run::run).
|
||||
pub fn new(config: &Config) -> (Self, ApiBackend) {
|
||||
let (request_sender, request_receiver) = mpsc::channel(config.substrate.request_buffer);
|
||||
(Self { request_sender }, ApiBackend { request_receiver })
|
||||
}
|
||||
|
||||
/// Submit an extrinsic via the mixnet.
|
||||
///
|
||||
/// Returns a [`Future`] which returns another `Future`.
|
||||
///
|
||||
/// The first `Future` resolves as soon as there is space in the mixnet service queue. The
|
||||
/// second `Future` resolves once a reply is received over the mixnet (or sooner if there is an
|
||||
/// error).
|
||||
///
|
||||
/// The first `Future` references `self`, but the second does not. This makes it possible to
|
||||
/// submit concurrent mixnet requests using a single `Api` instance.
|
||||
pub async fn submit_extrinsic(
|
||||
&mut self,
|
||||
extrinsic: Bytes,
|
||||
) -> impl Future<Output = Result<(), Error>> {
|
||||
let (reply_sender, reply_receiver) = oneshot::channel();
|
||||
let res = self
|
||||
.request_sender
|
||||
.feed(Request::SubmitExtrinsic { extrinsic, reply_sender })
|
||||
.await;
|
||||
async move {
|
||||
res.map_err(|_| Error::ServiceUnavailable)?;
|
||||
reply_receiver.await.map_err(|_| Error::ServiceUnavailable)?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub use mixnet::core::Config as CoreConfig;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Substrate-specific mixnet configuration.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SubstrateConfig {
|
||||
/// Attempt to register the local node as a mixnode?
|
||||
pub register: bool,
|
||||
/// Maximum number of incoming mixnet connections to accept from non-mixnodes. If the local
|
||||
/// node will never be a mixnode, this can be set to 0.
|
||||
pub num_gateway_slots: u32,
|
||||
|
||||
/// Number of requests to the mixnet service that can be buffered, in addition to the one per
|
||||
/// [`Api`](super::api::Api) instance. Note that this does not include requests that are being
|
||||
/// actively handled.
|
||||
pub request_buffer: usize,
|
||||
/// Used to determine the number of SURBs to include in request messages: the maximum number of
|
||||
/// SURBs needed for a single reply is multiplied by this. This should not be set to 0.
|
||||
pub surb_factor: usize,
|
||||
|
||||
/// Maximum number of submit extrinsic requests waiting for their delay to elapse. When at the
|
||||
/// limit, any submit extrinsic requests that arrive will simply be dropped.
|
||||
pub extrinsic_queue_capacity: usize,
|
||||
/// Mean delay between receiving a submit extrinsic request and actually submitting the
|
||||
/// extrinsic. This should really be the same for all nodes!
|
||||
pub mean_extrinsic_delay: Duration,
|
||||
/// Maximum number of extrinsics being actively submitted. If a submit extrinsic request's
|
||||
/// delay elapses and we are already at this limit, the request will simply be dropped.
|
||||
pub max_pending_extrinsics: usize,
|
||||
}
|
||||
|
||||
impl Default for SubstrateConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
register: true,
|
||||
num_gateway_slots: 150,
|
||||
|
||||
request_buffer: 4,
|
||||
surb_factor: 2,
|
||||
|
||||
extrinsic_queue_capacity: 50,
|
||||
mean_extrinsic_delay: Duration::from_secs(1),
|
||||
max_pending_extrinsics: 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mixnet configuration.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// Core configuration.
|
||||
pub core: CoreConfig,
|
||||
/// Request manager configuration.
|
||||
pub request_manager: mixnet::request_manager::Config,
|
||||
/// Reply manager configuration.
|
||||
pub reply_manager: mixnet::reply_manager::Config,
|
||||
/// Substrate-specific configuration.
|
||||
pub substrate: SubstrateConfig,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
core: Default::default(),
|
||||
request_manager: Default::default(),
|
||||
reply_manager: Default::default(),
|
||||
substrate: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use mixnet::core::PostErr;
|
||||
|
||||
/// Error handling a request. Sent in replies over the mixnet.
|
||||
#[derive(Debug, thiserror::Error, Decode, Encode)]
|
||||
pub enum RemoteErr {
|
||||
/// An error that doesn't map to any of the other variants.
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
/// Failed to decode the request.
|
||||
#[error("Failed to decode the request: {0}")]
|
||||
Decode(String),
|
||||
}
|
||||
|
||||
/// Mixnet error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Failed to communicate with the mixnet service. Possibly it panicked. The node probably
|
||||
/// needs to be restarted.
|
||||
#[error(
|
||||
"Failed to communicate with the mixnet service; the node probably needs to be restarted"
|
||||
)]
|
||||
ServiceUnavailable,
|
||||
/// Did not receive a reply after the configured number of attempts.
|
||||
#[error("Did not receive a reply from the mixnet after the configured number of attempts")]
|
||||
NoReply,
|
||||
/// Received a malformed reply.
|
||||
#[error("Received a malformed reply from the mixnet")]
|
||||
BadReply,
|
||||
/// Failed to post the request to the mixnet. Note that some [`PostErr`] variants, eg
|
||||
/// [`PostErr::NotEnoughSpaceInQueue`], are handled internally and will never be returned from
|
||||
/// the top-level API.
|
||||
#[error("Failed to post the request to the mixnet: {0}")]
|
||||
Post(#[from] PostErr),
|
||||
/// Error reported by destination mixnode.
|
||||
#[error("Error reported by the destination mixnode: {0}")]
|
||||
Remote(#[from] RemoteErr),
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! [`ExtrinsicQueue`] is a queue for extrinsics received from the mixnet. These extrinsics are
|
||||
//! explicitly delayed by a random amount, to decorrelate the times at which they are received from
|
||||
//! the times at which they are broadcast to peers.
|
||||
|
||||
use mixnet::reply_manager::ReplyContext;
|
||||
use std::{cmp::Ordering, collections::BinaryHeap, time::Instant};
|
||||
|
||||
/// An extrinsic that should be submitted to the transaction pool after `deadline`. `Eq` and `Ord`
|
||||
/// are implemented for this to support use in `BinaryHeap`s. Only `deadline` is compared.
|
||||
struct DelayedExtrinsic<E> {
|
||||
/// When the extrinsic should actually be submitted to the pool.
|
||||
deadline: Instant,
|
||||
extrinsic: E,
|
||||
reply_context: ReplyContext,
|
||||
}
|
||||
|
||||
impl<E> PartialEq for DelayedExtrinsic<E> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.deadline == other.deadline
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Eq for DelayedExtrinsic<E> {}
|
||||
|
||||
impl<E> PartialOrd for DelayedExtrinsic<E> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Ord for DelayedExtrinsic<E> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
// Extrinsics with the earliest deadline considered greatest
|
||||
self.deadline.cmp(&other.deadline).reverse()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtrinsicQueue<E> {
|
||||
capacity: usize,
|
||||
queue: BinaryHeap<DelayedExtrinsic<E>>,
|
||||
next_deadline_changed: bool,
|
||||
}
|
||||
|
||||
impl<E> ExtrinsicQueue<E> {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self { capacity, queue: BinaryHeap::with_capacity(capacity), next_deadline_changed: false }
|
||||
}
|
||||
|
||||
pub fn next_deadline(&self) -> Option<Instant> {
|
||||
self.queue.peek().map(|extrinsic| extrinsic.deadline)
|
||||
}
|
||||
|
||||
pub fn next_deadline_changed(&mut self) -> bool {
|
||||
let changed = self.next_deadline_changed;
|
||||
self.next_deadline_changed = false;
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn has_space(&self) -> bool {
|
||||
self.queue.len() < self.capacity
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, deadline: Instant, extrinsic: E, reply_context: ReplyContext) {
|
||||
debug_assert!(self.has_space());
|
||||
let prev_deadline = self.next_deadline();
|
||||
self.queue.push(DelayedExtrinsic { deadline, extrinsic, reply_context });
|
||||
if self.next_deadline() != prev_deadline {
|
||||
self.next_deadline_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<(E, ReplyContext)> {
|
||||
self.next_deadline_changed = true;
|
||||
self.queue.pop().map(|extrinsic| (extrinsic.extrinsic, extrinsic.reply_context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate mixnet service. This implements the [Substrate Mix Network
|
||||
//! Specification](https://paritytech.github.io/mixnet-spec/).
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod api;
|
||||
mod config;
|
||||
mod error;
|
||||
mod extrinsic_queue;
|
||||
mod maybe_inf_delay;
|
||||
mod packet_dispatcher;
|
||||
mod peer_id;
|
||||
mod protocol;
|
||||
mod request;
|
||||
mod run;
|
||||
mod sync_with_runtime;
|
||||
|
||||
pub use self::{
|
||||
api::{Api, ApiBackend},
|
||||
config::{Config, CoreConfig, SubstrateConfig},
|
||||
error::{Error, RemoteErr},
|
||||
protocol::{peers_set_config, protocol_name},
|
||||
run::run,
|
||||
};
|
||||
pub use mixnet::core::{KxSecret, PostErr, TopologyErr};
|
||||
@@ -0,0 +1,111 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use futures::{future::FusedFuture, FutureExt};
|
||||
use futures_timer::Delay;
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll, Waker},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
enum Inner {
|
||||
Infinite {
|
||||
/// Waker from the most recent `poll` call. If `None`, either `poll` has not been called
|
||||
/// yet, we returned `Poll::Ready` from the last call, or the waker is attached to `delay`.
|
||||
waker: Option<Waker>,
|
||||
delay: Option<Delay>,
|
||||
},
|
||||
Finite(Delay),
|
||||
}
|
||||
|
||||
/// Like [`Delay`] but the duration can be infinite (in which case the future will never fire).
|
||||
/// Unlike [`Delay`], implements [`FusedFuture`], with [`is_terminated`](Self::is_terminated)
|
||||
/// returning `true` when the delay is infinite. As with [`Delay`], once [`poll`](Self::poll)
|
||||
/// returns [`Poll::Ready`], it will continue to do so until [`reset`](Self::reset) is called.
|
||||
pub struct MaybeInfDelay(Inner);
|
||||
|
||||
impl MaybeInfDelay {
|
||||
/// Create a new `MaybeInfDelay` future. If `duration` is [`Some`], the future will fire after
|
||||
/// the given duration has elapsed. If `duration` is [`None`], the future will "never" fire
|
||||
/// (although see [`reset`](Self::reset)).
|
||||
pub fn new(duration: Option<Duration>) -> Self {
|
||||
match duration {
|
||||
Some(duration) => Self(Inner::Finite(Delay::new(duration))),
|
||||
None => Self(Inner::Infinite { waker: None, delay: None }),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the timer. `duration` is handled just like in [`new`](Self::new). Note that while
|
||||
/// this is similar to `std::mem::replace(&mut self, MaybeInfDelay::new(duration))`, with
|
||||
/// `replace` you would have to manually ensure [`poll`](Self::poll) was called again; with
|
||||
/// `reset` this is not necessary.
|
||||
pub fn reset(&mut self, duration: Option<Duration>) {
|
||||
match duration {
|
||||
Some(duration) => match &mut self.0 {
|
||||
Inner::Infinite { waker, delay } => {
|
||||
let mut delay = match delay.take() {
|
||||
Some(mut delay) => {
|
||||
delay.reset(duration);
|
||||
delay
|
||||
},
|
||||
None => Delay::new(duration),
|
||||
};
|
||||
if let Some(waker) = waker.take() {
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
match delay.poll_unpin(&mut cx) {
|
||||
Poll::Pending => (), // Waker attached to delay
|
||||
Poll::Ready(_) => waker.wake(),
|
||||
}
|
||||
}
|
||||
self.0 = Inner::Finite(delay);
|
||||
},
|
||||
Inner::Finite(delay) => delay.reset(duration),
|
||||
},
|
||||
None =>
|
||||
self.0 = match std::mem::replace(
|
||||
&mut self.0,
|
||||
Inner::Infinite { waker: None, delay: None },
|
||||
) {
|
||||
Inner::Finite(delay) => Inner::Infinite { waker: None, delay: Some(delay) },
|
||||
infinite => infinite,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for MaybeInfDelay {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match &mut self.0 {
|
||||
Inner::Infinite { waker, .. } => {
|
||||
*waker = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
},
|
||||
Inner::Finite(delay) => delay.poll_unpin(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedFuture for MaybeInfDelay {
|
||||
fn is_terminated(&self) -> bool {
|
||||
matches!(self.0, Inner::Infinite { .. })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! [`AddressedPacket`] dispatching.
|
||||
|
||||
use super::peer_id::{from_core_peer_id, to_core_peer_id};
|
||||
use arrayvec::ArrayVec;
|
||||
use libp2p_identity::PeerId;
|
||||
use log::{debug, warn};
|
||||
use mixnet::core::{AddressedPacket, NetworkStatus, Packet, PeerId as CorePeerId};
|
||||
use parking_lot::Mutex;
|
||||
use sc_network::{NetworkNotification, ProtocolName};
|
||||
use std::{collections::HashMap, future::Future, sync::Arc};
|
||||
|
||||
const LOG_TARGET: &str = "mixnet";
|
||||
|
||||
/// Packet queue for a peer.
|
||||
///
|
||||
/// Ideally we would use `Rc<RefCell<_>>`, but that would prevent the top-level future from being
|
||||
/// automatically marked `Send`. I believe it would be safe to manually mark it `Send`, but using
|
||||
/// `Arc<Mutex<_>>` here is not really a big deal.
|
||||
struct PeerQueue(Mutex<ArrayVec<Box<Packet>, 2>>);
|
||||
|
||||
impl PeerQueue {
|
||||
fn new() -> Self {
|
||||
Self(Mutex::new(ArrayVec::new()))
|
||||
}
|
||||
|
||||
/// Push `packet` onto the queue. Returns `true` if the queue was previously empty. Fails if
|
||||
/// the queue is full.
|
||||
fn push(&self, packet: Box<Packet>) -> Result<bool, ()> {
|
||||
let mut queue = self.0.lock();
|
||||
if queue.is_full() {
|
||||
Err(())
|
||||
} else {
|
||||
let was_empty = queue.is_empty();
|
||||
queue.push(packet);
|
||||
Ok(was_empty)
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop all packets from the queue.
|
||||
fn clear(&self) {
|
||||
let mut queue = self.0.lock();
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
/// Pop the packet at the head of the queue and return it, or, if the queue is empty, return
|
||||
/// `None`. Also returns `true` if there are more packets in the queue.
|
||||
fn pop(&self) -> (Option<Box<Packet>>, bool) {
|
||||
let mut queue = self.0.lock();
|
||||
let packet = queue.pop();
|
||||
(packet, !queue.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
/// A peer which has packets ready to send but is not currently being serviced.
|
||||
pub struct ReadyPeer {
|
||||
id: PeerId,
|
||||
/// The peer's packet queue. Not empty.
|
||||
queue: Arc<PeerQueue>,
|
||||
}
|
||||
|
||||
impl ReadyPeer {
|
||||
/// If a future is returned, and if that future returns `Some`, this function should be called
|
||||
/// again to send the next packet queued for the peer; `self` is placed in the `Some` to make
|
||||
/// this straightforward. Otherwise, we have either sent or dropped all packets queued for the
|
||||
/// peer, and it can be forgotten about for the time being.
|
||||
pub fn send_packet(
|
||||
self,
|
||||
network: &impl NetworkNotification,
|
||||
protocol_name: ProtocolName,
|
||||
) -> Option<impl Future<Output = Option<Self>>> {
|
||||
match network.notification_sender(self.id, protocol_name) {
|
||||
Err(err) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to get notification sender for peer ID {}: {err}", self.id
|
||||
);
|
||||
self.queue.clear();
|
||||
None
|
||||
},
|
||||
Ok(sender) => Some(async move {
|
||||
match sender.ready().await.and_then(|mut ready| {
|
||||
let (packet, more_packets) = self.queue.pop();
|
||||
let packet =
|
||||
packet.expect("Should only be called if there is a packet to send");
|
||||
ready.send((packet as Box<[_]>).into())?;
|
||||
Ok(more_packets)
|
||||
}) {
|
||||
Err(err) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Notification sender for peer ID {} failed: {err}", self.id
|
||||
);
|
||||
self.queue.clear();
|
||||
None
|
||||
},
|
||||
Ok(more_packets) => more_packets.then(|| self),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PacketDispatcher {
|
||||
/// Peer ID of the local node. Only used to implement [`NetworkStatus`].
|
||||
local_peer_id: CorePeerId,
|
||||
/// Packet queue for each connected peer. These queues are very short and only exist to give
|
||||
/// packets somewhere to sit while waiting for notification senders to be ready.
|
||||
peer_queues: HashMap<CorePeerId, Arc<PeerQueue>>,
|
||||
}
|
||||
|
||||
impl PacketDispatcher {
|
||||
pub fn new(local_peer_id: &CorePeerId) -> Self {
|
||||
Self { local_peer_id: *local_peer_id, peer_queues: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn add_peer(&mut self, id: &PeerId) {
|
||||
let Some(core_id) = to_core_peer_id(id) else {
|
||||
debug!(target: LOG_TARGET,
|
||||
"Cannot add peer; failed to convert libp2p peer ID {id} to mixnet peer ID");
|
||||
return
|
||||
};
|
||||
if self.peer_queues.insert(core_id, Arc::new(PeerQueue::new())).is_some() {
|
||||
warn!(target: LOG_TARGET, "Two stream opened notifications for peer ID {id}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_peer(&mut self, id: &PeerId) {
|
||||
let Some(core_id) = to_core_peer_id(id) else {
|
||||
debug!(target: LOG_TARGET,
|
||||
"Cannot remove peer; failed to convert libp2p peer ID {id} to mixnet peer ID");
|
||||
return
|
||||
};
|
||||
if self.peer_queues.remove(&core_id).is_none() {
|
||||
warn!(target: LOG_TARGET, "Stream closed notification for unknown peer ID {id}");
|
||||
}
|
||||
}
|
||||
|
||||
/// If the peer is not connected or the peer's packet queue is full, the packet is dropped.
|
||||
/// Otherwise the packet is pushed onto the peer's queue, and if the queue was previously empty
|
||||
/// a [`ReadyPeer`] is returned.
|
||||
pub fn dispatch(&mut self, packet: AddressedPacket) -> Option<ReadyPeer> {
|
||||
let Some(queue) = self.peer_queues.get_mut(&packet.peer_id) else {
|
||||
debug!(target: LOG_TARGET, "Dropped packet to mixnet peer ID {:x?}; not connected",
|
||||
packet.peer_id);
|
||||
return None
|
||||
};
|
||||
|
||||
match queue.push(packet.packet) {
|
||||
Err(_) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Dropped packet to mixnet peer ID {:x?}; peer queue full", packet.peer_id
|
||||
);
|
||||
None
|
||||
},
|
||||
Ok(true) => {
|
||||
// Queue was empty. Construct and return a ReadyPeer.
|
||||
let Some(id) = from_core_peer_id(&packet.peer_id) else {
|
||||
debug!(target: LOG_TARGET, "Cannot send packet; \
|
||||
failed to convert mixnet peer ID {:x?} to libp2p peer ID",
|
||||
packet.peer_id);
|
||||
queue.clear();
|
||||
return None
|
||||
};
|
||||
Some(ReadyPeer { id, queue: queue.clone() })
|
||||
},
|
||||
Ok(false) => None, // Queue was not empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkStatus for PacketDispatcher {
|
||||
fn local_peer_id(&self) -> CorePeerId {
|
||||
self.local_peer_id
|
||||
}
|
||||
|
||||
fn is_connected(&self, peer_id: &CorePeerId) -> bool {
|
||||
self.peer_queues.contains_key(peer_id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use libp2p_identity::PeerId;
|
||||
use mixnet::core::PeerId as CorePeerId;
|
||||
|
||||
/// Convert a libp2p [`PeerId`] into a mixnet core [`PeerId`](CorePeerId).
|
||||
///
|
||||
/// This will succeed only if `peer_id` is an Ed25519 public key ("hashed" using the identity
|
||||
/// hasher). Returns `None` on failure.
|
||||
pub fn to_core_peer_id(peer_id: &PeerId) -> Option<CorePeerId> {
|
||||
let hash = peer_id.as_ref();
|
||||
if hash.code() != 0 {
|
||||
// Hash is not identity
|
||||
return None
|
||||
}
|
||||
let public = libp2p_identity::PublicKey::try_decode_protobuf(hash.digest()).ok()?;
|
||||
public.try_into_ed25519().ok().map(|public| public.to_bytes())
|
||||
}
|
||||
|
||||
/// Convert a mixnet core [`PeerId`](CorePeerId) into a libp2p [`PeerId`].
|
||||
///
|
||||
/// This will succeed only if `peer_id` represents a point on the Ed25519 curve. Returns `None` on
|
||||
/// failure.
|
||||
pub fn from_core_peer_id(core_peer_id: &CorePeerId) -> Option<PeerId> {
|
||||
let public = libp2p_identity::ed25519::PublicKey::try_from_bytes(core_peer_id).ok()?;
|
||||
let public: libp2p_identity::PublicKey = public.into();
|
||||
Some(public.into())
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::config::Config;
|
||||
use mixnet::core::PACKET_SIZE;
|
||||
use sc_network::{config::NonDefaultSetConfig, ProtocolName};
|
||||
|
||||
/// Returns the protocol name to use for the mixnet controlled by the given chain.
|
||||
pub fn protocol_name(genesis_hash: &[u8], fork_id: Option<&str>) -> ProtocolName {
|
||||
let name = if let Some(fork_id) = fork_id {
|
||||
format!("/{}/{}/mixnet/1", array_bytes::bytes2hex("", genesis_hash), fork_id)
|
||||
} else {
|
||||
format!("/{}/mixnet/1", array_bytes::bytes2hex("", genesis_hash))
|
||||
};
|
||||
name.into()
|
||||
}
|
||||
|
||||
/// Returns the peers set configuration for the mixnet protocol.
|
||||
pub fn peers_set_config(name: ProtocolName, config: &Config) -> NonDefaultSetConfig {
|
||||
let mut set_config = NonDefaultSetConfig::new(name, PACKET_SIZE as u64);
|
||||
if config.substrate.num_gateway_slots != 0 {
|
||||
// out_peers is always 0; we are only interested in connecting to mixnodes, which we do by
|
||||
// setting them as reserved nodes
|
||||
set_config.allow_non_reserved(config.substrate.num_gateway_slots, 0);
|
||||
}
|
||||
set_config
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Sender-side request logic. Some things from this module are also used on the receiver side, eg
|
||||
//! [`extrinsic_delay`], but most of the receiver-side request logic lives elsewhere.
|
||||
|
||||
use super::{config::SubstrateConfig, error::Error};
|
||||
use blake2::{
|
||||
digest::{consts::U16, Mac},
|
||||
Blake2bMac,
|
||||
};
|
||||
use codec::{Decode, DecodeAll};
|
||||
use futures::channel::oneshot;
|
||||
use log::debug;
|
||||
use mixnet::core::{Delay, MessageId, PostErr, Scattered};
|
||||
use sp_core::Bytes;
|
||||
use std::time::Duration;
|
||||
|
||||
const LOG_TARGET: &str = "mixnet";
|
||||
|
||||
fn send_err<T>(reply_sender: oneshot::Sender<Result<T, Error>>, err: Error) {
|
||||
if let Err(Err(err)) = reply_sender.send(Err(err)) {
|
||||
debug!(target: LOG_TARGET, "Failed to inform requester of error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
fn send_reply<T: Decode>(reply_sender: oneshot::Sender<Result<T, Error>>, mut data: &[u8]) {
|
||||
let res = match Result::decode_all(&mut data) {
|
||||
Ok(res) => res.map_err(Error::Remote),
|
||||
Err(_) => Err(Error::BadReply),
|
||||
};
|
||||
match reply_sender.send(res) {
|
||||
Ok(_) => (),
|
||||
Err(Ok(_)) => debug!(target: LOG_TARGET, "Failed to send reply to requester"),
|
||||
Err(Err(err)) => debug!(target: LOG_TARGET, "Failed to inform requester of error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// First byte of a submit extrinsic request, identifying it as such.
|
||||
pub const SUBMIT_EXTRINSIC: u8 = 1;
|
||||
|
||||
const EXTRINSIC_DELAY_PERSONA: &[u8; 16] = b"submit-extrn-dly";
|
||||
|
||||
/// Returns the artificial delay that should be inserted between receipt of a submit extrinsic
|
||||
/// request with the given message ID and import of the extrinsic into the local transaction pool.
|
||||
pub fn extrinsic_delay(message_id: &MessageId, config: &SubstrateConfig) -> Duration {
|
||||
let h = Blake2bMac::<U16>::new_with_salt_and_personal(message_id, b"", EXTRINSIC_DELAY_PERSONA)
|
||||
.expect("Key, salt, and persona sizes are fixed and small enough");
|
||||
let delay = Delay::exp(h.finalize().into_bytes().as_ref());
|
||||
delay.to_duration(config.mean_extrinsic_delay)
|
||||
}
|
||||
|
||||
/// Request parameters and local reply channel. Stored by the
|
||||
/// [`RequestManager`](mixnet::request_manager::RequestManager).
|
||||
pub enum Request {
|
||||
SubmitExtrinsic { extrinsic: Bytes, reply_sender: oneshot::Sender<Result<(), Error>> },
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Forward an error to the user of the mixnet service.
|
||||
fn send_err(self, err: Error) {
|
||||
match self {
|
||||
Request::SubmitExtrinsic { reply_sender, .. } => send_err(reply_sender, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Forward a reply to the user of the mixnet service.
|
||||
pub fn send_reply(self, data: &[u8]) {
|
||||
match self {
|
||||
Request::SubmitExtrinsic { reply_sender, .. } => send_reply(reply_sender, data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mixnet::request_manager::Request for Request {
|
||||
type Context = SubstrateConfig;
|
||||
|
||||
fn with_data<T>(&self, f: impl FnOnce(Scattered<u8>) -> T, _context: &Self::Context) -> T {
|
||||
match self {
|
||||
Request::SubmitExtrinsic { extrinsic, .. } =>
|
||||
f([&[SUBMIT_EXTRINSIC], extrinsic.as_ref()].as_slice().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn num_surbs(&self, context: &Self::Context) -> usize {
|
||||
match self {
|
||||
Request::SubmitExtrinsic { .. } => context.surb_factor,
|
||||
}
|
||||
}
|
||||
|
||||
fn handling_delay(&self, message_id: &MessageId, context: &Self::Context) -> Duration {
|
||||
match self {
|
||||
Request::SubmitExtrinsic { .. } => extrinsic_delay(message_id, context),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_post_err(self, err: PostErr, _context: &Self::Context) {
|
||||
self.send_err(err.into());
|
||||
}
|
||||
|
||||
fn handle_retry_limit_reached(self, _context: &Self::Context) {
|
||||
self.send_err(Error::NoReply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Top-level mixnet service function.
|
||||
|
||||
use super::{
|
||||
api::ApiBackend,
|
||||
config::{Config, SubstrateConfig},
|
||||
error::RemoteErr,
|
||||
extrinsic_queue::ExtrinsicQueue,
|
||||
maybe_inf_delay::MaybeInfDelay,
|
||||
packet_dispatcher::PacketDispatcher,
|
||||
peer_id::to_core_peer_id,
|
||||
request::{extrinsic_delay, Request, SUBMIT_EXTRINSIC},
|
||||
sync_with_runtime::sync_with_runtime,
|
||||
};
|
||||
use codec::{Decode, DecodeAll, Encode};
|
||||
use futures::{
|
||||
future::{pending, Either},
|
||||
stream::FuturesUnordered,
|
||||
StreamExt,
|
||||
};
|
||||
use log::{debug, error, trace, warn};
|
||||
use mixnet::{
|
||||
core::{Events, Message, Mixnet, Packet},
|
||||
reply_manager::{ReplyContext, ReplyManager},
|
||||
request_manager::RequestManager,
|
||||
};
|
||||
use sc_client_api::{BlockchainEvents, HeaderBackend};
|
||||
use sc_network::{
|
||||
Event::{NotificationStreamClosed, NotificationStreamOpened, NotificationsReceived},
|
||||
NetworkEventStream, NetworkNotification, NetworkPeers, NetworkStateInfo, ProtocolName,
|
||||
};
|
||||
use sc_transaction_pool_api::{
|
||||
LocalTransactionPool, OffchainTransactionPoolFactory, TransactionPool,
|
||||
};
|
||||
use sp_api::{ApiExt, ProvideRuntimeApi};
|
||||
use sp_consensus::SyncOracle;
|
||||
use sp_keystore::{KeystoreExt, KeystorePtr};
|
||||
use sp_mixnet::{runtime_api::MixnetApi, types::Mixnode};
|
||||
use sp_runtime::{
|
||||
traits::{Block, Header},
|
||||
transaction_validity::TransactionSource,
|
||||
Saturating,
|
||||
};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
const LOG_TARGET: &str = "mixnet";
|
||||
|
||||
const MIN_BLOCKS_BETWEEN_REGISTRATION_ATTEMPTS: u32 = 3;
|
||||
|
||||
fn complete_submit_extrinsic<X>(
|
||||
reply_manager: &mut ReplyManager,
|
||||
reply_context: ReplyContext,
|
||||
data: Result<(), RemoteErr>,
|
||||
mixnet: &mut Mixnet<X>,
|
||||
) {
|
||||
reply_manager.complete(reply_context, data.encode(), mixnet);
|
||||
}
|
||||
|
||||
fn handle_packet<X, E: Decode>(
|
||||
packet: &Packet,
|
||||
mixnet: &mut Mixnet<X>,
|
||||
request_manager: &mut RequestManager<Request>,
|
||||
reply_manager: &mut ReplyManager,
|
||||
extrinsic_queue: &mut ExtrinsicQueue<E>,
|
||||
config: &SubstrateConfig,
|
||||
) {
|
||||
match mixnet.handle_packet(packet) {
|
||||
Some(Message::Request(message)) => {
|
||||
let Some((reply_context, data)) = reply_manager.insert(message, mixnet) else { return };
|
||||
|
||||
match data.as_slice() {
|
||||
[SUBMIT_EXTRINSIC, encoded_extrinsic @ ..] => {
|
||||
if !extrinsic_queue.has_space() {
|
||||
debug!(target: LOG_TARGET, "No space in extrinsic queue; dropping request");
|
||||
// We don't send a reply in this case; we want the requester to retry
|
||||
reply_manager.abandon(reply_context);
|
||||
return
|
||||
}
|
||||
|
||||
// Decode the extrinsic
|
||||
let mut encoded_extrinsic = encoded_extrinsic;
|
||||
let extrinsic = match E::decode_all(&mut encoded_extrinsic) {
|
||||
Ok(extrinsic) => extrinsic,
|
||||
Err(err) => {
|
||||
complete_submit_extrinsic(
|
||||
reply_manager,
|
||||
reply_context,
|
||||
Err(RemoteErr::Decode(format!("Bad extrinsic: {}", err))),
|
||||
mixnet,
|
||||
);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let deadline =
|
||||
Instant::now() + extrinsic_delay(reply_context.message_id(), config);
|
||||
extrinsic_queue.insert(deadline, extrinsic, reply_context);
|
||||
},
|
||||
_ => {
|
||||
debug!(target: LOG_TARGET, "Unrecognised request; discarding");
|
||||
// To keep things simple we don't bother sending a reply in this case. The
|
||||
// requester will give up and try another mixnode eventually.
|
||||
reply_manager.abandon(reply_context);
|
||||
},
|
||||
}
|
||||
},
|
||||
Some(Message::Reply(message)) => {
|
||||
let Some(request) = request_manager.remove(&message.request_id) else {
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Received reply to already-completed request with message ID {:x?}",
|
||||
message.request_id
|
||||
);
|
||||
return
|
||||
};
|
||||
request.send_reply(&message.data);
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn time_until(instant: Instant) -> Duration {
|
||||
instant.saturating_duration_since(Instant::now())
|
||||
}
|
||||
|
||||
/// Run the mixnet service. If `keystore` is `None`, the service will not attempt to register the
|
||||
/// local node as a mixnode, even if `config.register` is `true`.
|
||||
pub async fn run<B, C, S, N, P>(
|
||||
config: Config,
|
||||
mut api_backend: ApiBackend,
|
||||
client: Arc<C>,
|
||||
sync: Arc<S>,
|
||||
network: Arc<N>,
|
||||
protocol_name: ProtocolName,
|
||||
transaction_pool: Arc<P>,
|
||||
keystore: Option<KeystorePtr>,
|
||||
) where
|
||||
B: Block,
|
||||
C: BlockchainEvents<B> + ProvideRuntimeApi<B> + HeaderBackend<B>,
|
||||
C::Api: MixnetApi<B>,
|
||||
S: SyncOracle,
|
||||
N: NetworkStateInfo + NetworkEventStream + NetworkNotification + NetworkPeers,
|
||||
P: TransactionPool<Block = B> + LocalTransactionPool<Block = B> + 'static,
|
||||
{
|
||||
let local_peer_id = network.local_peer_id();
|
||||
let Some(local_peer_id) = to_core_peer_id(&local_peer_id) else {
|
||||
error!(target: LOG_TARGET,
|
||||
"Failed to convert libp2p local peer ID {local_peer_id} to mixnet peer ID; \
|
||||
mixnet not running");
|
||||
return
|
||||
};
|
||||
|
||||
let offchain_transaction_pool_factory =
|
||||
OffchainTransactionPoolFactory::new(transaction_pool.clone());
|
||||
|
||||
let mut mixnet = Mixnet::new(config.core);
|
||||
// It would make sense to reset this to 0 when the session changes, but registrations aren't
|
||||
// allowed at the start of a session anyway, so it doesn't really matter
|
||||
let mut min_register_block = 0u32.into();
|
||||
let mut packet_dispatcher = PacketDispatcher::new(&local_peer_id);
|
||||
let mut request_manager = RequestManager::new(config.request_manager);
|
||||
let mut reply_manager = ReplyManager::new(config.reply_manager);
|
||||
let mut extrinsic_queue = ExtrinsicQueue::new(config.substrate.extrinsic_queue_capacity);
|
||||
|
||||
let mut finality_notifications = client.finality_notification_stream();
|
||||
// Import notifications only used for triggering registration attempts
|
||||
let mut import_notifications = if config.substrate.register && keystore.is_some() {
|
||||
Some(client.import_notification_stream())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut network_events = network.event_stream("mixnet").fuse();
|
||||
let mut next_forward_packet_delay = MaybeInfDelay::new(None);
|
||||
let mut next_authored_packet_delay = MaybeInfDelay::new(None);
|
||||
let mut ready_peers = FuturesUnordered::new();
|
||||
let mut next_retry_delay = MaybeInfDelay::new(None);
|
||||
let mut next_extrinsic_delay = MaybeInfDelay::new(None);
|
||||
let mut submit_extrinsic_results = FuturesUnordered::new();
|
||||
|
||||
loop {
|
||||
let mut next_request = if request_manager.has_space() {
|
||||
Either::Left(api_backend.request_receiver.select_next_some())
|
||||
} else {
|
||||
Either::Right(pending())
|
||||
};
|
||||
|
||||
let mut next_import_notification = import_notifications.as_mut().map_or_else(
|
||||
|| Either::Right(pending()),
|
||||
|notifications| Either::Left(notifications.select_next_some()),
|
||||
);
|
||||
|
||||
futures::select! {
|
||||
request = next_request =>
|
||||
request_manager.insert(request, &mut mixnet, &packet_dispatcher, &config.substrate),
|
||||
|
||||
notification = finality_notifications.select_next_some() => {
|
||||
// To avoid trying to connect to old mixnodes, ignore finality notifications while
|
||||
// offline or major syncing. This is a bit racy but should be good enough.
|
||||
if !sync.is_offline() && !sync.is_major_syncing() {
|
||||
let api = client.runtime_api();
|
||||
sync_with_runtime(&mut mixnet, api, notification.hash);
|
||||
request_manager.update_session_status(
|
||||
&mut mixnet, &packet_dispatcher, &config.substrate);
|
||||
}
|
||||
}
|
||||
|
||||
notification = next_import_notification => {
|
||||
if notification.is_new_best && (*notification.header.number() >= min_register_block) {
|
||||
let mut api = client.runtime_api();
|
||||
api.register_extension(KeystoreExt(keystore.clone().expect(
|
||||
"Import notification stream only setup if we have a keystore")));
|
||||
api.register_extension(offchain_transaction_pool_factory
|
||||
.offchain_transaction_pool(notification.hash));
|
||||
let session_index = mixnet.session_status().current_index;
|
||||
let mixnode = Mixnode {
|
||||
kx_public: *mixnet.next_kx_public(),
|
||||
peer_id: local_peer_id,
|
||||
external_addresses: network.external_addresses().into_iter()
|
||||
.map(|addr| addr.to_string().into_bytes()).collect(),
|
||||
};
|
||||
match api.maybe_register(notification.hash, session_index, mixnode) {
|
||||
Ok(true) => min_register_block = notification.header.number().saturating_add(
|
||||
MIN_BLOCKS_BETWEEN_REGISTRATION_ATTEMPTS.into()),
|
||||
Ok(false) => (),
|
||||
Err(err) => debug!(target: LOG_TARGET,
|
||||
"Error trying to register for the next session: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event = network_events.select_next_some() => match event {
|
||||
NotificationStreamOpened { remote, protocol, .. }
|
||||
if protocol == protocol_name => packet_dispatcher.add_peer(&remote),
|
||||
NotificationStreamClosed { remote, protocol }
|
||||
if protocol == protocol_name => packet_dispatcher.remove_peer(&remote),
|
||||
NotificationsReceived { remote, messages } => {
|
||||
for message in messages {
|
||||
if message.0 == protocol_name {
|
||||
match message.1.as_ref().try_into() {
|
||||
Ok(packet) => handle_packet(packet,
|
||||
&mut mixnet, &mut request_manager, &mut reply_manager,
|
||||
&mut extrinsic_queue, &config.substrate),
|
||||
Err(_) => debug!(target: LOG_TARGET,
|
||||
"Dropped incorrectly sized packet ({} bytes) from {remote}",
|
||||
message.1.len(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
},
|
||||
|
||||
_ = next_forward_packet_delay => {
|
||||
if let Some(packet) = mixnet.pop_next_forward_packet() {
|
||||
if let Some(ready_peer) = packet_dispatcher.dispatch(packet) {
|
||||
if let Some(fut) = ready_peer.send_packet(&*network, protocol_name.clone()) {
|
||||
ready_peers.push(fut);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(target: LOG_TARGET,
|
||||
"Next forward packet deadline reached, but no packet in queue; \
|
||||
this is a bug");
|
||||
}
|
||||
}
|
||||
|
||||
_ = next_authored_packet_delay => {
|
||||
if let Some(packet) = mixnet.pop_next_authored_packet(&packet_dispatcher) {
|
||||
if let Some(ready_peer) = packet_dispatcher.dispatch(packet) {
|
||||
if let Some(fut) = ready_peer.send_packet(&*network, protocol_name.clone()) {
|
||||
ready_peers.push(fut);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ready_peer = ready_peers.select_next_some() => {
|
||||
if let Some(ready_peer) = ready_peer {
|
||||
if let Some(fut) = ready_peer.send_packet(&*network, protocol_name.clone()) {
|
||||
ready_peers.push(fut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = next_retry_delay => {
|
||||
if !request_manager.pop_next_retry(&mut mixnet, &packet_dispatcher, &config.substrate) {
|
||||
warn!(target: LOG_TARGET,
|
||||
"Next retry deadline reached, but no request in retry queue; \
|
||||
this is a bug");
|
||||
}
|
||||
}
|
||||
|
||||
_ = next_extrinsic_delay => {
|
||||
if let Some((extrinsic, reply_context)) = extrinsic_queue.pop() {
|
||||
if submit_extrinsic_results.len() < config.substrate.max_pending_extrinsics {
|
||||
let fut = transaction_pool.submit_one(
|
||||
client.info().best_hash,
|
||||
TransactionSource::External,
|
||||
extrinsic);
|
||||
submit_extrinsic_results.push(async move {
|
||||
(fut.await, reply_context)
|
||||
});
|
||||
} else {
|
||||
// There are already too many pending extrinsics, just drop this one. We
|
||||
// don't send a reply; we want the requester to retry.
|
||||
debug!(target: LOG_TARGET,
|
||||
"Too many pending extrinsics; dropped submit extrinsic request");
|
||||
reply_manager.abandon(reply_context);
|
||||
}
|
||||
} else {
|
||||
warn!(target: LOG_TARGET,
|
||||
"Next extrinsic deadline reached, but no extrinsic in queue; \
|
||||
this is a bug");
|
||||
}
|
||||
}
|
||||
|
||||
res_reply_context = submit_extrinsic_results.select_next_some() => {
|
||||
let (res, reply_context) = res_reply_context;
|
||||
let res = match res {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(RemoteErr::Other(err.to_string())),
|
||||
};
|
||||
complete_submit_extrinsic(&mut reply_manager, reply_context, res, &mut mixnet);
|
||||
}
|
||||
}
|
||||
|
||||
let events = mixnet.take_events();
|
||||
if !events.is_empty() {
|
||||
if events.contains(Events::RESERVED_PEERS_CHANGED) {
|
||||
let reserved_peer_addrs = mixnet
|
||||
.reserved_peers()
|
||||
.flat_map(|mixnode| mixnode.extra.iter()) // External addresses
|
||||
.cloned()
|
||||
.collect();
|
||||
if let Err(err) =
|
||||
network.set_reserved_peers(protocol_name.clone(), reserved_peer_addrs)
|
||||
{
|
||||
debug!(target: LOG_TARGET, "Setting reserved peers failed: {err}");
|
||||
}
|
||||
}
|
||||
if events.contains(Events::NEXT_FORWARD_PACKET_DEADLINE_CHANGED) {
|
||||
next_forward_packet_delay
|
||||
.reset(mixnet.next_forward_packet_deadline().map(time_until));
|
||||
}
|
||||
if events.contains(Events::NEXT_AUTHORED_PACKET_DEADLINE_CHANGED) {
|
||||
next_authored_packet_delay.reset(mixnet.next_authored_packet_delay());
|
||||
}
|
||||
if events.contains(Events::SPACE_IN_AUTHORED_PACKET_QUEUE) {
|
||||
// Note this may cause the next retry deadline to change, but should not trigger
|
||||
// any mixnet events
|
||||
request_manager.process_post_queues(
|
||||
&mut mixnet,
|
||||
&packet_dispatcher,
|
||||
&config.substrate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if request_manager.next_retry_deadline_changed() {
|
||||
next_retry_delay.reset(request_manager.next_retry_deadline().map(time_until));
|
||||
}
|
||||
|
||||
if extrinsic_queue.next_deadline_changed() {
|
||||
next_extrinsic_delay.reset(extrinsic_queue.next_deadline().map(time_until));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! [`sync_with_runtime`] synchronises the session status and mixnode sets from the blockchain
|
||||
//! runtime to the core mixnet state. It is called every time a block is finalised.
|
||||
|
||||
use super::peer_id::from_core_peer_id;
|
||||
use libp2p_identity::PeerId;
|
||||
use log::{debug, info};
|
||||
use mixnet::core::{
|
||||
Mixnet, Mixnode as CoreMixnode, MixnodesErr as CoreMixnodesErr, RelSessionIndex,
|
||||
SessionPhase as CoreSessionPhase, SessionStatus as CoreSessionStatus,
|
||||
};
|
||||
use multiaddr::{multiaddr, Multiaddr, Protocol};
|
||||
use sp_api::{ApiError, ApiRef};
|
||||
use sp_mixnet::{
|
||||
runtime_api::MixnetApi,
|
||||
types::{
|
||||
Mixnode as RuntimeMixnode, MixnodesErr as RuntimeMixnodesErr,
|
||||
SessionPhase as RuntimeSessionPhase, SessionStatus as RuntimeSessionStatus,
|
||||
},
|
||||
};
|
||||
use sp_runtime::traits::Block;
|
||||
|
||||
const LOG_TARGET: &str = "mixnet";
|
||||
|
||||
/// Convert a [`RuntimeSessionStatus`] to a [`CoreSessionStatus`].
|
||||
///
|
||||
/// The [`RuntimeSessionStatus`] and [`CoreSessionStatus`] types are effectively the same.
|
||||
/// [`RuntimeSessionStatus`] is used in the runtime to avoid depending on the [`mixnet`] crate
|
||||
/// there.
|
||||
fn to_core_session_status(status: RuntimeSessionStatus) -> CoreSessionStatus {
|
||||
CoreSessionStatus {
|
||||
current_index: status.current_index,
|
||||
phase: match status.phase {
|
||||
RuntimeSessionPhase::CoverToCurrent => CoreSessionPhase::CoverToCurrent,
|
||||
RuntimeSessionPhase::RequestsToCurrent => CoreSessionPhase::RequestsToCurrent,
|
||||
RuntimeSessionPhase::CoverToPrev => CoreSessionPhase::CoverToPrev,
|
||||
RuntimeSessionPhase::DisconnectFromPrev => CoreSessionPhase::DisconnectFromPrev,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_external_addresses(external_addresses: Vec<Vec<u8>>) -> Vec<Multiaddr> {
|
||||
external_addresses
|
||||
.into_iter()
|
||||
.flat_map(|addr| {
|
||||
let addr = match String::from_utf8(addr) {
|
||||
Ok(addr) => addr,
|
||||
Err(addr) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Mixnode external address {:x?} is not valid UTF-8",
|
||||
addr.into_bytes(),
|
||||
);
|
||||
return None
|
||||
},
|
||||
};
|
||||
match addr.parse() {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(err) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Could not parse mixnode address {addr}: {err}",
|
||||
);
|
||||
None
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Modify `external_addresses` such that there is at least one address and the final component of
|
||||
/// each address matches `peer_id`.
|
||||
fn fixup_external_addresses(external_addresses: &mut Vec<Multiaddr>, peer_id: &PeerId) {
|
||||
// Ensure the final component of each address matches peer_id
|
||||
external_addresses.retain_mut(|addr| match PeerId::try_from_multiaddr(addr) {
|
||||
Some(addr_peer_id) if addr_peer_id == *peer_id => true,
|
||||
Some(_) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Mixnode address {} does not match mixnode peer ID {}, ignoring",
|
||||
addr,
|
||||
peer_id
|
||||
);
|
||||
false
|
||||
},
|
||||
None if matches!(addr.iter().last(), Some(Protocol::P2p(_))) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Mixnode address {} has unrecognised P2P protocol, ignoring",
|
||||
addr
|
||||
);
|
||||
false
|
||||
},
|
||||
None => {
|
||||
addr.push(Protocol::P2p(*peer_id.as_ref()));
|
||||
true
|
||||
},
|
||||
});
|
||||
|
||||
// If there are no addresses, insert one consisting of just the peer ID
|
||||
if external_addresses.is_empty() {
|
||||
external_addresses.push(multiaddr!(P2p(*peer_id.as_ref())));
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [`RuntimeMixnode`] to a [`CoreMixnode`]. If the conversion fails, an error message is
|
||||
/// logged, but a [`CoreMixnode`] is still returned.
|
||||
///
|
||||
/// It would be possible to handle conversion failure in a better way, but this would complicate
|
||||
/// things for what should be a rare case. Note that even if the conversion here succeeds, there is
|
||||
/// no guarantee that we will be able to connect to the mixnode or send packets to it. The most
|
||||
/// common failure case is expected to be that a mixnode is simply unreachable over the network.
|
||||
fn into_core_mixnode(mixnode: RuntimeMixnode) -> CoreMixnode<Vec<Multiaddr>> {
|
||||
let external_addresses = if let Some(peer_id) = from_core_peer_id(&mixnode.peer_id) {
|
||||
let mut external_addresses = parse_external_addresses(mixnode.external_addresses);
|
||||
fixup_external_addresses(&mut external_addresses, &peer_id);
|
||||
external_addresses
|
||||
} else {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to convert mixnet peer ID {:x?} to libp2p peer ID",
|
||||
mixnode.peer_id,
|
||||
);
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
CoreMixnode {
|
||||
kx_public: mixnode.kx_public,
|
||||
peer_id: mixnode.peer_id,
|
||||
extra: external_addresses,
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_set_mixnodes(
|
||||
mixnet: &mut Mixnet<Vec<Multiaddr>>,
|
||||
rel_session_index: RelSessionIndex,
|
||||
mixnodes: &dyn Fn() -> Result<Result<Vec<RuntimeMixnode>, RuntimeMixnodesErr>, ApiError>,
|
||||
) {
|
||||
let current_session_index = mixnet.session_status().current_index;
|
||||
mixnet.maybe_set_mixnodes(rel_session_index, &mut || {
|
||||
// Note that RelSessionIndex::Prev + 0 would panic, but this closure will not get called in
|
||||
// that case so we are fine. Do not move this out of the closure!
|
||||
let session_index = rel_session_index + current_session_index;
|
||||
match mixnodes() {
|
||||
Ok(Ok(mixnodes)) => Ok(mixnodes.into_iter().map(into_core_mixnode).collect()),
|
||||
Ok(Err(err)) => {
|
||||
info!(target: LOG_TARGET, "Session {session_index}: Mixnet disabled: {err}");
|
||||
Err(CoreMixnodesErr::Permanent) // Disable the session slot
|
||||
},
|
||||
Err(err) => {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Session {session_index}: Error getting mixnodes from runtime: {err}"
|
||||
);
|
||||
Err(CoreMixnodesErr::Transient) // Just leave the session slot empty; try again next block
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn sync_with_runtime<B, A>(mixnet: &mut Mixnet<Vec<Multiaddr>>, api: ApiRef<A>, hash: B::Hash)
|
||||
where
|
||||
B: Block,
|
||||
A: MixnetApi<B>,
|
||||
{
|
||||
let session_status = match api.session_status(hash) {
|
||||
Ok(session_status) => session_status,
|
||||
Err(err) => {
|
||||
debug!(target: LOG_TARGET, "Error getting session status from runtime: {err}");
|
||||
return
|
||||
},
|
||||
};
|
||||
mixnet.set_session_status(to_core_session_status(session_status));
|
||||
|
||||
maybe_set_mixnodes(mixnet, RelSessionIndex::Prev, &|| api.prev_mixnodes(hash));
|
||||
maybe_set_mixnodes(mixnet, RelSessionIndex::Current, &|| api.current_mixnodes(hash));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn fixup_empty_external_addresses() {
|
||||
let peer_id = PeerId::random();
|
||||
let mut external_addresses = Vec::new();
|
||||
fixup_external_addresses(&mut external_addresses, &peer_id);
|
||||
assert_eq!(external_addresses, vec![multiaddr!(P2p(peer_id))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixup_misc_external_addresses() {
|
||||
let peer_id = PeerId::random();
|
||||
let other_peer_id = PeerId::random();
|
||||
let mut external_addresses = vec![
|
||||
multiaddr!(Tcp(0u16), P2p(peer_id)),
|
||||
multiaddr!(Tcp(1u16), P2p(other_peer_id)),
|
||||
multiaddr!(Tcp(2u16)),
|
||||
Multiaddr::empty(),
|
||||
];
|
||||
fixup_external_addresses(&mut external_addresses, &peer_id);
|
||||
assert_eq!(
|
||||
external_addresses,
|
||||
vec![
|
||||
multiaddr!(Tcp(0u16), P2p(peer_id)),
|
||||
multiaddr!(Tcp(2u16), P2p(peer_id)),
|
||||
multiaddr!(P2p(peer_id)),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -493,8 +493,8 @@ impl ProtocolController {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the peer from the set of reserved peers. The peer is moved to the set of regular
|
||||
/// nodes.
|
||||
/// Remove the peer from the set of reserved peers. The peer is either moved to the set of
|
||||
/// regular nodes or disconnected.
|
||||
fn on_remove_reserved_peer(&mut self, peer_id: PeerId) {
|
||||
let state = match self.reserved_nodes.remove(&peer_id) {
|
||||
Some(state) => state,
|
||||
@@ -508,7 +508,14 @@ impl ProtocolController {
|
||||
};
|
||||
|
||||
if let PeerState::Connected(direction) = state {
|
||||
if self.reserved_only {
|
||||
// Disconnect if we're at (or over) the regular node limit
|
||||
let disconnect = self.reserved_only ||
|
||||
match direction {
|
||||
Direction::Inbound => self.num_in >= self.max_in,
|
||||
Direction::Outbound => self.num_out >= self.max_out,
|
||||
};
|
||||
|
||||
if disconnect {
|
||||
// Disconnect the node.
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
|
||||
@@ -19,6 +19,7 @@ serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
thiserror = "1.0"
|
||||
sc-chain-spec = { path = "../chain-spec" }
|
||||
sc-mixnet = { path = "../mixnet" }
|
||||
sc-transaction-pool-api = { path = "../transaction-pool/api" }
|
||||
sp-core = { path = "../../primitives/core" }
|
||||
sp-rpc = { path = "../../primitives/rpc" }
|
||||
|
||||
@@ -25,4 +25,5 @@ pub mod base {
|
||||
pub const OFFCHAIN: i32 = 5000;
|
||||
pub const DEV: i32 = 6000;
|
||||
pub const STATEMENT: i32 = 7000;
|
||||
pub const MIXNET: i32 = 8000;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub mod author;
|
||||
pub mod chain;
|
||||
pub mod child_state;
|
||||
pub mod dev;
|
||||
pub mod mixnet;
|
||||
pub mod offchain;
|
||||
pub mod state;
|
||||
pub mod statement;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Mixnet RPC module errors.
|
||||
|
||||
use jsonrpsee::types::error::{CallError, ErrorObject};
|
||||
use sc_mixnet::{PostErr, RemoteErr, TopologyErr};
|
||||
|
||||
/// Mixnet RPC error type.
|
||||
pub struct Error(pub sc_mixnet::Error);
|
||||
|
||||
/// Base code for all mixnet errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::MIXNET;
|
||||
|
||||
impl From<Error> for jsonrpsee::core::Error {
|
||||
fn from(err: Error) -> Self {
|
||||
let code = match err.0 {
|
||||
sc_mixnet::Error::ServiceUnavailable => BASE_ERROR + 1,
|
||||
sc_mixnet::Error::NoReply => BASE_ERROR + 2,
|
||||
sc_mixnet::Error::BadReply => BASE_ERROR + 3,
|
||||
sc_mixnet::Error::Post(PostErr::TooManyFragments) => BASE_ERROR + 101,
|
||||
sc_mixnet::Error::Post(PostErr::SessionMixnodesNotKnown(_)) => BASE_ERROR + 102,
|
||||
sc_mixnet::Error::Post(PostErr::SessionDisabled(_)) => BASE_ERROR + 103,
|
||||
sc_mixnet::Error::Post(PostErr::Topology(TopologyErr::NoConnectedGatewayMixnodes)) =>
|
||||
BASE_ERROR + 151,
|
||||
sc_mixnet::Error::Post(PostErr::Topology(_)) => BASE_ERROR + 150,
|
||||
sc_mixnet::Error::Post(_) => BASE_ERROR + 100,
|
||||
sc_mixnet::Error::Remote(RemoteErr::Other(_)) => BASE_ERROR + 200,
|
||||
sc_mixnet::Error::Remote(RemoteErr::Decode(_)) => BASE_ERROR + 201,
|
||||
};
|
||||
CallError::Custom(ErrorObject::owned(code, err.0.to_string(), None::<()>)).into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate mixnet API.
|
||||
|
||||
pub mod error;
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use sp_core::Bytes;
|
||||
|
||||
#[rpc(client, server)]
|
||||
pub trait MixnetApi {
|
||||
/// Submit encoded extrinsic over the mixnet for inclusion in block.
|
||||
#[method(name = "mixnet_submitExtrinsic")]
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<()>;
|
||||
}
|
||||
@@ -22,6 +22,7 @@ serde_json = "1.0.107"
|
||||
sc-block-builder = { path = "../block-builder" }
|
||||
sc-chain-spec = { path = "../chain-spec" }
|
||||
sc-client-api = { path = "../api" }
|
||||
sc-mixnet = { path = "../mixnet" }
|
||||
sc-rpc-api = { path = "../rpc-api" }
|
||||
sc-tracing = { path = "../tracing" }
|
||||
sc-transaction-pool-api = { path = "../transaction-pool/api" }
|
||||
|
||||
@@ -34,6 +34,7 @@ pub use sc_rpc_api::DenyUnsafe;
|
||||
pub mod author;
|
||||
pub mod chain;
|
||||
pub mod dev;
|
||||
pub mod mixnet;
|
||||
pub mod offchain;
|
||||
pub mod state;
|
||||
pub mod statement;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate mixnet API.
|
||||
|
||||
use jsonrpsee::core::{async_trait, RpcResult};
|
||||
use sc_mixnet::Api;
|
||||
use sc_rpc_api::mixnet::error::Error;
|
||||
pub use sc_rpc_api::mixnet::MixnetApiServer;
|
||||
use sp_core::Bytes;
|
||||
|
||||
/// Mixnet API.
|
||||
pub struct Mixnet(futures::lock::Mutex<Api>);
|
||||
|
||||
impl Mixnet {
|
||||
/// Create a new mixnet API instance.
|
||||
pub fn new(api: Api) -> Self {
|
||||
Self(futures::lock::Mutex::new(api))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl MixnetApiServer for Mixnet {
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<()> {
|
||||
// We only hold the lock while pushing the request into the requests channel
|
||||
let fut = {
|
||||
let mut api = self.0.lock().await;
|
||||
api.submit_extrinsic(extrinsic).await
|
||||
};
|
||||
Ok(fut.await.map_err(Error)?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
[package]
|
||||
description = "FRAME's mixnet pallet"
|
||||
name = "pallet-mixnet"
|
||||
version = "0.1.0-dev"
|
||||
license = "Apache-2.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||
frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" }
|
||||
frame-support = { default-features = false, path = "../support" }
|
||||
frame-system = { default-features = false, path = "../system" }
|
||||
log = { version = "0.4.17", default-features = false }
|
||||
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0.188", default-features = false, features = ["derive"] }
|
||||
sp-application-crypto = { default-features = false, path = "../../primitives/application-crypto" }
|
||||
sp-arithmetic = { default-features = false, path = "../../primitives/arithmetic" }
|
||||
sp-io = { default-features = false, path = "../../primitives/io" }
|
||||
sp-mixnet = { default-features = false, path = "../../primitives/mixnet" }
|
||||
sp-runtime = { default-features = false, path = "../../primitives/runtime" }
|
||||
sp-std = { default-features = false, path = "../../primitives/std" }
|
||||
|
||||
[features]
|
||||
default = [ "std" ]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame-benchmarking?/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-arithmetic/std",
|
||||
"sp-io/std",
|
||||
"sp-mixnet/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame-support/try-runtime",
|
||||
"frame-system/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
This pallet is responsible for determining the current mixnet session and phase, and the mixnode
|
||||
set for each session.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,598 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! This pallet is responsible for determining the current mixnet session and phase, and the
|
||||
//! mixnode set for each session.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
traits::{EstimateNextSessionRotation, Get, OneSessionHandler},
|
||||
BoundedVec,
|
||||
};
|
||||
use frame_system::{
|
||||
offchain::{SendTransactionTypes, SubmitTransaction},
|
||||
pallet_prelude::BlockNumberFor,
|
||||
};
|
||||
pub use pallet::*;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_application_crypto::RuntimeAppPublic;
|
||||
use sp_arithmetic::traits::{CheckedSub, Saturating, UniqueSaturatedInto, Zero};
|
||||
use sp_io::MultiRemovalResults;
|
||||
use sp_mixnet::types::{
|
||||
AuthorityId, AuthoritySignature, KxPublic, Mixnode, MixnodesErr, PeerId, SessionIndex,
|
||||
SessionPhase, SessionStatus, KX_PUBLIC_SIZE,
|
||||
};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{cmp::Ordering, vec::Vec};
|
||||
|
||||
const LOG_TARGET: &str = "runtime::mixnet";
|
||||
|
||||
/// Index of an authority in the authority list for a session.
|
||||
pub type AuthorityIndex = u32;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Bounded mixnode type
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Like [`Mixnode`], but encoded size is bounded.
|
||||
#[derive(
|
||||
Clone, Decode, Encode, MaxEncodedLen, PartialEq, TypeInfo, RuntimeDebug, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct BoundedMixnode<ExternalAddresses> {
|
||||
/// Key-exchange public key for the mixnode.
|
||||
pub kx_public: KxPublic,
|
||||
/// libp2p peer ID of the mixnode.
|
||||
pub peer_id: PeerId,
|
||||
/// External addresses for the mixnode, in multiaddr format, UTF-8 encoded.
|
||||
pub external_addresses: ExternalAddresses,
|
||||
}
|
||||
|
||||
impl<MaxExternalAddressSize, MaxExternalAddresses> Into<Mixnode>
|
||||
for BoundedMixnode<BoundedVec<BoundedVec<u8, MaxExternalAddressSize>, MaxExternalAddresses>>
|
||||
{
|
||||
fn into(self) -> Mixnode {
|
||||
Mixnode {
|
||||
kx_public: self.kx_public,
|
||||
peer_id: self.peer_id,
|
||||
external_addresses: self
|
||||
.external_addresses
|
||||
.into_iter()
|
||||
.map(BoundedVec::into_inner)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MaxExternalAddressSize: Get<u32>, MaxExternalAddresses: Get<u32>> From<Mixnode>
|
||||
for BoundedMixnode<BoundedVec<BoundedVec<u8, MaxExternalAddressSize>, MaxExternalAddresses>>
|
||||
{
|
||||
fn from(mixnode: Mixnode) -> Self {
|
||||
Self {
|
||||
kx_public: mixnode.kx_public,
|
||||
peer_id: mixnode.peer_id,
|
||||
external_addresses: mixnode
|
||||
.external_addresses
|
||||
.into_iter()
|
||||
.flat_map(|addr| match addr.try_into() {
|
||||
Ok(addr) => Some(addr),
|
||||
Err(addr) => {
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Mixnode external address {addr:x?} too long; discarding",
|
||||
);
|
||||
None
|
||||
},
|
||||
})
|
||||
.take(MaxExternalAddresses::get() as usize)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.expect("Excess external addresses discarded with take()"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`BoundedMixnode`] type for the given configuration.
|
||||
pub type BoundedMixnodeFor<T> = BoundedMixnode<
|
||||
BoundedVec<
|
||||
BoundedVec<u8, <T as Config>::MaxExternalAddressSize>,
|
||||
<T as Config>::MaxExternalAddressesPerMixnode,
|
||||
>,
|
||||
>;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Registration type
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// A mixnode registration. A registration transaction is formed from one of these plus an
|
||||
/// [`AuthoritySignature`].
|
||||
#[derive(Clone, Decode, Encode, PartialEq, TypeInfo, RuntimeDebug)]
|
||||
pub struct Registration<BlockNumber, BoundedMixnode> {
|
||||
/// Block number at the time of creation. When a registration transaction fails to make it on
|
||||
/// to the chain for whatever reason, we send out another one. We want this one to have a
|
||||
/// different hash in case the earlier transaction got banned somewhere; including the block
|
||||
/// number is a simple way of achieving this.
|
||||
pub block_number: BlockNumber,
|
||||
/// The session during which this registration should be processed. Note that on success the
|
||||
/// mixnode is registered for the _following_ session.
|
||||
pub session_index: SessionIndex,
|
||||
/// The index in the next session's authority list of the authority registering the mixnode.
|
||||
pub authority_index: AuthorityIndex,
|
||||
/// Mixnode information to register for the following session.
|
||||
pub mixnode: BoundedMixnode,
|
||||
}
|
||||
|
||||
/// [`Registration`] type for the given configuration.
|
||||
pub type RegistrationFor<T> = Registration<BlockNumberFor<T>, BoundedMixnodeFor<T>>;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Misc helper funcs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fn check_removed_all(res: MultiRemovalResults) {
|
||||
debug_assert!(res.maybe_cursor.is_none());
|
||||
}
|
||||
|
||||
fn twox<BlockNumber: UniqueSaturatedInto<u64>>(
|
||||
block_number: BlockNumber,
|
||||
kx_public: &KxPublic,
|
||||
) -> u64 {
|
||||
let block_number: u64 = block_number.unique_saturated_into();
|
||||
let mut data = [0; 8 + KX_PUBLIC_SIZE];
|
||||
data[..8].copy_from_slice(&block_number.to_le_bytes());
|
||||
data[8..].copy_from_slice(kx_public);
|
||||
u64::from_le_bytes(sp_io::hashing::twox_64(&data))
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// The pallet
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[frame_support::pallet(dev_mode)]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + SendTransactionTypes<Call<Self>> {
|
||||
/// The maximum number of authorities per session.
|
||||
#[pallet::constant]
|
||||
type MaxAuthorities: Get<AuthorityIndex>;
|
||||
|
||||
/// The maximum size of one of a mixnode's external addresses.
|
||||
#[pallet::constant]
|
||||
type MaxExternalAddressSize: Get<u32>;
|
||||
|
||||
/// The maximum number of external addresses for a mixnode.
|
||||
#[pallet::constant]
|
||||
type MaxExternalAddressesPerMixnode: Get<u32>;
|
||||
|
||||
/// Session progress/length estimation. Used to determine when to send registration
|
||||
/// transactions and the longevity of these transactions.
|
||||
type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
|
||||
|
||||
/// Length of the first phase of each session (`CoverToCurrent`), in blocks.
|
||||
#[pallet::constant]
|
||||
type NumCoverToCurrentBlocks: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// Length of the second phase of each session (`RequestsToCurrent`), in blocks.
|
||||
#[pallet::constant]
|
||||
type NumRequestsToCurrentBlocks: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// Length of the third phase of each session (`CoverToPrev`), in blocks.
|
||||
#[pallet::constant]
|
||||
type NumCoverToPrevBlocks: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The number of "slack" blocks at the start of each session, during which
|
||||
/// [`maybe_register`](Pallet::maybe_register) will not attempt to post registration
|
||||
/// transactions.
|
||||
#[pallet::constant]
|
||||
type NumRegisterStartSlackBlocks: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The number of "slack" blocks at the end of each session.
|
||||
/// [`maybe_register`](Pallet::maybe_register) will try to register before this slack
|
||||
/// period, but may post registration transactions during the slack period as a last
|
||||
/// resort.
|
||||
#[pallet::constant]
|
||||
type NumRegisterEndSlackBlocks: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// Priority of unsigned transactions used to register mixnodes.
|
||||
#[pallet::constant]
|
||||
type RegistrationPriority: Get<TransactionPriority>;
|
||||
|
||||
/// Minimum number of mixnodes. If there are fewer than this many mixnodes registered for a
|
||||
/// session, the mixnet will not be active during the session.
|
||||
#[pallet::constant]
|
||||
type MinMixnodes: Get<u32>;
|
||||
}
|
||||
|
||||
/// Index of the current session. This may be offset relative to the session index tracked by
|
||||
/// eg `pallet_session`; mixnet session indices are independent.
|
||||
#[pallet::storage]
|
||||
pub(crate) type CurrentSessionIndex<T> = StorageValue<_, SessionIndex, ValueQuery>;
|
||||
|
||||
/// Block in which the current session started.
|
||||
#[pallet::storage]
|
||||
pub(crate) type CurrentSessionStartBlock<T> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
|
||||
|
||||
/// Authority list for the next session.
|
||||
#[pallet::storage]
|
||||
pub(crate) type NextAuthorityIds<T> = StorageMap<_, Identity, AuthorityIndex, AuthorityId>;
|
||||
|
||||
/// Mixnode sets by session index. Only the mixnode sets for the previous, current, and next
|
||||
/// sessions are kept; older sets are discarded.
|
||||
///
|
||||
/// The mixnodes in each set are keyed by authority index so we can easily check if an
|
||||
/// authority has registered a mixnode. The authority indices should only be used during
|
||||
/// registration; the authority indices for the very first session are made up.
|
||||
#[pallet::storage]
|
||||
pub(crate) type Mixnodes<T> =
|
||||
StorageDoubleMap<_, Identity, SessionIndex, Identity, AuthorityIndex, BoundedMixnodeFor<T>>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(frame_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
/// The mixnode set for the very first session.
|
||||
pub mixnodes: BoundedVec<BoundedMixnodeFor<T>, T::MaxAuthorities>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
assert!(
|
||||
Mixnodes::<T>::iter_prefix_values(0).next().is_none(),
|
||||
"Initial mixnodes already set"
|
||||
);
|
||||
for (i, mixnode) in self.mixnodes.iter().enumerate() {
|
||||
// We just make up authority indices here. This doesn't matter as authority indices
|
||||
// are only used during registration to check an authority doesn't register twice.
|
||||
Mixnodes::<T>::insert(0, i as AuthorityIndex, mixnode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Register a mixnode for the following session.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(1)] // TODO
|
||||
pub fn register(
|
||||
origin: OriginFor<T>,
|
||||
registration: RegistrationFor<T>,
|
||||
_signature: AuthoritySignature,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
// Checked by ValidateUnsigned
|
||||
debug_assert_eq!(registration.session_index, CurrentSessionIndex::<T>::get());
|
||||
debug_assert!(registration.authority_index < T::MaxAuthorities::get());
|
||||
|
||||
Mixnodes::<T>::insert(
|
||||
// Registering for the _following_ session
|
||||
registration.session_index + 1,
|
||||
registration.authority_index,
|
||||
registration.mixnode,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
|
||||
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
let Self::Call::register { registration, signature } = call else {
|
||||
return InvalidTransaction::Call.into()
|
||||
};
|
||||
|
||||
// Check session index matches
|
||||
match registration.session_index.cmp(&CurrentSessionIndex::<T>::get()) {
|
||||
Ordering::Greater => return InvalidTransaction::Future.into(),
|
||||
Ordering::Less => return InvalidTransaction::Stale.into(),
|
||||
Ordering::Equal => (),
|
||||
}
|
||||
|
||||
// Check authority index is valid
|
||||
if registration.authority_index >= T::MaxAuthorities::get() {
|
||||
return InvalidTransaction::BadProof.into()
|
||||
}
|
||||
let Some(authority_id) = NextAuthorityIds::<T>::get(registration.authority_index)
|
||||
else {
|
||||
return InvalidTransaction::BadProof.into()
|
||||
};
|
||||
|
||||
// Check the authority hasn't registered a mixnode yet
|
||||
if Self::already_registered(registration.session_index, registration.authority_index) {
|
||||
return InvalidTransaction::Stale.into()
|
||||
}
|
||||
|
||||
// Check signature. Note that we don't use regular signed transactions for registration
|
||||
// as we don't want validators to have to pay to register. Spam is prevented by only
|
||||
// allowing one registration per session per validator (see above).
|
||||
let signature_ok = registration.using_encoded(|encoded_registration| {
|
||||
authority_id.verify(&encoded_registration, signature)
|
||||
});
|
||||
if !signature_ok {
|
||||
return InvalidTransaction::BadProof.into()
|
||||
}
|
||||
|
||||
ValidTransaction::with_tag_prefix("MixnetRegistration")
|
||||
.priority(T::RegistrationPriority::get())
|
||||
// Include both authority index _and_ ID in tag in case of forks with different
|
||||
// authority lists
|
||||
.and_provides((
|
||||
registration.session_index,
|
||||
registration.authority_index,
|
||||
authority_id,
|
||||
))
|
||||
.longevity(
|
||||
(T::NextSessionRotation::average_session_length() / 2_u32.into())
|
||||
.try_into()
|
||||
.unwrap_or(64_u64),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Returns the phase of the current session.
|
||||
fn session_phase() -> SessionPhase {
|
||||
let block_in_phase = frame_system::Pallet::<T>::block_number()
|
||||
.saturating_sub(CurrentSessionStartBlock::<T>::get());
|
||||
let Some(block_in_phase) = block_in_phase.checked_sub(&T::NumCoverToCurrentBlocks::get())
|
||||
else {
|
||||
return SessionPhase::CoverToCurrent
|
||||
};
|
||||
let Some(block_in_phase) =
|
||||
block_in_phase.checked_sub(&T::NumRequestsToCurrentBlocks::get())
|
||||
else {
|
||||
return SessionPhase::RequestsToCurrent
|
||||
};
|
||||
if block_in_phase < T::NumCoverToPrevBlocks::get() {
|
||||
SessionPhase::CoverToPrev
|
||||
} else {
|
||||
SessionPhase::DisconnectFromPrev
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index and phase of the current session.
|
||||
pub fn session_status() -> SessionStatus {
|
||||
SessionStatus {
|
||||
current_index: CurrentSessionIndex::<T>::get(),
|
||||
phase: Self::session_phase(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the mixnode set for the given session (which should be either the previous or the
|
||||
/// current session).
|
||||
fn mixnodes(session_index: SessionIndex) -> Result<Vec<Mixnode>, MixnodesErr> {
|
||||
let mixnodes: Vec<_> =
|
||||
Mixnodes::<T>::iter_prefix_values(session_index).map(Into::into).collect();
|
||||
if mixnodes.len() < T::MinMixnodes::get() as usize {
|
||||
Err(MixnodesErr::InsufficientRegistrations {
|
||||
num: mixnodes.len() as u32,
|
||||
min: T::MinMixnodes::get(),
|
||||
})
|
||||
} else {
|
||||
Ok(mixnodes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the mixnode set for the previous session.
|
||||
pub fn prev_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr> {
|
||||
let Some(prev_session_index) = CurrentSessionIndex::<T>::get().checked_sub(1) else {
|
||||
return Err(MixnodesErr::InsufficientRegistrations {
|
||||
num: 0,
|
||||
min: T::MinMixnodes::get(),
|
||||
})
|
||||
};
|
||||
Self::mixnodes(prev_session_index)
|
||||
}
|
||||
|
||||
/// Returns the mixnode set for the current session.
|
||||
pub fn current_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr> {
|
||||
Self::mixnodes(CurrentSessionIndex::<T>::get())
|
||||
}
|
||||
|
||||
/// Is now a good time to register, considering only session progress?
|
||||
fn should_register_by_session_progress(
|
||||
block_number: BlockNumberFor<T>,
|
||||
mixnode: &Mixnode,
|
||||
) -> bool {
|
||||
// At the start of each session there are some "slack" blocks during which we avoid
|
||||
// registering
|
||||
let block_in_session = block_number.saturating_sub(CurrentSessionStartBlock::<T>::get());
|
||||
if block_in_session < T::NumRegisterStartSlackBlocks::get() {
|
||||
return false
|
||||
}
|
||||
|
||||
let (Some(end_block), _weight) =
|
||||
T::NextSessionRotation::estimate_next_session_rotation(block_number)
|
||||
else {
|
||||
// Things aren't going to work terribly well in this case as all the authorities will
|
||||
// just pile in after the slack period...
|
||||
return true
|
||||
};
|
||||
|
||||
let remaining_blocks = end_block
|
||||
.saturating_sub(block_number)
|
||||
.saturating_sub(T::NumRegisterEndSlackBlocks::get());
|
||||
if remaining_blocks.is_zero() {
|
||||
// Into the slack time at the end of the session. Not necessarily too late;
|
||||
// registrations are accepted right up until the session ends.
|
||||
return true
|
||||
}
|
||||
|
||||
// Want uniform distribution over the remaining blocks, so pick this block with probability
|
||||
// 1/remaining_blocks. maybe_register may be called multiple times per block; ensure the
|
||||
// same decision gets made each time by using a hash of the block number and the mixnode's
|
||||
// public key as the "random" source. This is slightly biased as remaining_blocks most
|
||||
// likely won't divide into 2^64, but it doesn't really matter...
|
||||
let random = twox(block_number, &mixnode.kx_public);
|
||||
(random % remaining_blocks.try_into().unwrap_or(u64::MAX)) == 0
|
||||
}
|
||||
|
||||
fn next_local_authority() -> Option<(AuthorityIndex, AuthorityId)> {
|
||||
// In the case where multiple local IDs are in the next authority set, we just return the
|
||||
// first one. There's (currently at least) no point in registering multiple times.
|
||||
let mut local_ids = AuthorityId::all();
|
||||
local_ids.sort();
|
||||
NextAuthorityIds::<T>::iter().find(|(_index, id)| local_ids.binary_search(id).is_ok())
|
||||
}
|
||||
|
||||
/// `session_index` should be the index of the current session. `authority_index` is the
|
||||
/// authority index in the _next_ session.
|
||||
fn already_registered(session_index: SessionIndex, authority_index: AuthorityIndex) -> bool {
|
||||
Mixnodes::<T>::contains_key(session_index + 1, authority_index)
|
||||
}
|
||||
|
||||
/// Try to register a mixnode for the next session.
|
||||
///
|
||||
/// If a registration extrinsic is submitted, `true` is returned. The caller should avoid
|
||||
/// calling `maybe_register` again for a few blocks, to give the submitted extrinsic a chance
|
||||
/// to get included.
|
||||
///
|
||||
/// With the above exception, `maybe_register` is designed to be called every block. Most of
|
||||
/// the time it will not do anything, for example:
|
||||
///
|
||||
/// - If it is not an appropriate time to submit a registration extrinsic.
|
||||
/// - If the local node has already registered a mixnode for the next session.
|
||||
/// - If the local node is not permitted to register a mixnode for the next session.
|
||||
///
|
||||
/// `session_index` should match `session_status().current_index`; if it does not, `false` is
|
||||
/// returned immediately.
|
||||
pub fn maybe_register(session_index: SessionIndex, mixnode: Mixnode) -> bool {
|
||||
let current_session_index = CurrentSessionIndex::<T>::get();
|
||||
if session_index != current_session_index {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Session {session_index} registration attempted, \
|
||||
but current session is {current_session_index}",
|
||||
);
|
||||
return false
|
||||
}
|
||||
|
||||
let block_number = frame_system::Pallet::<T>::block_number();
|
||||
if !Self::should_register_by_session_progress(block_number, &mixnode) {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Waiting for the session to progress further before registering",
|
||||
);
|
||||
return false
|
||||
}
|
||||
|
||||
let Some((authority_index, authority_id)) = Self::next_local_authority() else {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Not an authority in the next session; cannot register a mixnode",
|
||||
);
|
||||
return false
|
||||
};
|
||||
|
||||
if Self::already_registered(session_index, authority_index) {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Already registered a mixnode for the next session",
|
||||
);
|
||||
return false
|
||||
}
|
||||
|
||||
let registration =
|
||||
Registration { block_number, session_index, authority_index, mixnode: mixnode.into() };
|
||||
let Some(signature) = authority_id.sign(®istration.encode()) else {
|
||||
log::debug!(target: LOG_TARGET, "Failed to sign registration");
|
||||
return false
|
||||
};
|
||||
let call = Call::register { registration, signature };
|
||||
match SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into()) {
|
||||
Ok(()) => true,
|
||||
Err(()) => {
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to submit registration transaction",
|
||||
);
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
||||
type Public = AuthorityId;
|
||||
}
|
||||
|
||||
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
|
||||
{
|
||||
assert!(
|
||||
NextAuthorityIds::<T>::iter().next().is_none(),
|
||||
"Initial authority IDs already set"
|
||||
);
|
||||
for (i, (_, authority_id)) in validators.enumerate() {
|
||||
NextAuthorityIds::<T>::insert(i as AuthorityIndex, authority_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, _validators: I, queued_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
|
||||
{
|
||||
let session_index = CurrentSessionIndex::<T>::mutate(|index| {
|
||||
*index += 1;
|
||||
*index
|
||||
});
|
||||
CurrentSessionStartBlock::<T>::put(frame_system::Pallet::<T>::block_number());
|
||||
|
||||
// Discard the previous previous mixnode set, which we don't need any more
|
||||
if let Some(prev_prev_session_index) = session_index.checked_sub(2) {
|
||||
check_removed_all(Mixnodes::<T>::clear_prefix(
|
||||
prev_prev_session_index,
|
||||
T::MaxAuthorities::get(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
if changed {
|
||||
// Save authority set for the next session. Note that we don't care about the authority
|
||||
// set for the current session; we just care about the key-exchange public keys that
|
||||
// were registered and are stored in Mixnodes.
|
||||
check_removed_all(NextAuthorityIds::<T>::clear(T::MaxAuthorities::get(), None));
|
||||
for (i, (_, authority_id)) in queued_validators.enumerate() {
|
||||
NextAuthorityIds::<T>::insert(i as AuthorityIndex, authority_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_disabled(_i: u32) {
|
||||
// For now, to keep things simple, just ignore
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
@@ -1161,6 +1161,8 @@ pub mod key_types {
|
||||
pub const STAKING: KeyTypeId = KeyTypeId(*b"stak");
|
||||
/// A key type for signing statements
|
||||
pub const STATEMENT: KeyTypeId = KeyTypeId(*b"stmt");
|
||||
/// Key type for Mixnet module, used to sign key-exchange public keys. Identified as `mixn`.
|
||||
pub const MIXNET: KeyTypeId = KeyTypeId(*b"mixn");
|
||||
/// A key type ID useful for tests.
|
||||
pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
description = "Substrate mixnet types and runtime interface"
|
||||
name = "sp-mixnet"
|
||||
version = "0.1.0-dev"
|
||||
license = "Apache-2.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||
sp-api = { default-features = false, path = "../api" }
|
||||
sp-application-crypto = { default-features = false, path = "../application-crypto" }
|
||||
sp-std = { default-features = false, path = "../std" }
|
||||
|
||||
[features]
|
||||
default = [ "std" ]
|
||||
std = [
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"sp-api/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Substrate mixnet types and runtime interface.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,24 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Substrate mixnet types and runtime interface.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod runtime_api;
|
||||
pub mod types;
|
||||
@@ -0,0 +1,52 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Runtime API for querying mixnet configuration and registering mixnodes.
|
||||
|
||||
use super::types::{Mixnode, MixnodesErr, SessionIndex, SessionStatus};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// API to query the mixnet session status and mixnode sets, and to register mixnodes.
|
||||
pub trait MixnetApi {
|
||||
/// Get the index and phase of the current session.
|
||||
fn session_status() -> SessionStatus;
|
||||
|
||||
/// Get the mixnode set for the previous session.
|
||||
fn prev_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr>;
|
||||
|
||||
/// Get the mixnode set for the current session.
|
||||
fn current_mixnodes() -> Result<Vec<Mixnode>, MixnodesErr>;
|
||||
|
||||
/// Try to register a mixnode for the next session.
|
||||
///
|
||||
/// If a registration extrinsic is submitted, `true` is returned. The caller should avoid
|
||||
/// calling `maybe_register` again for a few blocks, to give the submitted extrinsic a
|
||||
/// chance to get included.
|
||||
///
|
||||
/// With the above exception, `maybe_register` is designed to be called every block. Most
|
||||
/// of the time it will not do anything, for example:
|
||||
///
|
||||
/// - If it is not an appropriate time to submit a registration extrinsic.
|
||||
/// - If the local node has already registered a mixnode for the next session.
|
||||
/// - If the local node is not permitted to register a mixnode for the next session.
|
||||
///
|
||||
/// `session_index` should match `session_status().current_index`; if it does not, `false`
|
||||
/// is returned immediately.
|
||||
fn maybe_register(session_index: SessionIndex, mixnode: Mixnode) -> bool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Mixnet types used by both host and runtime.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
mod app {
|
||||
use sp_application_crypto::{app_crypto, key_types::MIXNET, sr25519};
|
||||
app_crypto!(sr25519, MIXNET);
|
||||
}
|
||||
|
||||
/// Authority public session key, used to verify registration signatures.
|
||||
pub type AuthorityId = app::Public;
|
||||
/// Authority signature, attached to mixnode registrations.
|
||||
pub type AuthoritySignature = app::Signature;
|
||||
|
||||
/// Absolute session index.
|
||||
pub type SessionIndex = u32;
|
||||
|
||||
/// Each session should progress through these phases in order.
|
||||
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq)]
|
||||
pub enum SessionPhase {
|
||||
/// Generate cover traffic to the current session's mixnode set.
|
||||
CoverToCurrent,
|
||||
/// Build requests using the current session's mixnode set.
|
||||
RequestsToCurrent,
|
||||
/// Only send cover (and forwarded) traffic to the previous session's mixnode set.
|
||||
CoverToPrev,
|
||||
/// Disconnect the previous session's mixnode set.
|
||||
DisconnectFromPrev,
|
||||
}
|
||||
|
||||
/// The index and phase of the current session.
|
||||
#[derive(Decode, Encode, TypeInfo)]
|
||||
pub struct SessionStatus {
|
||||
/// Index of the current session.
|
||||
pub current_index: SessionIndex,
|
||||
/// Current session phase.
|
||||
pub phase: SessionPhase,
|
||||
}
|
||||
|
||||
/// Size in bytes of a [`KxPublic`].
|
||||
pub const KX_PUBLIC_SIZE: usize = 32;
|
||||
|
||||
/// X25519 public key, used in key exchange between message senders and mixnodes. Mixnode public
|
||||
/// keys are published on-chain and change every session. Message senders generate a new key for
|
||||
/// every message they send.
|
||||
pub type KxPublic = [u8; KX_PUBLIC_SIZE];
|
||||
|
||||
/// Ed25519 public key of a libp2p peer.
|
||||
pub type PeerId = [u8; 32];
|
||||
|
||||
/// Information published on-chain for each mixnode every session.
|
||||
#[derive(Decode, Encode, TypeInfo)]
|
||||
pub struct Mixnode {
|
||||
/// Key-exchange public key for the mixnode.
|
||||
pub kx_public: KxPublic,
|
||||
/// libp2p peer ID of the mixnode.
|
||||
pub peer_id: PeerId,
|
||||
/// External addresses for the mixnode, in multiaddr format, UTF-8 encoded.
|
||||
pub external_addresses: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
/// Error querying the runtime for a session's mixnode set.
|
||||
#[derive(Decode, Encode, TypeInfo)]
|
||||
pub enum MixnodesErr {
|
||||
/// Insufficient mixnodes were registered for the session.
|
||||
InsufficientRegistrations {
|
||||
/// The number of mixnodes that were registered for the session.
|
||||
num: u32,
|
||||
/// The minimum number of mixnodes that must be registered for the mixnet to operate.
|
||||
min: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl sp_std::fmt::Display for MixnodesErr {
|
||||
fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
||||
match self {
|
||||
MixnodesErr::InsufficientRegistrations { num, min } =>
|
||||
write!(fmt, "{num} mixnode(s) registered; {min} is the minimum"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ exceptions = [
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-executor-wasmtime" },
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-informant" },
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-keystore" },
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-mixnet" },
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network" },
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-bitswap" },
|
||||
{ allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-common" },
|
||||
|
||||
Reference in New Issue
Block a user