Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dynamic min authority count #4224

Merged
merged 3 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions state-chain/cf-integration-tests/src/funding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ fn can_calculate_account_apy() {
#[test]
fn apy_can_be_above_100_percent() {
const EPOCH_BLOCKS: u32 = 1_000;
const MAX_AUTHORITIES: u32 = 1;
const NUM_BACKUPS: u32 = 1;
const MAX_AUTHORITIES: u32 = 2;
const NUM_BACKUPS: u32 = 2;
super::genesis::default()
.blocks_per_epoch(EPOCH_BLOCKS)
.max_authorities(MAX_AUTHORITIES)
Expand All @@ -266,10 +266,11 @@ fn apy_can_be_above_100_percent() {

// APY rate of > 100% can be calculated correctly.
let total = Flip::balance(&validator);
let reward = Emissions::current_authority_emission_per_block() * YEAR as u128;
let reward = Emissions::current_authority_emission_per_block() * YEAR as u128 /
MAX_AUTHORITIES as u128;
let apy_basis_point =
FixedU64::from_rational(reward, total).checked_mul_int(10_000u32).unwrap();
assert_eq!(apy_basis_point, 242_543_802u32);
assert_eq!(apy_basis_point, 241_377_726u32);
assert_eq!(calculate_account_apy(&validator), Some(apy_basis_point));
});
}
6 changes: 5 additions & 1 deletion state-chain/cf-integration-tests/src/mock_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ use crate::{
threshold_signing::{EthKeyComponents, KeyUtils},
GENESIS_KEY_SEED,
};
use cf_primitives::{AccountRole, AuthorityCount, BlockNumber, FlipBalance, GENESIS_EPOCH};
use cf_primitives::{
AccountRole, AuthorityCount, BlockNumber, FlipBalance, DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
GENESIS_EPOCH,
};

