mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-18 04:51:01 +00:00
Add subsystem benchmarks for availability-distribution and biftield-distribution (availability write) (#2970)
Introduce a new test objective : `DataAvailabilityWrite`. The new benchmark measures the network and cpu usage of `availability-distribution`, `biftield-distribution` and `availability-store` subsystems from the perspective of a validator node during the process when candidates are made available. Additionally I refactored the networking emulation to support bandwidth acounting and limits of incoming and outgoing requests. Screenshot of succesful run <img width="1293" alt="Screenshot 2024-01-17 at 19 17 44" src="https://github.com/paritytech/polkadot-sdk/assets/54316454/fde11280-e25b-4dc3-9dc9-d4b9752f9b7a"> --------- Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
This commit is contained in:
Generated
+12
-4
@@ -13454,6 +13454,7 @@ version = "1.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"bitvec",
|
||||||
"clap 4.4.18",
|
"clap 4.4.18",
|
||||||
"clap-num",
|
"clap-num",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
@@ -13462,12 +13463,17 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"futures-timer",
|
"futures-timer",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
|
"kvdb-memorydb",
|
||||||
"log",
|
"log",
|
||||||
"orchestra",
|
"orchestra",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"paste",
|
"paste",
|
||||||
|
"polkadot-availability-bitfield-distribution",
|
||||||
|
"polkadot-availability-distribution",
|
||||||
"polkadot-availability-recovery",
|
"polkadot-availability-recovery",
|
||||||
"polkadot-erasure-coding",
|
"polkadot-erasure-coding",
|
||||||
|
"polkadot-node-core-av-store",
|
||||||
|
"polkadot-node-core-chain-api",
|
||||||
"polkadot-node-metrics",
|
"polkadot-node-metrics",
|
||||||
"polkadot-node-network-protocol",
|
"polkadot-node-network-protocol",
|
||||||
"polkadot-node-primitives",
|
"polkadot-node-primitives",
|
||||||
@@ -13482,12 +13488,14 @@ dependencies = [
|
|||||||
"pyroscope",
|
"pyroscope",
|
||||||
"pyroscope_pprofrs",
|
"pyroscope_pprofrs",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rand_distr",
|
||||||
"sc-keystore",
|
"sc-keystore",
|
||||||
"sc-network",
|
"sc-network",
|
||||||
"sc-service",
|
"sc-service",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"sp-application-crypto",
|
"sp-application-crypto",
|
||||||
|
"sp-consensus",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
"sp-keyring",
|
"sp-keyring",
|
||||||
"sp-keystore",
|
"sp-keystore",
|
||||||
@@ -17079,9 +17087,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_yaml"
|
name = "serde_yaml"
|
||||||
version = "0.9.30"
|
version = "0.9.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38"
|
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"itoa",
|
"itoa",
|
||||||
@@ -20752,9 +20760,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unsafe-libyaml"
|
||||||
version = "0.2.10"
|
version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsigned-varint"
|
name = "unsigned-varint"
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ members = [
|
|||||||
"polkadot/node/gum/proc-macro",
|
"polkadot/node/gum/proc-macro",
|
||||||
"polkadot/node/jaeger",
|
"polkadot/node/jaeger",
|
||||||
"polkadot/node/malus",
|
"polkadot/node/malus",
|
||||||
|
"polkadot/node/subsystem-bench",
|
||||||
"polkadot/node/metrics",
|
"polkadot/node/metrics",
|
||||||
"polkadot/node/network/approval-distribution",
|
"polkadot/node/network/approval-distribution",
|
||||||
"polkadot/node/network/availability-distribution",
|
"polkadot/node/network/availability-distribution",
|
||||||
|
|||||||
@@ -22,12 +22,16 @@ polkadot-node-subsystem-types = { path = "../subsystem-types" }
|
|||||||
polkadot-node-primitives = { path = "../primitives" }
|
polkadot-node-primitives = { path = "../primitives" }
|
||||||
polkadot-primitives = { path = "../../primitives" }
|
polkadot-primitives = { path = "../../primitives" }
|
||||||
polkadot-node-network-protocol = { path = "../network/protocol" }
|
polkadot-node-network-protocol = { path = "../network/protocol" }
|
||||||
polkadot-availability-recovery = { path = "../network/availability-recovery", features = ["subsystem-benchmarks"] }
|
polkadot-availability-recovery = { path = "../network/availability-recovery", features=["subsystem-benchmarks"]}
|
||||||
|
polkadot-availability-distribution = { path = "../network/availability-distribution"}
|
||||||
|
polkadot-node-core-av-store = { path = "../core/av-store"}
|
||||||
|
polkadot-node-core-chain-api = { path = "../core/chain-api"}
|
||||||
|
polkadot-availability-bitfield-distribution = { path = "../network/bitfield-distribution"}
|
||||||
color-eyre = { version = "0.6.1", default-features = false }
|
color-eyre = { version = "0.6.1", default-features = false }
|
||||||
polkadot-overseer = { path = "../overseer" }
|
polkadot-overseer = { path = "../overseer" }
|
||||||
colored = "2.0.4"
|
colored = "2.0.4"
|
||||||
assert_matches = "1.5"
|
assert_matches = "1.5"
|
||||||
async-trait = "0.1.74"
|
async-trait = "0.1.57"
|
||||||
sp-keystore = { path = "../../../substrate/primitives/keystore" }
|
sp-keystore = { path = "../../../substrate/primitives/keystore" }
|
||||||
sc-keystore = { path = "../../../substrate/client/keystore" }
|
sc-keystore = { path = "../../../substrate/client/keystore" }
|
||||||
sp-core = { path = "../../../substrate/primitives/core" }
|
sp-core = { path = "../../../substrate/primitives/core" }
|
||||||
@@ -39,7 +43,12 @@ polkadot-erasure-coding = { package = "polkadot-erasure-coding", path = "../../e
|
|||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
parity-scale-codec = { version = "3.6.1", features = ["derive", "std"] }
|
# `rand` only supports uniform distribution, we need normal distribution for latency.
|
||||||
|
rand_distr = "0.4.3"
|
||||||
|
bitvec="1.0.1"
|
||||||
|
kvdb-memorydb = "0.13.0"
|
||||||
|
|
||||||
|
parity-scale-codec = { version = "3.6.1", features = ["std", "derive"] }
|
||||||
tokio = "1.24.2"
|
tokio = "1.24.2"
|
||||||
clap-num = "1.0.2"
|
clap-num = "1.0.2"
|
||||||
polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" }
|
polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" }
|
||||||
@@ -47,6 +56,7 @@ sp-keyring = { path = "../../../substrate/primitives/keyring" }
|
|||||||
sp-application-crypto = { path = "../../../substrate/primitives/application-crypto" }
|
sp-application-crypto = { path = "../../../substrate/primitives/application-crypto" }
|
||||||
sc-network = { path = "../../../substrate/client/network" }
|
sc-network = { path = "../../../substrate/client/network" }
|
||||||
sc-service = { path = "../../../substrate/client/service" }
|
sc-service = { path = "../../../substrate/client/service" }
|
||||||
|
sp-consensus = { path = "../../../substrate/primitives/consensus/common" }
|
||||||
polkadot-node-metrics = { path = "../metrics" }
|
polkadot-node-metrics = { path = "../metrics" }
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" }
|
polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Subsystem benchmark client
|
# Subsystem benchmark client
|
||||||
|
|
||||||
Run parachain consensus stress and performance tests on your development machine.
|
Run parachain consensus stress and performance tests on your development machine.
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
@@ -111,30 +111,28 @@ Commands:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note: `test-sequence` is a special test objective that wraps up an arbitrary number of test objectives. It is tipically
|
Note: `test-sequence` is a special test objective that wraps up an arbitrary number of test objectives. It is tipically
|
||||||
used to run a suite of tests defined in a `yaml` file like in this [example](examples/availability_read.yaml).
|
used to run a suite of tests defined in a `yaml` file like in this [example](examples/availability_read.yaml).
|
||||||
|
|
||||||
### Standard test options
|
### Standard test options
|
||||||
|
|
||||||
```
|
```
|
||||||
Options:
|
--network <NETWORK> The type of network to be emulated [default: ideal] [possible values: ideal, healthy,
|
||||||
--network <NETWORK> The type of network to be emulated [default: ideal] [possible
|
degraded]
|
||||||
values: ideal, healthy, degraded]
|
--n-cores <N_CORES> Number of cores to fetch availability for [default: 100]
|
||||||
--n-cores <N_CORES> Number of cores to fetch availability for [default: 100]
|
--n-validators <N_VALIDATORS> Number of validators to fetch chunks from [default: 500]
|
||||||
--n-validators <N_VALIDATORS> Number of validators to fetch chunks from [default: 500]
|
--min-pov-size <MIN_POV_SIZE> The minimum pov size in KiB [default: 5120]
|
||||||
--min-pov-size <MIN_POV_SIZE> The minimum pov size in KiB [default: 5120]
|
--max-pov-size <MAX_POV_SIZE> The maximum pov size bytes [default: 5120]
|
||||||
--max-pov-size <MAX_POV_SIZE> The maximum pov size bytes [default: 5120]
|
-n, --num-blocks <NUM_BLOCKS> The number of blocks the test is going to run [default: 1]
|
||||||
-n, --num-blocks <NUM_BLOCKS> The number of blocks the test is going to run [default: 1]
|
-p, --peer-bandwidth <PEER_BANDWIDTH> The bandwidth of emulated remote peers in KiB
|
||||||
-p, --peer-bandwidth <PEER_BANDWIDTH> The bandwidth of simulated remote peers in KiB
|
-b, --bandwidth <BANDWIDTH> The bandwidth of our node in KiB
|
||||||
-b, --bandwidth <BANDWIDTH> The bandwidth of our simulated node in KiB
|
--connectivity <CONNECTIVITY> Emulated peer connection ratio [0-100]
|
||||||
--peer-error <PEER_ERROR> Simulated conection error ratio [0-100]
|
--peer-mean-latency <PEER_MEAN_LATENCY> Mean remote peer latency in milliseconds [0-5000]
|
||||||
--peer-min-latency <PEER_MIN_LATENCY> Minimum remote peer latency in milliseconds [0-5000]
|
--peer-latency-std-dev <PEER_LATENCY_STD_DEV> Remote peer latency standard deviation
|
||||||
--peer-max-latency <PEER_MAX_LATENCY> Maximum remote peer latency in milliseconds [0-5000]
|
--profile Enable CPU Profiling with Pyroscope
|
||||||
--profile Enable CPU Profiling with Pyroscope
|
--pyroscope-url <PYROSCOPE_URL> Pyroscope Server URL [default: http://localhost:4040]
|
||||||
--pyroscope-url <PYROSCOPE_URL> Pyroscope Server URL [default: http://localhost:4040]
|
--pyroscope-sample-rate <PYROSCOPE_SAMPLE_RATE> Pyroscope Sample Rate [default: 113]
|
||||||
--pyroscope-sample-rate <PYROSCOPE_SAMPLE_RATE> Pyroscope Sample Rate [default: 113]
|
--cache-misses Enable Cache Misses Profiling with Valgrind. Linux only, Valgrind must be in the PATH
|
||||||
--cache-misses Enable Cache Misses Profiling with Valgrind. Linux only, Valgrind
|
-h, --help Print help
|
||||||
must be in the PATH
|
|
||||||
-h, --help Print help
|
|
||||||
```
|
```
|
||||||
|
|
||||||
These apply to all test objectives, except `test-sequence` which relies on the values being specified in a file.
|
These apply to all test objectives, except `test-sequence` which relies on the values being specified in a file.
|
||||||
@@ -152,8 +150,8 @@ Benchmark availability recovery strategies
|
|||||||
Usage: subsystem-bench data-availability-read [OPTIONS]
|
Usage: subsystem-bench data-availability-read [OPTIONS]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-f, --fetch-from-backers Turbo boost AD Read by fetching the full availability datafrom backers first. Saves CPU
|
-f, --fetch-from-backers Turbo boost AD Read by fetching the full availability datafrom backers first. Saves CPU
|
||||||
as we don't need to re-construct from chunks. Tipically this is only faster if nodes
|
as we don't need to re-construct from chunks. Tipically this is only faster if nodes
|
||||||
have enough bandwidth
|
have enough bandwidth
|
||||||
-h, --help Print help
|
-h, --help Print help
|
||||||
```
|
```
|
||||||
@@ -181,9 +179,9 @@ Let's run an availabilty read test which will recover availability for 10 cores
|
|||||||
node validator network.
|
node validator network.
|
||||||
|
|
||||||
```
|
```
|
||||||
target/testnet/subsystem-bench --n-cores 10 data-availability-read
|
target/testnet/subsystem-bench --n-cores 10 data-availability-read
|
||||||
[2023-11-28T09:01:59Z INFO subsystem_bench::core::display] n_validators = 500, n_cores = 10, pov_size = 5120 - 5120,
|
[2023-11-28T09:01:59Z INFO subsystem_bench::core::display] n_validators = 500, n_cores = 10, pov_size = 5120 - 5120,
|
||||||
error = 0, latency = None
|
latency = None
|
||||||
[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Generating template candidate index=0 pov_size=5242880
|
[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Generating template candidate index=0 pov_size=5242880
|
||||||
[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Created test environment.
|
[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Created test environment.
|
||||||
[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Pre-generating 10 candidates.
|
[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Pre-generating 10 candidates.
|
||||||
@@ -196,8 +194,8 @@ node validator network.
|
|||||||
[2023-11-28T09:02:07Z INFO subsystem_bench::availability] All blocks processed in 6001ms
|
[2023-11-28T09:02:07Z INFO subsystem_bench::availability] All blocks processed in 6001ms
|
||||||
[2023-11-28T09:02:07Z INFO subsystem_bench::availability] Throughput: 51200 KiB/block
|
[2023-11-28T09:02:07Z INFO subsystem_bench::availability] Throughput: 51200 KiB/block
|
||||||
[2023-11-28T09:02:07Z INFO subsystem_bench::availability] Block time: 6001 ms
|
[2023-11-28T09:02:07Z INFO subsystem_bench::availability] Block time: 6001 ms
|
||||||
[2023-11-28T09:02:07Z INFO subsystem_bench::availability]
|
[2023-11-28T09:02:07Z INFO subsystem_bench::availability]
|
||||||
|
|
||||||
Total received from network: 66 MiB
|
Total received from network: 66 MiB
|
||||||
Total sent to network: 58 KiB
|
Total sent to network: 58 KiB
|
||||||
Total subsystem CPU usage 4.16s
|
Total subsystem CPU usage 4.16s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
TestConfiguration:
|
TestConfiguration:
|
||||||
# Test 1
|
# Test 1
|
||||||
- objective: !DataAvailabilityRead
|
- objective: !DataAvailabilityRead
|
||||||
fetch_from_backers: false
|
fetch_from_backers: true
|
||||||
n_validators: 300
|
n_validators: 300
|
||||||
n_cores: 20
|
n_cores: 20
|
||||||
min_pov_size: 5120
|
min_pov_size: 5120
|
||||||
@@ -9,18 +9,14 @@ TestConfiguration:
|
|||||||
peer_bandwidth: 52428800
|
peer_bandwidth: 52428800
|
||||||
bandwidth: 52428800
|
bandwidth: 52428800
|
||||||
latency:
|
latency:
|
||||||
min_latency:
|
mean_latency_ms: 100
|
||||||
secs: 0
|
std_dev: 1
|
||||||
nanos: 1000000
|
|
||||||
max_latency:
|
|
||||||
secs: 0
|
|
||||||
nanos: 100000000
|
|
||||||
error: 3
|
|
||||||
num_blocks: 3
|
num_blocks: 3
|
||||||
|
connectivity: 90
|
||||||
|
|
||||||
# Test 2
|
# Test 2
|
||||||
- objective: !DataAvailabilityRead
|
- objective: !DataAvailabilityRead
|
||||||
fetch_from_backers: false
|
fetch_from_backers: true
|
||||||
n_validators: 500
|
n_validators: 500
|
||||||
n_cores: 20
|
n_cores: 20
|
||||||
min_pov_size: 5120
|
min_pov_size: 5120
|
||||||
@@ -28,18 +24,14 @@ TestConfiguration:
|
|||||||
peer_bandwidth: 52428800
|
peer_bandwidth: 52428800
|
||||||
bandwidth: 52428800
|
bandwidth: 52428800
|
||||||
latency:
|
latency:
|
||||||
min_latency:
|
mean_latency_ms: 100
|
||||||
secs: 0
|
std_dev: 1
|
||||||
nanos: 1000000
|
|
||||||
max_latency:
|
|
||||||
secs: 0
|
|
||||||
nanos: 100000000
|
|
||||||
error: 3
|
|
||||||
num_blocks: 3
|
num_blocks: 3
|
||||||
|
connectivity: 90
|
||||||
|
|
||||||
# Test 3
|
# Test 3
|
||||||
- objective: !DataAvailabilityRead
|
- objective: !DataAvailabilityRead
|
||||||
fetch_from_backers: false
|
fetch_from_backers: true
|
||||||
n_validators: 1000
|
n_validators: 1000
|
||||||
n_cores: 20
|
n_cores: 20
|
||||||
min_pov_size: 5120
|
min_pov_size: 5120
|
||||||
@@ -47,11 +39,7 @@ TestConfiguration:
|
|||||||
peer_bandwidth: 52428800
|
peer_bandwidth: 52428800
|
||||||
bandwidth: 52428800
|
bandwidth: 52428800
|
||||||
latency:
|
latency:
|
||||||
min_latency:
|
mean_latency_ms: 100
|
||||||
secs: 0
|
std_dev: 1
|
||||||
nanos: 1000000
|
|
||||||
max_latency:
|
|
||||||
secs: 0
|
|
||||||
nanos: 100000000
|
|
||||||
error: 3
|
|
||||||
num_blocks: 3
|
num_blocks: 3
|
||||||
|
connectivity: 90
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
TestConfiguration:
|
||||||
|
# Test 1kV, 200 cores, max Pov
|
||||||
|
- objective: DataAvailabilityWrite
|
||||||
|
n_validators: 1000
|
||||||
|
n_cores: 200
|
||||||
|
max_validators_per_core: 5
|
||||||
|
min_pov_size: 5120
|
||||||
|
max_pov_size: 5120
|
||||||
|
peer_bandwidth: 52428800
|
||||||
|
bandwidth: 52428800
|
||||||
|
latency:
|
||||||
|
mean_latency_ms: 30
|
||||||
|
std_dev: 2.0
|
||||||
|
connectivity: 75
|
||||||
|
num_blocks: 3
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot 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.
|
||||||
|
|
||||||
|
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use polkadot_node_metrics::metrics::Metrics;
|
||||||
|
|
||||||
|
use polkadot_node_core_av_store::Config;
|
||||||
|
use polkadot_node_subsystem_util::database::Database;
|
||||||
|
|
||||||
|
use polkadot_node_core_av_store::AvailabilityStoreSubsystem;
|
||||||
|
|
||||||
|
mod columns {
|
||||||
|
pub const DATA: u32 = 0;
|
||||||
|
pub const META: u32 = 1;
|
||||||
|
pub const NUM_COLUMNS: u32 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_CONFIG: Config = Config { col_data: columns::DATA, col_meta: columns::META };
|
||||||
|
|
||||||
|
struct DumbOracle;
|
||||||
|
|
||||||
|
impl sp_consensus::SyncOracle for DumbOracle {
|
||||||
|
fn is_major_syncing(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_offline(&self) -> bool {
|
||||||
|
unimplemented!("oh no!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_av_store(dependencies: &TestEnvironmentDependencies) -> AvailabilityStoreSubsystem {
|
||||||
|
let metrics = Metrics::try_register(&dependencies.registry).unwrap();
|
||||||
|
|
||||||
|
AvailabilityStoreSubsystem::new(test_store(), TEST_CONFIG, Box::new(DumbOracle), metrics)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_store() -> Arc<dyn Database> {
|
||||||
|
let db = kvdb_memorydb::create(columns::NUM_COLUMNS);
|
||||||
|
let db =
|
||||||
|
polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[columns::META]);
|
||||||
|
Arc::new(db)
|
||||||
|
}
|
||||||
@@ -13,25 +13,41 @@
|
|||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
use crate::{core::mock::ChainApiState, TestEnvironment};
|
||||||
|
use av_store::NetworkAvailabilityState;
|
||||||
|
use bitvec::bitvec;
|
||||||
|
use colored::Colorize;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use polkadot_availability_bitfield_distribution::BitfieldDistribution;
|
||||||
|
use polkadot_node_core_av_store::AvailabilityStoreSubsystem;
|
||||||
|
use polkadot_node_subsystem::{Overseer, OverseerConnector, SpawnGlue};
|
||||||
|
use polkadot_node_subsystem_types::{
|
||||||
|
messages::{AvailabilityStoreMessage, NetworkBridgeEvent},
|
||||||
|
Span,
|
||||||
|
};
|
||||||
|
use polkadot_overseer::Handle as OverseerHandle;
|
||||||
|
use sc_network::{request_responses::ProtocolConfig, PeerId};
|
||||||
|
use sp_core::H256;
|
||||||
use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant};
|
use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use crate::TestEnvironment;
|
use av_store_helpers::new_av_store;
|
||||||
use polkadot_node_subsystem::{Overseer, OverseerConnector, SpawnGlue};
|
|
||||||
use polkadot_node_subsystem_test_helpers::derive_erasure_chunks_with_proofs_and_root;
|
|
||||||
use polkadot_overseer::Handle as OverseerHandle;
|
|
||||||
use sc_network::request_responses::ProtocolConfig;
|
|
||||||
|
|
||||||
use colored::Colorize;
|
|
||||||
|
|
||||||
use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt};
|
use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt};
|
||||||
|
use polkadot_availability_distribution::{
|
||||||
|
AvailabilityDistributionSubsystem, IncomingRequestReceivers,
|
||||||
|
};
|
||||||
use polkadot_node_metrics::metrics::Metrics;
|
use polkadot_node_metrics::metrics::Metrics;
|
||||||
|
|
||||||
use polkadot_availability_recovery::AvailabilityRecoverySubsystem;
|
use polkadot_availability_recovery::AvailabilityRecoverySubsystem;
|
||||||
|
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||||
|
|
||||||
use crate::GENESIS_HASH;
|
use crate::GENESIS_HASH;
|
||||||
use parity_scale_codec::Encode;
|
use parity_scale_codec::Encode;
|
||||||
use polkadot_node_network_protocol::request_response::{IncomingRequest, ReqProtocolNames};
|
use polkadot_node_network_protocol::{
|
||||||
|
request_response::{v1::ChunkFetchingRequest, IncomingRequest, ReqProtocolNames},
|
||||||
|
OurView, Versioned, VersionedValidationProtocol,
|
||||||
|
};
|
||||||
|
use sc_network::request_responses::IncomingRequest as RawIncomingRequest;
|
||||||
|
|
||||||
use polkadot_node_primitives::{BlockData, PoV};
|
use polkadot_node_primitives::{BlockData, PoV};
|
||||||
use polkadot_node_subsystem::messages::{AllMessages, AvailabilityRecoveryMessage};
|
use polkadot_node_subsystem::messages::{AllMessages, AvailabilityRecoveryMessage};
|
||||||
|
|
||||||
@@ -39,8 +55,8 @@ use crate::core::{
|
|||||||
environment::TestEnvironmentDependencies,
|
environment::TestEnvironmentDependencies,
|
||||||
mock::{
|
mock::{
|
||||||
av_store,
|
av_store,
|
||||||
network_bridge::{self, MockNetworkBridgeTx, NetworkAvailabilityState},
|
network_bridge::{self, MockNetworkBridgeRx, MockNetworkBridgeTx},
|
||||||
runtime_api, MockAvailabilityStore, MockRuntimeApi,
|
runtime_api, MockAvailabilityStore, MockChainApi, MockRuntimeApi,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,24 +64,26 @@ use super::core::{configuration::TestConfiguration, mock::dummy_builder, network
|
|||||||
|
|
||||||
const LOG_TARGET: &str = "subsystem-bench::availability";
|
const LOG_TARGET: &str = "subsystem-bench::availability";
|
||||||
|
|
||||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
|
||||||
|
|
||||||
use super::{cli::TestObjective, core::mock::AlwaysSupportsParachains};
|
use super::{cli::TestObjective, core::mock::AlwaysSupportsParachains};
|
||||||
use polkadot_node_subsystem_test_helpers::mock::new_block_import_info;
|
use polkadot_node_subsystem_test_helpers::{
|
||||||
|
derive_erasure_chunks_with_proofs_and_root, mock::new_block_import_info,
|
||||||
|
};
|
||||||
use polkadot_primitives::{
|
use polkadot_primitives::{
|
||||||
CandidateHash, CandidateReceipt, GroupIndex, Hash, HeadData, PersistedValidationData,
|
AvailabilityBitfield, BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash, HeadData,
|
||||||
|
Header, PersistedValidationData, Signed, SigningContext, ValidatorIndex,
|
||||||
};
|
};
|
||||||
use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
|
use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash};
|
||||||
use sc_service::SpawnTaskHandle;
|
use sc_service::SpawnTaskHandle;
|
||||||
|
|
||||||
|
mod av_store_helpers;
|
||||||
mod cli;
|
mod cli;
|
||||||
pub use cli::{DataAvailabilityReadOptions, NetworkEmulation};
|
pub use cli::{DataAvailabilityReadOptions, NetworkEmulation};
|
||||||
|
|
||||||
fn build_overseer(
|
fn build_overseer_for_availability_read(
|
||||||
spawn_task_handle: SpawnTaskHandle,
|
spawn_task_handle: SpawnTaskHandle,
|
||||||
runtime_api: MockRuntimeApi,
|
runtime_api: MockRuntimeApi,
|
||||||
av_store: MockAvailabilityStore,
|
av_store: MockAvailabilityStore,
|
||||||
network_bridge: MockNetworkBridgeTx,
|
network_bridge: (MockNetworkBridgeTx, MockNetworkBridgeRx),
|
||||||
availability_recovery: AvailabilityRecoverySubsystem,
|
availability_recovery: AvailabilityRecoverySubsystem,
|
||||||
) -> (Overseer<SpawnGlue<SpawnTaskHandle>, AlwaysSupportsParachains>, OverseerHandle) {
|
) -> (Overseer<SpawnGlue<SpawnTaskHandle>, AlwaysSupportsParachains>, OverseerHandle) {
|
||||||
let overseer_connector = OverseerConnector::with_event_capacity(64000);
|
let overseer_connector = OverseerConnector::with_event_capacity(64000);
|
||||||
@@ -73,7 +91,8 @@ fn build_overseer(
|
|||||||
let builder = dummy
|
let builder = dummy
|
||||||
.replace_runtime_api(|_| runtime_api)
|
.replace_runtime_api(|_| runtime_api)
|
||||||
.replace_availability_store(|_| av_store)
|
.replace_availability_store(|_| av_store)
|
||||||
.replace_network_bridge_tx(|_| network_bridge)
|
.replace_network_bridge_tx(|_| network_bridge.0)
|
||||||
|
.replace_network_bridge_rx(|_| network_bridge.1)
|
||||||
.replace_availability_recovery(|_| availability_recovery);
|
.replace_availability_recovery(|_| availability_recovery);
|
||||||
|
|
||||||
let (overseer, raw_handle) =
|
let (overseer, raw_handle) =
|
||||||
@@ -82,11 +101,38 @@ fn build_overseer(
|
|||||||
(overseer, OverseerHandle::new(raw_handle))
|
(overseer, OverseerHandle::new(raw_handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a test configuration and uses it to creates the `TestEnvironment`.
|
fn build_overseer_for_availability_write(
|
||||||
|
spawn_task_handle: SpawnTaskHandle,
|
||||||
|
runtime_api: MockRuntimeApi,
|
||||||
|
network_bridge: (MockNetworkBridgeTx, MockNetworkBridgeRx),
|
||||||
|
availability_distribution: AvailabilityDistributionSubsystem,
|
||||||
|
chain_api: MockChainApi,
|
||||||
|
availability_store: AvailabilityStoreSubsystem,
|
||||||
|
bitfield_distribution: BitfieldDistribution,
|
||||||
|
) -> (Overseer<SpawnGlue<SpawnTaskHandle>, AlwaysSupportsParachains>, OverseerHandle) {
|
||||||
|
let overseer_connector = OverseerConnector::with_event_capacity(64000);
|
||||||
|
let dummy = dummy_builder!(spawn_task_handle);
|
||||||
|
let builder = dummy
|
||||||
|
.replace_runtime_api(|_| runtime_api)
|
||||||
|
.replace_availability_store(|_| availability_store)
|
||||||
|
.replace_network_bridge_tx(|_| network_bridge.0)
|
||||||
|
.replace_network_bridge_rx(|_| network_bridge.1)
|
||||||
|
.replace_chain_api(|_| chain_api)
|
||||||
|
.replace_bitfield_distribution(|_| bitfield_distribution)
|
||||||
|
// This is needed to test own chunk recovery for `n_cores`.
|
||||||
|
.replace_availability_distribution(|_| availability_distribution);
|
||||||
|
|
||||||
|
let (overseer, raw_handle) =
|
||||||
|
builder.build_with_connector(overseer_connector).expect("Should not fail");
|
||||||
|
|
||||||
|
(overseer, OverseerHandle::new(raw_handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a test configuration and uses it to create the `TestEnvironment`.
|
||||||
pub fn prepare_test(
|
pub fn prepare_test(
|
||||||
config: TestConfiguration,
|
config: TestConfiguration,
|
||||||
state: &mut TestState,
|
state: &mut TestState,
|
||||||
) -> (TestEnvironment, ProtocolConfig) {
|
) -> (TestEnvironment, Vec<ProtocolConfig>) {
|
||||||
prepare_test_inner(config, state, TestEnvironmentDependencies::default())
|
prepare_test_inner(config, state, TestEnvironmentDependencies::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,14 +140,38 @@ fn prepare_test_inner(
|
|||||||
config: TestConfiguration,
|
config: TestConfiguration,
|
||||||
state: &mut TestState,
|
state: &mut TestState,
|
||||||
dependencies: TestEnvironmentDependencies,
|
dependencies: TestEnvironmentDependencies,
|
||||||
) -> (TestEnvironment, ProtocolConfig) {
|
) -> (TestEnvironment, Vec<ProtocolConfig>) {
|
||||||
// Generate test authorities.
|
// Generate test authorities.
|
||||||
let test_authorities = config.generate_authorities();
|
let test_authorities = config.generate_authorities();
|
||||||
|
|
||||||
let runtime_api = runtime_api::MockRuntimeApi::new(config.clone(), test_authorities.clone());
|
let mut candidate_hashes: HashMap<H256, Vec<CandidateReceipt>> = HashMap::new();
|
||||||
|
|
||||||
let av_store =
|
// Prepare per block candidates.
|
||||||
av_store::MockAvailabilityStore::new(state.chunks.clone(), state.candidate_hashes.clone());
|
// Genesis block is always finalized, so we start at 1.
|
||||||
|
for block_num in 1..=config.num_blocks {
|
||||||
|
for _ in 0..config.n_cores {
|
||||||
|
candidate_hashes
|
||||||
|
.entry(Hash::repeat_byte(block_num as u8))
|
||||||
|
.or_default()
|
||||||
|
.push(state.next_candidate().expect("Cycle iterator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First candidate is our backed candidate.
|
||||||
|
state.backed_candidates.push(
|
||||||
|
candidate_hashes
|
||||||
|
.get(&Hash::repeat_byte(block_num as u8))
|
||||||
|
.expect("just inserted above")
|
||||||
|
.get(0)
|
||||||
|
.expect("just inserted above")
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let runtime_api = runtime_api::MockRuntimeApi::new(
|
||||||
|
config.clone(),
|
||||||
|
test_authorities.clone(),
|
||||||
|
candidate_hashes,
|
||||||
|
);
|
||||||
|
|
||||||
let availability_state = NetworkAvailabilityState {
|
let availability_state = NetworkAvailabilityState {
|
||||||
candidate_hashes: state.candidate_hashes.clone(),
|
candidate_hashes: state.candidate_hashes.clone(),
|
||||||
@@ -109,45 +179,112 @@ fn prepare_test_inner(
|
|||||||
chunks: state.chunks.clone(),
|
chunks: state.chunks.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let req_protocol_names = ReqProtocolNames::new(GENESIS_HASH, None);
|
let mut req_cfgs = Vec::new();
|
||||||
let (collation_req_receiver, req_cfg) =
|
|
||||||
IncomingRequest::get_config_receiver(&req_protocol_names);
|
|
||||||
|
|
||||||
let network =
|
let (collation_req_receiver, collation_req_cfg) =
|
||||||
NetworkEmulator::new(&config, &dependencies, &test_authorities, req_protocol_names);
|
IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None));
|
||||||
|
req_cfgs.push(collation_req_cfg);
|
||||||
|
|
||||||
|
let (pov_req_receiver, pov_req_cfg) =
|
||||||
|
IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None));
|
||||||
|
|
||||||
|
let (chunk_req_receiver, chunk_req_cfg) =
|
||||||
|
IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None));
|
||||||
|
req_cfgs.push(pov_req_cfg);
|
||||||
|
|
||||||
|
let (network, network_interface, network_receiver) =
|
||||||
|
new_network(&config, &dependencies, &test_authorities, vec![Arc::new(availability_state)]);
|
||||||
|
|
||||||
let network_bridge_tx = network_bridge::MockNetworkBridgeTx::new(
|
let network_bridge_tx = network_bridge::MockNetworkBridgeTx::new(
|
||||||
config.clone(),
|
|
||||||
availability_state,
|
|
||||||
network.clone(),
|
network.clone(),
|
||||||
|
network_interface.subsystem_sender(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let use_fast_path = match &state.config().objective {
|
let network_bridge_rx =
|
||||||
TestObjective::DataAvailabilityRead(options) => options.fetch_from_backers,
|
network_bridge::MockNetworkBridgeRx::new(network_receiver, Some(chunk_req_cfg.clone()));
|
||||||
_ => panic!("Unexpected objective"),
|
|
||||||
|
let (overseer, overseer_handle) = match &state.config().objective {
|
||||||
|
TestObjective::DataAvailabilityRead(options) => {
|
||||||
|
let use_fast_path = options.fetch_from_backers;
|
||||||
|
|
||||||
|
let subsystem = if use_fast_path {
|
||||||
|
AvailabilityRecoverySubsystem::with_fast_path(
|
||||||
|
collation_req_receiver,
|
||||||
|
Metrics::try_register(&dependencies.registry).unwrap(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AvailabilityRecoverySubsystem::with_chunks_only(
|
||||||
|
collation_req_receiver,
|
||||||
|
Metrics::try_register(&dependencies.registry).unwrap(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use a mocked av-store.
|
||||||
|
let av_store = av_store::MockAvailabilityStore::new(
|
||||||
|
state.chunks.clone(),
|
||||||
|
state.candidate_hashes.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
build_overseer_for_availability_read(
|
||||||
|
dependencies.task_manager.spawn_handle(),
|
||||||
|
runtime_api,
|
||||||
|
av_store,
|
||||||
|
(network_bridge_tx, network_bridge_rx),
|
||||||
|
subsystem,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
TestObjective::DataAvailabilityWrite => {
|
||||||
|
let availability_distribution = AvailabilityDistributionSubsystem::new(
|
||||||
|
test_authorities.keyring.keystore(),
|
||||||
|
IncomingRequestReceivers { pov_req_receiver, chunk_req_receiver },
|
||||||
|
Metrics::try_register(&dependencies.registry).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let block_headers = (0..=config.num_blocks)
|
||||||
|
.map(|block_number| {
|
||||||
|
(
|
||||||
|
Hash::repeat_byte(block_number as u8),
|
||||||
|
Header {
|
||||||
|
digest: Default::default(),
|
||||||
|
number: block_number as BlockNumber,
|
||||||
|
parent_hash: Default::default(),
|
||||||
|
extrinsics_root: Default::default(),
|
||||||
|
state_root: Default::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
|
let chain_api_state = ChainApiState { block_headers };
|
||||||
|
let chain_api = MockChainApi::new(chain_api_state);
|
||||||
|
let bitfield_distribution =
|
||||||
|
BitfieldDistribution::new(Metrics::try_register(&dependencies.registry).unwrap());
|
||||||
|
build_overseer_for_availability_write(
|
||||||
|
dependencies.task_manager.spawn_handle(),
|
||||||
|
runtime_api,
|
||||||
|
(network_bridge_tx, network_bridge_rx),
|
||||||
|
availability_distribution,
|
||||||
|
chain_api,
|
||||||
|
new_av_store(&dependencies),
|
||||||
|
bitfield_distribution,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
unimplemented!("Invalid test objective")
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let subsystem = if use_fast_path {
|
(
|
||||||
AvailabilityRecoverySubsystem::with_fast_path(
|
TestEnvironment::new(
|
||||||
collation_req_receiver,
|
dependencies,
|
||||||
Metrics::try_register(&dependencies.registry).unwrap(),
|
config,
|
||||||
)
|
network,
|
||||||
} else {
|
overseer,
|
||||||
AvailabilityRecoverySubsystem::with_chunks_only(
|
overseer_handle,
|
||||||
collation_req_receiver,
|
test_authorities,
|
||||||
Metrics::try_register(&dependencies.registry).unwrap(),
|
),
|
||||||
)
|
req_cfgs,
|
||||||
};
|
)
|
||||||
|
|
||||||
let (overseer, overseer_handle) = build_overseer(
|
|
||||||
dependencies.task_manager.spawn_handle(),
|
|
||||||
runtime_api,
|
|
||||||
av_store,
|
|
||||||
network_bridge_tx,
|
|
||||||
subsystem,
|
|
||||||
);
|
|
||||||
|
|
||||||
(TestEnvironment::new(dependencies, config, network, overseer, overseer_handle), req_cfg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -169,6 +306,8 @@ pub struct TestState {
|
|||||||
available_data: Vec<AvailableData>,
|
available_data: Vec<AvailableData>,
|
||||||
// Per candiadte index chunks
|
// Per candiadte index chunks
|
||||||
chunks: Vec<Vec<ErasureChunk>>,
|
chunks: Vec<Vec<ErasureChunk>>,
|
||||||
|
// Per relay chain block - candidate backed by our backing group
|
||||||
|
backed_candidates: Vec<CandidateReceipt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestState {
|
impl TestState {
|
||||||
@@ -255,24 +394,27 @@ impl TestState {
|
|||||||
candidate_receipt_templates.push(candidate_receipt);
|
candidate_receipt_templates.push(candidate_receipt);
|
||||||
}
|
}
|
||||||
|
|
||||||
let pov_sizes = config.pov_sizes().to_owned();
|
|
||||||
let pov_sizes = pov_sizes.into_iter().cycle();
|
|
||||||
gum::info!(target: LOG_TARGET, "{}","Created test environment.".bright_blue());
|
gum::info!(target: LOG_TARGET, "{}","Created test environment.".bright_blue());
|
||||||
|
|
||||||
let mut _self = Self {
|
let mut _self = Self {
|
||||||
config,
|
|
||||||
available_data,
|
available_data,
|
||||||
candidate_receipt_templates,
|
candidate_receipt_templates,
|
||||||
chunks,
|
chunks,
|
||||||
pov_size_to_candidate,
|
pov_size_to_candidate,
|
||||||
pov_sizes,
|
pov_sizes: Vec::from(config.pov_sizes()).into_iter().cycle(),
|
||||||
candidate_hashes: HashMap::new(),
|
candidate_hashes: HashMap::new(),
|
||||||
candidates: Vec::new().into_iter().cycle(),
|
candidates: Vec::new().into_iter().cycle(),
|
||||||
|
backed_candidates: Vec::new(),
|
||||||
|
config,
|
||||||
};
|
};
|
||||||
|
|
||||||
_self.generate_candidates();
|
_self.generate_candidates();
|
||||||
_self
|
_self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn backed_candidates(&mut self) -> &mut Vec<CandidateReceipt> {
|
||||||
|
&mut self.backed_candidates
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: TestState) {
|
pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: TestState) {
|
||||||
@@ -280,15 +422,15 @@ pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: T
|
|||||||
|
|
||||||
env.import_block(new_block_import_info(Hash::repeat_byte(1), 1)).await;
|
env.import_block(new_block_import_info(Hash::repeat_byte(1), 1)).await;
|
||||||
|
|
||||||
let start_marker = Instant::now();
|
let test_start = Instant::now();
|
||||||
let mut batch = FuturesUnordered::new();
|
let mut batch = FuturesUnordered::new();
|
||||||
let mut availability_bytes = 0u128;
|
let mut availability_bytes = 0u128;
|
||||||
|
|
||||||
env.metrics().set_n_validators(config.n_validators);
|
env.metrics().set_n_validators(config.n_validators);
|
||||||
env.metrics().set_n_cores(config.n_cores);
|
env.metrics().set_n_cores(config.n_cores);
|
||||||
|
|
||||||
for block_num in 0..env.config().num_blocks {
|
for block_num in 1..=env.config().num_blocks {
|
||||||
gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num + 1, env.config().num_blocks);
|
gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num, env.config().num_blocks);
|
||||||
env.metrics().set_current_block(block_num);
|
env.metrics().set_current_block(block_num);
|
||||||
|
|
||||||
let block_start_ts = Instant::now();
|
let block_start_ts = Instant::now();
|
||||||
@@ -311,7 +453,7 @@ pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: T
|
|||||||
env.send_message(message).await;
|
env.send_message(message).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
gum::info!("{}", format!("{} recoveries pending", batch.len()).bright_black());
|
gum::info!(target: LOG_TARGET, "{}", format!("{} recoveries pending", batch.len()).bright_black());
|
||||||
while let Some(completed) = batch.next().await {
|
while let Some(completed) = batch.next().await {
|
||||||
let available_data = completed.unwrap().unwrap();
|
let available_data = completed.unwrap().unwrap();
|
||||||
env.metrics().on_pov_size(available_data.encoded_size());
|
env.metrics().on_pov_size(available_data.encoded_size());
|
||||||
@@ -320,22 +462,199 @@ pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: T
|
|||||||
|
|
||||||
let block_time = Instant::now().sub(block_start_ts).as_millis() as u64;
|
let block_time = Instant::now().sub(block_start_ts).as_millis() as u64;
|
||||||
env.metrics().set_block_time(block_time);
|
env.metrics().set_block_time(block_time);
|
||||||
gum::info!("All work for block completed in {}", format!("{:?}ms", block_time).cyan());
|
gum::info!(target: LOG_TARGET, "All work for block completed in {}", format!("{:?}ms", block_time).cyan());
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration: u128 = start_marker.elapsed().as_millis();
|
let duration: u128 = test_start.elapsed().as_millis();
|
||||||
let availability_bytes = availability_bytes / 1024;
|
let availability_bytes = availability_bytes / 1024;
|
||||||
gum::info!("All blocks processed in {}", format!("{:?}ms", duration).cyan());
|
gum::info!(target: LOG_TARGET, "All blocks processed in {}", format!("{:?}ms", duration).cyan());
|
||||||
gum::info!(
|
gum::info!(target: LOG_TARGET,
|
||||||
"Throughput: {}",
|
"Throughput: {}",
|
||||||
format!("{} KiB/block", availability_bytes / env.config().num_blocks as u128).bright_red()
|
format!("{} KiB/block", availability_bytes / env.config().num_blocks as u128).bright_red()
|
||||||
);
|
);
|
||||||
gum::info!(
|
gum::info!(target: LOG_TARGET,
|
||||||
"Block time: {}",
|
"Avg block time: {}",
|
||||||
format!("{} ms", start_marker.elapsed().as_millis() / env.config().num_blocks as u128)
|
format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red()
|
||||||
.red()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gum::info!("{}", &env);
|
env.display_network_usage();
|
||||||
|
env.display_cpu_usage(&["availability-recovery"]);
|
||||||
env.stop().await;
|
env.stop().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: TestState) {
|
||||||
|
let config = env.config().clone();
|
||||||
|
|
||||||
|
env.metrics().set_n_validators(config.n_validators);
|
||||||
|
env.metrics().set_n_cores(config.n_cores);
|
||||||
|
|
||||||
|
gum::info!(target: LOG_TARGET, "Seeding availability store with candidates ...");
|
||||||
|
for backed_candidate in state.backed_candidates().clone() {
|
||||||
|
let candidate_index = *state.candidate_hashes.get(&backed_candidate.hash()).unwrap();
|
||||||
|
let available_data = state.available_data[candidate_index].clone();
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
env.send_message(AllMessages::AvailabilityStore(
|
||||||
|
AvailabilityStoreMessage::StoreAvailableData {
|
||||||
|
candidate_hash: backed_candidate.hash(),
|
||||||
|
n_validators: config.n_validators as u32,
|
||||||
|
available_data,
|
||||||
|
expected_erasure_root: backed_candidate.descriptor().erasure_root,
|
||||||
|
tx,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
rx.await
|
||||||
|
.unwrap()
|
||||||
|
.expect("Test candidates are stored nicely in availability store");
|
||||||
|
}
|
||||||
|
|
||||||
|
gum::info!(target: LOG_TARGET, "Done");
|
||||||
|
|
||||||
|
let test_start = Instant::now();
|
||||||
|
|
||||||
|
for block_num in 1..=env.config().num_blocks {
|
||||||
|
gum::info!(target: LOG_TARGET, "Current block #{}", block_num);
|
||||||
|
env.metrics().set_current_block(block_num);
|
||||||
|
|
||||||
|
let block_start_ts = Instant::now();
|
||||||
|
let relay_block_hash = Hash::repeat_byte(block_num as u8);
|
||||||
|
env.import_block(new_block_import_info(relay_block_hash, block_num as BlockNumber))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Inform bitfield distribution about our view of current test block
|
||||||
|
let message = polkadot_node_subsystem_types::messages::BitfieldDistributionMessage::NetworkBridgeUpdate(
|
||||||
|
NetworkBridgeEvent::OurViewChange(OurView::new(vec![(relay_block_hash, Arc::new(Span::Disabled))], 0))
|
||||||
|
);
|
||||||
|
env.send_message(AllMessages::BitfieldDistribution(message)).await;
|
||||||
|
|
||||||
|
let chunk_fetch_start_ts = Instant::now();
|
||||||
|
|
||||||
|
// Request chunks of our own backed candidate from all other validators.
|
||||||
|
let mut receivers = Vec::new();
|
||||||
|
for index in 1..config.n_validators {
|
||||||
|
let (pending_response, pending_response_receiver) = oneshot::channel();
|
||||||
|
|
||||||
|
let request = RawIncomingRequest {
|
||||||
|
peer: PeerId::random(),
|
||||||
|
payload: ChunkFetchingRequest {
|
||||||
|
candidate_hash: state.backed_candidates()[block_num - 1].hash(),
|
||||||
|
index: ValidatorIndex(index as u32),
|
||||||
|
}
|
||||||
|
.encode(),
|
||||||
|
pending_response,
|
||||||
|
};
|
||||||
|
|
||||||
|
let peer = env
|
||||||
|
.authorities()
|
||||||
|
.validator_authority_id
|
||||||
|
.get(index)
|
||||||
|
.expect("all validators have keys");
|
||||||
|
|
||||||
|
if env.network().is_peer_connected(peer) &&
|
||||||
|
env.network().send_request_from_peer(peer, request).is_ok()
|
||||||
|
{
|
||||||
|
receivers.push(pending_response_receiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gum::info!(target: LOG_TARGET, "Waiting for all emulated peers to receive their chunk from us ...");
|
||||||
|
for receiver in receivers.into_iter() {
|
||||||
|
let response = receiver.await.expect("Chunk is always served succesfully");
|
||||||
|
// TODO: check if chunk is the one the peer expects to receive.
|
||||||
|
assert!(response.result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk_fetch_duration = Instant::now().sub(chunk_fetch_start_ts).as_millis();
|
||||||
|
|
||||||
|
gum::info!(target: LOG_TARGET, "All chunks received in {}ms", chunk_fetch_duration);
|
||||||
|
|
||||||
|
let signing_context = SigningContext { session_index: 0, parent_hash: relay_block_hash };
|
||||||
|
let network = env.network().clone();
|
||||||
|
let authorities = env.authorities().clone();
|
||||||
|
let n_validators = config.n_validators;
|
||||||
|
|
||||||
|
// Spawn a task that will generate `n_validator` - 1 signed bitfiends and
|
||||||
|
// send them from the emulated peers to the subsystem.
|
||||||
|
// TODO: Implement topology.
|
||||||
|
env.spawn_blocking("send-bitfields", async move {
|
||||||
|
for index in 1..n_validators {
|
||||||
|
let validator_public =
|
||||||
|
authorities.validator_public.get(index).expect("All validator keys are known");
|
||||||
|
|
||||||
|
// Node has all the chunks in the world.
|
||||||
|
let payload: AvailabilityBitfield =
|
||||||
|
AvailabilityBitfield(bitvec![u8, bitvec::order::Lsb0; 1u8; 32]);
|
||||||
|
// TODO(soon): Use pre-signed messages. This is quite intensive on the CPU.
|
||||||
|
let signed_bitfield = Signed::<AvailabilityBitfield>::sign(
|
||||||
|
&authorities.keyring.keystore(),
|
||||||
|
payload,
|
||||||
|
&signing_context,
|
||||||
|
ValidatorIndex(index as u32),
|
||||||
|
validator_public,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.expect("should be signed");
|
||||||
|
|
||||||
|
let from_peer = &authorities.validator_authority_id[index];
|
||||||
|
|
||||||
|
let message = peer_bitfield_message_v2(relay_block_hash, signed_bitfield);
|
||||||
|
|
||||||
|
// Send the action from peer only if it is connected to our node.
|
||||||
|
if network.is_peer_connected(from_peer) {
|
||||||
|
let _ = network.send_message_from_peer(from_peer, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gum::info!(
|
||||||
|
"Waiting for {} bitfields to be received and processed",
|
||||||
|
config.connected_count()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for all bitfields to be processed.
|
||||||
|
env.wait_until_metric_eq(
|
||||||
|
"polkadot_parachain_received_availabilty_bitfields_total",
|
||||||
|
config.connected_count() * block_num,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
gum::info!(target: LOG_TARGET, "All bitfields processed");
|
||||||
|
|
||||||
|
let block_time = Instant::now().sub(block_start_ts).as_millis() as u64;
|
||||||
|
env.metrics().set_block_time(block_time);
|
||||||
|
gum::info!(target: LOG_TARGET, "All work for block completed in {}", format!("{:?}ms", block_time).cyan());
|
||||||
|
}
|
||||||
|
|
||||||
|
let duration: u128 = test_start.elapsed().as_millis();
|
||||||
|
gum::info!(target: LOG_TARGET, "All blocks processed in {}", format!("{:?}ms", duration).cyan());
|
||||||
|
gum::info!(target: LOG_TARGET,
|
||||||
|
"Avg block time: {}",
|
||||||
|
format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red()
|
||||||
|
);
|
||||||
|
|
||||||
|
env.display_network_usage();
|
||||||
|
|
||||||
|
env.display_cpu_usage(&[
|
||||||
|
"availability-distribution",
|
||||||
|
"bitfield-distribution",
|
||||||
|
"availability-store",
|
||||||
|
]);
|
||||||
|
|
||||||
|
env.stop().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peer_bitfield_message_v2(
|
||||||
|
relay_hash: H256,
|
||||||
|
signed_bitfield: Signed<AvailabilityBitfield>,
|
||||||
|
) -> VersionedValidationProtocol {
|
||||||
|
let bitfield = polkadot_node_network_protocol::v2::BitfieldDistributionMessage::Bitfield(
|
||||||
|
relay_hash,
|
||||||
|
signed_bitfield.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Versioned::V2(polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution(
|
||||||
|
bitfield,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ pub struct TestSequenceOptions {
|
|||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define the supported benchmarks targets
|
/// Supported test objectives
|
||||||
#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)]
|
#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)]
|
||||||
#[command(rename_all = "kebab-case")]
|
#[command(rename_all = "kebab-case")]
|
||||||
pub enum TestObjective {
|
pub enum TestObjective {
|
||||||
/// Benchmark availability recovery strategies.
|
/// Benchmark availability recovery strategies.
|
||||||
DataAvailabilityRead(DataAvailabilityReadOptions),
|
DataAvailabilityRead(DataAvailabilityReadOptions),
|
||||||
|
/// Benchmark availability and bitfield distribution.
|
||||||
|
DataAvailabilityWrite,
|
||||||
/// Run a test sequence specified in a file
|
/// Run a test sequence specified in a file
|
||||||
TestSequence(TestSequenceOptions),
|
TestSequence(TestSequenceOptions),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,13 @@
|
|||||||
//! Test configuration definition and helpers.
|
//! Test configuration definition and helpers.
|
||||||
use super::*;
|
use super::*;
|
||||||
use keyring::Keyring;
|
use keyring::Keyring;
|
||||||
use std::{path::Path, time::Duration};
|
use std::path::Path;
|
||||||
|
|
||||||
pub use crate::cli::TestObjective;
|
pub use crate::cli::TestObjective;
|
||||||
use polkadot_primitives::{AuthorityDiscoveryId, ValidatorId};
|
use polkadot_primitives::{AuthorityDiscoveryId, ValidatorId};
|
||||||
use rand::{distributions::Uniform, prelude::Distribution, thread_rng};
|
use rand::thread_rng;
|
||||||
|
use rand_distr::{Distribution, Normal, Uniform};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize {
|
pub fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize {
|
||||||
@@ -34,13 +36,13 @@ fn random_uniform_sample<T: Into<usize> + From<usize>>(min_value: T, max_value:
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer response latency configuration.
|
/// Peer networking latency configuration.
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct PeerLatency {
|
pub struct PeerLatency {
|
||||||
/// Min latency for `NetworkAction` completion.
|
/// The mean latency(milliseconds) of the peers.
|
||||||
pub min_latency: Duration,
|
pub mean_latency_ms: usize,
|
||||||
/// Max latency or `NetworkAction` completion.
|
/// The standard deviation
|
||||||
pub max_latency: Duration,
|
pub std_dev: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default PoV size in KiB.
|
// Default PoV size in KiB.
|
||||||
@@ -58,6 +60,11 @@ fn default_connectivity() -> usize {
|
|||||||
100
|
100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default backing group size
|
||||||
|
fn default_backing_group_size() -> usize {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
|
||||||
/// The test input parameters
|
/// The test input parameters
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TestConfiguration {
|
pub struct TestConfiguration {
|
||||||
@@ -67,6 +74,9 @@ pub struct TestConfiguration {
|
|||||||
pub n_validators: usize,
|
pub n_validators: usize,
|
||||||
/// Number of cores
|
/// Number of cores
|
||||||
pub n_cores: usize,
|
pub n_cores: usize,
|
||||||
|
/// Maximum backing group size
|
||||||
|
#[serde(default = "default_backing_group_size")]
|
||||||
|
pub max_validators_per_core: usize,
|
||||||
/// The min PoV size
|
/// The min PoV size
|
||||||
#[serde(default = "default_pov_size")]
|
#[serde(default = "default_pov_size")]
|
||||||
pub min_pov_size: usize,
|
pub min_pov_size: usize,
|
||||||
@@ -82,12 +92,9 @@ pub struct TestConfiguration {
|
|||||||
/// The amount of bandiwdth our node has.
|
/// The amount of bandiwdth our node has.
|
||||||
#[serde(default = "default_bandwidth")]
|
#[serde(default = "default_bandwidth")]
|
||||||
pub bandwidth: usize,
|
pub bandwidth: usize,
|
||||||
/// Optional peer emulation latency
|
/// Optional peer emulation latency (round trip time) wrt node under test
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub latency: Option<PeerLatency>,
|
pub latency: Option<PeerLatency>,
|
||||||
/// Error probability, applies to sending messages to the emulated network peers
|
|
||||||
#[serde(default)]
|
|
||||||
pub error: usize,
|
|
||||||
/// Connectivity ratio, the percentage of peers we are not connected to, but ar part of
|
/// Connectivity ratio, the percentage of peers we are not connected to, but ar part of
|
||||||
/// the topology.
|
/// the topology.
|
||||||
#[serde(default = "default_connectivity")]
|
#[serde(default = "default_connectivity")]
|
||||||
@@ -129,7 +136,7 @@ impl TestSequence {
|
|||||||
/// Helper struct for authority related state.
|
/// Helper struct for authority related state.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TestAuthorities {
|
pub struct TestAuthorities {
|
||||||
pub keyrings: Vec<Keyring>,
|
pub keyring: Keyring,
|
||||||
pub validator_public: Vec<ValidatorId>,
|
pub validator_public: Vec<ValidatorId>,
|
||||||
pub validator_authority_id: Vec<AuthorityDiscoveryId>,
|
pub validator_authority_id: Vec<AuthorityDiscoveryId>,
|
||||||
}
|
}
|
||||||
@@ -146,25 +153,27 @@ impl TestConfiguration {
|
|||||||
pub fn pov_sizes(&self) -> &[usize] {
|
pub fn pov_sizes(&self) -> &[usize] {
|
||||||
&self.pov_sizes
|
&self.pov_sizes
|
||||||
}
|
}
|
||||||
|
/// Return the number of peers connected to our node.
|
||||||
|
pub fn connected_count(&self) -> usize {
|
||||||
|
((self.n_validators - 1) as f64 / (100.0 / self.connectivity as f64)) as usize
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates the authority keys we need for the network emulation.
|
/// Generates the authority keys we need for the network emulation.
|
||||||
pub fn generate_authorities(&self) -> TestAuthorities {
|
pub fn generate_authorities(&self) -> TestAuthorities {
|
||||||
let keyrings = (0..self.n_validators)
|
let keyring = Keyring::default();
|
||||||
.map(|peer_index| Keyring::new(format!("Node{}", peer_index)))
|
|
||||||
|
let keys = (0..self.n_validators)
|
||||||
|
.map(|peer_index| keyring.sr25519_new(format!("Node{}", peer_index)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Generate `AuthorityDiscoveryId`` for each peer
|
// Generate `AuthorityDiscoveryId`` for each peer
|
||||||
let validator_public: Vec<ValidatorId> = keyrings
|
let validator_public: Vec<ValidatorId> =
|
||||||
.iter()
|
keys.iter().map(|key| (*key).into()).collect::<Vec<_>>();
|
||||||
.map(|keyring: &Keyring| keyring.clone().public().into())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let validator_authority_id: Vec<AuthorityDiscoveryId> = keyrings
|
let validator_authority_id: Vec<AuthorityDiscoveryId> =
|
||||||
.iter()
|
keys.iter().map(|key| (*key).into()).collect::<Vec<_>>();
|
||||||
.map(|keyring| keyring.clone().public().into())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
TestAuthorities { keyrings, validator_public, validator_authority_id }
|
TestAuthorities { keyring, validator_public, validator_authority_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An unconstrained standard configuration matching Polkadot/Kusama
|
/// An unconstrained standard configuration matching Polkadot/Kusama
|
||||||
@@ -180,12 +189,12 @@ impl TestConfiguration {
|
|||||||
objective,
|
objective,
|
||||||
n_cores,
|
n_cores,
|
||||||
n_validators,
|
n_validators,
|
||||||
|
max_validators_per_core: 5,
|
||||||
pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size),
|
pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size),
|
||||||
bandwidth: 50 * 1024 * 1024,
|
bandwidth: 50 * 1024 * 1024,
|
||||||
peer_bandwidth: 50 * 1024 * 1024,
|
peer_bandwidth: 50 * 1024 * 1024,
|
||||||
// No latency
|
// No latency
|
||||||
latency: None,
|
latency: None,
|
||||||
error: 0,
|
|
||||||
num_blocks,
|
num_blocks,
|
||||||
min_pov_size,
|
min_pov_size,
|
||||||
max_pov_size,
|
max_pov_size,
|
||||||
@@ -205,14 +214,11 @@ impl TestConfiguration {
|
|||||||
objective,
|
objective,
|
||||||
n_cores,
|
n_cores,
|
||||||
n_validators,
|
n_validators,
|
||||||
|
max_validators_per_core: 5,
|
||||||
pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size),
|
pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size),
|
||||||
bandwidth: 50 * 1024 * 1024,
|
bandwidth: 50 * 1024 * 1024,
|
||||||
peer_bandwidth: 50 * 1024 * 1024,
|
peer_bandwidth: 50 * 1024 * 1024,
|
||||||
latency: Some(PeerLatency {
|
latency: Some(PeerLatency { mean_latency_ms: 50, std_dev: 12.5 }),
|
||||||
min_latency: Duration::from_millis(1),
|
|
||||||
max_latency: Duration::from_millis(100),
|
|
||||||
}),
|
|
||||||
error: 3,
|
|
||||||
num_blocks,
|
num_blocks,
|
||||||
min_pov_size,
|
min_pov_size,
|
||||||
max_pov_size,
|
max_pov_size,
|
||||||
@@ -232,14 +238,11 @@ impl TestConfiguration {
|
|||||||
objective,
|
objective,
|
||||||
n_cores,
|
n_cores,
|
||||||
n_validators,
|
n_validators,
|
||||||
|
max_validators_per_core: 5,
|
||||||
pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size),
|
pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size),
|
||||||
bandwidth: 50 * 1024 * 1024,
|
bandwidth: 50 * 1024 * 1024,
|
||||||
peer_bandwidth: 50 * 1024 * 1024,
|
peer_bandwidth: 50 * 1024 * 1024,
|
||||||
latency: Some(PeerLatency {
|
latency: Some(PeerLatency { mean_latency_ms: 150, std_dev: 40.0 }),
|
||||||
min_latency: Duration::from_millis(10),
|
|
||||||
max_latency: Duration::from_millis(500),
|
|
||||||
}),
|
|
||||||
error: 33,
|
|
||||||
num_blocks,
|
num_blocks,
|
||||||
min_pov_size,
|
min_pov_size,
|
||||||
max_pov_size,
|
max_pov_size,
|
||||||
@@ -248,15 +251,14 @@ impl TestConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a randomized duration between `min` and `max`.
|
/// Sample latency (in milliseconds) from a normal distribution with parameters
|
||||||
pub fn random_latency(maybe_peer_latency: Option<&PeerLatency>) -> Option<Duration> {
|
/// specified in `maybe_peer_latency`.
|
||||||
maybe_peer_latency.map(|peer_latency| {
|
pub fn random_latency(maybe_peer_latency: Option<&PeerLatency>) -> usize {
|
||||||
Uniform::from(peer_latency.min_latency..=peer_latency.max_latency).sample(&mut thread_rng())
|
maybe_peer_latency
|
||||||
})
|
.map(|latency_config| {
|
||||||
}
|
Normal::new(latency_config.mean_latency_ms as f64, latency_config.std_dev)
|
||||||
|
.expect("normal distribution parameters are good")
|
||||||
/// Generate a random error based on `probability`.
|
.sample(&mut thread_rng())
|
||||||
/// `probability` should be a number between 0 and 100.
|
})
|
||||||
pub fn random_error(probability: usize) -> bool {
|
.unwrap_or(0.0) as usize
|
||||||
Uniform::from(0..=99).sample(&mut thread_rng()) < probability
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,12 +180,13 @@ pub fn parse_metrics(registry: &Registry) -> MetricCollection {
|
|||||||
|
|
||||||
pub fn display_configuration(test_config: &TestConfiguration) {
|
pub fn display_configuration(test_config: &TestConfiguration) {
|
||||||
gum::info!(
|
gum::info!(
|
||||||
"{}, {}, {}, {}, {}",
|
"[{}] {}, {}, {}, {}, {}",
|
||||||
|
format!("objective = {:?}", test_config.objective).green(),
|
||||||
format!("n_validators = {}", test_config.n_validators).blue(),
|
format!("n_validators = {}", test_config.n_validators).blue(),
|
||||||
format!("n_cores = {}", test_config.n_cores).blue(),
|
format!("n_cores = {}", test_config.n_cores).blue(),
|
||||||
format!("pov_size = {} - {}", test_config.min_pov_size, test_config.max_pov_size)
|
format!("pov_size = {} - {}", test_config.min_pov_size, test_config.max_pov_size)
|
||||||
.bright_black(),
|
.bright_black(),
|
||||||
format!("error = {}", test_config.error).bright_black(),
|
format!("connectivity = {}", test_config.connectivity).bright_black(),
|
||||||
format!("latency = {:?}", test_config.latency).bright_black(),
|
format!("latency = {:?}", test_config.latency).bright_black(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//! Test environment implementation
|
//! Test environment implementation
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{mock::AlwaysSupportsParachains, network::NetworkEmulator},
|
core::{mock::AlwaysSupportsParachains, network::NetworkEmulatorHandle},
|
||||||
TestConfiguration,
|
TestConfiguration,
|
||||||
};
|
};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use futures::FutureExt;
|
use futures::{Future, FutureExt};
|
||||||
use polkadot_overseer::{BlockInfo, Handle as OverseerHandle};
|
use polkadot_overseer::{BlockInfo, Handle as OverseerHandle};
|
||||||
|
|
||||||
use polkadot_node_subsystem::{messages::AllMessages, Overseer, SpawnGlue, TimeoutExt};
|
use polkadot_node_subsystem::{messages::AllMessages, Overseer, SpawnGlue, TimeoutExt};
|
||||||
@@ -29,15 +29,12 @@ use polkadot_node_subsystem_util::metrics::prometheus::{
|
|||||||
self, Gauge, Histogram, PrometheusError, Registry, U64,
|
self, Gauge, Histogram, PrometheusError, Registry, U64,
|
||||||
};
|
};
|
||||||
|
|
||||||
use sc_network::peer_store::LOG_TARGET;
|
|
||||||
use sc_service::{SpawnTaskHandle, TaskManager};
|
use sc_service::{SpawnTaskHandle, TaskManager};
|
||||||
use std::{
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
fmt::Display,
|
|
||||||
net::{Ipv4Addr, SocketAddr},
|
|
||||||
};
|
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
|
|
||||||
const MIB: f64 = 1024.0 * 1024.0;
|
const LOG_TARGET: &str = "subsystem-bench::environment";
|
||||||
|
use super::configuration::TestAuthorities;
|
||||||
|
|
||||||
/// Test environment/configuration metrics
|
/// Test environment/configuration metrics
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -56,9 +53,8 @@ pub struct TestEnvironmentMetrics {
|
|||||||
|
|
||||||
impl TestEnvironmentMetrics {
|
impl TestEnvironmentMetrics {
|
||||||
pub fn new(registry: &Registry) -> Result<Self, PrometheusError> {
|
pub fn new(registry: &Registry) -> Result<Self, PrometheusError> {
|
||||||
let mut buckets = prometheus::exponential_buckets(16384.0, 2.0, 9)
|
let buckets = prometheus::exponential_buckets(16384.0, 2.0, 9)
|
||||||
.expect("arguments are always valid; qed");
|
.expect("arguments are always valid; qed");
|
||||||
buckets.extend(vec![5.0 * MIB, 6.0 * MIB, 7.0 * MIB, 8.0 * MIB, 9.0 * MIB, 10.0 * MIB]);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
n_validators: prometheus::register(
|
n_validators: prometheus::register(
|
||||||
@@ -150,7 +146,7 @@ pub const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
|
|||||||
// We use this to bail out sending messages to the subsystem if it is overloaded such that
|
// We use this to bail out sending messages to the subsystem if it is overloaded such that
|
||||||
// the time of flight is breaches 5s.
|
// the time of flight is breaches 5s.
|
||||||
// This should eventually be a test parameter.
|
// This should eventually be a test parameter.
|
||||||
const MAX_TIME_OF_FLIGHT: Duration = Duration::from_millis(5000);
|
pub const MAX_TIME_OF_FLIGHT: Duration = Duration::from_millis(5000);
|
||||||
|
|
||||||
/// The test environment is the high level wrapper of all things required to test
|
/// The test environment is the high level wrapper of all things required to test
|
||||||
/// a certain subsystem.
|
/// a certain subsystem.
|
||||||
@@ -189,9 +185,11 @@ pub struct TestEnvironment {
|
|||||||
/// The test configuration.
|
/// The test configuration.
|
||||||
config: TestConfiguration,
|
config: TestConfiguration,
|
||||||
/// A handle to the network emulator.
|
/// A handle to the network emulator.
|
||||||
network: NetworkEmulator,
|
network: NetworkEmulatorHandle,
|
||||||
/// Configuration/env metrics
|
/// Configuration/env metrics
|
||||||
metrics: TestEnvironmentMetrics,
|
metrics: TestEnvironmentMetrics,
|
||||||
|
/// Test authorities generated from the configuration.
|
||||||
|
authorities: TestAuthorities,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestEnvironment {
|
impl TestEnvironment {
|
||||||
@@ -199,9 +197,10 @@ impl TestEnvironment {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
dependencies: TestEnvironmentDependencies,
|
dependencies: TestEnvironmentDependencies,
|
||||||
config: TestConfiguration,
|
config: TestConfiguration,
|
||||||
network: NetworkEmulator,
|
network: NetworkEmulatorHandle,
|
||||||
overseer: Overseer<SpawnGlue<SpawnTaskHandle>, AlwaysSupportsParachains>,
|
overseer: Overseer<SpawnGlue<SpawnTaskHandle>, AlwaysSupportsParachains>,
|
||||||
overseer_handle: OverseerHandle,
|
overseer_handle: OverseerHandle,
|
||||||
|
authorities: TestAuthorities,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let metrics = TestEnvironmentMetrics::new(&dependencies.registry)
|
let metrics = TestEnvironmentMetrics::new(&dependencies.registry)
|
||||||
.expect("Metrics need to be registered");
|
.expect("Metrics need to be registered");
|
||||||
@@ -230,30 +229,62 @@ impl TestEnvironment {
|
|||||||
config,
|
config,
|
||||||
network,
|
network,
|
||||||
metrics,
|
metrics,
|
||||||
|
authorities,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the test configuration.
|
||||||
pub fn config(&self) -> &TestConfiguration {
|
pub fn config(&self) -> &TestConfiguration {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn network(&self) -> &NetworkEmulator {
|
/// Returns a reference to the inner network emulator handle.
|
||||||
|
pub fn network(&self) -> &NetworkEmulatorHandle {
|
||||||
&self.network
|
&self.network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Prometheus registry.
|
||||||
pub fn registry(&self) -> &Registry {
|
pub fn registry(&self) -> &Registry {
|
||||||
&self.dependencies.registry
|
&self.dependencies.registry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawn a named task in the `test-environment` task group.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn spawn(&self, name: &'static str, task: impl Future<Output = ()> + Send + 'static) {
|
||||||
|
self.dependencies
|
||||||
|
.task_manager
|
||||||
|
.spawn_handle()
|
||||||
|
.spawn(name, "test-environment", task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a blocking named task in the `test-environment` task group.
|
||||||
|
pub fn spawn_blocking(
|
||||||
|
&self,
|
||||||
|
name: &'static str,
|
||||||
|
task: impl Future<Output = ()> + Send + 'static,
|
||||||
|
) {
|
||||||
|
self.dependencies.task_manager.spawn_handle().spawn_blocking(
|
||||||
|
name,
|
||||||
|
"test-environment",
|
||||||
|
task,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// Returns a reference to the test environment metrics instance
|
||||||
pub fn metrics(&self) -> &TestEnvironmentMetrics {
|
pub fn metrics(&self) -> &TestEnvironmentMetrics {
|
||||||
&self.metrics
|
&self.metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a handle to the tokio runtime.
|
||||||
pub fn runtime(&self) -> Handle {
|
pub fn runtime(&self) -> Handle {
|
||||||
self.runtime_handle.clone()
|
self.runtime_handle.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a message to the subsystem under test environment.
|
/// Returns a reference to the authority keys used in the test.
|
||||||
|
pub fn authorities(&self) -> &TestAuthorities {
|
||||||
|
&self.authorities
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a message to the subsystem under test environment.
|
||||||
pub async fn send_message(&mut self, msg: AllMessages) {
|
pub async fn send_message(&mut self, msg: AllMessages) {
|
||||||
self.overseer_handle
|
self.overseer_handle
|
||||||
.send_msg(msg, LOG_TARGET)
|
.send_msg(msg, LOG_TARGET)
|
||||||
@@ -264,7 +295,7 @@ impl TestEnvironment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an `ActiveLeavesUpdate` signal to all subsystems under test.
|
/// Send an `ActiveLeavesUpdate` signal to all subsystems under test.
|
||||||
pub async fn import_block(&mut self, block: BlockInfo) {
|
pub async fn import_block(&mut self, block: BlockInfo) {
|
||||||
self.overseer_handle
|
self.overseer_handle
|
||||||
.block_imported(block)
|
.block_imported(block)
|
||||||
@@ -275,59 +306,79 @@ impl TestEnvironment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop overseer and subsystems.
|
/// Stop overseer and subsystems.
|
||||||
pub async fn stop(&mut self) {
|
pub async fn stop(&mut self) {
|
||||||
self.overseer_handle.stop().await;
|
self.overseer_handle.stop().await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for TestEnvironment {
|
/// Blocks until `metric_name` == `value`
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
pub async fn wait_until_metric_eq(&self, metric_name: &str, value: usize) {
|
||||||
let stats = self.network().stats();
|
let value = value as f64;
|
||||||
|
loop {
|
||||||
|
let test_metrics = super::display::parse_metrics(self.registry());
|
||||||
|
let current_value = test_metrics.sum_by(metric_name);
|
||||||
|
|
||||||
writeln!(f, "\n")?;
|
gum::debug!(target: LOG_TARGET, metric_name, current_value, value, "Waiting for metric");
|
||||||
writeln!(
|
if current_value == value {
|
||||||
f,
|
break
|
||||||
"Total received from network: {}",
|
}
|
||||||
format!(
|
|
||||||
"{} MiB",
|
|
||||||
stats
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(_index, stats)| stats.tx_bytes_total as u128)
|
|
||||||
.sum::<u128>() / (1024 * 1024)
|
|
||||||
)
|
|
||||||
.cyan()
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"Total sent to network: {}",
|
|
||||||
format!("{} KiB", stats[0].tx_bytes_total / (1024)).cyan()
|
|
||||||
)?;
|
|
||||||
|
|
||||||
|
// Check value every 50ms.
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display network usage stats.
|
||||||
|
pub fn display_network_usage(&self) {
|
||||||
|
let stats = self.network().peer_stats(0);
|
||||||
|
|
||||||
|
let total_node_received = stats.received() / 1024;
|
||||||
|
let total_node_sent = stats.sent() / 1024;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"\nPayload bytes received from peers: {}, {}",
|
||||||
|
format!("{:.2} KiB total", total_node_received).blue(),
|
||||||
|
format!("{:.2} KiB/block", total_node_received / self.config().num_blocks)
|
||||||
|
.bright_blue()
|
||||||
|
);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Payload bytes sent to peers: {}, {}",
|
||||||
|
format!("{:.2} KiB total", total_node_sent).blue(),
|
||||||
|
format!("{:.2} KiB/block", total_node_sent / self.config().num_blocks).bright_blue()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print CPU usage stats in the CLI.
|
||||||
|
pub fn display_cpu_usage(&self, subsystems_under_test: &[&str]) {
|
||||||
let test_metrics = super::display::parse_metrics(self.registry());
|
let test_metrics = super::display::parse_metrics(self.registry());
|
||||||
let subsystem_cpu_metrics =
|
|
||||||
test_metrics.subset_with_label_value("task_group", "availability-recovery");
|
for subsystem in subsystems_under_test.iter() {
|
||||||
let total_cpu = subsystem_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum");
|
let subsystem_cpu_metrics =
|
||||||
writeln!(f, "Total subsystem CPU usage {}", format!("{:.2}s", total_cpu).bright_purple())?;
|
test_metrics.subset_with_label_value("task_group", subsystem);
|
||||||
writeln!(
|
let total_cpu = subsystem_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum");
|
||||||
f,
|
println!(
|
||||||
"CPU usage per block {}",
|
"{} CPU usage {}",
|
||||||
format!("{:.2}s", total_cpu / self.config().num_blocks as f64).bright_purple()
|
subsystem.to_string().bright_green(),
|
||||||
)?;
|
format!("{:.3}s", total_cpu).bright_purple()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"{} CPU usage per block {}",
|
||||||
|
subsystem.to_string().bright_green(),
|
||||||
|
format!("{:.3}s", total_cpu / self.config().num_blocks as f64).bright_purple()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let test_env_cpu_metrics =
|
let test_env_cpu_metrics =
|
||||||
test_metrics.subset_with_label_value("task_group", "test-environment");
|
test_metrics.subset_with_label_value("task_group", "test-environment");
|
||||||
let total_cpu = test_env_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum");
|
let total_cpu = test_env_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum");
|
||||||
writeln!(
|
println!(
|
||||||
f,
|
|
||||||
"Total test environment CPU usage {}",
|
"Total test environment CPU usage {}",
|
||||||
format!("{:.2}s", total_cpu).bright_purple()
|
format!("{:.3}s", total_cpu).bright_purple()
|
||||||
)?;
|
);
|
||||||
writeln!(
|
println!(
|
||||||
f,
|
"Test environment CPU usage per block {}",
|
||||||
"CPU usage per block {}",
|
format!("{:.3}s", total_cpu / self.config().num_blocks as f64).bright_purple()
|
||||||
format!("{:.2}s", total_cpu / self.config().num_blocks as f64).bright_purple()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,26 +14,34 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use sp_core::{
|
use polkadot_primitives::ValidatorId;
|
||||||
sr25519::{Pair, Public},
|
use sc_keystore::LocalKeystore;
|
||||||
Pair as PairT,
|
use sp_application_crypto::AppCrypto;
|
||||||
};
|
pub use sp_core::sr25519;
|
||||||
/// Set of test accounts.
|
use sp_core::sr25519::Public;
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
use sp_keystore::Keystore;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Set of test accounts generated and kept safe by a keystore.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Keyring {
|
pub struct Keyring {
|
||||||
name: String,
|
keystore: Arc<LocalKeystore>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Keyring {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { keystore: Arc::new(LocalKeystore::in_memory()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyring {
|
impl Keyring {
|
||||||
pub fn new(name: String) -> Keyring {
|
pub fn sr25519_new(&self, name: String) -> Public {
|
||||||
Self { name }
|
self.keystore
|
||||||
|
.sr25519_generate_new(ValidatorId::ID, Some(&format!("//{}", name)))
|
||||||
|
.expect("Insert key into keystore")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pair(self) -> Pair {
|
pub fn keystore(&self) -> Arc<dyn Keystore> {
|
||||||
Pair::from_string(&format!("//{}", self.name), None).expect("input is always good; qed")
|
self.keystore.clone()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn public(self) -> Public {
|
|
||||||
self.pair().public()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,18 @@
|
|||||||
//! A generic av store subsystem mockup suitable to be used in benchmarks.
|
//! A generic av store subsystem mockup suitable to be used in benchmarks.
|
||||||
|
|
||||||
use parity_scale_codec::Encode;
|
use parity_scale_codec::Encode;
|
||||||
|
use polkadot_node_network_protocol::request_response::{
|
||||||
|
v1::{AvailableDataFetchingResponse, ChunkFetchingResponse, ChunkResponse},
|
||||||
|
Requests,
|
||||||
|
};
|
||||||
use polkadot_primitives::CandidateHash;
|
use polkadot_primitives::CandidateHash;
|
||||||
|
use sc_network::ProtocolName;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use futures::{channel::oneshot, FutureExt};
|
use futures::{channel::oneshot, FutureExt};
|
||||||
|
|
||||||
use polkadot_node_primitives::ErasureChunk;
|
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
||||||
|
|
||||||
use polkadot_node_subsystem::{
|
use polkadot_node_subsystem::{
|
||||||
messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError,
|
messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError,
|
||||||
@@ -31,6 +36,8 @@ use polkadot_node_subsystem::{
|
|||||||
|
|
||||||
use polkadot_node_subsystem_types::OverseerSignal;
|
use polkadot_node_subsystem_types::OverseerSignal;
|
||||||
|
|
||||||
|
use crate::core::network::{HandleNetworkMessage, NetworkMessage};
|
||||||
|
|
||||||
pub struct AvailabilityStoreState {
|
pub struct AvailabilityStoreState {
|
||||||
candidate_hashes: HashMap<CandidateHash, usize>,
|
candidate_hashes: HashMap<CandidateHash, usize>,
|
||||||
chunks: Vec<Vec<ErasureChunk>>,
|
chunks: Vec<Vec<ErasureChunk>>,
|
||||||
@@ -38,6 +45,75 @@ pub struct AvailabilityStoreState {
|
|||||||
|
|
||||||
const LOG_TARGET: &str = "subsystem-bench::av-store-mock";
|
const LOG_TARGET: &str = "subsystem-bench::av-store-mock";
|
||||||
|
|
||||||
|
/// Mockup helper. Contains Ccunks and full availability data of all parachain blocks
|
||||||
|
/// used in a test.
|
||||||
|
pub struct NetworkAvailabilityState {
|
||||||
|
pub candidate_hashes: HashMap<CandidateHash, usize>,
|
||||||
|
pub available_data: Vec<AvailableData>,
|
||||||
|
pub chunks: Vec<Vec<ErasureChunk>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement access to the state.
|
||||||
|
impl HandleNetworkMessage for NetworkAvailabilityState {
|
||||||
|
fn handle(
|
||||||
|
&self,
|
||||||
|
message: NetworkMessage,
|
||||||
|
_node_sender: &mut futures::channel::mpsc::UnboundedSender<NetworkMessage>,
|
||||||
|
) -> Option<NetworkMessage> {
|
||||||
|
match message {
|
||||||
|
NetworkMessage::RequestFromNode(peer, request) => match request {
|
||||||
|
Requests::ChunkFetchingV1(outgoing_request) => {
|
||||||
|
gum::debug!(target: LOG_TARGET, request = ?outgoing_request, "Received `RequestFromNode`");
|
||||||
|
let validator_index: usize = outgoing_request.payload.index.0 as usize;
|
||||||
|
let candidate_hash = outgoing_request.payload.candidate_hash;
|
||||||
|
|
||||||
|
let candidate_index = self
|
||||||
|
.candidate_hashes
|
||||||
|
.get(&candidate_hash)
|
||||||
|
.expect("candidate was generated previously; qed");
|
||||||
|
gum::warn!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
||||||
|
|
||||||
|
let chunk: ChunkResponse =
|
||||||
|
self.chunks.get(*candidate_index).unwrap()[validator_index].clone().into();
|
||||||
|
let response = Ok((
|
||||||
|
ChunkFetchingResponse::from(Some(chunk)).encode(),
|
||||||
|
ProtocolName::Static("dummy"),
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Err(err) = outgoing_request.pending_response.send(response) {
|
||||||
|
gum::error!(target: LOG_TARGET, ?err, "Failed to send `ChunkFetchingResponse`");
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Requests::AvailableDataFetchingV1(outgoing_request) => {
|
||||||
|
let candidate_hash = outgoing_request.payload.candidate_hash;
|
||||||
|
let candidate_index = self
|
||||||
|
.candidate_hashes
|
||||||
|
.get(&candidate_hash)
|
||||||
|
.expect("candidate was generated previously; qed");
|
||||||
|
gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
||||||
|
|
||||||
|
let available_data = self.available_data.get(*candidate_index).unwrap().clone();
|
||||||
|
|
||||||
|
let response = Ok((
|
||||||
|
AvailableDataFetchingResponse::from(Some(available_data)).encode(),
|
||||||
|
ProtocolName::Static("dummy"),
|
||||||
|
));
|
||||||
|
outgoing_request
|
||||||
|
.pending_response
|
||||||
|
.send(response)
|
||||||
|
.expect("Response is always sent succesfully");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => Some(NetworkMessage::RequestFromNode(peer, request)),
|
||||||
|
},
|
||||||
|
|
||||||
|
message => Some(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A mock of the availability store subsystem. This one also generates all the
|
/// A mock of the availability store subsystem. This one also generates all the
|
||||||
/// candidates that a
|
/// candidates that a
|
||||||
pub struct MockAvailabilityStore {
|
pub struct MockAvailabilityStore {
|
||||||
@@ -127,6 +203,10 @@ impl MockAvailabilityStore {
|
|||||||
self.state.chunks.get(*candidate_index).unwrap()[0].encoded_size();
|
self.state.chunks.get(*candidate_index).unwrap()[0].encoded_size();
|
||||||
let _ = tx.send(Some(chunk_size));
|
let _ = tx.send(Some(chunk_size));
|
||||||
},
|
},
|
||||||
|
AvailabilityStoreMessage::StoreChunk { candidate_hash, chunk, tx } => {
|
||||||
|
gum::debug!(target: LOG_TARGET, chunk_index = ?chunk.index ,candidate_hash = ?candidate_hash, "Responding to StoreChunk");
|
||||||
|
let _ = tx.send(Ok(()));
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
unimplemented!("Unexpected av-store message")
|
unimplemented!("Unexpected av-store message")
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Polkadot.
|
||||||
|
|
||||||
|
// Polkadot 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.
|
||||||
|
|
||||||
|
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//!
|
||||||
|
//! A generic runtime api subsystem mockup suitable to be used in benchmarks.
|
||||||
|
|
||||||
|
use polkadot_primitives::Header;
|
||||||
|
|
||||||
|
use polkadot_node_subsystem::{
|
||||||
|
messages::ChainApiMessage, overseer, SpawnedSubsystem, SubsystemError,
|
||||||
|
};
|
||||||
|
use polkadot_node_subsystem_types::OverseerSignal;
|
||||||
|
use sp_core::H256;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use futures::FutureExt;
|
||||||
|
|
||||||
|
const LOG_TARGET: &str = "subsystem-bench::chain-api-mock";
|
||||||
|
|
||||||
|
/// State used to respond to `BlockHeader` requests.
|
||||||
|
pub struct ChainApiState {
|
||||||
|
pub block_headers: HashMap<H256, Header>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MockChainApi {
|
||||||
|
state: ChainApiState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockChainApi {
|
||||||
|
pub fn new(state: ChainApiState) -> MockChainApi {
|
||||||
|
Self { state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[overseer::subsystem(ChainApi, error=SubsystemError, prefix=self::overseer)]
|
||||||
|
impl<Context> MockChainApi {
|
||||||
|
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||||
|
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||||
|
|
||||||
|
SpawnedSubsystem { name: "test-environment", future }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[overseer::contextbounds(ChainApi, prefix = self::overseer)]
|
||||||
|
impl MockChainApi {
|
||||||
|
async fn run<Context>(self, mut ctx: Context) {
|
||||||
|
loop {
|
||||||
|
let msg = ctx.recv().await.expect("Overseer never fails us");
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
orchestra::FromOrchestra::Signal(signal) =>
|
||||||
|
if signal == OverseerSignal::Conclude {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
orchestra::FromOrchestra::Communication { msg } => {
|
||||||
|
gum::debug!(target: LOG_TARGET, msg=?msg, "recv message");
|
||||||
|
|
||||||
|
match msg {
|
||||||
|
ChainApiMessage::BlockHeader(hash, response_channel) => {
|
||||||
|
let _ = response_channel.send(Ok(Some(
|
||||||
|
self.state
|
||||||
|
.block_headers
|
||||||
|
.get(&hash)
|
||||||
|
.cloned()
|
||||||
|
.expect("Relay chain block hashes are known"),
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
ChainApiMessage::Ancestors { hash: _hash, k: _k, response_channel } => {
|
||||||
|
// For our purposes, no ancestors is fine.
|
||||||
|
let _ = response_channel.send(Ok(Vec::new()));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
unimplemented!("Unexpected chain-api message")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,6 +73,7 @@ macro_rules! mock {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate dummy implementation for all subsystems
|
||||||
mock!(AvailabilityStore);
|
mock!(AvailabilityStore);
|
||||||
mock!(StatementDistribution);
|
mock!(StatementDistribution);
|
||||||
mock!(BitfieldSigning);
|
mock!(BitfieldSigning);
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ use polkadot_node_subsystem::HeadSupportsParachains;
|
|||||||
use polkadot_node_subsystem_types::Hash;
|
use polkadot_node_subsystem_types::Hash;
|
||||||
|
|
||||||
pub mod av_store;
|
pub mod av_store;
|
||||||
|
pub mod chain_api;
|
||||||
pub mod dummy;
|
pub mod dummy;
|
||||||
pub mod network_bridge;
|
pub mod network_bridge;
|
||||||
pub mod runtime_api;
|
pub mod runtime_api;
|
||||||
|
|
||||||
pub use av_store::*;
|
pub use av_store::*;
|
||||||
|
pub use chain_api::*;
|
||||||
|
pub use network_bridge::*;
|
||||||
pub use runtime_api::*;
|
pub use runtime_api::*;
|
||||||
|
|
||||||
pub struct AlwaysSupportsParachains {}
|
pub struct AlwaysSupportsParachains {}
|
||||||
|
|||||||
@@ -14,244 +14,61 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//!
|
//!
|
||||||
//! A generic av store subsystem mockup suitable to be used in benchmarks.
|
//! Mocked `network-bridge` subsystems that uses a `NetworkInterface` to access
|
||||||
|
//! the emulated network.
|
||||||
|
use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt};
|
||||||
|
use polkadot_node_subsystem_types::{
|
||||||
|
messages::{BitfieldDistributionMessage, NetworkBridgeEvent},
|
||||||
|
OverseerSignal,
|
||||||
|
};
|
||||||
|
|
||||||
use futures::Future;
|
use sc_network::{request_responses::ProtocolConfig, PeerId, RequestFailure};
|
||||||
use parity_scale_codec::Encode;
|
|
||||||
use polkadot_node_subsystem_types::OverseerSignal;
|
|
||||||
use std::{collections::HashMap, pin::Pin};
|
|
||||||
|
|
||||||
use futures::FutureExt;
|
|
||||||
|
|
||||||
use polkadot_node_primitives::{AvailableData, ErasureChunk};
|
|
||||||
|
|
||||||
use polkadot_primitives::CandidateHash;
|
|
||||||
use sc_network::{OutboundFailure, RequestFailure};
|
|
||||||
|
|
||||||
use polkadot_node_subsystem::{
|
use polkadot_node_subsystem::{
|
||||||
messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError,
|
messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use polkadot_node_network_protocol::request_response::{
|
use polkadot_node_network_protocol::Versioned;
|
||||||
self as req_res,
|
|
||||||
v1::{AvailableDataFetchingRequest, ChunkFetchingRequest, ChunkResponse},
|
|
||||||
IsRequest, Requests,
|
|
||||||
};
|
|
||||||
use polkadot_primitives::AuthorityDiscoveryId;
|
|
||||||
|
|
||||||
use crate::core::{
|
use crate::core::network::{
|
||||||
configuration::{random_error, random_latency, TestConfiguration},
|
NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt,
|
||||||
network::{NetworkAction, NetworkEmulator, RateLimit},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The availability store state of all emulated peers.
|
const LOG_TARGET: &str = "subsystem-bench::network-bridge";
|
||||||
/// The network bridge tx mock will respond to requests as if the request is being serviced
|
const CHUNK_REQ_PROTOCOL_NAME_V1: &str =
|
||||||
/// by a remote peer on the network
|
"/ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/req_chunk/1";
|
||||||
pub struct NetworkAvailabilityState {
|
|
||||||
pub candidate_hashes: HashMap<CandidateHash, usize>,
|
|
||||||
pub available_data: Vec<AvailableData>,
|
|
||||||
pub chunks: Vec<Vec<ErasureChunk>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const LOG_TARGET: &str = "subsystem-bench::network-bridge-tx-mock";
|
|
||||||
|
|
||||||
/// A mock of the network bridge tx subsystem.
|
/// A mock of the network bridge tx subsystem.
|
||||||
pub struct MockNetworkBridgeTx {
|
pub struct MockNetworkBridgeTx {
|
||||||
/// The test configurationg
|
/// A network emulator handle
|
||||||
config: TestConfiguration,
|
network: NetworkEmulatorHandle,
|
||||||
/// The network availability state
|
/// A channel to the network interface,
|
||||||
availabilty: NetworkAvailabilityState,
|
to_network_interface: UnboundedSender<NetworkMessage>,
|
||||||
/// A network emulator instance
|
}
|
||||||
network: NetworkEmulator,
|
|
||||||
|
/// A mock of the network bridge tx subsystem.
|
||||||
|
pub struct MockNetworkBridgeRx {
|
||||||
|
/// A network interface receiver
|
||||||
|
network_receiver: NetworkInterfaceReceiver,
|
||||||
|
/// Chunk request sender
|
||||||
|
chunk_request_sender: Option<ProtocolConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockNetworkBridgeTx {
|
impl MockNetworkBridgeTx {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: TestConfiguration,
|
network: NetworkEmulatorHandle,
|
||||||
availabilty: NetworkAvailabilityState,
|
to_network_interface: UnboundedSender<NetworkMessage>,
|
||||||
network: NetworkEmulator,
|
|
||||||
) -> MockNetworkBridgeTx {
|
) -> MockNetworkBridgeTx {
|
||||||
Self { config, availabilty, network }
|
Self { network, to_network_interface }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn not_connected_response(
|
impl MockNetworkBridgeRx {
|
||||||
&self,
|
pub fn new(
|
||||||
authority_discovery_id: &AuthorityDiscoveryId,
|
network_receiver: NetworkInterfaceReceiver,
|
||||||
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
chunk_request_sender: Option<ProtocolConfig>,
|
||||||
) -> NetworkAction {
|
) -> MockNetworkBridgeRx {
|
||||||
// The network action will send the error after a random delay expires.
|
Self { network_receiver, chunk_request_sender }
|
||||||
return NetworkAction::new(
|
|
||||||
authority_discovery_id.clone(),
|
|
||||||
future,
|
|
||||||
0,
|
|
||||||
// Generate a random latency based on configuration.
|
|
||||||
random_latency(self.config.latency.as_ref()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/// Returns an `NetworkAction` corresponding to the peer sending the response. If
|
|
||||||
/// the peer is connected, the error is sent with a randomized latency as defined in
|
|
||||||
/// configuration.
|
|
||||||
fn respond_to_send_request(
|
|
||||||
&mut self,
|
|
||||||
request: Requests,
|
|
||||||
ingress_tx: &mut tokio::sync::mpsc::UnboundedSender<NetworkAction>,
|
|
||||||
) -> NetworkAction {
|
|
||||||
let ingress_tx = ingress_tx.clone();
|
|
||||||
|
|
||||||
match request {
|
|
||||||
Requests::ChunkFetchingV1(outgoing_request) => {
|
|
||||||
let authority_discovery_id = match outgoing_request.peer {
|
|
||||||
req_res::Recipient::Authority(authority_discovery_id) => authority_discovery_id,
|
|
||||||
_ => unimplemented!("Peer recipient not supported yet"),
|
|
||||||
};
|
|
||||||
// Account our sent request bytes.
|
|
||||||
self.network.peer_stats(0).inc_sent(outgoing_request.payload.encoded_size());
|
|
||||||
|
|
||||||
// If peer is disconnected return an error
|
|
||||||
if !self.network.is_peer_connected(&authority_discovery_id) {
|
|
||||||
// We always send `NotConnected` error and we ignore `IfDisconnected` value in
|
|
||||||
// the caller.
|
|
||||||
let future = async move {
|
|
||||||
let _ = outgoing_request
|
|
||||||
.pending_response
|
|
||||||
.send(Err(RequestFailure::NotConnected));
|
|
||||||
}
|
|
||||||
.boxed();
|
|
||||||
return self.not_connected_response(&authority_discovery_id, future)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account for remote received request bytes.
|
|
||||||
self.network
|
|
||||||
.peer_stats_by_id(&authority_discovery_id)
|
|
||||||
.inc_received(outgoing_request.payload.encoded_size());
|
|
||||||
|
|
||||||
let validator_index: usize = outgoing_request.payload.index.0 as usize;
|
|
||||||
let candidate_hash = outgoing_request.payload.candidate_hash;
|
|
||||||
|
|
||||||
let candidate_index = self
|
|
||||||
.availabilty
|
|
||||||
.candidate_hashes
|
|
||||||
.get(&candidate_hash)
|
|
||||||
.expect("candidate was generated previously; qed");
|
|
||||||
gum::warn!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
|
||||||
|
|
||||||
let chunk: ChunkResponse = self.availabilty.chunks.get(*candidate_index).unwrap()
|
|
||||||
[validator_index]
|
|
||||||
.clone()
|
|
||||||
.into();
|
|
||||||
let mut size = chunk.encoded_size();
|
|
||||||
|
|
||||||
let response = if random_error(self.config.error) {
|
|
||||||
// Error will not account to any bandwidth used.
|
|
||||||
size = 0;
|
|
||||||
Err(RequestFailure::Network(OutboundFailure::ConnectionClosed))
|
|
||||||
} else {
|
|
||||||
Ok((
|
|
||||||
req_res::v1::ChunkFetchingResponse::from(Some(chunk)).encode(),
|
|
||||||
self.network.req_protocol_names().get_name(ChunkFetchingRequest::PROTOCOL),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
let authority_discovery_id_clone = authority_discovery_id.clone();
|
|
||||||
|
|
||||||
let future = async move {
|
|
||||||
let _ = outgoing_request.pending_response.send(response);
|
|
||||||
}
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
let future_wrapper = async move {
|
|
||||||
// Forward the response to the ingress channel of our node.
|
|
||||||
// On receive side we apply our node receiving rate limit.
|
|
||||||
let action =
|
|
||||||
NetworkAction::new(authority_discovery_id_clone, future, size, None);
|
|
||||||
ingress_tx.send(action).unwrap();
|
|
||||||
}
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
NetworkAction::new(
|
|
||||||
authority_discovery_id,
|
|
||||||
future_wrapper,
|
|
||||||
size,
|
|
||||||
// Generate a random latency based on configuration.
|
|
||||||
random_latency(self.config.latency.as_ref()),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
Requests::AvailableDataFetchingV1(outgoing_request) => {
|
|
||||||
let candidate_hash = outgoing_request.payload.candidate_hash;
|
|
||||||
let candidate_index = self
|
|
||||||
.availabilty
|
|
||||||
.candidate_hashes
|
|
||||||
.get(&candidate_hash)
|
|
||||||
.expect("candidate was generated previously; qed");
|
|
||||||
gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index");
|
|
||||||
|
|
||||||
let authority_discovery_id = match outgoing_request.peer {
|
|
||||||
req_res::Recipient::Authority(authority_discovery_id) => authority_discovery_id,
|
|
||||||
_ => unimplemented!("Peer recipient not supported yet"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Account our sent request bytes.
|
|
||||||
self.network.peer_stats(0).inc_sent(outgoing_request.payload.encoded_size());
|
|
||||||
|
|
||||||
// If peer is disconnected return an error
|
|
||||||
if !self.network.is_peer_connected(&authority_discovery_id) {
|
|
||||||
let future = async move {
|
|
||||||
let _ = outgoing_request
|
|
||||||
.pending_response
|
|
||||||
.send(Err(RequestFailure::NotConnected));
|
|
||||||
}
|
|
||||||
.boxed();
|
|
||||||
return self.not_connected_response(&authority_discovery_id, future)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account for remote received request bytes.
|
|
||||||
self.network
|
|
||||||
.peer_stats_by_id(&authority_discovery_id)
|
|
||||||
.inc_received(outgoing_request.payload.encoded_size());
|
|
||||||
|
|
||||||
let available_data =
|
|
||||||
self.availabilty.available_data.get(*candidate_index).unwrap().clone();
|
|
||||||
|
|
||||||
let size = available_data.encoded_size();
|
|
||||||
|
|
||||||
let response = if random_error(self.config.error) {
|
|
||||||
Err(RequestFailure::Network(OutboundFailure::ConnectionClosed))
|
|
||||||
} else {
|
|
||||||
Ok((
|
|
||||||
req_res::v1::AvailableDataFetchingResponse::from(Some(available_data))
|
|
||||||
.encode(),
|
|
||||||
self.network
|
|
||||||
.req_protocol_names()
|
|
||||||
.get_name(AvailableDataFetchingRequest::PROTOCOL),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
let future = async move {
|
|
||||||
let _ = outgoing_request.pending_response.send(response);
|
|
||||||
}
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
let authority_discovery_id_clone = authority_discovery_id.clone();
|
|
||||||
|
|
||||||
let future_wrapper = async move {
|
|
||||||
// Forward the response to the ingress channel of our node.
|
|
||||||
// On receive side we apply our node receiving rate limit.
|
|
||||||
let action =
|
|
||||||
NetworkAction::new(authority_discovery_id_clone, future, size, None);
|
|
||||||
ingress_tx.send(action).unwrap();
|
|
||||||
}
|
|
||||||
.boxed();
|
|
||||||
|
|
||||||
NetworkAction::new(
|
|
||||||
authority_discovery_id,
|
|
||||||
future_wrapper,
|
|
||||||
size,
|
|
||||||
// Generate a random latency based on configuration.
|
|
||||||
random_latency(self.config.latency.as_ref()),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
_ => panic!("received an unexpected request"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,43 +77,26 @@ impl<Context> MockNetworkBridgeTx {
|
|||||||
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||||
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||||
|
|
||||||
SpawnedSubsystem { name: "test-environment", future }
|
SpawnedSubsystem { name: "network-bridge-tx", future }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[overseer::subsystem(NetworkBridgeRx, error=SubsystemError, prefix=self::overseer)]
|
||||||
|
impl<Context> MockNetworkBridgeRx {
|
||||||
|
fn start(self, ctx: Context) -> SpawnedSubsystem {
|
||||||
|
let future = self.run(ctx).map(|_| Ok(())).boxed();
|
||||||
|
|
||||||
|
SpawnedSubsystem { name: "network-bridge-rx", future }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[overseer::contextbounds(NetworkBridgeTx, prefix = self::overseer)]
|
#[overseer::contextbounds(NetworkBridgeTx, prefix = self::overseer)]
|
||||||
impl MockNetworkBridgeTx {
|
impl MockNetworkBridgeTx {
|
||||||
async fn run<Context>(mut self, mut ctx: Context) {
|
async fn run<Context>(self, mut ctx: Context) {
|
||||||
let (mut ingress_tx, mut ingress_rx) =
|
|
||||||
tokio::sync::mpsc::unbounded_channel::<NetworkAction>();
|
|
||||||
|
|
||||||
// Initialize our node bandwidth limits.
|
|
||||||
let mut rx_limiter = RateLimit::new(10, self.config.bandwidth);
|
|
||||||
|
|
||||||
let our_network = self.network.clone();
|
|
||||||
|
|
||||||
// This task will handle node messages receipt from the simulated network.
|
|
||||||
ctx.spawn_blocking(
|
|
||||||
"network-receive",
|
|
||||||
async move {
|
|
||||||
while let Some(action) = ingress_rx.recv().await {
|
|
||||||
let size = action.size();
|
|
||||||
|
|
||||||
// account for our node receiving the data.
|
|
||||||
our_network.inc_received(size);
|
|
||||||
rx_limiter.reap(size).await;
|
|
||||||
action.run().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.expect("We never fail to spawn tasks");
|
|
||||||
|
|
||||||
// Main subsystem loop.
|
// Main subsystem loop.
|
||||||
loop {
|
loop {
|
||||||
let msg = ctx.recv().await.expect("Overseer never fails us");
|
let subsystem_message = ctx.recv().await.expect("Overseer never fails us");
|
||||||
|
match subsystem_message {
|
||||||
match msg {
|
|
||||||
orchestra::FromOrchestra::Signal(signal) =>
|
orchestra::FromOrchestra::Signal(signal) =>
|
||||||
if signal == OverseerSignal::Conclude {
|
if signal == OverseerSignal::Conclude {
|
||||||
return
|
return
|
||||||
@@ -305,14 +105,27 @@ impl MockNetworkBridgeTx {
|
|||||||
NetworkBridgeTxMessage::SendRequests(requests, _if_disconnected) => {
|
NetworkBridgeTxMessage::SendRequests(requests, _if_disconnected) => {
|
||||||
for request in requests {
|
for request in requests {
|
||||||
gum::debug!(target: LOG_TARGET, request = ?request, "Processing request");
|
gum::debug!(target: LOG_TARGET, request = ?request, "Processing request");
|
||||||
self.network.inc_sent(request_size(&request));
|
let peer_id =
|
||||||
let action = self.respond_to_send_request(request, &mut ingress_tx);
|
request.authority_id().expect("all nodes are authorities").clone();
|
||||||
|
|
||||||
// Will account for our node sending the request over the emulated
|
if !self.network.is_peer_connected(&peer_id) {
|
||||||
// network.
|
// Attempting to send a request to a disconnected peer.
|
||||||
self.network.submit_peer_action(action.peer(), action);
|
request
|
||||||
|
.into_response_sender()
|
||||||
|
.send(Err(RequestFailure::NotConnected))
|
||||||
|
.expect("send never fails");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let peer_message =
|
||||||
|
NetworkMessage::RequestFromNode(peer_id.clone(), request);
|
||||||
|
|
||||||
|
let _ = self.to_network_interface.unbounded_send(peer_message);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
NetworkBridgeTxMessage::ReportPeer(_) => {
|
||||||
|
// ingore rep changes
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
unimplemented!("Unexpected network bridge message")
|
unimplemented!("Unexpected network bridge message")
|
||||||
},
|
},
|
||||||
@@ -322,12 +135,56 @@ impl MockNetworkBridgeTx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A helper to determine the request payload size.
|
#[overseer::contextbounds(NetworkBridgeRx, prefix = self::overseer)]
|
||||||
fn request_size(request: &Requests) -> usize {
|
impl MockNetworkBridgeRx {
|
||||||
match request {
|
async fn run<Context>(mut self, mut ctx: Context) {
|
||||||
Requests::ChunkFetchingV1(outgoing_request) => outgoing_request.payload.encoded_size(),
|
// Main subsystem loop.
|
||||||
Requests::AvailableDataFetchingV1(outgoing_request) =>
|
let mut from_network_interface = self.network_receiver.0;
|
||||||
outgoing_request.payload.encoded_size(),
|
loop {
|
||||||
_ => unimplemented!("received an unexpected request"),
|
futures::select! {
|
||||||
|
maybe_peer_message = from_network_interface.next() => {
|
||||||
|
if let Some(message) = maybe_peer_message {
|
||||||
|
match message {
|
||||||
|
NetworkMessage::MessageFromPeer(message) => match message {
|
||||||
|
Versioned::V2(
|
||||||
|
polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution(
|
||||||
|
bitfield,
|
||||||
|
),
|
||||||
|
) => {
|
||||||
|
ctx.send_message(
|
||||||
|
BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(PeerId::random(), polkadot_node_network_protocol::Versioned::V2(bitfield)))
|
||||||
|
).await;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
unimplemented!("We only talk v2 network protocol")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NetworkMessage::RequestFromPeer(request) => {
|
||||||
|
if let Some(protocol) = self.chunk_request_sender.as_mut() {
|
||||||
|
assert_eq!(&*protocol.name, CHUNK_REQ_PROTOCOL_NAME_V1);
|
||||||
|
if let Some(inbound_queue) = protocol.inbound_queue.as_ref() {
|
||||||
|
inbound_queue
|
||||||
|
.send(request)
|
||||||
|
.await
|
||||||
|
.expect("Forwarding requests to subsystem never fails");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
panic!("NetworkMessage::RequestFromNode is not expected to be received from a peer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subsystem_message = ctx.recv().fuse() => {
|
||||||
|
match subsystem_message.expect("Overseer never fails us") {
|
||||||
|
orchestra::FromOrchestra::Signal(signal) => if signal == OverseerSignal::Conclude { return },
|
||||||
|
_ => {
|
||||||
|
unimplemented!("Unexpected network bridge rx message")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,31 +16,45 @@
|
|||||||
//!
|
//!
|
||||||
//! A generic runtime api subsystem mockup suitable to be used in benchmarks.
|
//! A generic runtime api subsystem mockup suitable to be used in benchmarks.
|
||||||
|
|
||||||
use polkadot_primitives::{GroupIndex, IndexedVec, SessionInfo, ValidatorIndex};
|
use polkadot_primitives::{
|
||||||
|
CandidateReceipt, CoreState, GroupIndex, IndexedVec, OccupiedCore, SessionInfo, ValidatorIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bitvec::prelude::BitVec;
|
||||||
use polkadot_node_subsystem::{
|
use polkadot_node_subsystem::{
|
||||||
messages::{RuntimeApiMessage, RuntimeApiRequest},
|
messages::{RuntimeApiMessage, RuntimeApiRequest},
|
||||||
overseer, SpawnedSubsystem, SubsystemError,
|
overseer, SpawnedSubsystem, SubsystemError,
|
||||||
};
|
};
|
||||||
use polkadot_node_subsystem_types::OverseerSignal;
|
use polkadot_node_subsystem_types::OverseerSignal;
|
||||||
|
use sp_core::H256;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::core::configuration::{TestAuthorities, TestConfiguration};
|
use crate::core::configuration::{TestAuthorities, TestConfiguration};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
|
|
||||||
const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock";
|
const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock";
|
||||||
|
|
||||||
|
/// Minimal state to answer requests.
|
||||||
pub struct RuntimeApiState {
|
pub struct RuntimeApiState {
|
||||||
|
// All authorities in the test,
|
||||||
authorities: TestAuthorities,
|
authorities: TestAuthorities,
|
||||||
|
// Candidate
|
||||||
|
candidate_hashes: HashMap<H256, Vec<CandidateReceipt>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mocked `runtime-api` subsystem.
|
||||||
pub struct MockRuntimeApi {
|
pub struct MockRuntimeApi {
|
||||||
state: RuntimeApiState,
|
state: RuntimeApiState,
|
||||||
config: TestConfiguration,
|
config: TestConfiguration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockRuntimeApi {
|
impl MockRuntimeApi {
|
||||||
pub fn new(config: TestConfiguration, authorities: TestAuthorities) -> MockRuntimeApi {
|
pub fn new(
|
||||||
Self { state: RuntimeApiState { authorities }, config }
|
config: TestConfiguration,
|
||||||
|
authorities: TestAuthorities,
|
||||||
|
candidate_hashes: HashMap<H256, Vec<CandidateReceipt>>,
|
||||||
|
) -> MockRuntimeApi {
|
||||||
|
Self { state: RuntimeApiState { authorities, candidate_hashes }, config }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session_info(&self) -> SessionInfo {
|
fn session_info(&self) -> SessionInfo {
|
||||||
@@ -48,8 +62,10 @@ impl MockRuntimeApi {
|
|||||||
.map(|i| ValidatorIndex(i as _))
|
.map(|i| ValidatorIndex(i as _))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let validator_groups = all_validators.chunks(5).map(Vec::from).collect::<Vec<_>>();
|
let validator_groups = all_validators
|
||||||
|
.chunks(self.config.max_validators_per_core)
|
||||||
|
.map(Vec::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
SessionInfo {
|
SessionInfo {
|
||||||
validators: self.state.authorities.validator_public.clone().into(),
|
validators: self.state.authorities.validator_public.clone().into(),
|
||||||
discovery_keys: self.state.authorities.validator_authority_id.clone(),
|
discovery_keys: self.state.authorities.validator_authority_id.clone(),
|
||||||
@@ -80,6 +96,8 @@ impl<Context> MockRuntimeApi {
|
|||||||
#[overseer::contextbounds(RuntimeApi, prefix = self::overseer)]
|
#[overseer::contextbounds(RuntimeApi, prefix = self::overseer)]
|
||||||
impl MockRuntimeApi {
|
impl MockRuntimeApi {
|
||||||
async fn run<Context>(self, mut ctx: Context) {
|
async fn run<Context>(self, mut ctx: Context) {
|
||||||
|
let validator_group_count = self.session_info().validator_groups.len();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let msg = ctx.recv().await.expect("Overseer never fails us");
|
let msg = ctx.recv().await.expect("Overseer never fails us");
|
||||||
|
|
||||||
@@ -93,14 +111,79 @@ impl MockRuntimeApi {
|
|||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
RuntimeApiMessage::Request(
|
RuntimeApiMessage::Request(
|
||||||
_request,
|
_block_hash,
|
||||||
RuntimeApiRequest::SessionInfo(_session_index, sender),
|
RuntimeApiRequest::SessionInfo(_session_index, sender),
|
||||||
) => {
|
) => {
|
||||||
let _ = sender.send(Ok(Some(self.session_info())));
|
let _ = sender.send(Ok(Some(self.session_info())));
|
||||||
},
|
},
|
||||||
|
RuntimeApiMessage::Request(
|
||||||
|
_block_hash,
|
||||||
|
RuntimeApiRequest::SessionExecutorParams(_session_index, sender),
|
||||||
|
) => {
|
||||||
|
let _ = sender.send(Ok(Some(Default::default())));
|
||||||
|
},
|
||||||
|
RuntimeApiMessage::Request(
|
||||||
|
_block_hash,
|
||||||
|
RuntimeApiRequest::Validators(sender),
|
||||||
|
) => {
|
||||||
|
let _ =
|
||||||
|
sender.send(Ok(self.state.authorities.validator_public.clone()));
|
||||||
|
},
|
||||||
|
RuntimeApiMessage::Request(
|
||||||
|
_block_hash,
|
||||||
|
RuntimeApiRequest::CandidateEvents(sender),
|
||||||
|
) => {
|
||||||
|
let _ = sender.send(Ok(Default::default()));
|
||||||
|
},
|
||||||
|
RuntimeApiMessage::Request(
|
||||||
|
_block_hash,
|
||||||
|
RuntimeApiRequest::SessionIndexForChild(sender),
|
||||||
|
) => {
|
||||||
|
// Session is always the same.
|
||||||
|
let _ = sender.send(Ok(0));
|
||||||
|
},
|
||||||
|
RuntimeApiMessage::Request(
|
||||||
|
block_hash,
|
||||||
|
RuntimeApiRequest::AvailabilityCores(sender),
|
||||||
|
) => {
|
||||||
|
let candidate_hashes = self
|
||||||
|
.state
|
||||||
|
.candidate_hashes
|
||||||
|
.get(&block_hash)
|
||||||
|
.expect("Relay chain block hashes are generated at test start");
|
||||||
|
|
||||||
|
// All cores are always occupied.
|
||||||
|
let cores = candidate_hashes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, candidate_receipt)| {
|
||||||
|
// Ensure test breaks if badly configured.
|
||||||
|
assert!(index < validator_group_count);
|
||||||
|
|
||||||
|
CoreState::Occupied(OccupiedCore {
|
||||||
|
next_up_on_available: None,
|
||||||
|
occupied_since: 0,
|
||||||
|
time_out_at: 0,
|
||||||
|
next_up_on_time_out: None,
|
||||||
|
availability: BitVec::default(),
|
||||||
|
group_responsible: GroupIndex(index as u32),
|
||||||
|
candidate_hash: candidate_receipt.hash(),
|
||||||
|
candidate_descriptor: candidate_receipt.descriptor.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let _ = sender.send(Ok(cores));
|
||||||
|
},
|
||||||
|
RuntimeApiMessage::Request(
|
||||||
|
_block_hash,
|
||||||
|
RuntimeApiRequest::NodeFeatures(_session_index, sender),
|
||||||
|
) => {
|
||||||
|
let _ = sender.send(Ok(Default::default()));
|
||||||
|
},
|
||||||
// Long term TODO: implement more as needed.
|
// Long term TODO: implement more as needed.
|
||||||
_ => {
|
message => {
|
||||||
unimplemented!("Unexpected runtime-api message")
|
unimplemented!("Unexpected runtime-api message: {:?}", message)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,20 +16,23 @@
|
|||||||
|
|
||||||
//! A tool for running subsystem benchmark tests designed for development and
|
//! A tool for running subsystem benchmark tests designed for development and
|
||||||
//! CI regression testing.
|
//! CI regression testing.
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use pyroscope::PyroscopeAgent;
|
use pyroscope::PyroscopeAgent;
|
||||||
use pyroscope_pprofrs::{pprof_backend, PprofConfig};
|
use pyroscope_pprofrs::{pprof_backend, PprofConfig};
|
||||||
|
|
||||||
use colored::Colorize;
|
use std::path::Path;
|
||||||
use std::{path::Path, time::Duration};
|
|
||||||
|
|
||||||
pub(crate) mod availability;
|
pub(crate) mod availability;
|
||||||
pub(crate) mod cli;
|
pub(crate) mod cli;
|
||||||
pub(crate) mod core;
|
pub(crate) mod core;
|
||||||
mod valgrind;
|
mod valgrind;
|
||||||
|
|
||||||
|
const LOG_TARGET: &str = "subsystem-bench";
|
||||||
|
|
||||||
use availability::{prepare_test, NetworkEmulation, TestState};
|
use availability::{prepare_test, NetworkEmulation, TestState};
|
||||||
use cli::TestObjective;
|
use cli::TestObjective;
|
||||||
|
|
||||||
@@ -61,24 +64,24 @@ struct BenchCli {
|
|||||||
pub standard_configuration: cli::StandardTestOptions,
|
pub standard_configuration: cli::StandardTestOptions,
|
||||||
|
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
/// The bandwidth of simulated remote peers in KiB
|
/// The bandwidth of emulated remote peers in KiB
|
||||||
pub peer_bandwidth: Option<usize>,
|
pub peer_bandwidth: Option<usize>,
|
||||||
|
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
/// The bandwidth of our simulated node in KiB
|
/// The bandwidth of our node in KiB
|
||||||
pub bandwidth: Option<usize>,
|
pub bandwidth: Option<usize>,
|
||||||
|
|
||||||
#[clap(long, value_parser=le_100)]
|
#[clap(long, value_parser=le_100)]
|
||||||
/// Simulated conection error ratio [0-100].
|
/// Emulated peer connection ratio [0-100].
|
||||||
pub peer_error: Option<usize>,
|
pub connectivity: Option<usize>,
|
||||||
|
|
||||||
#[clap(long, value_parser=le_5000)]
|
#[clap(long, value_parser=le_5000)]
|
||||||
/// Minimum remote peer latency in milliseconds [0-5000].
|
/// Mean remote peer latency in milliseconds [0-5000].
|
||||||
pub peer_min_latency: Option<u64>,
|
pub peer_mean_latency: Option<usize>,
|
||||||
|
|
||||||
#[clap(long, value_parser=le_5000)]
|
#[clap(long, value_parser=le_5000)]
|
||||||
/// Maximum remote peer latency in milliseconds [0-5000].
|
/// Remote peer latency standard deviation
|
||||||
pub peer_max_latency: Option<u64>,
|
pub peer_latency_std_dev: Option<f64>,
|
||||||
|
|
||||||
#[clap(long, default_value_t = false)]
|
#[clap(long, default_value_t = false)]
|
||||||
/// Enable CPU Profiling with Pyroscope
|
/// Enable CPU Profiling with Pyroscope
|
||||||
@@ -101,6 +104,37 @@ struct BenchCli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BenchCli {
|
impl BenchCli {
|
||||||
|
fn create_test_configuration(&self) -> TestConfiguration {
|
||||||
|
let configuration = &self.standard_configuration;
|
||||||
|
|
||||||
|
match self.network {
|
||||||
|
NetworkEmulation::Healthy => TestConfiguration::healthy_network(
|
||||||
|
self.objective.clone(),
|
||||||
|
configuration.num_blocks,
|
||||||
|
configuration.n_validators,
|
||||||
|
configuration.n_cores,
|
||||||
|
configuration.min_pov_size,
|
||||||
|
configuration.max_pov_size,
|
||||||
|
),
|
||||||
|
NetworkEmulation::Degraded => TestConfiguration::degraded_network(
|
||||||
|
self.objective.clone(),
|
||||||
|
configuration.num_blocks,
|
||||||
|
configuration.n_validators,
|
||||||
|
configuration.n_cores,
|
||||||
|
configuration.min_pov_size,
|
||||||
|
configuration.max_pov_size,
|
||||||
|
),
|
||||||
|
NetworkEmulation::Ideal => TestConfiguration::ideal_network(
|
||||||
|
self.objective.clone(),
|
||||||
|
configuration.num_blocks,
|
||||||
|
configuration.n_validators,
|
||||||
|
configuration.n_cores,
|
||||||
|
configuration.min_pov_size,
|
||||||
|
configuration.max_pov_size,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn launch(self) -> eyre::Result<()> {
|
fn launch(self) -> eyre::Result<()> {
|
||||||
let is_valgrind_running = valgrind::is_valgrind_running();
|
let is_valgrind_running = valgrind::is_valgrind_running();
|
||||||
if !is_valgrind_running && self.cache_misses {
|
if !is_valgrind_running && self.cache_misses {
|
||||||
@@ -117,7 +151,6 @@ impl BenchCli {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let configuration = self.standard_configuration;
|
|
||||||
let mut test_config = match self.objective {
|
let mut test_config = match self.objective {
|
||||||
TestObjective::TestSequence(options) => {
|
TestObjective::TestSequence(options) => {
|
||||||
let test_sequence =
|
let test_sequence =
|
||||||
@@ -130,56 +163,48 @@ impl BenchCli {
|
|||||||
format!("Sequence contains {} step(s)", num_steps).bright_purple()
|
format!("Sequence contains {} step(s)", num_steps).bright_purple()
|
||||||
);
|
);
|
||||||
for (index, test_config) in test_sequence.into_iter().enumerate() {
|
for (index, test_config) in test_sequence.into_iter().enumerate() {
|
||||||
gum::info!("{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),);
|
gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),);
|
||||||
display_configuration(&test_config);
|
display_configuration(&test_config);
|
||||||
|
|
||||||
let mut state = TestState::new(&test_config);
|
match test_config.objective {
|
||||||
let (mut env, _protocol_config) = prepare_test(test_config, &mut state);
|
TestObjective::DataAvailabilityRead(ref _opts) => {
|
||||||
env.runtime()
|
let mut state = TestState::new(&test_config);
|
||||||
.block_on(availability::benchmark_availability_read(&mut env, state));
|
let (mut env, _protocol_config) = prepare_test(test_config, &mut state);
|
||||||
|
env.runtime().block_on(availability::benchmark_availability_read(
|
||||||
|
&mut env, state,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
TestObjective::DataAvailabilityWrite => {
|
||||||
|
let mut state = TestState::new(&test_config);
|
||||||
|
let (mut env, _protocol_config) = prepare_test(test_config, &mut state);
|
||||||
|
env.runtime().block_on(availability::benchmark_availability_write(
|
||||||
|
&mut env, state,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
_ => gum::error!("Invalid test objective in sequence"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Ok(())
|
return Ok(())
|
||||||
},
|
},
|
||||||
TestObjective::DataAvailabilityRead(ref _options) => match self.network {
|
TestObjective::DataAvailabilityRead(ref _options) => self.create_test_configuration(),
|
||||||
NetworkEmulation::Healthy => TestConfiguration::healthy_network(
|
TestObjective::DataAvailabilityWrite => self.create_test_configuration(),
|
||||||
self.objective,
|
|
||||||
configuration.num_blocks,
|
|
||||||
configuration.n_validators,
|
|
||||||
configuration.n_cores,
|
|
||||||
configuration.min_pov_size,
|
|
||||||
configuration.max_pov_size,
|
|
||||||
),
|
|
||||||
NetworkEmulation::Degraded => TestConfiguration::degraded_network(
|
|
||||||
self.objective,
|
|
||||||
configuration.num_blocks,
|
|
||||||
configuration.n_validators,
|
|
||||||
configuration.n_cores,
|
|
||||||
configuration.min_pov_size,
|
|
||||||
configuration.max_pov_size,
|
|
||||||
),
|
|
||||||
NetworkEmulation::Ideal => TestConfiguration::ideal_network(
|
|
||||||
self.objective,
|
|
||||||
configuration.num_blocks,
|
|
||||||
configuration.n_validators,
|
|
||||||
configuration.n_cores,
|
|
||||||
configuration.min_pov_size,
|
|
||||||
configuration.max_pov_size,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut latency_config = test_config.latency.clone().unwrap_or_default();
|
let mut latency_config = test_config.latency.clone().unwrap_or_default();
|
||||||
|
|
||||||
if let Some(latency) = self.peer_min_latency {
|
if let Some(latency) = self.peer_mean_latency {
|
||||||
latency_config.min_latency = Duration::from_millis(latency);
|
latency_config.mean_latency_ms = latency;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(latency) = self.peer_max_latency {
|
if let Some(std_dev) = self.peer_latency_std_dev {
|
||||||
latency_config.max_latency = Duration::from_millis(latency);
|
latency_config.std_dev = std_dev;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(error) = self.peer_error {
|
// Write back the updated latency.
|
||||||
test_config.error = error;
|
test_config.latency = Some(latency_config);
|
||||||
|
|
||||||
|
if let Some(connectivity) = self.connectivity {
|
||||||
|
test_config.connectivity = connectivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(bandwidth) = self.peer_bandwidth {
|
if let Some(bandwidth) = self.peer_bandwidth {
|
||||||
@@ -197,8 +222,17 @@ impl BenchCli {
|
|||||||
let mut state = TestState::new(&test_config);
|
let mut state = TestState::new(&test_config);
|
||||||
let (mut env, _protocol_config) = prepare_test(test_config, &mut state);
|
let (mut env, _protocol_config) = prepare_test(test_config, &mut state);
|
||||||
|
|
||||||
env.runtime()
|
match self.objective {
|
||||||
.block_on(availability::benchmark_availability_read(&mut env, state));
|
TestObjective::DataAvailabilityRead(_options) => {
|
||||||
|
env.runtime()
|
||||||
|
.block_on(availability::benchmark_availability_read(&mut env, state));
|
||||||
|
},
|
||||||
|
TestObjective::DataAvailabilityWrite => {
|
||||||
|
env.runtime()
|
||||||
|
.block_on(availability::benchmark_availability_write(&mut env, state));
|
||||||
|
},
|
||||||
|
TestObjective::TestSequence(_options) => {},
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(agent_running) = agent_running {
|
if let Some(agent_running) = agent_running {
|
||||||
let agent_ready = agent_running.stop()?;
|
let agent_ready = agent_running.stop()?;
|
||||||
@@ -216,6 +250,7 @@ fn main() -> eyre::Result<()> {
|
|||||||
// Avoid `Terminating due to subsystem exit subsystem` warnings
|
// Avoid `Terminating due to subsystem exit subsystem` warnings
|
||||||
.filter(Some("polkadot_overseer"), log::LevelFilter::Error)
|
.filter(Some("polkadot_overseer"), log::LevelFilter::Error)
|
||||||
.filter(None, log::LevelFilter::Info)
|
.filter(None, log::LevelFilter::Info)
|
||||||
|
.format_timestamp_millis()
|
||||||
// .filter(None, log::LevelFilter::Trace)
|
// .filter(None, log::LevelFilter::Trace)
|
||||||
.try_init()
|
.try_init()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
|
||||||
|
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
|
||||||
|
|
||||||
|
title: Add `availability-distribution` and `biftield-distribution` subsystem benchmark
|
||||||
|
|
||||||
|
doc:
|
||||||
|
- audience: Node Dev
|
||||||
|
description: |
|
||||||
|
The new subsystem benchmark test objective (`DataAvailabilityWrite`) is designed to stress
|
||||||
|
test the part of the pipeline that takes as input a backed candidate and then distributes
|
||||||
|
it as erasure coded chunks to other validators. The test pulls in the `av-store`,
|
||||||
|
`bitfield-distribution` and `availability-distribution` subsystems while the whole network and rest
|
||||||
|
of the node subsystems are emulated.
|
||||||
|
|
||||||
|
crates: [ ]
|
||||||
Reference in New Issue
Block a user