mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-08-05 07:55:42 +00:00
002d9260f9
**Update:** Pushed additional changes based on the review comments. **This pull request fixes various spelling mistakes in this repository.** Most of the changes are contained in the first **3** commits: - `Fix spelling mistakes in comments and docs` - `Fix spelling mistakes in test names` - `Fix spelling mistakes in error messages, panic messages, logs and tracing` Other source code spelling mistakes are separated into individual commits for easier reviewing: - `Fix the spelling of 'authority'` - `Fix the spelling of 'REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY'` - `Fix the spelling of 'prev_enqueud_messages'` - `Fix the spelling of 'endpoint'` - `Fix the spelling of 'children'` - `Fix the spelling of 'PenpalSiblingSovereignAccount'` - `Fix the spelling of 'PenpalSudoAccount'` - `Fix the spelling of 'insufficient'` - `Fix the spelling of 'PalletXcmExtrinsicsBenchmark'` - `Fix the spelling of 'subtracted'` - `Fix the spelling of 'CandidatePendingAvailability'` - `Fix the spelling of 'exclusive'` - `Fix the spelling of 'until'` - `Fix the spelling of 'discriminator'` - `Fix the spelling of 'nonexistent'` - `Fix the spelling of 'subsystem'` - `Fix the spelling of 'indices'` - `Fix the spelling of 'committed'` - `Fix the spelling of 'topology'` - `Fix the spelling of 'response'` - `Fix the spelling of 'beneficiary'` - `Fix the spelling of 'formatted'` - `Fix the spelling of 'UNKNOWN_PROOF_REQUEST'` - `Fix the spelling of 'succeeded'` - `Fix the spelling of 'reopened'` - `Fix the spelling of 'proposer'` - `Fix the spelling of 'InstantiationNonce'` - `Fix the spelling of 'depositor'` - `Fix the spelling of 'expiration'` - `Fix the spelling of 'phantom'` - `Fix the spelling of 'AggregatedKeyValue'` - `Fix the spelling of 'randomness'` - `Fix the spelling of 'defendant'` - `Fix the spelling of 'AquaticMammal'` - `Fix the spelling of 'transactions'` - `Fix the spelling of 'PassingTracingSubscriber'` - `Fix the spelling of 'TxSignaturePayload'` - `Fix the spelling of 'versioning'` - `Fix the spelling of 'descendant'` - `Fix the spelling of 'overridden'` - `Fix the spelling of 'network'` Let me know if this structure is adequate. **Note:** The usage of the words `Merkle`, `Merkelize`, `Merklization`, `Merkelization`, `Merkleization`, is somewhat inconsistent but I left it as it is. ~~**Note:** In some places the term `Receival` is used to refer to message reception, IMO `Reception` is the correct word here, but I left it as it is.~~ ~~**Note:** In some places the term `Overlayed` is used instead of the more acceptable version `Overlaid` but I also left it as it is.~~ ~~**Note:** In some places the term `Applyable` is used instead of the correct version `Applicable` but I also left it as it is.~~ **Note:** Some usage of British vs American english e.g. `judgement` vs `judgment`, `initialise` vs `initialize`, `optimise` vs `optimize` etc. are both present in different places, but I suppose that's understandable given the number of contributors. ~~**Note:** There is a spelling mistake in `.github/CODEOWNERS` but it triggers errors in CI when I make changes to it, so I left it as it is.~~
1073 lines
35 KiB
Rust
1073 lines
35 KiB
Rust
// 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 crate::{
|
|
approval::{
|
|
helpers::{
|
|
generate_babe_epoch, generate_new_session_topology, generate_peer_view_change_for,
|
|
make_header, PastSystemClock,
|
|
},
|
|
message_generator::PeerMessagesGenerator,
|
|
mock_chain_selection::MockChainSelection,
|
|
test_message::{MessagesBundle, TestMessageInfo},
|
|
},
|
|
configuration::{TestAuthorities, TestConfiguration},
|
|
dummy_builder,
|
|
environment::{TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT},
|
|
mock::{
|
|
chain_api::{ChainApiState, MockChainApi},
|
|
network_bridge::{MockNetworkBridgeRx, MockNetworkBridgeTx},
|
|
runtime_api::MockRuntimeApi,
|
|
AlwaysSupportsParachains, TestSyncOracle,
|
|
},
|
|
network::{
|
|
new_network, HandleNetworkMessage, NetworkEmulatorHandle, NetworkInterface,
|
|
NetworkInterfaceReceiver,
|
|
},
|
|
usage::BenchmarkUsage,
|
|
NODE_UNDER_TEST,
|
|
};
|
|
use colored::Colorize;
|
|
use futures::channel::oneshot;
|
|
use itertools::Itertools;
|
|
use orchestra::TimeoutExt;
|
|
use overseer::{metrics::Metrics as OverseerMetrics, MetricsTrait};
|
|
use parity_scale_codec::{Decode, Encode};
|
|
use polkadot_approval_distribution::ApprovalDistribution;
|
|
use polkadot_node_core_approval_voting::{
|
|
time::{slot_number_to_tick, tick_to_slot_number, Clock, ClockExt, SystemClock},
|
|
ApprovalVotingSubsystem, Config as ApprovalVotingConfig, Metrics as ApprovalVotingMetrics,
|
|
};
|
|
use polkadot_node_network_protocol::v3 as protocol_v3;
|
|
use polkadot_node_primitives::approval::{self, v1::RelayVRFStory};
|
|
use polkadot_node_subsystem::{overseer, AllMessages, Overseer, OverseerConnector, SpawnGlue};
|
|
use polkadot_node_subsystem_test_helpers::mock::new_block_import_info;
|
|
use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, ApprovalVotingMessage};
|
|
use polkadot_node_subsystem_util::metrics::Metrics;
|
|
use polkadot_overseer::Handle as OverseerHandleReal;
|
|
use polkadot_primitives::{
|
|
BlockNumber, CandidateEvent, CandidateIndex, CandidateReceipt, Hash, Header, Slot,
|
|
ValidatorIndex,
|
|
};
|
|
use prometheus::Registry;
|
|
use sc_keystore::LocalKeystore;
|
|
use sc_service::SpawnTaskHandle;
|
|
use serde::{Deserialize, Serialize};
|
|
use sp_consensus_babe::Epoch as BabeEpoch;
|
|
use sp_core::H256;
|
|
use std::{
|
|
cmp::max,
|
|
collections::{HashMap, HashSet},
|
|
fs,
|
|
io::Read,
|
|
ops::Sub,
|
|
sync::{
|
|
atomic::{AtomicBool, AtomicU32, AtomicU64},
|
|
Arc,
|
|
},
|
|
time::{Duration, Instant},
|
|
};
|
|
use tokio::time::sleep;
|
|
|
|
mod helpers;
|
|
mod message_generator;
|
|
mod mock_chain_selection;
|
|
mod test_message;
|
|
|
|
pub(crate) const LOG_TARGET: &str = "subsystem-bench::approval";
|
|
pub(crate) const NUM_COLUMNS: u32 = 1;
|
|
pub(crate) const SLOT_DURATION_MILLIS: u64 = 6000;
|
|
pub(crate) const TEST_CONFIG: ApprovalVotingConfig = ApprovalVotingConfig {
|
|
col_approval_data: DATA_COL,
|
|
slot_duration_millis: SLOT_DURATION_MILLIS,
|
|
};
|
|
|
|
const DATA_COL: u32 = 0;
|
|
|
|
/// Start generating messages for a slot into the future, so that the
|
|
/// generation nevers falls behind the current slot.
|
|
const BUFFER_FOR_GENERATION_MILLIS: u64 = 30_000;
|
|
|
|
/// Parameters specific to the approvals benchmark
|
|
#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)]
|
|
#[clap(rename_all = "kebab-case")]
|
|
#[allow(missing_docs)]
|
|
pub struct ApprovalsOptions {
|
|
#[clap(short, long, default_value_t = 89)]
|
|
/// The last considered tranche for which we send the message.
|
|
pub last_considered_tranche: u32,
|
|
#[clap(short, long, default_value_t = 1.0)]
|
|
/// Min candidates to be signed in a single approval.
|
|
pub coalesce_mean: f32,
|
|
#[clap(short, long, default_value_t = 1.0)]
|
|
/// Max candidate to be signed in a single approval.
|
|
pub coalesce_std_dev: f32,
|
|
/// The maximum tranche diff between approvals coalesced together.
|
|
pub coalesce_tranche_diff: u32,
|
|
#[clap(short, long, default_value_t = false)]
|
|
/// Enable assignments v2.
|
|
pub enable_assignments_v2: bool,
|
|
#[clap(short, long, default_value_t = true)]
|
|
/// Sends messages only till block is approved.
|
|
pub stop_when_approved: bool,
|
|
#[clap(short, long)]
|
|
/// Work directory.
|
|
#[clap(short, long, default_value_t = format!("/tmp"))]
|
|
pub workdir_prefix: String,
|
|
/// The number of no shows per candidate
|
|
#[clap(short, long, default_value_t = 0)]
|
|
pub num_no_shows_per_candidate: u32,
|
|
}
|
|
|
|
impl ApprovalsOptions {
|
|
// Generates a fingerprint use to determine if messages need to be re-generated.
|
|
fn fingerprint(&self) -> Vec<u8> {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend(self.coalesce_mean.to_be_bytes());
|
|
bytes.extend(self.coalesce_std_dev.to_be_bytes());
|
|
bytes.extend(self.coalesce_tranche_diff.to_be_bytes());
|
|
bytes.extend((self.enable_assignments_v2 as i32).to_be_bytes());
|
|
bytes
|
|
}
|
|
}
|
|
|
|
/// Information about a block. It is part of test state and it is used by the mock
|
|
/// subsystems to be able to answer the calls approval-voting and approval-distribution
|
|
/// do into the outside world.
|
|
#[derive(Clone, Debug)]
|
|
struct BlockTestData {
|
|
/// The slot this block occupies, see implementer's guide to understand what a slot
|
|
/// is in the context of polkadot.
|
|
slot: Slot,
|
|
/// The hash of the block.
|
|
hash: Hash,
|
|
/// The block number.
|
|
block_number: BlockNumber,
|
|
/// The list of candidates included in this block.
|
|
candidates: Vec<CandidateEvent>,
|
|
/// The block header.
|
|
header: Header,
|
|
/// The vrf story for the given block.
|
|
relay_vrf_story: RelayVRFStory,
|
|
/// If the block has been approved by the approval-voting subsystem.
|
|
/// This set on `true` when ChainSelectionMessage::Approved is received inside the chain
|
|
/// selection mock subsystem.
|
|
approved: Arc<AtomicBool>,
|
|
/// The total number of candidates before this block.
|
|
total_candidates_before: u64,
|
|
/// The votes we sent.
|
|
/// votes[validator_index][candidate_index] tells if validator sent vote for candidate.
|
|
/// We use this to mark the test as successful if GetApprovalSignatures returns all the votes
|
|
/// from here.
|
|
votes: Arc<Vec<Vec<AtomicBool>>>,
|
|
}
|
|
|
|
/// Candidate information used during the test to decide if more messages are needed.
|
|
#[derive(Debug)]
|
|
struct CandidateTestData {
|
|
/// The configured maximum number of no-shows for this candidate.
|
|
max_no_shows: u32,
|
|
/// The last tranche where we had a no-show.
|
|
last_tranche_with_no_show: u32,
|
|
/// The number of sent assignments.
|
|
sent_assignment: u32,
|
|
/// The number of no-shows.
|
|
num_no_shows: u32,
|
|
/// The maximum tranche were we covered the needed approvals
|
|
max_tranche: u32,
|
|
/// Minimum needed votes to approve candidate.
|
|
needed_approvals: u32,
|
|
}
|
|
|
|
impl CandidateTestData {
|
|
/// If message in this tranche needs to be sent.
|
|
fn should_send_tranche(&self, tranche: u32) -> bool {
|
|
self.sent_assignment <= self.needed_approvals ||
|
|
tranche <= self.max_tranche + self.num_no_shows
|
|
}
|
|
|
|
/// Sets max tranche
|
|
fn set_max_tranche(&mut self, tranche: u32) {
|
|
self.max_tranche = max(tranche, self.max_tranche);
|
|
}
|
|
|
|
/// Records no-show for candidate.
|
|
fn record_no_show(&mut self, tranche: u32) {
|
|
self.num_no_shows += 1;
|
|
self.last_tranche_with_no_show = max(tranche, self.last_tranche_with_no_show);
|
|
}
|
|
|
|
/// Marks an assignment sent.
|
|
fn mark_sent_assignment(&mut self, tranche: u32) {
|
|
if self.sent_assignment < self.needed_approvals {
|
|
self.set_max_tranche(tranche);
|
|
}
|
|
|
|
self.sent_assignment += 1;
|
|
}
|
|
|
|
/// Tells if a message in this tranche should be a no-show.
|
|
fn should_no_show(&self, tranche: u32) -> bool {
|
|
(self.num_no_shows < self.max_no_shows && self.last_tranche_with_no_show < tranche) ||
|
|
(tranche == 0 && self.num_no_shows == 0 && self.max_no_shows > 0)
|
|
}
|
|
}
|
|
|
|
/// Test state that is pre-generated and loaded from a file that matches the fingerprint
|
|
/// of the TestConfiguration.
|
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
|
struct GeneratedState {
|
|
/// All assignments and approvals
|
|
all_messages: Option<Vec<test_message::MessagesBundle>>,
|
|
/// The first slot in the test.
|
|
initial_slot: Slot,
|
|
}
|
|
|
|
/// Approval test state used by all mock subsystems to be able to answer messages emitted
|
|
/// by the approval-voting and approval-distribution-subsystems.
|
|
///
|
|
/// This gets cloned across all mock subsystems, so if there is any information that gets
|
|
/// updated between subsystems, they would have to be wrapped in Arc's.
|
|
#[derive(Clone)]
|
|
pub struct ApprovalTestState {
|
|
/// The main test configuration
|
|
configuration: TestConfiguration,
|
|
/// The specific test configurations passed when starting the benchmark.
|
|
options: ApprovalsOptions,
|
|
/// The list of blocks used for testing.
|
|
blocks: Vec<BlockTestData>,
|
|
/// The babe epoch used during testing.
|
|
babe_epoch: BabeEpoch,
|
|
/// The pre-generated state.
|
|
generated_state: GeneratedState,
|
|
/// The test authorities
|
|
test_authorities: TestAuthorities,
|
|
/// Last approved block number.
|
|
last_approved_block: Arc<AtomicU32>,
|
|
/// Total sent messages from peers to node
|
|
total_sent_messages_to_node: Arc<AtomicU64>,
|
|
/// Total sent messages from test node to other peers
|
|
total_sent_messages_from_node: Arc<AtomicU64>,
|
|
/// Total unique sent messages.
|
|
total_unique_messages: Arc<AtomicU64>,
|
|
/// Approval voting metrics.
|
|
approval_voting_metrics: ApprovalVotingMetrics,
|
|
/// The delta ticks from the tick the messages were generated to the the time we start this
|
|
/// message.
|
|
delta_tick_from_generated: Arc<AtomicU64>,
|
|
}
|
|
|
|
impl ApprovalTestState {
|
|
/// Build a new `ApprovalTestState` object out of the configurations passed when the benchmark
|
|
/// was tested.
|
|
fn new(
|
|
configuration: &TestConfiguration,
|
|
options: ApprovalsOptions,
|
|
dependencies: &TestEnvironmentDependencies,
|
|
) -> Self {
|
|
let test_authorities = configuration.generate_authorities();
|
|
let start = Instant::now();
|
|
|
|
let messages_path = PeerMessagesGenerator::generate_messages_if_needed(
|
|
configuration,
|
|
&test_authorities,
|
|
&options,
|
|
&dependencies.task_manager.spawn_handle(),
|
|
);
|
|
|
|
let mut messages_file =
|
|
fs::OpenOptions::new().read(true).open(messages_path.as_path()).unwrap();
|
|
let mut messages_bytes = Vec::<u8>::with_capacity(2000000);
|
|
|
|
messages_file
|
|
.read_to_end(&mut messages_bytes)
|
|
.expect("Could not initialize list of messages");
|
|
let generated_state: GeneratedState =
|
|
Decode::decode(&mut messages_bytes.as_slice()).expect("Could not decode messages");
|
|
|
|
gum::info!(
|
|
"It took {:?} ms to load {:?} unique messages",
|
|
start.elapsed().as_millis(),
|
|
generated_state.all_messages.as_ref().map(|val| val.len()).unwrap_or_default()
|
|
);
|
|
|
|
let babe_epoch =
|
|
generate_babe_epoch(generated_state.initial_slot, test_authorities.clone());
|
|
let blocks = Self::generate_blocks_information(
|
|
configuration,
|
|
&babe_epoch,
|
|
generated_state.initial_slot,
|
|
);
|
|
|
|
let state = ApprovalTestState {
|
|
blocks,
|
|
babe_epoch: babe_epoch.clone(),
|
|
generated_state,
|
|
test_authorities,
|
|
last_approved_block: Arc::new(AtomicU32::new(0)),
|
|
total_sent_messages_to_node: Arc::new(AtomicU64::new(0)),
|
|
total_sent_messages_from_node: Arc::new(AtomicU64::new(0)),
|
|
total_unique_messages: Arc::new(AtomicU64::new(0)),
|
|
options,
|
|
approval_voting_metrics: ApprovalVotingMetrics::try_register(&dependencies.registry)
|
|
.unwrap(),
|
|
delta_tick_from_generated: Arc::new(AtomicU64::new(630720000)),
|
|
configuration: configuration.clone(),
|
|
};
|
|
|
|
gum::info!("Built testing state");
|
|
|
|
state
|
|
}
|
|
|
|
/// Generates the blocks and the information about the blocks that will be used
|
|
/// to drive this test.
|
|
fn generate_blocks_information(
|
|
configuration: &TestConfiguration,
|
|
babe_epoch: &BabeEpoch,
|
|
initial_slot: Slot,
|
|
) -> Vec<BlockTestData> {
|
|
let mut per_block_heads: Vec<BlockTestData> = Vec::new();
|
|
let mut prev_candidates = 0;
|
|
for block_number in 1..=configuration.num_blocks {
|
|
let block_hash = Hash::repeat_byte(block_number as u8);
|
|
let parent_hash =
|
|
per_block_heads.last().map(|val| val.hash).unwrap_or(Hash::repeat_byte(0xde));
|
|
let slot_for_block = initial_slot + (block_number as u64 - 1);
|
|
|
|
let header = make_header(parent_hash, slot_for_block, block_number as u32);
|
|
|
|
let unsafe_vrf = approval::v1::babe_unsafe_vrf_info(&header)
|
|
.expect("Can not continue without vrf generator");
|
|
let relay_vrf_story = unsafe_vrf
|
|
.compute_randomness(
|
|
&babe_epoch.authorities,
|
|
&babe_epoch.randomness,
|
|
babe_epoch.epoch_index,
|
|
)
|
|
.expect("Can not continue without vrf story");
|
|
let block_info = BlockTestData {
|
|
slot: slot_for_block,
|
|
block_number: block_number as BlockNumber,
|
|
hash: block_hash,
|
|
header,
|
|
candidates: helpers::make_candidates(
|
|
block_hash,
|
|
block_number as BlockNumber,
|
|
configuration.n_cores as u32,
|
|
configuration.n_cores as u32,
|
|
),
|
|
relay_vrf_story,
|
|
approved: Arc::new(AtomicBool::new(false)),
|
|
total_candidates_before: prev_candidates,
|
|
votes: Arc::new(
|
|
(0..configuration.n_validators)
|
|
.map(|_| {
|
|
(0..configuration.n_cores).map(|_| AtomicBool::new(false)).collect_vec()
|
|
})
|
|
.collect_vec(),
|
|
),
|
|
};
|
|
prev_candidates += block_info.candidates.len() as u64;
|
|
per_block_heads.push(block_info)
|
|
}
|
|
per_block_heads
|
|
}
|
|
|
|
/// Starts the generation of messages(Assignments & Approvals) needed for approving blocks.
|
|
async fn start_message_production(
|
|
&mut self,
|
|
network_emulator: &NetworkEmulatorHandle,
|
|
overseer_handle: OverseerHandleReal,
|
|
env: &TestEnvironment,
|
|
registry: Registry,
|
|
) -> oneshot::Receiver<()> {
|
|
gum::info!(target: LOG_TARGET, "Start assignments/approvals production");
|
|
|
|
let (producer_tx, producer_rx) = oneshot::channel();
|
|
let peer_message_source = PeerMessageProducer {
|
|
network: network_emulator.clone(),
|
|
overseer_handle: overseer_handle.clone(),
|
|
state: self.clone(),
|
|
options: self.options.clone(),
|
|
notify_done: producer_tx,
|
|
registry,
|
|
};
|
|
|
|
peer_message_source
|
|
.produce_messages(env, self.generated_state.all_messages.take().unwrap());
|
|
producer_rx
|
|
}
|
|
|
|
// Generates a ChainApiState used for driving MockChainApi
|
|
fn build_chain_api_state(&self) -> ChainApiState {
|
|
ChainApiState {
|
|
block_headers: self
|
|
.blocks
|
|
.iter()
|
|
.map(|block| (block.hash, block.header.clone()))
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
// Builds a map with the list of candidate events per-block.
|
|
fn candidate_events_by_block(&self) -> HashMap<H256, Vec<CandidateEvent>> {
|
|
self.blocks.iter().map(|block| (block.hash, block.candidates.clone())).collect()
|
|
}
|
|
|
|
// Builds a map with the list of candidate hashes per-block.
|
|
fn candidate_hashes_by_block(&self) -> HashMap<H256, Vec<CandidateReceipt>> {
|
|
self.blocks
|
|
.iter()
|
|
.map(|block| {
|
|
(
|
|
block.hash,
|
|
block
|
|
.candidates
|
|
.iter()
|
|
.map(|candidate_event| match candidate_event {
|
|
CandidateEvent::CandidateBacked(_, _, _, _) => todo!(),
|
|
CandidateEvent::CandidateIncluded(receipt, _, _, _) => receipt.clone(),
|
|
CandidateEvent::CandidateTimedOut(_, _, _) => todo!(),
|
|
})
|
|
.collect_vec(),
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
impl ApprovalTestState {
|
|
/// Returns test data for the given hash
|
|
fn get_info_by_hash(&self, requested_hash: Hash) -> &BlockTestData {
|
|
self.blocks
|
|
.iter()
|
|
.find(|block| block.hash == requested_hash)
|
|
.expect("Mocks should not use unknown hashes")
|
|
}
|
|
|
|
/// Returns test data for the given slot
|
|
fn get_info_by_slot(&self, slot: Slot) -> Option<&BlockTestData> {
|
|
self.blocks.iter().find(|block| block.slot == slot)
|
|
}
|
|
}
|
|
|
|
impl HandleNetworkMessage for ApprovalTestState {
|
|
fn handle(
|
|
&self,
|
|
_message: crate::network::NetworkMessage,
|
|
_node_sender: &mut futures::channel::mpsc::UnboundedSender<crate::network::NetworkMessage>,
|
|
) -> Option<crate::network::NetworkMessage> {
|
|
self.total_sent_messages_from_node
|
|
.as_ref()
|
|
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
None
|
|
}
|
|
}
|
|
|
|
/// A generator of messages coming from a given Peer/Validator
|
|
struct PeerMessageProducer {
|
|
/// The state state used to know what messages to generate.
|
|
state: ApprovalTestState,
|
|
/// Configuration options, passed at the beginning of the test.
|
|
options: ApprovalsOptions,
|
|
/// A reference to the network emulator
|
|
network: NetworkEmulatorHandle,
|
|
/// A handle to the overseer, used for sending messages to the node
|
|
/// under test.
|
|
overseer_handle: OverseerHandleReal,
|
|
/// Channel for producer to notify main loop it finished sending
|
|
/// all messages and they have been processed.
|
|
notify_done: oneshot::Sender<()>,
|
|
/// The metrics registry.
|
|
registry: Registry,
|
|
}
|
|
|
|
impl PeerMessageProducer {
|
|
/// Generates messages by spawning a blocking task in the background which begins creating
|
|
/// the assignments/approvals and peer view changes at the beginning of each block.
|
|
fn produce_messages(
|
|
mut self,
|
|
env: &TestEnvironment,
|
|
all_messages: Vec<test_message::MessagesBundle>,
|
|
) {
|
|
env.spawn_blocking("produce-messages", async move {
|
|
let mut initialized_blocks = HashSet::new();
|
|
let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> =
|
|
self.initialize_candidates_test_data();
|
|
let mut skipped_messages: Vec<test_message::MessagesBundle> = Vec::new();
|
|
let mut re_process_skipped = false;
|
|
|
|
let system_clock =
|
|
PastSystemClock::new(SystemClock {}, self.state.delta_tick_from_generated.clone());
|
|
let mut all_messages = all_messages.into_iter().peekable();
|
|
|
|
while all_messages.peek().is_some() {
|
|
let current_slot =
|
|
tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now());
|
|
let block_to_initialize = self
|
|
.state
|
|
.blocks
|
|
.iter()
|
|
.filter(|block_info| {
|
|
block_info.slot <= current_slot &&
|
|
!initialized_blocks.contains(&block_info.hash)
|
|
})
|
|
.cloned()
|
|
.collect_vec();
|
|
for block_info in block_to_initialize {
|
|
if !TestEnvironment::metric_lower_than(
|
|
&self.registry,
|
|
"polkadot_parachain_imported_candidates_total",
|
|
(block_info.total_candidates_before + block_info.candidates.len() as u64 -
|
|
1) as f64,
|
|
) {
|
|
initialized_blocks.insert(block_info.hash);
|
|
self.initialize_block(&block_info).await;
|
|
}
|
|
}
|
|
|
|
let mut maybe_need_skip = if re_process_skipped {
|
|
skipped_messages.clone().into_iter().peekable()
|
|
} else {
|
|
vec![].into_iter().peekable()
|
|
};
|
|
|
|
let progressing_iterator = if !re_process_skipped {
|
|
&mut all_messages
|
|
} else {
|
|
re_process_skipped = false;
|
|
skipped_messages.clear();
|
|
&mut maybe_need_skip
|
|
};
|
|
|
|
while progressing_iterator
|
|
.peek()
|
|
.map(|bundle| {
|
|
self.time_to_process_message(
|
|
bundle,
|
|
current_slot,
|
|
&initialized_blocks,
|
|
&system_clock,
|
|
&per_candidate_data,
|
|
)
|
|
})
|
|
.unwrap_or_default()
|
|
{
|
|
let bundle = progressing_iterator.next().unwrap();
|
|
re_process_skipped = self.process_message(
|
|
bundle,
|
|
&mut per_candidate_data,
|
|
&mut skipped_messages,
|
|
) || re_process_skipped;
|
|
}
|
|
// Sleep, so that we don't busy wait in this loop when don't have anything to send.
|
|
sleep(Duration::from_millis(50)).await;
|
|
}
|
|
|
|
gum::info!(
|
|
"All messages sent max_tranche {:?} last_tranche_with_no_show {:?}",
|
|
per_candidate_data.values().map(|data| data.max_tranche).max(),
|
|
per_candidate_data.values().map(|data| data.last_tranche_with_no_show).max()
|
|
);
|
|
sleep(Duration::from_secs(6)).await;
|
|
// Send an empty GetApprovalSignatures as the last message
|
|
// so when the approval-distribution answered to it, we know it doesn't have anything
|
|
// else to process.
|
|
let (tx, rx) = oneshot::channel();
|
|
let msg = ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx);
|
|
self.send_overseer_message(
|
|
AllMessages::ApprovalDistribution(msg),
|
|
ValidatorIndex(0),
|
|
None,
|
|
)
|
|
.await;
|
|
rx.await.expect("Failed to get signatures");
|
|
self.notify_done.send(()).expect("Failed to notify main loop");
|
|
gum::info!("All messages processed ");
|
|
});
|
|
}
|
|
|
|
// Processes a single message bundle and queue the messages to be sent by the peers that would
|
|
// send the message in our simulation.
|
|
pub fn process_message(
|
|
&mut self,
|
|
bundle: test_message::MessagesBundle,
|
|
per_candidate_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
skipped_messages: &mut Vec<test_message::MessagesBundle>,
|
|
) -> bool {
|
|
let mut reprocess_skipped = false;
|
|
let block_info = self
|
|
.state
|
|
.get_info_by_hash(bundle.assignments.first().unwrap().block_hash)
|
|
.clone();
|
|
|
|
if bundle.should_send(per_candidate_data, &self.options) {
|
|
bundle.record_sent_assignment(per_candidate_data);
|
|
|
|
let assignments = bundle.assignments.clone();
|
|
|
|
for message in bundle.assignments.into_iter().chain(bundle.approvals.into_iter()) {
|
|
if message.no_show_if_required(&assignments, per_candidate_data) {
|
|
reprocess_skipped = true;
|
|
continue;
|
|
} else {
|
|
message.record_vote(&block_info);
|
|
}
|
|
self.state
|
|
.total_unique_messages
|
|
.as_ref()
|
|
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
for (peer, messages) in
|
|
message.clone().split_by_peer_id(&self.state.test_authorities)
|
|
{
|
|
for message in messages {
|
|
self.state
|
|
.total_sent_messages_to_node
|
|
.as_ref()
|
|
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
self.queue_message_from_peer(message, peer.0)
|
|
}
|
|
}
|
|
}
|
|
} else if !block_info.approved.load(std::sync::atomic::Ordering::SeqCst) &&
|
|
self.options.num_no_shows_per_candidate > 0
|
|
{
|
|
skipped_messages.push(bundle);
|
|
}
|
|
reprocess_skipped
|
|
}
|
|
|
|
// Tells if it is the time to process a message.
|
|
pub fn time_to_process_message(
|
|
&self,
|
|
bundle: &MessagesBundle,
|
|
current_slot: Slot,
|
|
initialized_blocks: &HashSet<Hash>,
|
|
system_clock: &PastSystemClock,
|
|
per_candidate_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>,
|
|
) -> bool {
|
|
let block_info =
|
|
self.state.get_info_by_hash(bundle.assignments.first().unwrap().block_hash);
|
|
let tranche_now = system_clock.tranche_now(SLOT_DURATION_MILLIS, block_info.slot);
|
|
|
|
Self::is_past_tranche(
|
|
bundle,
|
|
tranche_now,
|
|
current_slot,
|
|
block_info,
|
|
initialized_blocks.contains(&block_info.hash),
|
|
) || !bundle.should_send(per_candidate_data, &self.options)
|
|
}
|
|
|
|
// Tells if the tranche where the bundle should be sent has passed.
|
|
pub fn is_past_tranche(
|
|
bundle: &MessagesBundle,
|
|
tranche_now: u32,
|
|
current_slot: Slot,
|
|
block_info: &BlockTestData,
|
|
block_initialized: bool,
|
|
) -> bool {
|
|
bundle.tranche_to_send() <= tranche_now &&
|
|
current_slot >= block_info.slot &&
|
|
block_initialized
|
|
}
|
|
|
|
// Queue message to be sent by validator `sent_by`
|
|
fn queue_message_from_peer(&mut self, message: TestMessageInfo, sent_by: ValidatorIndex) {
|
|
let peer_authority_id = self
|
|
.state
|
|
.test_authorities
|
|
.validator_authority_id
|
|
.get(sent_by.0 as usize)
|
|
.expect("We can't handle unknown peers")
|
|
.clone();
|
|
|
|
self.network
|
|
.send_message_from_peer(
|
|
&peer_authority_id,
|
|
protocol_v3::ValidationProtocol::ApprovalDistribution(message.msg).into(),
|
|
)
|
|
.unwrap_or_else(|_| panic!("Network should be up and running {:?}", sent_by));
|
|
}
|
|
|
|
// Queues a message to be sent by the peer identified by the `sent_by` value.
|
|
async fn send_overseer_message(
|
|
&mut self,
|
|
message: AllMessages,
|
|
_sent_by: ValidatorIndex,
|
|
_latency: Option<Duration>,
|
|
) {
|
|
self.overseer_handle
|
|
.send_msg(message, LOG_TARGET)
|
|
.timeout(MAX_TIME_OF_FLIGHT)
|
|
.await
|
|
.unwrap_or_else(|| {
|
|
panic!("{} ms maximum time of flight breached", MAX_TIME_OF_FLIGHT.as_millis())
|
|
});
|
|
}
|
|
|
|
// Sends the messages needed by approval-distribution and approval-voting for processing a
|
|
// message. E.g: PeerViewChange.
|
|
async fn initialize_block(&mut self, block_info: &BlockTestData) {
|
|
gum::info!("Initialize block {:?}", block_info.hash);
|
|
let (tx, rx) = oneshot::channel();
|
|
self.overseer_handle.wait_for_activation(block_info.hash, tx).await;
|
|
|
|
rx.await
|
|
.expect("We should not fail waiting for block to be activated")
|
|
.expect("We should not fail waiting for block to be activated");
|
|
|
|
for validator in 1..self.state.test_authorities.validator_authority_id.len() as u32 {
|
|
let peer_id = self.state.test_authorities.peer_ids.get(validator as usize).unwrap();
|
|
let validator = ValidatorIndex(validator);
|
|
let view_update = generate_peer_view_change_for(block_info.hash, *peer_id);
|
|
|
|
self.send_overseer_message(view_update, validator, None).await;
|
|
}
|
|
}
|
|
|
|
// Initializes the candidates test data. This is used for bookkeeping if more assignments and
|
|
// approvals would be needed.
|
|
fn initialize_candidates_test_data(
|
|
&self,
|
|
) -> HashMap<(Hash, CandidateIndex), CandidateTestData> {
|
|
let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> =
|
|
HashMap::new();
|
|
for block_info in self.state.blocks.iter() {
|
|
for (candidate_index, _) in block_info.candidates.iter().enumerate() {
|
|
per_candidate_data.insert(
|
|
(block_info.hash, candidate_index as CandidateIndex),
|
|
CandidateTestData {
|
|
max_no_shows: self.options.num_no_shows_per_candidate,
|
|
last_tranche_with_no_show: 0,
|
|
sent_assignment: 0,
|
|
num_no_shows: 0,
|
|
max_tranche: 0,
|
|
needed_approvals: self.state.configuration.needed_approvals as u32,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
per_candidate_data
|
|
}
|
|
}
|
|
|
|
/// Helper function to build an overseer with the real implementation for `ApprovalDistribution` and
|
|
/// `ApprovalVoting` subsystems and mock subsystems for all others.
|
|
fn build_overseer(
|
|
state: &ApprovalTestState,
|
|
network: &NetworkEmulatorHandle,
|
|
config: &TestConfiguration,
|
|
dependencies: &TestEnvironmentDependencies,
|
|
network_interface: &NetworkInterface,
|
|
network_receiver: NetworkInterfaceReceiver,
|
|
) -> (Overseer<SpawnGlue<SpawnTaskHandle>, AlwaysSupportsParachains>, OverseerHandleReal) {
|
|
let overseer_connector = OverseerConnector::with_event_capacity(6400000);
|
|
|
|
let spawn_task_handle = dependencies.task_manager.spawn_handle();
|
|
|
|
let db = kvdb_memorydb::create(NUM_COLUMNS);
|
|
let db: polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter<kvdb_memorydb::InMemory> =
|
|
polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]);
|
|
let keystore = LocalKeystore::in_memory();
|
|
|
|
let system_clock =
|
|
PastSystemClock::new(SystemClock {}, state.delta_tick_from_generated.clone());
|
|
let approval_voting = ApprovalVotingSubsystem::with_config_and_clock(
|
|
TEST_CONFIG,
|
|
Arc::new(db),
|
|
Arc::new(keystore),
|
|
Box::new(TestSyncOracle {}),
|
|
state.approval_voting_metrics.clone(),
|
|
Box::new(system_clock.clone()),
|
|
);
|
|
|
|
let approval_distribution =
|
|
ApprovalDistribution::new(Metrics::register(Some(&dependencies.registry)).unwrap());
|
|
let mock_chain_api = MockChainApi::new(state.build_chain_api_state());
|
|
let mock_chain_selection = MockChainSelection { state: state.clone(), clock: system_clock };
|
|
let mock_runtime_api = MockRuntimeApi::new(
|
|
config.clone(),
|
|
state.test_authorities.clone(),
|
|
state.candidate_hashes_by_block(),
|
|
state.candidate_events_by_block(),
|
|
Some(state.babe_epoch.clone()),
|
|
1,
|
|
);
|
|
let mock_tx_bridge = MockNetworkBridgeTx::new(
|
|
network.clone(),
|
|
network_interface.subsystem_sender(),
|
|
state.test_authorities.clone(),
|
|
);
|
|
let mock_rx_bridge = MockNetworkBridgeRx::new(network_receiver, None);
|
|
let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap();
|
|
let dummy = dummy_builder!(spawn_task_handle, overseer_metrics)
|
|
.replace_approval_distribution(|_| approval_distribution)
|
|
.replace_approval_voting(|_| approval_voting)
|
|
.replace_chain_api(|_| mock_chain_api)
|
|
.replace_chain_selection(|_| mock_chain_selection)
|
|
.replace_runtime_api(|_| mock_runtime_api)
|
|
.replace_network_bridge_tx(|_| mock_tx_bridge)
|
|
.replace_network_bridge_rx(|_| mock_rx_bridge);
|
|
|
|
let (overseer, raw_handle) =
|
|
dummy.build_with_connector(overseer_connector).expect("Should not fail");
|
|
|
|
let overseer_handle = OverseerHandleReal::new(raw_handle);
|
|
(overseer, overseer_handle)
|
|
}
|
|
|
|
/// Takes a test configuration and uses it to creates the `TestEnvironment`.
|
|
pub fn prepare_test(
|
|
config: TestConfiguration,
|
|
options: ApprovalsOptions,
|
|
with_prometheus_endpoint: bool,
|
|
) -> (TestEnvironment, ApprovalTestState) {
|
|
prepare_test_inner(
|
|
config,
|
|
TestEnvironmentDependencies::default(),
|
|
options,
|
|
with_prometheus_endpoint,
|
|
)
|
|
}
|
|
|
|
/// Build the test environment for an Approval benchmark.
|
|
fn prepare_test_inner(
|
|
config: TestConfiguration,
|
|
dependencies: TestEnvironmentDependencies,
|
|
options: ApprovalsOptions,
|
|
with_prometheus_endpoint: bool,
|
|
) -> (TestEnvironment, ApprovalTestState) {
|
|
gum::info!("Prepare test state");
|
|
let state = ApprovalTestState::new(&config, options, &dependencies);
|
|
|
|
gum::info!("Build network emulator");
|
|
|
|
let (network, network_interface, network_receiver) =
|
|
new_network(&config, &dependencies, &state.test_authorities, vec![Arc::new(state.clone())]);
|
|
|
|
gum::info!("Build overseer");
|
|
|
|
let (overseer, overseer_handle) = build_overseer(
|
|
&state,
|
|
&network,
|
|
&config,
|
|
&dependencies,
|
|
&network_interface,
|
|
network_receiver,
|
|
);
|
|
|
|
(
|
|
TestEnvironment::new(
|
|
dependencies,
|
|
config,
|
|
network,
|
|
overseer,
|
|
overseer_handle,
|
|
state.test_authorities.clone(),
|
|
with_prometheus_endpoint,
|
|
),
|
|
state,
|
|
)
|
|
}
|
|
|
|
pub async fn bench_approvals(
|
|
benchmark_name: &str,
|
|
env: &mut TestEnvironment,
|
|
mut state: ApprovalTestState,
|
|
) -> BenchmarkUsage {
|
|
let producer_rx = state
|
|
.start_message_production(
|
|
env.network(),
|
|
env.overseer_handle().clone(),
|
|
env,
|
|
env.registry().clone(),
|
|
)
|
|
.await;
|
|
bench_approvals_run(benchmark_name, env, state, producer_rx).await
|
|
}
|
|
|
|
/// Runs the approval benchmark.
|
|
pub async fn bench_approvals_run(
|
|
benchmark_name: &str,
|
|
env: &mut TestEnvironment,
|
|
state: ApprovalTestState,
|
|
producer_rx: oneshot::Receiver<()>,
|
|
) -> BenchmarkUsage {
|
|
let config = env.config().clone();
|
|
|
|
env.metrics().set_n_validators(config.n_validators);
|
|
env.metrics().set_n_cores(config.n_cores);
|
|
|
|
// First create the initialization messages that make sure that then node under
|
|
// tests receives notifications about the topology used and the connected peers.
|
|
let mut initialization_messages = env.network().generate_peer_connected();
|
|
initialization_messages.extend(generate_new_session_topology(
|
|
&state.test_authorities,
|
|
ValidatorIndex(NODE_UNDER_TEST),
|
|
));
|
|
for message in initialization_messages {
|
|
env.send_message(message).await;
|
|
}
|
|
|
|
let start_marker = Instant::now();
|
|
let real_clock = SystemClock {};
|
|
state.delta_tick_from_generated.store(
|
|
real_clock.tick_now() -
|
|
slot_number_to_tick(SLOT_DURATION_MILLIS, state.generated_state.initial_slot),
|
|
std::sync::atomic::Ordering::SeqCst,
|
|
);
|
|
let system_clock = PastSystemClock::new(real_clock, state.delta_tick_from_generated.clone());
|
|
|
|
for block_num in 0..env.config().num_blocks {
|
|
let mut current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now());
|
|
|
|
// Wait until the time arrives at the first slot under test.
|
|
while current_slot < state.generated_state.initial_slot {
|
|
sleep(Duration::from_millis(5)).await;
|
|
current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now());
|
|
}
|
|
|
|
gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num + 1, env.config().num_blocks);
|
|
env.metrics().set_current_block(block_num);
|
|
let block_start_ts = Instant::now();
|
|
|
|
if let Some(block_info) = state.get_info_by_slot(current_slot) {
|
|
env.import_block(new_block_import_info(block_info.hash, block_info.block_number))
|
|
.await;
|
|
}
|
|
|
|
let block_time = Instant::now().sub(block_start_ts).as_millis() as u64;
|
|
env.metrics().set_block_time(block_time);
|
|
gum::info!("Block time {}", format!("{:?}ms", block_time).cyan());
|
|
|
|
system_clock
|
|
.wait(slot_number_to_tick(SLOT_DURATION_MILLIS, current_slot + 1))
|
|
.await;
|
|
}
|
|
|
|
// Wait for all blocks to be approved before exiting.
|
|
// This is an invariant of the benchmark, if this does not happen something went terribly wrong.
|
|
while state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst) <
|
|
env.config().num_blocks as u32
|
|
{
|
|
gum::info!(
|
|
"Waiting for all blocks to be approved current approved {:} num_sent {:} num_unique {:}",
|
|
state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst),
|
|
state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst),
|
|
state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst)
|
|
);
|
|
tokio::time::sleep(Duration::from_secs(6)).await;
|
|
}
|
|
|
|
gum::info!("Awaiting producer to signal done");
|
|
|
|
producer_rx.await.expect("Failed to receive done from message producer");
|
|
|
|
gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed");
|
|
let at_least_messages =
|
|
state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize;
|
|
env.wait_until_metric(
|
|
"polkadot_parachain_subsystem_bounded_received",
|
|
Some(("subsystem_name", "approval-distribution-subsystem")),
|
|
|value| {
|
|
gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric");
|
|
value >= at_least_messages as f64
|
|
},
|
|
)
|
|
.await;
|
|
gum::info!("Requesting approval votes ms");
|
|
|
|
for info in &state.blocks {
|
|
for (index, candidates) in info.candidates.iter().enumerate() {
|
|
match candidates {
|
|
CandidateEvent::CandidateBacked(_, _, _, _) => todo!(),
|
|
CandidateEvent::CandidateIncluded(receipt_fetch, _head, _, _) => {
|
|
let (tx, rx) = oneshot::channel();
|
|
|
|
let msg = ApprovalVotingMessage::GetApprovalSignaturesForCandidate(
|
|
receipt_fetch.hash(),
|
|
tx,
|
|
);
|
|
env.send_message(AllMessages::ApprovalVoting(msg)).await;
|
|
|
|
let result = rx.await.unwrap();
|
|
|
|
for (validator, _) in result.iter() {
|
|
info.votes
|
|
.get(validator.0 as usize)
|
|
.unwrap()
|
|
.get(index)
|
|
.unwrap()
|
|
.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
}
|
|
},
|
|
|
|
CandidateEvent::CandidateTimedOut(_, _, _) => todo!(),
|
|
};
|
|
}
|
|
}
|
|
|
|
gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed");
|
|
let at_least_messages =
|
|
state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize;
|
|
env.wait_until_metric(
|
|
"polkadot_parachain_subsystem_bounded_received",
|
|
Some(("subsystem_name", "approval-distribution-subsystem")),
|
|
|value| {
|
|
gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric");
|
|
value >= at_least_messages as f64
|
|
},
|
|
)
|
|
.await;
|
|
|
|
for state in &state.blocks {
|
|
for (validator, votes) in state
|
|
.votes
|
|
.as_ref()
|
|
.iter()
|
|
.enumerate()
|
|
.filter(|(validator, _)| *validator != NODE_UNDER_TEST as usize)
|
|
{
|
|
for (index, candidate) in votes.iter().enumerate() {
|
|
assert_eq!(
|
|
(
|
|
validator,
|
|
index,
|
|
candidate.load(std::sync::atomic::Ordering::SeqCst),
|
|
state.hash
|
|
),
|
|
(validator, index, false, state.hash)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
env.stop().await;
|
|
|
|
let duration: u128 = start_marker.elapsed().as_millis();
|
|
gum::info!(
|
|
"All blocks processed in {} total_sent_messages_to_node {} total_sent_messages_from_node {} num_unique_messages {}",
|
|
format!("{:?}ms", duration).cyan(),
|
|
state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst),
|
|
state.total_sent_messages_from_node.load(std::sync::atomic::Ordering::SeqCst),
|
|
state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst)
|
|
);
|
|
|
|
env.collect_resource_usage(benchmark_name, &["approval-distribution", "approval-voting"])
|
|
}
|