pub struct ExtBuilder {
pub genesis_accounts: Vec<(AccountId, AccountRole, FlipBalance)>,
Expand Down Expand Up @@ -163,6 +166,7 @@ impl ExtBuilder {
min_size: self.min_authorities,
max_size: self.max_authorities,
max_expansion: self.max_authorities,
max_authority_set_contraction_percentage: DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
},
ethereum_vault: EthereumVaultConfig {
vault_key: Some(ethereum_vault_key),
Expand Down
9 changes: 8 additions & 1 deletion state-chain/node/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use cf_chains::{
dot::{PolkadotAccountId, PolkadotHash},
ChainState,
};
use cf_primitives::{chains::assets, AccountRole, AssetAmount, AuthorityCount, NetworkEnvironment};
use cf_primitives::{
chains::assets, AccountRole, AssetAmount, AuthorityCount, NetworkEnvironment,
DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
};

use cf_chains::{
btc::{BitcoinFeeInfo, BitcoinTrackedData},
Expand Down Expand Up @@ -231,6 +234,7 @@ pub fn inner_cf_development_config(
testnet::SNOW_WHITE_SR25519.into(),
1,
devnet::MAX_AUTHORITIES,
DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
EnvironmentConfig {
flip_token_address: flip_token_address.into(),
eth_usdc_address: eth_usdc_address.into(),
Expand Down Expand Up @@ -358,6 +362,7 @@ macro_rules! network_spec {
SNOW_WHITE_SR25519.into(),
MIN_AUTHORITIES,
MAX_AUTHORITIES,
DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
EnvironmentConfig {
flip_token_address: flip_token_address.into(),
eth_usdc_address: eth_usdc_address.into(),
Expand Down Expand Up @@ -428,6 +433,7 @@ fn testnet_genesis(
root_key: AccountId,
min_authorities: AuthorityCount,
max_authorities: AuthorityCount,
max_authority_set_contraction_percentage: Percent,
config_set: EnvironmentConfig,
eth_init_agg_key: [u8; 33],
ethereum_deployment_block: u64,
Expand Down Expand Up @@ -556,6 +562,7 @@ fn testnet_genesis(
min_size: min_authorities,
max_size: max_authorities,
max_expansion: max_authorities,
max_authority_set_contraction_percentage,
},
session: SessionConfig {
keys: initial_authorities
Expand Down
28 changes: 27 additions & 1 deletion state-chain/pallets/cf-validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ mod migrations;
mod rotation_state;
pub use auction_resolver::*;

use cf_primitives::{AuthorityCount, EpochIndex, NodeCFEVersions, SemVer, FLIPPERINOS_PER_FLIP};
use cf_primitives::{
AuthorityCount, EpochIndex, NodeCFEVersions, SemVer, DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
FLIPPERINOS_PER_FLIP,
};

use cf_traits::{
impl_pallet_safe_mode, offence_reporting::OffenceReporter, AsyncResult, AuthoritiesCfeVersions,
Expand Down Expand Up @@ -62,6 +65,7 @@ pub enum PalletConfigUpdate {
AuthoritySetMinSize { min_size: AuthorityCount },
AuctionParameters { parameters: SetSizeParameters },
MinimumReportedCfeVersion { version: SemVer },
MaxAuthoritySetContractionPercentage { percentage: Percent },
}

type RuntimeRotationState<T> =
Expand Down Expand Up @@ -286,6 +290,13 @@ pub mod pallet {
#[pallet::getter(fn minimum_reported_cfe_version)]
pub(super) type MinimumReportedCfeVersion<T: Config> = StorageValue<_, SemVer, ValueQuery>;

/// Determines the maximum allowed reduction of authority set size in percents between two
/// consecutive epochs.
#[pallet::storage]
#[pallet::getter(fn max_authority_set_contraction_percentage)]
pub(super) type MaxAuthoritySetContractionPercentage<T: Config> =
StorageValue<_, Percent, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
Expand Down Expand Up @@ -533,6 +544,9 @@ pub mod pallet {
PalletConfigUpdate::MinimumReportedCfeVersion { version } => {
MinimumReportedCfeVersion::<T>::put(version);
},
PalletConfigUpdate::MaxAuthoritySetContractionPercentage { percentage } => {
MaxAuthoritySetContractionPercentage::<T>::put(percentage);
},
}

Self::deposit_event(Event::PalletConfigUpdated { update });
Expand Down Expand Up @@ -780,6 +794,7 @@ pub mod pallet {
pub min_size: AuthorityCount,
pub max_size: AuthorityCount,
pub max_expansion: AuthorityCount,
pub max_authority_set_contraction_percentage: Percent,
}

impl<T: Config> Default for GenesisConfig<T> {
Expand All @@ -796,6 +811,7 @@ pub mod pallet {
min_size: 3,
max_size: 15,
max_expansion: 5,
max_authority_set_contraction_percentage: DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
}
}
}
Expand All @@ -811,6 +827,9 @@ pub mod pallet {
BackupRewardNodePercentage::<T>::set(self.backup_reward_node_percentage);
AuthoritySetMinSize::<T>::set(self.authority_set_min_size);
VanityNames::<T>::put(&self.genesis_vanity_names);
MaxAuthoritySetContractionPercentage::<T>::set(
self.max_authority_set_contraction_percentage,
);

CurrentEpoch::<T>::set(GENESIS_EPOCH);

Expand Down Expand Up @@ -1091,6 +1110,13 @@ impl<T: Config> Pallet<T> {
fn try_start_keygen(rotation_state: RuntimeRotationState<T>) {
let candidates = rotation_state.authority_candidates();
let SetSizeParameters { min_size, .. } = AuctionParameters::<T>::get();

let min_size = sp_std::cmp::max(
min_size,
(Percent::one().saturating_sub(MaxAuthoritySetContractionPercentage::<T>::get())) *
Self::current_authority_count(),
);

if (candidates.len() as u32) < min_size {
log::warn!(
target: "cf-validator",
Expand Down
1 change: 1 addition & 0 deletions state-chain/pallets/cf-validator/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ cf_test_utilities::impl_test_helpers! {
min_size: MIN_AUTHORITY_SIZE,
max_size: MAX_AUTHORITY_SIZE,
max_expansion: MAX_AUTHORITY_SET_EXPANSION,
max_authority_set_contraction_percentage: DEFAULT_MAX_AUTHORITY_SET_CONTRACTION,
},
},
||{
Expand Down
30 changes: 29 additions & 1 deletion state-chain/pallets/cf-validator/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,12 @@ const AUTHORITIES: Range<u64> = 0..10;

lazy_static::lazy_static! {
/// How many candidates can fail without preventing us from re-trying keygen
static ref MAX_ALLOWED_KEYGEN_OFFENDERS: usize = CANDIDATES.count().checked_sub(MIN_AUTHORITY_SIZE as usize).unwrap();
static ref MAX_ALLOWED_KEYGEN_OFFENDERS: usize = {

let min_size = std::cmp::max(MIN_AUTHORITY_SIZE, (Percent::one() - DEFAULT_MAX_AUTHORITY_SET_CONTRACTION) * AUTHORITIES.count() as u32);

CANDIDATES.count().checked_sub(min_size as usize).unwrap()
};

/// How many current authorities can fail to leave enough healthy ones to handover the key
static ref MAX_ALLOWED_SHARING_OFFENDERS: usize = {
Expand Down Expand Up @@ -788,6 +793,29 @@ mod keygen {
assert_rotation_aborted();
});
}

#[test]
fn rotation_aborts_if_candidates_below_min_percentage() {
new_test_ext().execute_with(|| {
// Ban half of the candidates:
let failing_count = CANDIDATES.count() / 2;
let remaining_count = CANDIDATES.count() - failing_count;

// We still have enough candidates according to auction resolver parameters:
assert!(remaining_count > MIN_AUTHORITY_SIZE as usize);

// But the rotation should be aborted since authority count would drop too much
// compared to the previous set:
assert!(
remaining_count <
(Percent::one() - DEFAULT_MAX_AUTHORITY_SET_CONTRACTION) *
AUTHORITIES.count()
);

failed_keygen_with_offenders(CANDIDATES.take(failing_count));
assert_rotation_aborted();
});
}
}

#[cfg(test)]
Expand Down
6 changes: 5 additions & 1 deletion state-chain/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::sp_runtime::{
traits::{IdentifyAccount, Verify},
MultiSignature, RuntimeDebug,
MultiSignature, Percent, RuntimeDebug,
};
use scale_info::TypeInfo;
use semver::{Error, Version};
Expand Down Expand Up @@ -75,6 +75,10 @@ pub const SECONDS_PER_BLOCK: u64 = MILLISECONDS_PER_BLOCK / 1000;

pub const STABLE_ASSET: Asset = Asset::Usdc;

/// Determines the default (genesis) maximum allowed reduction of authority set size in
/// between two consecutive epochs.
pub const DEFAULT_MAX_AUTHORITY_SET_CONTRACTION: Percent = Percent::from_percent(30);

// Polkadot extrinsics are uniquely identified by <block number>-<extrinsic index>
// https://wiki.polkadot.network/docs/build-protocol-info
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Eq)]
Expand Down