Skip to content

Commit

Permalink
Use a cuadratic backup curve for reactivate transactions
Browse files Browse the repository at this point in the history
When a validator is deactivated, the reactivate transaction will have a delay
based on the number of consecutive deactivations the validator has experienced
in the current epoch.
The amount of delayed blocks is of quadratic nature
  • Loading branch information
viquezclaudio committed Jan 14, 2025
1 parent 124258f commit 4bfe5c1
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 132 deletions.
3 changes: 2 additions & 1 deletion test-utils/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub async fn build_validators<N: TestNetwork + NetworkInterface>(
peer_ids: &[u64],
hub: &mut Option<MockHub>,
is_prover_active: bool,
automatic_reactivate: bool,
) -> Vec<Validator<ValidatorNetworkImpl<N>>>
where
N::Error: Send + Sync,
Expand Down Expand Up @@ -113,7 +114,7 @@ where
let (v, c) = build_validator(
peer_ids[i],
Address::from(&validator_keys[i]),
true,
automatic_reactivate,
signing_keys[i].clone(),
voting_keys[i].clone(),
fee_keys[i].clone(),
Expand Down
7 changes: 4 additions & 3 deletions validator/src/micro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,7 @@ impl<TValidatorNetwork: ValidatorNetwork + 'static> NextProduceMicroBlockEvent<T

if !self.health_state.read().publish {
log::warn!(block = block.block_number(), "Not publishing block");
let event = ProduceMicroBlockEvent::MicroBlock;
break Some(Some(event));
break Some(Some(ProduceMicroBlockEvent::MicroBlock));
}

// Publish the block. It is valid as we have just created it.
Expand Down Expand Up @@ -198,7 +197,9 @@ impl<TValidatorNetwork: ValidatorNetwork + 'static> NextProduceMicroBlockEvent<T
continue;
}

self.health_state.write().blk_cnt += 1;
// Each successfull block will decrease the number of inactivations
let current_inactivations = self.health_state.read().inactivations;
self.health_state.write().inactivations = current_inactivations.saturating_sub(1);

