Skip to content
20 changes: 20 additions & 0 deletions execution_engine/src/engine_state/engine_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub const DEFAULT_VESTING_SCHEDULE_LENGTH_MILLIS: u64 =
VESTING_SCHEDULE_LENGTH_DAYS as u64 * DAY_MILLIS as u64;
/// Default maximum number of delegators per validator.
pub const DEFAULT_MAX_DELEGATORS_PER_VALIDATOR: u32 = 1200;
/// Default delay (in number of eras) before delegators are undelegated from an inactive validator.
pub const DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY: u64 = 36;
/// Default value for allowing auction bids.
pub const DEFAULT_ALLOW_AUCTION_BIDS: bool = true;
/// Default value for allowing unrestricted transfers.
Expand All @@ -61,6 +63,7 @@ pub struct EngineConfig {
/// Vesting schedule period in milliseconds.
vesting_schedule_period_millis: u64,
max_delegators_per_validator: u32,
inactive_validator_undelegation_delay: u64,
wasm_config: WasmConfig,
system_config: SystemConfig,
protocol_version: ProtocolVersion,
Expand Down Expand Up @@ -92,6 +95,7 @@ impl Default for EngineConfig {
strict_argument_checking: DEFAULT_STRICT_ARGUMENT_CHECKING,
vesting_schedule_period_millis: DEFAULT_VESTING_SCHEDULE_LENGTH_MILLIS,
max_delegators_per_validator: DEFAULT_MAX_DELEGATORS_PER_VALIDATOR,
inactive_validator_undelegation_delay: DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY,
wasm_config: WasmConfig::default(),
system_config: SystemConfig::default(),
administrative_accounts: Default::default(),
Expand Down Expand Up @@ -194,6 +198,11 @@ impl EngineConfig {
pub fn set_protocol_version(&mut self, protocol_version: ProtocolVersion) {
self.protocol_version = protocol_version;
}

/// Returns the inactive validator undelegation delay.
pub fn inactive_validator_undelegation_delay(&self) -> u64 {
self.inactive_validator_undelegation_delay
}
}

/// A builder for an [`EngineConfig`].
Expand All @@ -209,6 +218,7 @@ pub struct EngineConfigBuilder {
strict_argument_checking: Option<bool>,
vesting_schedule_period_millis: Option<u64>,
max_delegators_per_validator: Option<u32>,
inactive_validator_undelegation_delay: Option<u64>,
wasm_config: Option<WasmConfig>,
system_config: Option<SystemConfig>,
protocol_version: Option<ProtocolVersion>,
Expand Down Expand Up @@ -266,6 +276,12 @@ impl EngineConfigBuilder {
self
}

/// Sets the max delegators per validator config option.
pub fn with_inactive_validator_undelegation_delay(mut self, value: u64) -> Self {
self.inactive_validator_undelegation_delay = Some(value);
self
}

/// Sets the wasm config options.
pub fn with_wasm_config(mut self, wasm_config: WasmConfig) -> Self {
self.wasm_config = Some(wasm_config);
Expand Down Expand Up @@ -394,6 +410,9 @@ impl EngineConfigBuilder {
.max_delegators_per_validator
.unwrap_or(DEFAULT_MAX_DELEGATORS_PER_VALIDATOR);
let compute_rewards = self.compute_rewards.unwrap_or(DEFAULT_COMPUTE_REWARDS);
let inactive_validator_undelegation_delay = self
.inactive_validator_undelegation_delay
.unwrap_or(DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY);

EngineConfig {
max_associated_keys,
Expand All @@ -411,6 +430,7 @@ impl EngineConfigBuilder {
vesting_schedule_period_millis,
max_delegators_per_validator,
compute_rewards,
inactive_validator_undelegation_delay,
}
}
}
18 changes: 17 additions & 1 deletion execution_engine/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,8 +829,24 @@ where
let delegation_rate =
Self::get_named_argument(runtime_args, auction::ARG_DELEGATION_RATE)?;
let amount = Self::get_named_argument(runtime_args, auction::ARG_AMOUNT)?;
let inactive_validator_undelegation_delay: Option<u64> = Self::get_named_argument(
runtime_args,
auction::ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY,
)?;

let maximum_inactive_validator_undelegation_delay = self
.context()
.engine_config()
.inactive_validator_undelegation_delay();

let result = runtime
.add_bid(account_hash, delegation_rate, amount)
.add_bid(
account_hash,
delegation_rate,
amount,
inactive_validator_undelegation_delay,
maximum_inactive_validator_undelegation_delay,
)
.map_err(Self::reverter)?;

CLValue::from_t(result).map_err(Self::reverter)
Expand Down
41 changes: 37 additions & 4 deletions execution_engine_testing/test_support/src/wasm_test_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ use casper_execution_engine::engine_state::{
};
use casper_storage::{
data_access_layer::{
balance::BalanceHandling, AuctionMethod, BalanceIdentifier, BalanceRequest, BalanceResult,
BiddingRequest, BiddingResult, BidsRequest, BlockRewardsRequest, BlockRewardsResult,
BlockStore, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, FeeRequest,
FeeResult, FlushRequest, FlushResult, GenesisRequest, GenesisResult, ProofHandling,
balance::BalanceHandling,
inactive_validators::{
InactiveValidatorsUndelegationRequest, InactiveValidatorsUndelegationResult,
},
AuctionMethod, BalanceIdentifier, BalanceRequest, BalanceResult, BiddingRequest,
BiddingResult, BidsRequest, BlockRewardsRequest, BlockRewardsResult, BlockStore,
DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, FeeRequest, FeeResult,
FlushRequest, FlushResult, GenesisRequest, GenesisResult, ProofHandling,
ProtocolUpgradeRequest, ProtocolUpgradeResult, PruneRequest, PruneResult, QueryRequest,
QueryResult, RoundSeigniorageRateRequest, RoundSeigniorageRateResult, StepRequest,
StepResult, SystemEntityRegistryPayload, SystemEntityRegistryRequest,
Expand Down Expand Up @@ -1019,6 +1023,35 @@ where
distribute_block_rewards_result
}

/// Undelegate delegators from inactive validators.
pub fn undelegate_from_inactive_validators(
&mut self,
pre_state_hash: Option<Digest>,
protocol_version: ProtocolVersion,
time: u64,
) -> InactiveValidatorsUndelegationResult {
let pre_state_hash = pre_state_hash.or(self.post_state_hash).unwrap();
let native_runtime_config = self.native_runtime_config();
let inactive_validators_req = InactiveValidatorsUndelegationRequest::new(
native_runtime_config,
pre_state_hash,
protocol_version,
BlockTime::new(time),
);
let inactive_validators_result = self
.data_access_layer
.undelegate_from_inactive_validators(inactive_validators_req);

if let InactiveValidatorsUndelegationResult::Success {
post_state_hash, ..
} = inactive_validators_result
{
self.post_state_hash = Some(post_state_hash);
}

inactive_validators_result
}

/// Expects a successful run
#[track_caller]
pub fn expect_success(&mut self) -> &mut Self {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ use casper_types::{
auction::{
self, BidsExt, DelegationRate, EraValidators, Error as AuctionError, UnbondingPurses,
ValidatorWeights, ARG_AMOUNT, ARG_DELEGATION_RATE, ARG_DELEGATOR, ARG_ENTRY_POINT,
ARG_NEW_PUBLIC_KEY, ARG_NEW_VALIDATOR, ARG_PUBLIC_KEY, ARG_REWARDS_MAP, ARG_VALIDATOR,
ERA_ID_KEY, INITIAL_ERA_ID, METHOD_DISTRIBUTE,
ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ARG_NEW_PUBLIC_KEY, ARG_NEW_VALIDATOR,
ARG_PUBLIC_KEY, ARG_REWARDS_MAP, ARG_VALIDATOR, ERA_ID_KEY, INITIAL_ERA_ID,
METHOD_DISTRIBUTE,
},
},
EntityAddr, EraId, GenesisAccount, GenesisConfigBuilder, GenesisValidator, Key, Motes,
Expand Down Expand Up @@ -5003,3 +5004,207 @@ fn should_handle_excessively_long_bridge_record_chains() {
)
);
}

#[ignore]
#[test]
fn should_undelegate_from_inactive_validator_after_delay() {
// fund accounts
let system_fund_request = ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
CONTRACT_TRANSFER_TO_ACCOUNT,
runtime_args! {
ARG_TARGET => *SYSTEM_ADDR,
ARG_AMOUNT => U512::from(SYSTEM_TRANSFER_AMOUNT)
},
)
.build();

let validator_1_fund_request = ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
CONTRACT_TRANSFER_TO_ACCOUNT,
runtime_args! {
ARG_TARGET => *NON_FOUNDER_VALIDATOR_1_ADDR,
ARG_AMOUNT => U512::from(TRANSFER_AMOUNT)
},
)
.build();

let validator_2_fund_request = ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
CONTRACT_TRANSFER_TO_ACCOUNT,
runtime_args! {
ARG_TARGET => *NON_FOUNDER_VALIDATOR_2_ADDR,
ARG_AMOUNT => U512::from(TRANSFER_AMOUNT)
},
)
.build();

let delegator_1_fund_request = ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
CONTRACT_TRANSFER_TO_ACCOUNT,
runtime_args! {
ARG_TARGET => *BID_ACCOUNT_1_ADDR,
ARG_AMOUNT => U512::from(TRANSFER_AMOUNT)
},
)
.build();

let delegator_2_fund_request = ExecuteRequestBuilder::standard(
*DEFAULT_ACCOUNT_ADDR,
CONTRACT_TRANSFER_TO_ACCOUNT,
runtime_args! {
ARG_TARGET => *BID_ACCOUNT_2_ADDR,
ARG_AMOUNT => U512::from(TRANSFER_AMOUNT)
},
)
.build();

// setup validator bids
let validator_1_add_bid_request = ExecuteRequestBuilder::standard(
*NON_FOUNDER_VALIDATOR_1_ADDR,
CONTRACT_ADD_BID,
runtime_args! {
ARG_PUBLIC_KEY => NON_FOUNDER_VALIDATOR_1_PK.clone(),
ARG_AMOUNT => U512::from(ADD_BID_AMOUNT_1),
ARG_DELEGATION_RATE => ADD_BID_DELEGATION_RATE_1,
ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY => None::<u64>,
},
)
.build();

let validator_2_add_bid_request = ExecuteRequestBuilder::standard(
*NON_FOUNDER_VALIDATOR_2_ADDR,
CONTRACT_ADD_BID,
runtime_args! {
ARG_PUBLIC_KEY => NON_FOUNDER_VALIDATOR_2_PK.clone(),
ARG_AMOUNT => U512::from(ADD_BID_AMOUNT_2),
ARG_DELEGATION_RATE => ADD_BID_DELEGATION_RATE_2,
ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY => Some(5u64),
},
)
.build();

let delegator_1_validator_1_delegate_request = ExecuteRequestBuilder::standard(
*BID_ACCOUNT_1_ADDR,
CONTRACT_DELEGATE,
runtime_args! {
ARG_AMOUNT => U512::from(DELEGATE_AMOUNT_1),
ARG_VALIDATOR => NON_FOUNDER_VALIDATOR_1_PK.clone(),
ARG_DELEGATOR => BID_ACCOUNT_1_PK.clone(),
},
)
.build();

// setup delegator bids
let delegator_2_validator_2_delegate_request = ExecuteRequestBuilder::standard(
*BID_ACCOUNT_2_ADDR,
CONTRACT_DELEGATE,
runtime_args! {
ARG_AMOUNT => U512::from(DELEGATE_AMOUNT_2),
ARG_VALIDATOR => NON_FOUNDER_VALIDATOR_2_PK.clone(),
ARG_DELEGATOR => BID_ACCOUNT_2_PK.clone(),
},
)
.build();

let post_genesis_requests = vec![
system_fund_request,
delegator_1_fund_request,
delegator_2_fund_request,
validator_1_fund_request,
validator_2_fund_request,
validator_1_add_bid_request,
validator_2_add_bid_request,
delegator_1_validator_1_delegate_request,
delegator_2_validator_2_delegate_request,
];

let mut builder = LmdbWasmTestBuilder::default();

builder.run_genesis(LOCAL_GENESIS_REQUEST.clone());

for request in post_genesis_requests {
builder.exec(request).commit().expect_success();
}

let chainspec = builder.chainspec();
let global_inactive_validator_undelegation_delay =
chainspec.core_config.inactive_validator_undelegation_delay;

// check validator-level delay config validation
let validator_1_add_bid_request = ExecuteRequestBuilder::standard(
*NON_FOUNDER_VALIDATOR_1_ADDR,
CONTRACT_ADD_BID,
runtime_args! {
ARG_PUBLIC_KEY => NON_FOUNDER_VALIDATOR_1_PK.clone(),
ARG_AMOUNT => U512::from(ADD_BID_AMOUNT_1),
ARG_DELEGATION_RATE => ADD_BID_DELEGATION_RATE_1,
ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY => Some(global_inactive_validator_undelegation_delay + 1),
},
)
.build();
builder.exec(validator_1_add_bid_request).expect_failure();

let mut timestamp = DEFAULT_GENESIS_TIMESTAMP_MILLIS;
let era_id = builder.get_era();

// evict validator 1
builder.run_auction(timestamp, vec![NON_FOUNDER_VALIDATOR_1_PK.clone()]);
timestamp += WEEK_MILLIS;

let bids = builder.get_bids();
assert_eq!(bids.len(), 4);

let validator_bid = bids
.validator_bid(&NON_FOUNDER_VALIDATOR_1_PK.clone())
.expect("should have validator 1 bid");
assert!(validator_bid.inactive());
assert_eq!(validator_bid.eviction_era(), Some(era_id));

// advance eras by global delay - 1 since running the auction already advanced by 1
builder.advance_eras_by(global_inactive_validator_undelegation_delay - 1);

// undelegate from inactive validators
builder.undelegate_from_inactive_validators(None, DEFAULT_PROTOCOL_VERSION, timestamp);

let bids = builder.get_bids();
assert_eq!(bids.len(), 3);

// check that UnbondingPurse has been created for delegator 1
let unbonding_purses: UnbondingPurses = builder.get_unbonds();
let unbond_list = unbonding_purses
.get(&BID_ACCOUNT_1_ADDR)
.expect("should have unbonded");
assert_eq!(unbond_list.len(), 1);

let era_id = builder.get_era();

// evict validator 2
builder.run_auction(timestamp, vec![NON_FOUNDER_VALIDATOR_2_PK.clone()]);
timestamp += WEEK_MILLIS;

let bids = builder.get_bids();
assert_eq!(bids.len(), 3);

let validator_bid = bids
.validator_bid(&NON_FOUNDER_VALIDATOR_2_PK.clone())
.expect("should have validator 1 bid");
assert!(validator_bid.inactive());
assert_eq!(validator_bid.eviction_era(), Some(era_id));

// advance eras
builder.advance_eras_by(4);

// undelegate from inactive validators
builder.undelegate_from_inactive_validators(None, DEFAULT_PROTOCOL_VERSION, timestamp);

let bids = builder.get_bids();
assert_eq!(bids.len(), 2);

// check that UnbondingPurse has been created for delegator 2
let unbonding_purses: UnbondingPurses = builder.get_unbonds();
let unbond_list = unbonding_purses
.get(&BID_ACCOUNT_2_ADDR)
.expect("should have unbonded");
assert_eq!(unbond_list.len(), 1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,8 @@ fn should_distribute_rewards_after_restaking_delegated_funds() {
public_key: VALIDATOR_1.clone(),
amount,
delegation_rate: 0,
inactive_validator_undelegation_delay: None,
maximum_inactive_validator_undelegation_delay: 36,
}
} else {
AuctionMethod::WithdrawBid {
Expand Down
Loading