diff --git a/consensus/src/agreement/verifiers.rs b/consensus/src/agreement/verifiers.rs index e175a58a0a..0abc25a9ec 100644 --- a/consensus/src/agreement/verifiers.rs +++ b/consensus/src/agreement/verifiers.rs @@ -193,13 +193,13 @@ pub async fn verify_votes( impl Cluster { fn aggregate_pks(&self) -> Result { - let pks: Vec<&dusk_bls12_381_sign::PublicKey> = - self.iter().map(|(pubkey, _)| pubkey.inner()).collect(); + let pks: Vec<_> = + self.iter().map(|(pubkey, _)| *pubkey.inner()).collect(); match pks.split_first() { - Some((&first, rest)) => { + Some((first, rest)) => { let mut apk = dusk_bls12_381_sign::APK::from(first); - rest.iter().for_each(|&&p| apk.aggregate(&[p])); + apk.aggregate(rest); Ok(apk) } None => Err(Error::EmptyApk), diff --git a/consensus/src/user/cluster.rs b/consensus/src/user/cluster.rs index 6e8bc3a675..2e6bbedfb8 100644 --- a/consensus/src/user/cluster.rs +++ b/consensus/src/user/cluster.rs @@ -7,7 +7,7 @@ use std::collections::btree_map::Iter; use std::collections::BTreeMap; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Cluster(BTreeMap); impl Cluster diff --git a/consensus/src/user/committee.rs b/consensus/src/user/committee.rs index 035288e4f3..0693ac807c 100644 --- a/consensus/src/user/committee.rs +++ b/consensus/src/user/committee.rs @@ -22,14 +22,9 @@ pub struct Committee { cfg: sortition::Config, } -impl Iterator for Committee { - type Item = PublicKey; - - fn next(&mut self) -> Option { - self.members - .iter() - .next() - .map(|(public_key, _)| public_key.clone()) +impl Committee { + pub fn iter(&self) -> impl Iterator { + self.members.keys() } } diff --git a/consensus/src/user/provisioners.rs b/consensus/src/user/provisioners.rs index 19362457f7..8769831b9a 100644 --- a/consensus/src/user/provisioners.rs +++ b/consensus/src/user/provisioners.rs @@ -147,7 +147,7 @@ impl Provisioners { /// committee members for a given round, step and seed. /// /// Returns a vector of provisioners public keys. - pub fn create_committee( + pub(crate) fn create_committee( &mut self, cfg: &sortition::Config, ) -> Vec { diff --git a/node/Cargo.toml b/node/Cargo.toml index 0abfdbfd53..33a7c7fc9b 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -41,9 +41,15 @@ node-data = { version = "0.1", path = "../node-data", features = ["faker"] } rand = "0.8" rand_core = "0.6" tempdir = "0.3" +criterion = { version = "0.5", features = ["async_futures"] } [build-dependencies] rustc_tools_util = "=0.2.0" [features] with_telemetry = ["dep:console-subscriber"] + + +[[bench]] +name = "accept" +harness = false diff --git a/node/benches/accept.rs b/node/benches/accept.rs new file mode 100644 index 0000000000..31c7df8881 --- /dev/null +++ b/node/benches/accept.rs @@ -0,0 +1,176 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use std::time::Duration; + +use node::chain; + +use criterion::async_executor::FuturesExecutor; +use criterion::measurement::WallTime; +use criterion::{ + criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, +}; + +use dusk_bls12_381_sign::{ + PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, + Signature as BlsSignature, +}; +use dusk_bytes::Serializable; +use dusk_consensus::user::{ + cluster::Cluster, committee::Committee, provisioners::Provisioners, + sortition::Config as SortitionConfig, +}; +use node_data::{ + bls::PublicKey, + ledger::{Certificate, Signature, StepVotes}, + message, +}; +use rand::rngs::StdRng; +use rand::SeedableRng; + +fn create_step_votes( + seed: Signature, + round: u64, + block_hash: [u8; 32], + step: u8, + iteration: u8, + provisioners: Provisioners, + keys: &[(PublicKey, BlsSecretKey)], +) -> StepVotes { + let sortition_config = + SortitionConfig::new(seed, round, iteration * 3 + step, 64); + + let committee = Committee::new( + PublicKey::default(), + &mut provisioners.clone(), + sortition_config, + ); + + let hdr = message::Header { + round, + step: step % 3, + block_hash, + ..Default::default() + }; + let mut signatures = vec![]; + let mut cluster = Cluster::::default(); + for (pk, sk) in keys.iter() { + if let Some(weight) = committee.votes_for(pk) { + let sig = hdr.sign(sk, pk.inner()); + signatures.push(BlsSignature::from_bytes(&sig).unwrap()); + cluster.set_weight(pk, weight); + } + } + + let bitset = committee.bits(&cluster); + + let (first, rest) = signatures.split_first().unwrap(); + let aggregate_signature = first.aggregate(rest).to_bytes(); + StepVotes::new(aggregate_signature, bitset) +} + +pub fn with_group(name: &str, c: &mut Criterion, closure: F) -> T +where + F: FnOnce(&mut BenchmarkGroup) -> T, +{ + let mut group = c.benchmark_group(name); + let r = closure(&mut group); + group.finish(); + r +} + +pub fn verify_block_cert(c: &mut Criterion) { + with_group("verify_block_cert", c, |group| { + for input in INPUTS { + group.measurement_time(Duration::from_secs(input.measurement_time)); + let mut keys = vec![]; + let mut provisioners = Provisioners::new(); + let rng = &mut StdRng::seed_from_u64(0xbeef); + for _ in 0..input.provisioners { + let sk = BlsSecretKey::random(rng); + let pk = BlsPublicKey::from(&sk); + let pk = PublicKey::new(pk); + keys.push((pk.clone(), sk)); + provisioners.add_member_with_value(pk, 1000000000000) + } + let height = 1; + let seed = Signature([5; 48]); + let block_hash = [1; 32]; + let curr_public_key = keys.first().unwrap().0.clone(); + let iteration = 0; + let mut cert = Certificate::default(); + + cert.first_reduction = create_step_votes( + seed, + height, + block_hash, + 1, + iteration, + provisioners.clone(), + &keys[..], + ); + cert.second_reduction = create_step_votes( + seed, + height, + block_hash, + 2, + iteration, + provisioners.clone(), + &keys[..], + ); + group.bench_function( + BenchmarkId::new( + "verify_block_cert", + format!("{} prov", input.provisioners), + ), + move |b| { + b.to_async(FuturesExecutor).iter(|| { + chain::verify_block_cert( + seed, + &provisioners, + &curr_public_key, + block_hash, + height, + &cert, + iteration, + true, + ) + }) + }, + ); + } + }) +} + +struct Input { + provisioners: usize, + measurement_time: u64, // secs +} + +const INPUTS: &[Input] = &[ + Input { + provisioners: 1, + measurement_time: 10, + }, + Input { + provisioners: 30, + measurement_time: 10, + }, + Input { + provisioners: 64, + measurement_time: 10, + }, + Input { + provisioners: 256, + measurement_time: 15, + }, + Input { + provisioners: 1000, + measurement_time: 15, + }, +]; +criterion_group!(benches, verify_block_cert); +criterion_main!(benches); diff --git a/node/src/chain.rs b/node/src/chain.rs index e8d376657e..e443916936 100644 --- a/node/src/chain.rs +++ b/node/src/chain.rs @@ -43,6 +43,8 @@ use self::acceptor::{Acceptor, RevertTarget}; use self::consensus::Task; use self::fsm::SimpleFSM; +pub use acceptor::verify_block_cert; + const TOPICS: &[u8] = &[ Topics::Block as u8, Topics::NewBlock as u8, diff --git a/node/src/chain/acceptor.rs b/node/src/chain/acceptor.rs index 7917cc90ad..52950d35ce 100644 --- a/node/src/chain/acceptor.rs +++ b/node/src/chain/acceptor.rs @@ -17,7 +17,7 @@ use dusk_consensus::consensus::{self, Consensus}; use dusk_consensus::contract_state::{ CallParams, Error, Operations, Output, StateRoot, }; -use dusk_consensus::user::committee::CommitteeSet; +use dusk_consensus::user::committee::{Committee, CommitteeSet}; use dusk_consensus::user::provisioners::Provisioners; use dusk_consensus::user::sortition; use hex::ToHex; @@ -190,22 +190,27 @@ impl Acceptor { return; } let mut prov = provisioners_list.clone(); - prov.update_eligibility_flag(round); for iter in 0..iteration { - let committee_keys = prov.create_committee(&sortition::Config { - committee_size: SELECTION_COMMITTEE_SIZE, - round, - seed, - step: iter * 3, - }); - if committee_keys.len() != 1 { - let len = committee_keys.len(); + let committee_keys = Committee::new( + node_data::bls::PublicKey::default(), + &mut prov, + sortition::Config { + committee_size: SELECTION_COMMITTEE_SIZE, + round, + seed, + step: iter * 3, + }, + ); + + if committee_keys.size() != 1 { + let len = committee_keys.size(); error!( "Unable to generate voting committee for missed block: {len}", ) } else { let generator = committee_keys - .first() + .iter() + .next() .expect("committee to have 1 entry") .to_bs58(); warn!( @@ -553,7 +558,7 @@ pub(crate) async fn verify_block_header( } #[allow(clippy::too_many_arguments)] -async fn verify_block_cert( +pub async fn verify_block_cert( curr_seed: Signature, curr_eligible_provisioners: &Provisioners, curr_public_key: &node_data::bls::PublicKey, diff --git a/node/testbed.sh b/node/testbed.sh index 816572fdf5..8dc5da859b 100755 --- a/node/testbed.sh +++ b/node/testbed.sh @@ -1,89 +1,101 @@ -#!/bin/bash -killall rusk-node - -# Determines how many pre-loaded provisioners will be in use. -PROV_NUM=$1 -MODE=$2 -BOOTSTRAP_ADDR="127.0.0.1:7000" - -# DUSK_WALLET_DIR is the path to the directory containing a set of consensus keys files. -if [ -z "$DUSK_WALLET_DIR" ]; then - echo "Warning: DUSK_WALLET_DIR is not set" -fi - -# Create a temporary directory. -TEMPD=/tmp/rust-harness/ -rm -fr $TEMPD -mkdir $TEMPD - -# Exit if the temp directory wasn't created successfully. -if [ ! -e "$TEMPD" ]; then - >&2 echo "Failed to create temp directory" - exit 1 -fi - -echo "test harness folder:$TEMPD" - -run_node() { - if [ "$MODE" = "--with_telemetry" ]; then - run_node_with_telemetry "$@" - else - run_node_debug_mode "$@" - fi -} - -run_node_debug_mode() { - local BOOTSTRAP_ADDR="$1" - local PUBLIC_ADDR="$2" - local LOG_LEVEL="$3" - local KEYS_PATH="$4" - local ID="$5" - local TEMPD="$6" - - echo "starting node $ID ..." - cargo run --release --bin rusk-node -- --kadcast_bootstrap "$BOOTSTRAP_ADDR" --kadcast_public_address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="${TEMPD}/db/${ID}" --delay_on_resp_msg=10 --config="default.config.toml" > "${TEMPD}/node_${ID}.log" & -} - -## Use ~/.cargo/bin/tokio-console --retain-for 0s http://127.0.0.1:10000 to connect console to first node -run_node_with_telemetry() { - local BOOTSTRAP_ADDR="$1" - local PUBLIC_ADDR="$2" - local LOG_LEVEL="$3" - local KEYS_PATH="$4" - local ID="$5" - local TEMPD="$6" - - T_BIND_PORT=$((10000+$ID)) - - echo "starting node $ID ..." - - RUST_LOG="info" TOKIO_CONSOLE_BIND="127.0.0.1:$T_BIND_PORT" \ - cargo --config 'build.rustflags = ["--cfg", "tokio_unstable"]' run --features with_telemetry --bin rusk-node --\ - --kadcast_bootstrap "$BOOTSTRAP_ADDR" --kadcast_public_address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" \ + #!/bin/bash + killall rusk + + # Determines how many pre-loaded provisioners will be in use. + PROV_NUM=$1 + MODE=$2 + #GENESIS_PATH="/home/tech/repo/dusk-network/dusk-blockchain/harness/tests/rusk_localnet_state.toml" + GENESIS_PATH="./rusk-recovery/config/testnet.toml" + + #BOOTSTRAP_ADDR="127.0.0.1:10000" + BOOTSTRAP_ADDR="127.0.0.1:7000" + # DUSK_WALLET_DIR is the path to the directory containing a set of consensus keys files. + if [ -z "$DUSK_WALLET_DIR" ]; then + echo "Warning: DUSK_WALLET_DIR is not set" + fi + + # Create a temporary directory.` + TEMPD=/tmp/rust-harness/ + rm -fr $TEMPD + mkdir $TEMPD + + # Exit if the temp directory wasn't created successfully. + if [ ! -e "$TEMPD" ]; then + >&2 echo "Failed to create temp directory" + exit 1 + fi + + echo "test harness folder:$TEMPD" + + run_node() { + if [ "$MODE" = "--with_telemetry" ]; then + run_node_with_telemetry "$@" + else + run_node_debug_mode "$@" + fi + } + + run_node_debug_mode() { + local BOOTSTRAP_ADDR="$1" + local PUBLIC_ADDR="$2" + local LOG_LEVEL="$3" + local KEYS_PATH="$4" + local ID="$5" + local TEMPD="$6" + local WS_LISTEN_ADDR="$7" + + local NODE_FOLDER=${TEMPD}/node_${ID} + + local RUSK_STATE_PATH=${NODE_FOLDER}/state + + RUSK_STATE_PATH=${RUSK_STATE_PATH} cargo r --release -p rusk -- recovery-state --init $GENESIS_PATH + echo "starting node $ID ..." + echo "${KEYS_PATH}/node_$ID.keys" + RUSK_STATE_PATH=${RUSK_STATE_PATH} ./target/release/rusk --kadcast-bootstrap "$BOOTSTRAP_ADDR" --kadcast-public-address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="$NODE_FOLDER" --http-listen-addr "$WS_LISTEN_ADDR" --delay-on-resp-msg=10 > "${TEMPD}/node_${ID}.log" & + } + + ## Use ~/.cargo/bin/tokio-console --retain-for 0s http://127.0.0.1:10000 to connect console to first node + run_node_with_telemetry() { + local BOOTSTRAP_ADDR="$1" + local PUBLIC_ADDR="$2" + local LOG_LEVEL="$3" + local KEYS_PATH="$4" + local ID="$5" + local TEMPD="$6" + + T_BIND_PORT=$((10000+$ID)) + + echo "starting node $ID ..." + + RUST_LOG="info" TOKIO_CONSOLE_BIND="127.0.0.1:$T_BIND_PORT" \ + cargo --config 'build.rustflags = ["--cfg", "tokio_unstable"]' run --features with_telemetry --bin rusk-node --\ + --kadcast_bootstrap "$BOOTSTRAP_ADDR" --kadcast_public_address "$PUBLIC_ADDR" --log-level="$LOG_LEVEL" \ --consensus-keys-path="${KEYS_PATH}/node_$ID.keys" --db-path="${TEMPD}/db/${ID}" --config="default.config.toml" > "${TEMPD}/node_${ID}.log" & } - # Spawn N (up to 9) nodes for (( i=0; i<$PROV_NUM; i++ )) do PORT=$((7000+$i)) - run_node "$BOOTSTRAP_ADDR" "127.0.0.1:$PORT" "info" "$DUSK_WALLET_DIR" "$i" "$TEMPD" + WS_PORT=$((8000+$i)) + + #delay=$(($i)) + #sleep 20 + + run_node "$BOOTSTRAP_ADDR" "127.0.0.1:$PORT" "info" "$DUSK_WALLET_DIR" "$i" "$TEMPD" "127.0.0.1:$WS_PORT" & - # Assuming first node is the bootstrap node, we need to wait for it to start - if [ $i -eq 0 ]; then - sleep 3 - fi - + # wait for bootstrappers + # TODO done # monitor -sleep 2 -tail -F ${TEMPD}node_*.log | grep -e "accepted\|ERROR" +sleep 10 +tail -F ${TEMPD}node_*.log | grep -e "accepted\|ERROR\|gen_candidate" +# tail -F ${TEMPD}node_*.log # Stop all running rusk-node processes when script is interrupted or terminated -trap 'killall rusk-node || true; rm -rf "$TEMPD"' INT TERM EXIT +trap 'killall rusk || true; rm -rf "$TEMPD"' INT TERM EXIT # Wait for all child processes to complete wait