let event = result
.map(move |_result| ProduceMicroBlockEvent::MicroBlock)
Expand Down
156 changes: 95 additions & 61 deletions validator/src/validator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{
cmp,
error::Error,
future::Future,
pin::Pin,
Expand Down Expand Up @@ -51,8 +52,12 @@ use crate::{
r#macro::{MappedReturn, ProduceMacroBlock, ProposalTopic},
};

/// The number of blocks a validator needs to produce in time to improve its health
const VALIDATOR_HEALTH_THRESHOLD: u32 = 5;
/// The number of consecutive inactivations from which a validator is considered with yellow health
const VALIDATOR_YELLOW_HEALTH_INACTIVATIONS: u32 = 2;
/// The number of consecutive inactivations from which a validator is considered with yellow health
const VALIDATOR_RED_HEALTH_INACTIVATIONS: u32 = 4;
// The maximum number of blocks the reactivate transaction can be delayed
const MAX_REACTIVATE_DELAY: u32 = 10_000;

#[derive(PartialEq)]
enum ValidatorStakingState {
Expand All @@ -75,18 +80,22 @@ pub struct ConsensusState {
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ValidatorHealth {
Green,
Yellow(u32),
Red(u32),
Yellow,
Red,
}

/// Struct that represents the overall Validator Health
pub struct HealthState {
/// The current validator health
pub health: ValidatorHealth,
/// Number of blocks that we have produced in time(without being inactivated)
pub blk_cnt: u32,
/// For testing/debug purposes control wether produced blocks are published by the validator
/// For testing/debug purposes control whether produced blocks are published by the validator
pub publish: bool,
/// Number of inactivations that have ocurred in the current epoch
pub inactivations: u32,
/// Next block number where the re-activate txns should be sent
pub reactivate_bn: u32,
/// Flag that indicates an ongoing reactivation
pub pending_reactivate: bool,
}

/// Validator inactivity
Expand Down Expand Up @@ -234,7 +243,9 @@ where
let health_state = HealthState {
health: ValidatorHealth::Green,
publish: true,
blk_cnt: 0,
inactivations: 0,
reactivate_bn: 0,
pending_reactivate: false,
};

Self {
Expand Down Expand Up @@ -508,6 +519,10 @@ where
self.on_blockchain_extended(hash);
}
BlockchainEvent::EpochFinalized(ref hash) => {
// Reset the inactivations counter
self.health_state.write().inactivations = 0;
// Reset the validator health every epoch
self.health_state.write().health = ValidatorHealth::Green;
self.init_epoch();
// The on_blockchain_extended is necessary for the order of events to not matter.
self.on_blockchain_extended(hash);
Expand Down Expand Up @@ -537,6 +552,14 @@ where

self.check_reactivate(block.block_number());
self.init_block_producer(Some(hash));

let block_number = block.block_number();
let blockchain = self.blockchain.read();

if block_number == self.health_state.read().reactivate_bn {
let inactivity_state = self.reactivate(&blockchain);
self.validator_state = Some(inactivity_state);
}
}

fn on_blockchain_rebranched(
Expand Down Expand Up @@ -718,6 +741,11 @@ where
)
}

// Computes the next block number where we should send the next reactivate transaction
fn get_reactivate_delay(&self, inactivations: u32) -> u32 {
cmp::min(inactivations.pow(2), MAX_REACTIVATE_DELAY)
}

fn reactivate(&self, blockchain: &Blockchain) -> InactivityState {
let validity_start_height = blockchain.block_number();

Expand All @@ -733,7 +761,7 @@ where

let cn = self.consensus.clone();
spawn(async move {
debug!("Sending reactivate transaction to the network");
info!("Sending reactivate transaction to the network");
if cn
.send_transaction(reactivate_transaction.clone())
.await
Expand Down Expand Up @@ -878,39 +906,34 @@ where
let block_number = blockchain.block_number();
match self.get_staking_state(&blockchain) {
ValidatorStakingState::Active => {
drop(blockchain);
if self.validator_state.is_some() {
drop(blockchain);
self.validator_state = None;
self.health_state.write().pending_reactivate = false;
info!("Automatically reactivated.");
}
let inactivations = self.health_state.read().inactivations;

log::warn!(
address=%self.validator_address.read(),
inactivations,
"Inactivations counter",
);

let validator_health = self.health_state.read().health;

match validator_health {
ValidatorHealth::Green => {}
ValidatorHealth::Yellow(yellow_block_number) => {
debug!(
address = %self.validator_address.read(),
inactivated = yellow_block_number,
good_blocks = %self.health_state.read().blk_cnt,
"Current validator health is yellow",
);
if self.health_state.read().blk_cnt >= VALIDATOR_HEALTH_THRESHOLD {
log::info!("Changing the validator health back to green");
ValidatorHealth::Yellow => {
if inactivations < VALIDATOR_YELLOW_HEALTH_INACTIVATIONS {
log::info!(inactivations, "Changed validator health to green");
self.health_state.write().health = ValidatorHealth::Green;
self.health_state.write().blk_cnt = 0;
}
}
ValidatorHealth::Red(red_block_number) => {
debug!(
address = %self.validator_address.read(),
inactivated = red_block_number,
"Current validator health is red",
);
if self.health_state.read().blk_cnt >= VALIDATOR_HEALTH_THRESHOLD {
log::info!("Changing the validator health back to yellow");
self.health_state.write().health =
ValidatorHealth::Yellow(block_number);
self.health_state.write().blk_cnt = 0;
ValidatorHealth::Red => {
if inactivations < VALIDATOR_RED_HEALTH_INACTIVATIONS {
log::info!(inactivations, "Changed validator health to yellow");
self.health_state.write().health = ValidatorHealth::Yellow;
}
}
}
Expand All @@ -924,37 +947,48 @@ where
.unwrap_or(true)
&& self.automatic_reactivate.load(Ordering::Acquire)
{
let validator_health = self.health_state.read().health;
match validator_health {
ValidatorHealth::Green => {
log::warn!(
address=%self.validator_address.read(),
"The validator was inactivated, changing its health to Yellow",
);
let inactivity_state = self.reactivate(&blockchain);
drop(blockchain);
self.validator_state = Some(inactivity_state);
self.health_state.write().health =
ValidatorHealth::Yellow(block_number);
self.health_state.write().blk_cnt = 0;
}
ValidatorHealth::Yellow(_) => {
log::warn!(
address=%self.validator_address.read(),
"The validator was inactivated again, changing its health to Red",
);
let inactivity_state = self.reactivate(&blockchain);
drop(blockchain);
self.validator_state = Some(inactivity_state);
self.health_state.write().health =
ValidatorHealth::Red(block_number);
self.health_state.write().blk_cnt = 0;
}
ValidatorHealth::Red(_) => {
log::warn!(
"The validator needs human intervention, no automatic reactivate"
);
// Keep track of how many times we have been deactivated in the current epoch.
if !self.health_state.read().pending_reactivate {
let mut health_state = self.health_state.write();
health_state.inactivations += 1;
health_state.reactivate_bn = block_number
+ self.get_reactivate_delay(health_state.inactivations);
health_state.pending_reactivate = true;
drop(health_state);

let inactivations = self.health_state.read().inactivations;

let validator_health = self.health_state.read().health;
match validator_health {
ValidatorHealth::Green => {
if inactivations >= VALIDATOR_YELLOW_HEALTH_INACTIVATIONS {
log::warn!(
inactivations,
"Changed validator health to yellow"
);
self.health_state.write().health = ValidatorHealth::Yellow;
}
}
ValidatorHealth::Yellow => {
if inactivations >= VALIDATOR_RED_HEALTH_INACTIVATIONS {
log::warn!(
inactivations,
"Changed validator health to red"
);
self.health_state.write().health = ValidatorHealth::Red;
}
}
ValidatorHealth::Red => {
log::warn!("Validator health is still red")
}
}

log::warn!(
"Current inactivations counter: {}, next reactivate bn {}, current bn {} ",
inactivations,
self.health_state.read().reactivate_bn,
block_number
);
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions validator/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,14 @@ async fn four_validators_can_create_an_epoch() {
let env =
MdbxDatabase::new_volatile(Default::default()).expect("Could not open a volatile database");

let validators =
build_validators::<Network>(env, &(1u64..=4u64).collect::<Vec<_>>(), &mut None, false)
.await;
let validators = build_validators::<Network>(
env,
&(1u64..=4u64).collect::<Vec<_>>(),
&mut None,
false,
false,
)
.await;

let blockchain = Arc::clone(&validators.first().unwrap().blockchain);

Expand Down
Loading

0 comments on commit 4bfe5c1

Please sign in to comment.