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:
David Emett
2023-10-09 15:56:30 +02:00
committed by GitHub
parent 1dc935c715
commit a808a3a091
52 changed files with 3010 additions and 109 deletions
Generated
+241 -70
View File
@@ -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",
+3
View File
@@ -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",
+2
View File
@@ -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> {
+28 -4
View File
@@ -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,
+4
View File
@@ -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
+10 -9
View File
@@ -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)?;
+50 -16
View File
@@ -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,
+1
View File
@@ -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" }
+9
View File
@@ -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)
}
+6
View File
@@ -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",
+44 -1
View File
@@ -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(())
+1
View File
@@ -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
})
}
}
+5 -3
View File
@@ -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
+36
View File
@@ -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"
+3
View File
@@ -0,0 +1,3 @@
Substrate mixnet service.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
+69
View File
@@ -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)?
}
}
}
+88
View File
@@ -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(),
}
}
}
+56
View File
@@ -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))
}
}
+44
View File
@@ -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)
}
}
+44
View File
@@ -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())
}
+42
View File
@@ -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
}
+119
View File
@@ -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);
}
}
+388
View File
@@ -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,
+1
View File
@@ -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" }
+1
View File
@@ -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;
}
+1
View File
@@ -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<()>;
}
+1
View File
@@ -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" }
+1
View File
@@ -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;
+47
View File
@@ -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)?)
}
}
+57
View File
@@ -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",
]
+4
View File
@@ -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
+598
View File
@@ -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(&registration.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
}
}
+2
View File
@@ -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");
}
+30
View File
@@ -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",
]
+3
View File
@@ -0,0 +1,3 @@
Substrate mixnet types and runtime interface.
License: Apache-2.0
+24
View File
@@ -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;
}
}
+100
View File
@@ -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"),
}
}
}
+1
View File
@@ -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" },