diff --git a/client/src/main.rs b/client/src/main.rs index 570a0a1228..2597d59ba5 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -101,7 +101,10 @@ async fn main_inner() -> Result<(), Error> { // Start validator let val_metric_monitor = tokio_metrics::TaskMonitor::new(); if let Some(validator) = client.take_validator() { - info!("Initializing validator {}", validator.validator_address()); + info!( + "Initializing validator {}", + validator.state().read().validator_address + ); if metrics_enabled { let mp_metrics_monitor = validator.get_mempool_monitor(); diff --git a/lib/src/client.rs b/lib/src/client.rs index 35ed252aaa..6dbd4ed6f5 100644 --- a/lib/src/client.rs +++ b/lib/src/client.rs @@ -39,7 +39,7 @@ use nimiq_validator::key_utils::VotingKeys; #[cfg(feature = "validator")] use nimiq_validator::validator::Validator as AbstractValidator; #[cfg(feature = "validator")] -use nimiq_validator::validator::ValidatorProxy as AbstractValidatorProxy; +use nimiq_validator::validator::ValidatorState as AbstractValidatorState; #[cfg(feature = "validator")] use nimiq_validator_network::network_impl::ValidatorNetworkImpl; #[cfg(feature = "wallet")] @@ -72,7 +72,7 @@ pub type ConsensusProxy = AbstractConsensusProxy; #[cfg(feature = "validator")] pub type Validator = AbstractValidator>; #[cfg(feature = "validator")] -pub type ValidatorProxy = AbstractValidatorProxy; +pub type ValidatorState = AbstractValidatorState; pub type ZKPComponent = AbstractZKPComponent; pub type ZKPComponentProxy = AbstractZKPComponentProxy; @@ -104,7 +104,7 @@ pub(crate) struct ClientInner { blockchain: BlockchainProxy, #[cfg(feature = "validator")] - validator: Option, + validator: Option>>, /// Wallet that stores key pairs for transaction signing #[cfg(feature = "wallet")] @@ -615,9 +615,9 @@ impl ClientInner { blockchain.write().tx_verification_cache = Arc::::clone(&validator.mempool_task.mempool); - let validator_proxy = validator.proxy(); + let validator_state = Arc::clone(validator.state()); validator_or_mempool = Some(ValidatorOrMempool::Validator(validator)); - Some(validator_proxy) + Some(validator_state) } else { None } @@ -768,7 +768,7 @@ impl Client { #[cfg(feature = "validator")] /// Returns a reference to the *Validator proxy*. - pub fn validator_proxy(&self) -> Option { + pub fn validator_proxy(&self) -> Option>> { self.inner.validator.clone() } diff --git a/rpc-server/src/dispatchers/validator.rs b/rpc-server/src/dispatchers/validator.rs index 07dd4220b1..632b25ab92 100644 --- a/rpc-server/src/dispatchers/validator.rs +++ b/rpc-server/src/dispatchers/validator.rs @@ -1,4 +1,4 @@ -use std::sync::atomic::Ordering; +use std::{mem, sync::Arc}; use async_trait::async_trait; use nimiq_bls::{KeyPair as BlsKeyPair, SecretKey as BlsSecretKey}; @@ -7,17 +7,18 @@ use nimiq_keys::Address; use nimiq_network_libp2p::Network; use nimiq_rpc_interface::{types::RPCResult, validator::ValidatorInterface}; use nimiq_serde::{Deserialize, Serialize}; -use nimiq_validator::validator::ValidatorProxy; +use nimiq_validator::validator::ValidatorState; +use parking_lot::RwLock; use crate::error::Error; pub struct ValidatorDispatcher { - validator: ValidatorProxy, + validator: Arc>, consensus: ConsensusProxy, } impl ValidatorDispatcher { - pub fn new(validator: ValidatorProxy, consensus: ConsensusProxy) -> Self { + pub fn new(validator: Arc>, consensus: ConsensusProxy) -> Self { ValidatorDispatcher { validator, consensus, @@ -31,18 +32,19 @@ impl ValidatorInterface for ValidatorDispatcher { type Error = Error; async fn get_address(&mut self) -> RPCResult { - Ok(self.validator.validator_address.read().clone().into()) + Ok(self.validator.read().validator_address.clone().into()) } + // TODO: why do we give out secret keys via RPC? async fn get_signing_key(&mut self) -> RPCResult { - Ok(hex::encode(self.validator.signing_key.read().private.serialize_to_vec()).into()) + Ok(hex::encode(self.validator.read().signing_key.private.serialize_to_vec()).into()) } async fn get_voting_key(&mut self) -> RPCResult { Ok(hex::encode( self.validator - .voting_keys .read() + .voting_keys .get_current_key() .secret_key .serialize_to_vec(), @@ -53,8 +55,8 @@ impl ValidatorInterface for ValidatorDispatcher { async fn get_voting_keys(&mut self) -> RPCResult, (), Self::Error> { Ok(self .validator - .voting_keys .read() + .voting_keys .get_keys() .into_iter() .map(|key| hex::encode(key.secret_key.serialize_to_vec())) @@ -63,7 +65,7 @@ impl ValidatorInterface for ValidatorDispatcher { } async fn add_voting_key(&mut self, secret_key: String) -> RPCResult<(), (), Self::Error> { - self.validator.voting_keys.write().add_key(BlsKeyPair::from( + self.validator.write().voting_keys.add_key(BlsKeyPair::from( BlsSecretKey::deserialize_from_vec(&hex::decode(secret_key)?)?, )); Ok(().into()) @@ -73,16 +75,21 @@ impl ValidatorInterface for ValidatorDispatcher { &mut self, automatic_reactivate: bool, ) -> RPCResult<(), (), Self::Error> { - self.validator - .automatic_reactivate - .store(automatic_reactivate, Ordering::Release); + let old = mem::replace( + &mut self.validator.write().automatic_reactivate, + automatic_reactivate, + ); - log::debug!("Automatic reactivation set to {}.", automatic_reactivate); + log::debug!( + "Automatic reactivation set to {} (from {}).", + automatic_reactivate, + old + ); Ok(().into()) } async fn is_validator_elected(&mut self) -> RPCResult { - let is_elected = self.validator.slot_band.read().is_some(); + let is_elected = self.validator.read().slot_band.is_some(); Ok(is_elected.into()) } diff --git a/test-utils/src/validator.rs b/test-utils/src/validator.rs index 7be7081fb0..53c739a665 100644 --- a/test-utils/src/validator.rs +++ b/test-utils/src/validator.rs @@ -166,7 +166,13 @@ where validators .iter() .find(|validator| { - &validator.current_voting_key().public_key.compress() + &validator + .state() + .read() + .voting_keys + .get_current_key() + .public_key + .compress() == slot.validator.voting_key.compressed() }) .unwrap() @@ -193,7 +199,13 @@ where let index = validators .iter() .position(|validator| { - &validator.current_voting_key().public_key.compress() + &validator + .state() + .read() + .voting_keys + .get_current_key() + .public_key + .compress() == slot.validator.voting_key.compressed() }) .unwrap(); diff --git a/validator/src/validator.rs b/validator/src/validator.rs index 4c007fd451..bc34c80ea8 100644 --- a/validator/src/validator.rs +++ b/validator/src/validator.rs @@ -2,10 +2,7 @@ use std::{ error::Error, future::Future, pin::Pin, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::Arc, task::{Context, Poll}, time::Duration, }; @@ -15,7 +12,6 @@ use nimiq_account::Validator as ValidatorAccount; use nimiq_block::{Block, BlockType, EquivocationProof}; use nimiq_blockchain::{interface::HistoryInterface, BlockProducer, Blockchain}; use nimiq_blockchain_interface::{AbstractBlockchain, BlockchainEvent, ForkEvent}; -use nimiq_bls::KeyPair as BlsKeyPair; use nimiq_consensus::{ messages::{BlockBodyTopic, BlockHeaderMessage, BlockHeaderTopic}, Consensus, ConsensusEvent, ConsensusProxy, @@ -63,33 +59,19 @@ pub struct ConsensusState { } /// Validator inactivity -struct InactivityState { - inactive_tx_hash: Blake2bHash, - inactive_tx_validity_window_start: u32, +pub struct InactivityState { + pub inactive_tx_hash: Blake2bHash, + pub inactive_tx_validity_window_start: u32, } -pub struct ValidatorProxy { - pub validator_address: Arc>, - pub signing_key: Arc>, - pub voting_keys: Arc>, - pub fee_key: Arc>, - pub automatic_reactivate: Arc, - pub slot_band: Arc>>, - pub consensus_state: Arc>, -} - -impl Clone for ValidatorProxy { - fn clone(&self) -> Self { - Self { - validator_address: Arc::clone(&self.validator_address), - signing_key: Arc::clone(&self.signing_key), - voting_keys: Arc::clone(&self.voting_keys), - fee_key: Arc::clone(&self.fee_key), - automatic_reactivate: Arc::clone(&self.automatic_reactivate), - slot_band: Arc::clone(&self.slot_band), - consensus_state: Arc::clone(&self.consensus_state), - } - } +pub struct ValidatorState { + pub validator_address: Address, + pub signing_key: SchnorrKeyPair, + pub voting_keys: VotingKeys, + pub fee_key: SchnorrKeyPair, + pub automatic_reactivate: bool, + pub slot_band: Option, + pub consensus: ConsensusState, } declare_table!(ValidatorTable, "ValidatorState", () => MacroState); @@ -105,10 +87,8 @@ where table: ValidatorTable, env: MdbxDatabase, - validator_address: Arc>, - signing_key: Arc>, - voting_keys: Arc>, - fee_key: Arc>, + state: Arc>, + inactivity_state: Option, proposal_receiver: ProposalReceiver, @@ -116,11 +96,6 @@ where network_event_rx: SubscribeEvents<::PeerId>, fork_event_rx: BroadcastStream, - slot_band: Arc>>, - consensus_state: Arc>, - validator_state: Option, - automatic_reactivate: Arc, - macro_producer: Option>, macro_state: Arc>>, @@ -129,6 +104,34 @@ where pub mempool_task: MempoolTask, } +impl ValidatorState { + fn get_validator(&self, blockchain: &Blockchain) -> Option { + blockchain + .get_staking_contract_if_complete(None) + .and_then(|staking_contract| { + // Then fetch the validator to see if it is active. + let data_store = blockchain.get_staking_contract_store(); + let txn = blockchain.read_transaction(); + staking_contract.get_validator(&data_store.read(&txn), &self.validator_address) + }) + } + + fn get_staking_state(&self, blockchain: &Blockchain) -> ValidatorStakingState { + self.get_validator(blockchain).map_or( + ValidatorStakingState::UnknownOrNoStake, + |validator| match validator.inactive_from { + Some(_) => ValidatorStakingState::Inactive(validator.jailed_from), + None => ValidatorStakingState::Active, + }, + ) + } + + /// Checks whether we are an elected validator in the current epoch. + fn is_elected(&self) -> bool { + self.slot_band.is_some() + } +} + impl Validator where PubsubId: std::fmt::Debug + Unpin, @@ -186,8 +189,6 @@ where let mempool = MempoolTask::new(consensus, Arc::clone(&blockchain), mempool_config); - let automatic_reactivate = Arc::new(AtomicBool::new(automatic_reactivate)); - Self::init_network_request_receivers(&consensus.network, ¯o_state); let network1 = Arc::clone(&network); @@ -208,10 +209,16 @@ where table: ValidatorTable, env, - validator_address: Arc::new(RwLock::new(validator_address)), - signing_key: Arc::new(RwLock::new(signing_key)), - voting_keys: Arc::new(RwLock::new(voting_keys)), - fee_key: Arc::new(RwLock::new(fee_key)), + state: Arc::new(RwLock::new(ValidatorState { + validator_address, + signing_key, + voting_keys, + fee_key, + automatic_reactivate, + slot_band: None, + consensus: blockchain_state, + })), + inactivity_state: None, proposal_receiver, @@ -219,11 +226,6 @@ where network_event_rx, fork_event_rx, - slot_band: Arc::new(RwLock::new(None)), - consensus_state: Arc::new(RwLock::new(blockchain_state)), - validator_state: None, - automatic_reactivate, - macro_producer: None, macro_state: Arc::clone(¯o_state), @@ -300,32 +302,32 @@ where /// ourselves if the previous tx didn't get included within the validity window. fn check_reactivate(&mut self, block_number: u32) { // Check if the reactivate/activate transaction was sent - if let Some(validator_state) = &self.validator_state { + if let Some(inactivity_state) = &self.inactivity_state { // We check this in the last possible block of the validity window - let tx_validity_window_start = validator_state.inactive_tx_validity_window_start; + let tx_validity_window_start = inactivity_state.inactive_tx_validity_window_start; if block_number >= tx_validity_window_start + Policy::transaction_validity_window_blocks() - 1 { let blockchain = self.blockchain.read(); - let staking_state = self.get_staking_state(&blockchain); + let staking_state = self.state.read().get_staking_state(&blockchain); // Check that the transaction was sent in the validity window if (matches!(staking_state, ValidatorStakingState::Inactive(..))) && !blockchain.history_store.tx_in_validity_window( - &validator_state.inactive_tx_hash.clone().into(), + &inactivity_state.inactive_tx_hash.clone().into(), None, ) { // If we are inactive and no transaction has been seen in the expected validity window // after an epoch, reset our inactive state log::debug!("Resetting state to re-send reactivate transactions since we are inactive and validity window doesn't contain the transaction sent"); - self.validator_state = None; + self.inactivity_state = None; } } } } fn init_epoch(&mut self) { - *self.slot_band.write() = None; + self.state.write().slot_band = None; if !self.is_synced() { return; @@ -334,12 +336,13 @@ where let blockchain = self.blockchain.read(); let validators = blockchain.current_validators().unwrap(); - *self.slot_band.write() = validators.get_slot_band_by_address(&self.validator_address()); + let mut state = self.state.write(); + state.slot_band = validators.get_slot_band_by_address(&state.validator_address); - if let Some(slot_band) = *self.slot_band.read() { + if let Some(slot_band) = state.slot_band { let epoch_validator = validators.get_validator_by_slot_band(slot_band); log::info!( - validator_address = %self.validator_address(), + validator_address = %state.validator_address, validator_slot_band = slot_band, epoch_number = blockchain.epoch_number(), slots = ?epoch_validator.slots, @@ -347,9 +350,8 @@ where ); // Update the validator key to be the expected one (relevant in case of a key rotation). - if self + if state .voting_keys - .write() .update_current_key(epoch_validator.voting_key.compressed()) .is_err() { @@ -357,27 +359,27 @@ where } } else { log::info!( - validator_address = %self.validator_address(), + validator_address = %state.validator_address, epoch_number = blockchain.epoch_number(), "We are NOT ELECTED in this epoch" ); } // Inform the network about the current validator ID. - self.network.set_validator_id(*self.slot_band.read()); + self.network.set_validator_id(state.slot_band); // Set the elected validators of the current epoch in the network as well. self.network.set_validators(validators); // Check validator configuration - if let Some(validator) = self.get_validator(&blockchain) { + if let Some(validator) = state.get_validator(&blockchain) { // Compare configured validator voting key to the one in the contract to make sure it is the same. - if validator.voting_key != self.current_voting_key().public_key.compress() { + if validator.voting_key != state.voting_keys.get_current_key().public_key.compress() { error!("Invalid validator configuration: Configured voting key does not match voting key in staking contract"); } // Compare configured validator signing key to the one in the contract to make sure it is the same. - if validator.signing_key != self.signing_key().public { + if validator.signing_key != state.signing_key.public { error!("Invalid validator configuration: Configured signing key does not match signing key in staking contract"); } } @@ -387,10 +389,16 @@ where self.macro_producer = None; self.micro_producer = None; - if !self.is_elected() || !self.is_synced() { + if !self.is_synced() { return; } + let state = self.state.read(); + let Some(slot_band) = state.slot_band else { + // Not elected. + return; + }; + let blockchain = self.blockchain.read(); if let Some(hash) = head_hash { @@ -403,7 +411,10 @@ where let head = blockchain.head(); let next_block_number = head.block_number() + 1; let network_id = head.network(); - let block_producer = BlockProducer::new(self.signing_key(), self.current_voting_key()); + let block_producer = BlockProducer::new( + state.signing_key.clone(), + state.voting_keys.get_current_key(), + ); debug!( next_block_number = next_block_number, @@ -421,7 +432,7 @@ where Arc::clone(&self.blockchain), Arc::clone(&self.network), block_producer, - self.validator_slot_band(), + slot_band, active_validators, network_id, next_block_number, @@ -430,9 +441,8 @@ where )); } BlockType::Micro => { - let equivocation_proofs = self - .consensus_state - .read() + let equivocation_proofs = state + .consensus .equivocation_proofs .get_equivocation_proofs_for_block(Self::EQUIVOCATION_PROOFS_MAX_SIZE); let prev_seed = head.seed().clone(); @@ -442,7 +452,7 @@ where Arc::clone(&self.mempool_task.mempool), Arc::clone(&self.network), block_producer, - self.validator_slot_band(), + slot_band, equivocation_proofs, prev_seed, next_block_number, @@ -454,7 +464,7 @@ where } fn pause(&mut self) { - *self.slot_band.write() = None; + self.state.write().slot_band = None; self.macro_producer = None; self.micro_producer = None; } @@ -489,8 +499,9 @@ where .expect("Head block not found"); // Update mempool and blockchain state - self.consensus_state + self.state .write() + .consensus .equivocation_proofs .apply_block(&block); @@ -504,14 +515,14 @@ where new_chain: &[(Blake2bHash, Block)], ) { // Update mempool and blockchain state - let mut consensus_state = self.consensus_state.write(); + let mut state = self.state.write(); for (_hash, block) in old_chain.iter() { - consensus_state.equivocation_proofs.revert_block(block); + state.consensus.equivocation_proofs.revert_block(block); } for (_hash, block) in new_chain.iter() { - consensus_state.equivocation_proofs.apply_block(block); + state.consensus.equivocation_proofs.apply_block(block); } - drop(consensus_state); + drop(state); let head_hash = &new_chain.last().expect("new_chain must not be empty").0; self.init_block_producer(Some(head_hash)); @@ -532,8 +543,9 @@ where { return; } - self.consensus_state + self.state .write() + .consensus .equivocation_proofs .insert(proof); } @@ -633,8 +645,9 @@ where /// Publish our own validator record to the DHT. fn publish_dht(&self) { - let key_pair = self.signing_key(); - let validator_address = self.validator_address(); + let state = self.state.read(); + let key_pair = state.signing_key.clone(); + let validator_address = state.validator_address.clone(); let network = Arc::clone(&self.network); spawn(async move { @@ -644,50 +657,26 @@ where }); } - /// Checks whether we are an elected validator in the current epoch. - fn is_elected(&self) -> bool { - self.slot_band.read().is_some() - } - /// Checks whether the validator fulfills the conditions for producing valid blocks. /// This includes having consensus, being able to extend the history tree and to enforce transaction validity. fn is_synced(&self) -> bool { self.consensus.is_ready_for_validation() } - fn get_validator(&self, blockchain: &Blockchain) -> Option { - let validator_address = self.validator_address(); - blockchain - .get_staking_contract_if_complete(None) - .and_then(|staking_contract| { - // Then fetch the validator to see if it is active. - let data_store = blockchain.get_staking_contract_store(); - let txn = blockchain.read_transaction(); - staking_contract.get_validator(&data_store.read(&txn), &validator_address) - }) - } - - fn get_staking_state(&self, blockchain: &Blockchain) -> ValidatorStakingState { - self.get_validator(blockchain).map_or( - ValidatorStakingState::UnknownOrNoStake, - |validator| match validator.inactive_from { - Some(_) => ValidatorStakingState::Inactive(validator.jailed_from), - None => ValidatorStakingState::Active, - }, - ) - } - fn reactivate(&self, blockchain: &Blockchain) -> InactivityState { let validity_start_height = blockchain.block_number(); - let reactivate_transaction = TransactionBuilder::new_reactivate_validator( - &self.fee_key(), - self.validator_address(), - &self.signing_key(), - Coin::ZERO, - validity_start_height, - blockchain.network_id(), - ); + let reactivate_transaction = { + let state = self.state.read(); + TransactionBuilder::new_reactivate_validator( + &state.fee_key, + state.validator_address.clone(), + &state.signing_key, + Coin::ZERO, + validity_start_height, + blockchain.network_id(), + ) + }; let tx_hash = reactivate_transaction.hash(); let cn = self.consensus.clone(); @@ -708,36 +697,8 @@ where } } - pub fn validator_slot_band(&self) -> u16 { - self.slot_band.read().expect("Validator not elected") - } - - pub fn validator_address(&self) -> Address { - self.validator_address.read().clone() - } - - pub fn current_voting_key(&self) -> BlsKeyPair { - self.voting_keys.read().get_current_key() - } - - pub fn signing_key(&self) -> SchnorrKeyPair { - self.signing_key.read().clone() - } - - pub fn fee_key(&self) -> SchnorrKeyPair { - self.fee_key.read().clone() - } - - pub fn proxy(&self) -> ValidatorProxy { - ValidatorProxy { - validator_address: Arc::clone(&self.validator_address), - signing_key: Arc::clone(&self.signing_key), - voting_keys: Arc::clone(&self.voting_keys), - fee_key: Arc::clone(&self.fee_key), - automatic_reactivate: Arc::clone(&self.automatic_reactivate), - slot_band: Arc::clone(&self.slot_band), - consensus_state: Arc::clone(&self.consensus_state), - } + pub fn state(&self) -> &Arc> { + &self.state } #[cfg(feature = "metrics")] @@ -821,7 +782,7 @@ where } // If we are an active validator, participate in block production. - if self.is_synced() && self.is_elected() { + if self.is_synced() && self.state.read().is_elected() { if self.macro_producer.is_some() { self.poll_macro(cx); } @@ -833,26 +794,29 @@ where // Once the validator can be active is established, check the validator staking state. if self.is_synced() { let blockchain = self.blockchain.read(); - match self.get_staking_state(&blockchain) { + let state = self.state.read(); + match state.get_staking_state(&blockchain) { ValidatorStakingState::Active => { + drop(state); drop(blockchain); - if self.validator_state.is_some() { - self.validator_state = None; + if self.inactivity_state.is_some() { + self.inactivity_state = None; info!("Automatically reactivated."); } } ValidatorStakingState::Inactive(jailed_from) => { - if self.validator_state.is_none() + drop(state); + if self.inactivity_state.is_none() && jailed_from .map(|jailed_from| { blockchain.block_number() >= Policy::block_after_jail(jailed_from) }) .unwrap_or(true) - && self.automatic_reactivate.load(Ordering::Acquire) + && self.state.read().automatic_reactivate { let inactivity_state = self.reactivate(&blockchain); drop(blockchain); - self.validator_state = Some(inactivity_state); + self.inactivity_state = Some(inactivity_state); } } ValidatorStakingState::UnknownOrNoStake => {} diff --git a/validator/tests/mock.rs b/validator/tests/mock.rs index 92e0510fcb..ccbd5903c5 100644 --- a/validator/tests/mock.rs +++ b/validator/tests/mock.rs @@ -248,14 +248,14 @@ async fn validator_can_catch_up() { .network .disconnect(CloseReason::GoingOffline) .await; - let id1 = validator.validator_slot_band(); + let id1 = validator.state().read().slot_band.expect("elected"); let validator = validator_for_slot(&mut validators, 2, 2); validator .consensus .network .disconnect(CloseReason::GoingOffline) .await; - let id2 = validator.validator_slot_band(); + let id2 = validator.state().read().slot_band.expect("elected"); assert_ne!(id2, id1); // ideally we would remove the validators from the vec for them to not even execute. @@ -271,8 +271,8 @@ async fn validator_can_catch_up() { .network .disconnect(CloseReason::GoingOffline) .await; - assert_ne!(id1, validator.validator_slot_band()); - assert_ne!(id2, validator.validator_slot_band()); + assert_ne!(id1, validator.state().read().slot_band.expect("elected")); + assert_ne!(id2, validator.state().read().slot_band.expect("elected")); (validator, validator.consensus.network.clone()) }; // assert_eq!(validators.len(), 7); @@ -282,7 +282,7 @@ async fn validator_can_catch_up() { let mut events = blockchain.read().notifier_as_stream(); let slots: Vec<_> = blockchain.read().current_validators().unwrap().validators - [validator.validator_slot_band() as usize] + [validator.state().read().slot_band.expect("elected") as usize] .slots .clone() .collect(); @@ -296,8 +296,8 @@ async fn validator_can_catch_up() { // Manually construct a skip block for the validator let vc = create_skip_block_update( skip_block_info, - validator.current_voting_key(), - validator.validator_slot_band(), + validator.state().read().voting_keys.get_current_key(), + validator.state().read().slot_band.expect("elected"), &slots, );