diff --git a/execution_engine/src/engine_state/engine_config.rs b/execution_engine/src/engine_state/engine_config.rs index dd33af8788..3bf5b2fbd4 100644 --- a/execution_engine/src/engine_state/engine_config.rs +++ b/execution_engine/src/engine_state/engine_config.rs @@ -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. @@ -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, @@ -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(), @@ -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`]. @@ -209,6 +218,7 @@ pub struct EngineConfigBuilder { strict_argument_checking: Option, vesting_schedule_period_millis: Option, max_delegators_per_validator: Option, + inactive_validator_undelegation_delay: Option, wasm_config: Option, system_config: Option, protocol_version: Option, @@ -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); @@ -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, @@ -411,6 +430,7 @@ impl EngineConfigBuilder { vesting_schedule_period_millis, max_delegators_per_validator, compute_rewards, + inactive_validator_undelegation_delay, } } } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 7da7b51bdc..cd16587f61 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -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 = 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) diff --git a/execution_engine_testing/test_support/src/wasm_test_builder.rs b/execution_engine_testing/test_support/src/wasm_test_builder.rs index 8dde3053ae..2a9b8310e4 100644 --- a/execution_engine_testing/test_support/src/wasm_test_builder.rs +++ b/execution_engine_testing/test_support/src/wasm_test_builder.rs @@ -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, @@ -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, + 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 { diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs index b29b3bb9e3..f7d0e00861 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/bids.rs @@ -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, @@ -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::, + }, + ) + .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); +} diff --git a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs index 3144f4900b..8b48e4b968 100644 --- a/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs +++ b/execution_engine_testing/tests/src/test/system_contracts/auction/distribute.rs @@ -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 { diff --git a/node/src/components/contract_runtime/error.rs b/node/src/components/contract_runtime/error.rs index cbd9d0cdc9..352d2818c5 100644 --- a/node/src/components/contract_runtime/error.rs +++ b/node/src/components/contract_runtime/error.rs @@ -7,7 +7,10 @@ use thiserror::Error; use casper_execution_engine::engine_state::Error as EngineStateError; use casper_storage::{ - data_access_layer::{BlockRewardsError, FeeError, StepError}, + data_access_layer::{ + inactive_validators::InactiveValidatorsUndelegationError, BlockRewardsError, FeeError, + StepError, + }, global_state::error::Error as GlobalStateError, tracking_copy::TrackingCopyError, }; @@ -89,6 +92,12 @@ pub enum BlockExecutionError { #[serde(skip_serializing)] BlockRewardsError, ), + #[error(transparent)] + InactiveValidatorsUndelegation( + #[from] + #[serde(skip_serializing)] + InactiveValidatorsUndelegationError, + ), /// Failed to compute the approvals checksum. #[error("failed to compute approvals checksum: {0}")] FailedToComputeApprovalsChecksum(bytesrepr::Error), diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index e545d17579..0932159998 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -7,12 +7,16 @@ use casper_execution_engine::engine_state::{ExecutionEngineV1, WasmV1Request, Wa use casper_storage::{ block_store::types::ApprovalsHashes, data_access_layer::{ - balance::BalanceHandling, AuctionMethod, BalanceHoldKind, BalanceHoldRequest, - BalanceIdentifier, BalanceRequest, BiddingRequest, BlockGlobalRequest, BlockGlobalResult, - BlockRewardsRequest, BlockRewardsResult, DataAccessLayer, EraValidatorsRequest, - EraValidatorsResult, EvictItem, FeeRequest, FeeResult, FlushRequest, HandleFeeMode, - HandleFeeRequest, HandleRefundMode, HandleRefundRequest, InsufficientBalanceHandling, - ProofHandling, PruneRequest, PruneResult, StepRequest, StepResult, TransferRequest, + balance::BalanceHandling, + inactive_validators::{ + InactiveValidatorsUndelegationRequest, InactiveValidatorsUndelegationResult, + }, + AuctionMethod, BalanceHoldKind, BalanceHoldRequest, BalanceIdentifier, BalanceRequest, + BiddingRequest, BlockGlobalRequest, BlockGlobalResult, BlockRewardsRequest, + BlockRewardsResult, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, EvictItem, + FeeRequest, FeeResult, FlushRequest, HandleFeeMode, HandleFeeRequest, HandleRefundMode, + HandleRefundRequest, InsufficientBalanceHandling, ProofHandling, PruneRequest, PruneResult, + StepRequest, StepResult, TransferRequest, }, global_state::state::{ lmdb::LmdbGlobalState, scratch::ScratchGlobalState, CommitProvider, ScratchProvider, @@ -723,6 +727,28 @@ pub fn execute_finalized_block( // if era report is some, this is a switch block. a series of end-of-era extra processing must // transpire before this block is entirely finished. let step_outcome = if let Some(era_report) = &executable_block.era_report { + // Undelegate delegators from validators that have been inactive for a number of eras + // specified by the `inactive_validator_undelegation_delay` chainspec option + let inactive_validators_req = InactiveValidatorsUndelegationRequest::new( + native_runtime_config.clone(), + state_root_hash, + protocol_version, + block_time, + ); + match scratch_state.undelegate_from_inactive_validators(inactive_validators_req) { + InactiveValidatorsUndelegationResult::RootNotFound => { + return Err(BlockExecutionError::RootNotFound(state_root_hash)) + } + InactiveValidatorsUndelegationResult::Failure(err) => { + return Err(BlockExecutionError::InactiveValidatorsUndelegation(err)) + } + InactiveValidatorsUndelegationResult::Success { + post_state_hash, .. + } => { + state_root_hash = post_state_hash; + } + } + // step processing starts now let step_processing_start = Instant::now(); let step_effects = match commit_step( diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 5e46e1c180..3f8c8c8e13 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -64,6 +64,8 @@ locked_funds_period = '0 days' vesting_schedule_period = '0 weeks' # Default number of eras that need to pass to be able to withdraw unbonded funds. unbonding_delay = 7 +# Default number of eras that a validator remains inactive, after which all of their delegators are automatically undelegated. +inactive_validator_undelegation_delay = 36 # Round seigniorage rate represented as a fraction of the total supply. # # A rate that makes the rewards roughly 0.05% of the initial stake per block under default NCTL settings. diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index 9cd4abd59a..230adc5d89 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -64,6 +64,8 @@ locked_funds_period = '0 days' vesting_schedule_period = '0 weeks' # Default number of eras that need to pass to be able to withdraw unbonded funds. unbonding_delay = 7 +# Default number of eras that a validator remains inactive, after which all of their delegators are automatically undelegated. +inactive_validator_undelegation_delay = 36 # Round seigniorage rate represented as a fraction of the total supply. # # Annual issuance: 8% diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 7f52f4f372..b43e3206b4 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -3257,6 +3257,7 @@ "bonding_purse", "delegation_rate", "inactive", + "inactive_validator_undelegation_delay", "staked_amount", "validator_public_key" ], @@ -3305,6 +3306,23 @@ "inactive": { "description": "`true` if validator has been \"evicted\"", "type": "boolean" + }, + "eviction_era": { + "description": "Era when validator has been \"evicted\"", + "anyOf": [ + { + "$ref": "#/definitions/EraId" + }, + { + "type": "null" + } + ] + }, + "inactive_validator_undelegation_delay": { + "description": "Delay after validator has been \"evicted\" before all delegators are undelegated", + "type": "integer", + "format": "uint64", + "minimum": 0.0 } }, "additionalProperties": false diff --git a/resources/test/valid/0_9_0/chainspec.toml b/resources/test/valid/0_9_0/chainspec.toml index cd02443c1c..7de75e9f60 100644 --- a/resources/test/valid/0_9_0/chainspec.toml +++ b/resources/test/valid/0_9_0/chainspec.toml @@ -18,6 +18,7 @@ locked_funds_period = '90days' vesting_schedule_period = '13 weeks' round_seigniorage_rate = [6_414, 623_437_335_209] unbonding_delay = 14 +inactive_validator_undelegation_delay = 36 max_associated_keys = 100 max_runtime_call_stack_height = 12 minimum_delegation_amount = 500_000_000_000 diff --git a/resources/test/valid/0_9_0_unordered/chainspec.toml b/resources/test/valid/0_9_0_unordered/chainspec.toml index 47d6bb5665..7d3cac6614 100644 --- a/resources/test/valid/0_9_0_unordered/chainspec.toml +++ b/resources/test/valid/0_9_0_unordered/chainspec.toml @@ -20,6 +20,7 @@ locked_funds_period = '90days' vesting_schedule_period = '13 weeks' round_seigniorage_rate = [6_414, 623_437_335_209] unbonding_delay = 14 +inactive_validator_undelegation_delay = 36 max_associated_keys = 100 max_runtime_call_stack_height = 12 minimum_delegation_amount = 500_000_000_000 diff --git a/resources/test/valid/1_0_0/chainspec.toml b/resources/test/valid/1_0_0/chainspec.toml index 877dc6cee7..286bef1b75 100644 --- a/resources/test/valid/1_0_0/chainspec.toml +++ b/resources/test/valid/1_0_0/chainspec.toml @@ -18,6 +18,7 @@ locked_funds_period = '90days' vesting_schedule_period = '13 weeks' round_seigniorage_rate = [6_414, 623_437_335_209] unbonding_delay = 14 +inactive_validator_undelegation_delay = 36 max_associated_keys = 100 max_runtime_call_stack_height = 12 minimum_delegation_amount = 500_000_000_000 diff --git a/smart_contracts/contract/src/contract_api/runtime.rs b/smart_contracts/contract/src/contract_api/runtime.rs index 1eadc136c0..4116c49e94 100644 --- a/smart_contracts/contract/src/contract_api/runtime.rs +++ b/smart_contracts/contract/src/contract_api/runtime.rs @@ -187,6 +187,37 @@ pub fn get_named_arg(name: &str) -> T { bytesrepr::deserialize(arg_bytes).unwrap_or_revert_with(ApiError::InvalidArgument) } +/// Returns given named argument passed to the host for the current module invocation. +/// If the argument is not found, returns `None`. +/// +/// Note that this is only relevant to contracts stored on-chain since a contract deployed directly +/// is not invoked with any arguments. +pub fn try_get_named_arg(name: &str) -> Option { + let arg_size = get_named_arg_size(name)?; + let arg_bytes = if arg_size > 0 { + let res = { + let data_non_null_ptr = contract_api::alloc_bytes(arg_size); + let ret = unsafe { + ext_ffi::casper_get_named_arg( + name.as_bytes().as_ptr(), + name.len(), + data_non_null_ptr.as_ptr(), + arg_size, + ) + }; + let data = + unsafe { Vec::from_raw_parts(data_non_null_ptr.as_ptr(), arg_size, arg_size) }; + api_error::result_from(ret).map(|_| data) + }; + // Assumed to be safe as `get_named_arg_size` checks the argument already + res.unwrap_or_revert() + } else { + // Avoids allocation with 0 bytes and a call to get_named_arg + Vec::new() + }; + bytesrepr::deserialize(arg_bytes).unwrap_or_revert_with(ApiError::InvalidArgument) +} + /// Returns the caller of the current context, i.e. the [`AccountHash`] of the account which made /// the deploy request. pub fn get_caller() -> AccountHash { diff --git a/smart_contracts/contracts/client/add-bid/src/main.rs b/smart_contracts/contracts/client/add-bid/src/main.rs index 206c49b741..d3d697e42b 100644 --- a/smart_contracts/contracts/client/add-bid/src/main.rs +++ b/smart_contracts/contracts/client/add-bid/src/main.rs @@ -6,20 +6,25 @@ extern crate alloc; use casper_contract::contract_api::{runtime, system}; use casper_types::{ runtime_args, - system::auction::{self, DelegationRate}, + system::auction::{ + self, DelegationRate, ARG_AMOUNT, ARG_DELEGATION_RATE, + ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ARG_PUBLIC_KEY, + }, PublicKey, U512, }; -const ARG_AMOUNT: &str = "amount"; -const ARG_DELEGATION_RATE: &str = "delegation_rate"; -const ARG_PUBLIC_KEY: &str = "public_key"; - -fn add_bid(public_key: PublicKey, bond_amount: U512, delegation_rate: DelegationRate) { +fn add_bid( + public_key: PublicKey, + bond_amount: U512, + delegation_rate: DelegationRate, + inactive_validator_undelegation_delay: Option, +) { let contract_hash = system::get_auction(); let args = runtime_args! { - auction::ARG_PUBLIC_KEY => public_key, - auction::ARG_AMOUNT => bond_amount, - auction::ARG_DELEGATION_RATE => delegation_rate, + ARG_PUBLIC_KEY => public_key, + ARG_AMOUNT => bond_amount, + ARG_DELEGATION_RATE => delegation_rate, + ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY => inactive_validator_undelegation_delay, }; runtime::call_contract::(contract_hash, auction::METHOD_ADD_BID, args); } @@ -33,6 +38,13 @@ pub extern "C" fn call() { let public_key = runtime::get_named_arg(ARG_PUBLIC_KEY); let bond_amount = runtime::get_named_arg(ARG_AMOUNT); let delegation_rate = runtime::get_named_arg(ARG_DELEGATION_RATE); + let inactive_validator_undelegation_delay = + runtime::try_get_named_arg(ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY); - add_bid(public_key, bond_amount, delegation_rate); + add_bid( + public_key, + bond_amount, + delegation_rate, + inactive_validator_undelegation_delay, + ); } diff --git a/storage/src/data_access_layer.rs b/storage/src/data_access_layer.rs index 3e3a0f9fb4..4dc94ff312 100644 --- a/storage/src/data_access_layer.rs +++ b/storage/src/data_access_layer.rs @@ -21,6 +21,7 @@ mod flush; mod genesis; pub mod handle_fee; mod handle_refund; +pub mod inactive_validators; mod key_prefix; pub mod mint; pub mod prefixed_values; diff --git a/storage/src/data_access_layer/auction.rs b/storage/src/data_access_layer/auction.rs index f85d6524f1..91699f67d6 100644 --- a/storage/src/data_access_layer/auction.rs +++ b/storage/src/data_access_layer/auction.rs @@ -45,6 +45,8 @@ pub enum AuctionMethod { public_key: PublicKey, delegation_rate: DelegationRate, amount: U512, + inactive_validator_undelegation_delay: Option, + maximum_inactive_validator_undelegation_delay: u64, }, WithdrawBid { public_key: PublicKey, @@ -86,7 +88,10 @@ impl AuctionMethod { Err(AuctionMethodError::InvalidEntryPoint(entry_point)) } TransactionEntryPoint::ActivateBid => Self::new_activate_bid(runtime_args), - TransactionEntryPoint::AddBid => Self::new_add_bid(runtime_args), + TransactionEntryPoint::AddBid => Self::new_add_bid( + runtime_args, + chainspec.core_config.inactive_validator_undelegation_delay, + ), TransactionEntryPoint::WithdrawBid => Self::new_withdraw_bid(runtime_args), TransactionEntryPoint::Delegate => Self::new_delegate( runtime_args, @@ -109,14 +114,24 @@ impl AuctionMethod { Ok(Self::ActivateBid { validator }) } - fn new_add_bid(runtime_args: &RuntimeArgs) -> Result { + fn new_add_bid( + runtime_args: &RuntimeArgs, + maximum_inactive_validator_undelegation_delay: u64, + ) -> Result { let public_key = Self::get_named_argument(runtime_args, auction::ARG_PUBLIC_KEY)?; 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 = Self::get_named_argument( + runtime_args, + auction::ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + )?; + Ok(Self::AddBid { public_key, delegation_rate, amount, + inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, }) } diff --git a/storage/src/data_access_layer/inactive_validators.rs b/storage/src/data_access_layer/inactive_validators.rs new file mode 100644 index 0000000000..c86d0294b6 --- /dev/null +++ b/storage/src/data_access_layer/inactive_validators.rs @@ -0,0 +1,85 @@ +use casper_types::{ + execution::Effects, system::auction::Error as AuctionError, BlockTime, Digest, ProtocolVersion, +}; +use thiserror::Error; + +use crate::{ + system::{runtime_native::Config, transfer::TransferError}, + tracking_copy::TrackingCopyError, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InactiveValidatorsUndelegationRequest { + config: Config, + state_hash: Digest, + protocol_version: ProtocolVersion, + block_time: BlockTime, +} + +impl InactiveValidatorsUndelegationRequest { + pub fn new( + config: Config, + state_hash: Digest, + protocol_version: ProtocolVersion, + block_time: BlockTime, + ) -> Self { + InactiveValidatorsUndelegationRequest { + config, + state_hash, + protocol_version, + block_time, + } + } + + /// Returns config. + pub fn config(&self) -> &Config { + &self.config + } + + /// Returns state_hash. + pub fn state_hash(&self) -> Digest { + self.state_hash + } + + /// Returns protocol_version. + pub fn protocol_version(&self) -> ProtocolVersion { + self.protocol_version + } + + /// Returns block time. + pub fn block_time(&self) -> BlockTime { + self.block_time + } +} + +#[derive(Clone, Error, Debug)] +pub enum InactiveValidatorsUndelegationError { + #[error("Undistributed rewards")] + UndistributedRewards, + #[error(transparent)] + TrackingCopy(TrackingCopyError), + #[error("Registry entry not found: {0}")] + RegistryEntryNotFound(String), + #[error(transparent)] + Transfer(TransferError), + #[error("Auction error: {0}")] + Auction(AuctionError), +} + +#[derive(Debug, Clone)] +pub enum InactiveValidatorsUndelegationResult { + RootNotFound, + Failure(InactiveValidatorsUndelegationError), + Success { + /// State hash after distribution outcome is committed to the global state. + post_state_hash: Digest, + /// Effects of the distribution process. + effects: Effects, + }, +} + +impl InactiveValidatorsUndelegationResult { + pub fn is_success(&self) -> bool { + matches!(self, InactiveValidatorsUndelegationResult::Success { .. }) + } +} diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 756d1ad6c0..3f6354a63d 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -44,6 +44,10 @@ use crate::{ balance::BalanceHandling, era_validators::EraValidatorsResult, handle_fee::{HandleFeeMode, HandleFeeRequest, HandleFeeResult}, + inactive_validators::{ + InactiveValidatorsUndelegationError, InactiveValidatorsUndelegationRequest, + InactiveValidatorsUndelegationResult, + }, mint::{TransferRequest, TransferRequestArgs, TransferResult}, prefixed_values::{PrefixedValuesRequest, PrefixedValuesResult}, tagged_values::{TaggedValuesRequest, TaggedValuesResult}, @@ -555,6 +559,94 @@ pub trait CommitProvider: StateProvider { effects: Box::new(effects), } } + + /// Undelegate delegators from inactive validators. + /// + /// Delegators are forcibly removed if a validator has been inactive + /// for `inactive_validator_undelegation_delay` eras. + fn undelegate_from_inactive_validators( + &self, + request: InactiveValidatorsUndelegationRequest, + ) -> InactiveValidatorsUndelegationResult { + let state_hash = request.state_hash(); + + let tc = match self.tracking_copy(state_hash) { + Ok(Some(tc)) => Rc::new(RefCell::new(tc)), + Ok(None) => return InactiveValidatorsUndelegationResult::RootNotFound, + Err(err) => { + return InactiveValidatorsUndelegationResult::Failure( + InactiveValidatorsUndelegationError::TrackingCopy(TrackingCopyError::Storage( + err, + )), + ) + } + }; + + let config = request.config(); + let protocol_version = request.protocol_version(); + let seed = { + let mut bytes = match request.block_time().into_bytes() { + Ok(bytes) => bytes, + Err(bre) => { + return InactiveValidatorsUndelegationResult::Failure( + InactiveValidatorsUndelegationError::TrackingCopy( + TrackingCopyError::BytesRepr(bre), + ), + ) + } + }; + match &mut protocol_version.into_bytes() { + Ok(next) => bytes.append(next), + Err(bre) => { + return InactiveValidatorsUndelegationResult::Failure( + InactiveValidatorsUndelegationError::TrackingCopy( + TrackingCopyError::BytesRepr(*bre), + ), + ) + } + }; + + crate::system::runtime_native::Id::Seed(bytes) + }; + + // this runtime uses the system's context + let mut runtime = match RuntimeNative::new_system_runtime( + config.clone(), + protocol_version, + seed, + Rc::clone(&tc), + Phase::Session, + ) { + Ok(rt) => rt, + Err(tce) => { + return InactiveValidatorsUndelegationResult::Failure( + InactiveValidatorsUndelegationError::TrackingCopy(tce), + ); + } + }; + + if let Err(auction_error) = runtime.undelegate_from_inactive_validators() { + error!( + "undelegating from inactive validators failed due to auction error {:?}", + auction_error + ); + return InactiveValidatorsUndelegationResult::Failure( + InactiveValidatorsUndelegationError::Auction(auction_error), + ); + } + + let effects = tc.borrow().effects(); + + match self.commit(state_hash, effects.clone()) { + Ok(post_state_hash) => InactiveValidatorsUndelegationResult::Success { + post_state_hash, + effects, + }, + Err(gse) => InactiveValidatorsUndelegationResult::Failure( + InactiveValidatorsUndelegationError::TrackingCopy(TrackingCopyError::Storage(gse)), + ), + } + } } /// A trait expressing operations over the trie. @@ -1050,8 +1142,16 @@ pub trait StateProvider { public_key, delegation_rate, amount, + inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, } => runtime - .add_bid(public_key, delegation_rate, amount) + .add_bid( + public_key, + delegation_rate, + amount, + inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, + ) .map(AuctionMethodRet::UpdatedAmount) .map_err(TrackingCopyError::Api), AuctionMethod::WithdrawBid { public_key, amount } => runtime diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index 99b4aa93bd..c36e6aed51 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -70,6 +70,8 @@ pub trait Auction: public_key: PublicKey, delegation_rate: DelegationRate, amount: U512, + inactive_validator_undelegation_delay: Option, + maximum_inactive_validator_undelegation_delay: u64, ) -> Result { if !self.allow_auction_bids() { // The validator set may be closed on some side chains, @@ -85,6 +87,12 @@ pub trait Auction: return Err(Error::DelegationRateTooLarge.into()); } + let inactive_validator_undelegation_delay = inactive_validator_undelegation_delay + .unwrap_or(maximum_inactive_validator_undelegation_delay); + if inactive_validator_undelegation_delay > maximum_inactive_validator_undelegation_delay { + return Err(Error::InactiveValidatorUndelegationDelayTooLarge.into()); + } + let provided_account_hash = AccountHash::from_public_key(&public_key, |x| self.blake2b(x)); if !self.is_allowed_session_caller(&provided_account_hash) { @@ -103,8 +111,13 @@ pub trait Auction: (*validator_bid.bonding_purse(), validator_bid) } else { let bonding_purse = self.create_purse()?; - let validator_bid = - ValidatorBid::unlocked(public_key, bonding_purse, amount, delegation_rate); + let validator_bid = ValidatorBid::unlocked( + public_key, + bonding_purse, + amount, + delegation_rate, + inactive_validator_undelegation_delay, + ); (bonding_purse, Box::new(validator_bid)) }; @@ -497,8 +510,9 @@ pub trait Auction: bids_modified = true; } + let current_era_id = self.read_era_id()?; if evicted_validators.contains(validator_public_key) { - bids_modified = validator_bid.deactivate(); + bids_modified = validator_bid.deactivate(current_era_id); } } @@ -700,6 +714,53 @@ pub trait Auction: Ok(()) } + fn undelegate_from_inactive_validators(&mut self) -> Result<(), Error> { + if self.get_caller() != PublicKey::System.to_account_hash() { + error!("invalid caller to auction undelegate_from_inactive_validators"); + return Err(Error::InvalidCaller); + } + + let current_era_id = self.read_era_id()?; + let era_end_timestamp_millis = detail::get_era_end_timestamp_millis(self)?; + + // fetch all validator bids + let validator_bids = detail::get_validator_bids(self)?; + // process inactive validator bids + for (validator_public_key, validator_bid) in validator_bids + .iter() + .filter(|(_pubkey, validator_bid)| validator_bid.inactive()) + { + if current_era_id + >= validator_bid.eviction_era().unwrap() + + validator_bid.inactive_validator_undelegation_delay() + { + let mut delegators = read_delegator_bids(self, validator_public_key)?; + for delegator in delegators.iter_mut() { + let delegator_public_key = delegator.delegator_public_key().clone(); + detail::create_unbonding_purse( + self, + validator_public_key.clone(), + delegator_public_key.clone(), + *delegator.bonding_purse(), + delegator.staked_amount(), + None, + )?; + delegator + .decrease_stake(delegator.staked_amount(), era_end_timestamp_millis)?; + let delegator_bid_addr = BidAddr::new_from_public_keys( + validator_public_key, + Some(&delegator_public_key), + ); + + debug!("pruning delegator bid {}", delegator_bid_addr); + self.prune_bid(delegator_bid_addr) + } + } + } + + Ok(()) + } + /// Reads current era id. fn read_era_id(&mut self) -> Result { detail::get_era_id(self) diff --git a/storage/src/system/genesis.rs b/storage/src/system/genesis.rs index ab068bd31b..265732575f 100644 --- a/storage/src/system/genesis.rs +++ b/storage/src/system/genesis.rs @@ -416,6 +416,7 @@ where staked_amount, delegation_rate, release_timestamp_millis, + self.config.inactive_validator_undelegation_delay(), ); // Set up delegator entries attached to genesis validators diff --git a/types/src/auction_state.rs b/types/src/auction_state.rs index 85fa32efd9..ae8e900203 100644 --- a/types/src/auction_state.rs +++ b/types/src/auction_state.rs @@ -49,6 +49,7 @@ static AUCTION_INFO: Lazy = Lazy::new(|| { URef::new([250; 32], AccessRights::READ_ADD_WRITE), U512::from(20), DelegationRate::zero(), + u64::MAX, ); bids.push(BidKind::Validator(Box::new(validator_bid))); @@ -65,7 +66,14 @@ static AUCTION_INFO: Lazy = Lazy::new(|| { let height: u64 = 10; let era_validators = ERA_VALIDATORS.clone(); - AuctionState::new(state_root_hash, height, era_validators, bids) + let inactive_validator_undelegation_delay: u64 = 36; + AuctionState::new( + state_root_hash, + height, + era_validators, + bids, + inactive_validator_undelegation_delay, + ) }); /// A validator's weight. @@ -109,6 +117,7 @@ impl AuctionState { block_height: u64, era_validators: EraValidators, bids: Vec, + inactive_validator_undelegation_delay: u64, ) -> Self { let mut json_era_validators: Vec = Vec::new(); for (era_id, validator_weights) in era_validators.iter() { @@ -135,6 +144,7 @@ impl AuctionState { *bid.bonding_purse(), *bid.staked_amount(), *bid.delegation_rate(), + inactive_validator_undelegation_delay, ); staking.insert(public_key, (validator_bid, bid.delegators().clone())); } diff --git a/types/src/chainspec/core_config.rs b/types/src/chainspec/core_config.rs index a0042e0bfc..e443b11370 100644 --- a/types/src/chainspec/core_config.rs +++ b/types/src/chainspec/core_config.rs @@ -109,6 +109,10 @@ pub struct CoreConfig { /// The delay in number of eras for paying out the unbonding amount. pub unbonding_delay: u64, + /// The delay in number of eras for automatically undelegating all delegators from an inactive + /// validator + pub inactive_validator_undelegation_delay: u64, + /// Round seigniorage rate represented as a fractional number. #[cfg_attr(feature = "datasize", data_size(skip))] pub round_seigniorage_rate: Ratio, @@ -214,6 +218,8 @@ impl CoreConfig { let locked_funds_period = TimeDiff::from_seconds(rng.gen_range(600..604_800)); let vesting_schedule_period = TimeDiff::from_seconds(rng.gen_range(600..604_800)); let unbonding_delay = rng.gen_range((auction_delay + 1)..1_000_000_000); + let inactive_validator_undelegation_delay = + rng.gen_range((auction_delay + 1)..1_000_000_000); let round_seigniorage_rate = Ratio::new( rng.gen_range(1..1_000_000_000), rng.gen_range(1..1_000_000_000), @@ -277,6 +283,7 @@ impl CoreConfig { locked_funds_period, vesting_schedule_period, unbonding_delay, + inactive_validator_undelegation_delay, round_seigniorage_rate, max_associated_keys, max_runtime_call_stack_height, @@ -320,6 +327,7 @@ impl Default for CoreConfig { locked_funds_period: Default::default(), vesting_schedule_period: Default::default(), unbonding_delay: 7, + inactive_validator_undelegation_delay: 36, round_seigniorage_rate: Ratio::new(1, 4_200_000_000_000_000_000), max_associated_keys: DEFAULT_MAX_ASSOCIATED_KEYS, max_runtime_call_stack_height: DEFAULT_MAX_RUNTIME_CALL_STACK_HEIGHT, @@ -365,6 +373,7 @@ impl ToBytes for CoreConfig { buffer.extend(self.locked_funds_period.to_bytes()?); buffer.extend(self.vesting_schedule_period.to_bytes()?); buffer.extend(self.unbonding_delay.to_bytes()?); + buffer.extend(self.inactive_validator_undelegation_delay.to_bytes()?); buffer.extend(self.round_seigniorage_rate.to_bytes()?); buffer.extend(self.max_associated_keys.to_bytes()?); buffer.extend(self.max_runtime_call_stack_height.to_bytes()?); @@ -406,6 +415,9 @@ impl ToBytes for CoreConfig { + self.locked_funds_period.serialized_length() + self.vesting_schedule_period.serialized_length() + self.unbonding_delay.serialized_length() + + self + .inactive_validator_undelegation_delay + .serialized_length() + self.round_seigniorage_rate.serialized_length() + self.max_associated_keys.serialized_length() + self.max_runtime_call_stack_height.serialized_length() @@ -447,6 +459,7 @@ impl FromBytes for CoreConfig { let (locked_funds_period, remainder) = TimeDiff::from_bytes(remainder)?; let (vesting_schedule_period, remainder) = TimeDiff::from_bytes(remainder)?; let (unbonding_delay, remainder) = u64::from_bytes(remainder)?; + let (inactive_validator_undelegation_delay, remainder) = u64::from_bytes(remainder)?; let (round_seigniorage_rate, remainder) = Ratio::::from_bytes(remainder)?; let (max_associated_keys, remainder) = u32::from_bytes(remainder)?; let (max_runtime_call_stack_height, remainder) = u32::from_bytes(remainder)?; @@ -483,6 +496,7 @@ impl FromBytes for CoreConfig { locked_funds_period, vesting_schedule_period, unbonding_delay, + inactive_validator_undelegation_delay, round_seigniorage_rate, max_associated_keys, max_runtime_call_stack_height, diff --git a/types/src/chainspec/genesis_config.rs b/types/src/chainspec/genesis_config.rs index c694939d80..e1bec98d48 100644 --- a/types/src/chainspec/genesis_config.rs +++ b/types/src/chainspec/genesis_config.rs @@ -24,6 +24,9 @@ pub const DEFAULT_AUCTION_DELAY: u64 = 1; pub const DEFAULT_LOCKED_FUNDS_PERIOD_MILLIS: u64 = 0; /// Default number of eras that need to pass to be able to withdraw unbonded funds. pub const DEFAULT_UNBONDING_DELAY: u64 = 7; +/// Default number of eras that need to pass after a validator becomes inactive +/// for all delegators to be automatically undelegated. +pub const DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY: u64 = 36; /// Default round seigniorage rate represented as a fractional number. /// /// Annual issuance: 2% @@ -51,6 +54,7 @@ pub struct GenesisConfig { locked_funds_period_millis: u64, round_seigniorage_rate: Ratio, unbonding_delay: u64, + inactive_validator_undelegation_delay: u64, genesis_timestamp_millis: u64, gas_hold_balance_handling: HoldBalanceHandling, gas_hold_interval_millis: u64, @@ -75,6 +79,7 @@ impl GenesisConfig { locked_funds_period_millis: u64, round_seigniorage_rate: Ratio, unbonding_delay: u64, + inactive_validator_undelegation_delay: u64, genesis_timestamp_millis: u64, gas_hold_balance_handling: HoldBalanceHandling, gas_hold_interval_millis: u64, @@ -88,6 +93,7 @@ impl GenesisConfig { locked_funds_period_millis, round_seigniorage_rate, unbonding_delay, + inactive_validator_undelegation_delay, genesis_timestamp_millis, gas_hold_balance_handling, gas_hold_interval_millis, @@ -166,6 +172,11 @@ impl GenesisConfig { self.unbonding_delay } + /// Returns inactive validator undelegation delay in eras. + pub fn inactive_validator_undelegation_delay(&self) -> u64 { + self.inactive_validator_undelegation_delay + } + /// Returns genesis timestamp expressed in milliseconds. pub fn genesis_timestamp_millis(&self) -> u64 { self.genesis_timestamp_millis @@ -206,6 +217,8 @@ impl Distribution for Standard { let unbonding_delay = rng.gen(); + let inactive_validator_undelegation_delay = rng.gen(); + let genesis_timestamp_millis = rng.gen(); let gas_hold_balance_handling = rng.gen(); let gas_hold_interval_millis = rng.gen(); @@ -219,6 +232,7 @@ impl Distribution for Standard { locked_funds_period_millis, round_seigniorage_rate, unbonding_delay, + inactive_validator_undelegation_delay, genesis_timestamp_millis, gas_hold_balance_handling, gas_hold_interval_millis, @@ -240,6 +254,7 @@ pub struct GenesisConfigBuilder { locked_funds_period_millis: Option, round_seigniorage_rate: Option>, unbonding_delay: Option, + inactive_validator_undelegation_delay: Option, genesis_timestamp_millis: Option, gas_hold_balance_handling: Option, gas_hold_interval_millis: Option, @@ -299,6 +314,15 @@ impl GenesisConfigBuilder { self } + /// Sets the inactive validator undelegation delay config option. + pub fn with_inactive_validator_undelegation_delay( + mut self, + inactive_validator_undelegation_delay: u64, + ) -> Self { + self.inactive_validator_undelegation_delay = Some(inactive_validator_undelegation_delay); + self + } + /// Sets the genesis timestamp config option. pub fn with_genesis_timestamp_millis(mut self, genesis_timestamp_millis: u64) -> Self { self.genesis_timestamp_millis = Some(genesis_timestamp_millis); @@ -335,6 +359,9 @@ impl GenesisConfigBuilder { .round_seigniorage_rate .unwrap_or(DEFAULT_ROUND_SEIGNIORAGE_RATE), unbonding_delay: self.unbonding_delay.unwrap_or(DEFAULT_UNBONDING_DELAY), + inactive_validator_undelegation_delay: self + .inactive_validator_undelegation_delay + .unwrap_or(DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY), genesis_timestamp_millis: self .genesis_timestamp_millis .unwrap_or(DEFAULT_GENESIS_TIMESTAMP_MILLIS), @@ -369,6 +396,9 @@ impl From<&Chainspec> for GenesisConfig { .with_locked_funds_period_millis(chainspec.core_config.locked_funds_period.millis()) .with_round_seigniorage_rate(chainspec.core_config.round_seigniorage_rate) .with_unbonding_delay(chainspec.core_config.unbonding_delay) + .with_inactive_validator_undelegation_delay( + chainspec.core_config.inactive_validator_undelegation_delay, + ) .with_genesis_timestamp_millis(genesis_timestamp_millis) .with_gas_hold_balance_handling(gas_hold_balance_handling) .with_gas_hold_interval_millis(gas_hold_interval_millis) diff --git a/types/src/gens.rs b/types/src/gens.rs index 29c7fcd2ad..97542ffcd2 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -12,7 +12,7 @@ use alloc::{ use proptest::{ array, bits, bool, collection::{self, vec, SizeRange}, - option, + num, option, prelude::*, result, }; @@ -691,9 +691,17 @@ pub(crate) fn validator_bid_arb() -> impl Strategy { u512_arb(), delegation_rate_arb(), bool::ANY, + num::u64::ANY, ) .prop_map( - |(validator_public_key, bonding_purse, staked_amount, delegation_rate, is_locked)| { + |( + validator_public_key, + bonding_purse, + staked_amount, + delegation_rate, + is_locked, + inactive_validator_undelegation_delay, + )| { let validator_bid = if is_locked { ValidatorBid::locked( validator_public_key, @@ -701,6 +709,7 @@ pub(crate) fn validator_bid_arb() -> impl Strategy { staked_amount, delegation_rate, 1u64, + inactive_validator_undelegation_delay, ) } else { ValidatorBid::unlocked( @@ -708,6 +717,7 @@ pub(crate) fn validator_bid_arb() -> impl Strategy { bonding_purse, staked_amount, delegation_rate, + inactive_validator_undelegation_delay, ) }; BidKind::Validator(Box::new(validator_bid)) diff --git a/types/src/system/auction/constants.rs b/types/src/system/auction/constants.rs index aea4568bb8..9a3b3f4fda 100644 --- a/types/src/system/auction/constants.rs +++ b/types/src/system/auction/constants.rs @@ -58,6 +58,8 @@ pub const ARG_EVICTED_VALIDATORS: &str = "evicted_validators"; pub const ARG_REWARDS_MAP: &str = "rewards_map"; /// Named constant for `entry_point`; pub const ARG_ENTRY_POINT: &str = "entry_point"; +/// Named constant for `inactive_validator_undelegation_delay`; +pub const ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY: &str = "inactive_validator_undelegation_delay"; /// Named constant for method `get_era_validators`. pub const METHOD_GET_ERA_VALIDATORS: &str = "get_era_validators"; diff --git a/types/src/system/auction/error.rs b/types/src/system/auction/error.rs index 98914cee99..89973c981e 100644 --- a/types/src/system/auction/error.rs +++ b/types/src/system/auction/error.rs @@ -353,6 +353,12 @@ pub enum Error { /// assert_eq!(53, Error::BridgeRecordChainTooLong as u8); /// ``` BridgeRecordChainTooLong = 53, + /// Inactive validator undelegation delay exceeds global limit. + /// ``` + /// # use casper_types::system::auction::Error; + /// assert_eq!(54, Error::InactiveValidatorUndelegationDelayTooLarge as u8); + /// ``` + InactiveValidatorUndelegationDelayTooLarge = 54, } impl Display for Error { @@ -412,6 +418,7 @@ impl Display for Error { Error::MissingPurse => formatter.write_str("Missing purse"), Error::ValidatorBidExistsAlready => formatter.write_str("Validator bid with given public key already exists"), Error::BridgeRecordChainTooLong => formatter.write_str("Bridge record chain is too long to find current validator bid"), + Error::InactiveValidatorUndelegationDelayTooLarge => formatter.write_str("Inactive validator undelegation delay exceeds global limit"), } } } @@ -499,6 +506,9 @@ impl TryFrom for Error { Ok(Error::ValidatorBidExistsAlready) } d if d == Error::BridgeRecordChainTooLong as u8 => Ok(Error::BridgeRecordChainTooLong), + d if d == Error::InactiveValidatorUndelegationDelayTooLarge as u8 => { + Ok(Error::InactiveValidatorUndelegationDelayTooLarge) + } _ => Err(TryFromU8ForError(())), } } diff --git a/types/src/system/auction/validator_bid.rs b/types/src/system/auction/validator_bid.rs index 57a096c6bd..6c20761729 100644 --- a/types/src/system/auction/validator_bid.rs +++ b/types/src/system/auction/validator_bid.rs @@ -8,7 +8,7 @@ use crate::{ system::auction::{ bid::VestingSchedule, DelegationRate, Error, VESTING_SCHEDULE_LENGTH_MILLIS, }, - CLType, CLTyped, PublicKey, URef, U512, + CLType, CLTyped, EraId, PublicKey, URef, U512, }; use crate::system::auction::Bid; @@ -18,6 +18,10 @@ use datasize::DataSize; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +/// Default number of eras that need to pass after a validator becomes inactive +/// for all delegators to be automatically undelegated. +pub const DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY: u64 = 36; + /// An entry in the validator map. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] #[cfg_attr(feature = "datasize", derive(DataSize))] @@ -36,6 +40,11 @@ pub struct ValidatorBid { vesting_schedule: Option, /// `true` if validator has been "evicted" inactive: bool, + /// Era when validator has been "evicted" + eviction_era: Option, + /// Delay after validator has been "evicted" + /// before all delegators are undelegated + inactive_validator_undelegation_delay: u64, } impl ValidatorBid { @@ -46,9 +55,11 @@ impl ValidatorBid { staked_amount: U512, delegation_rate: DelegationRate, release_timestamp_millis: u64, + inactive_validator_undelegation_delay: u64, ) -> Self { let vesting_schedule = Some(VestingSchedule::new(release_timestamp_millis)); let inactive = false; + let eviction_era = None; Self { validator_public_key, bonding_purse, @@ -56,6 +67,8 @@ impl ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, + inactive_validator_undelegation_delay, } } @@ -65,9 +78,11 @@ impl ValidatorBid { bonding_purse: URef, staked_amount: U512, delegation_rate: DelegationRate, + inactive_validator_undelegation_delay: u64, ) -> Self { let vesting_schedule = None; let inactive = false; + let eviction_era = None; Self { validator_public_key, bonding_purse, @@ -75,13 +90,20 @@ impl ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, + inactive_validator_undelegation_delay, } } /// Creates a new inactive instance of a bid with 0 staked amount. - pub fn empty(validator_public_key: PublicKey, bonding_purse: URef) -> Self { + pub fn empty( + validator_public_key: PublicKey, + bonding_purse: URef, + inactive_validator_undelegation_delay: u64, + ) -> Self { let vesting_schedule = None; let inactive = true; + let eviction_era = Some(EraId::default()); let staked_amount = 0.into(); let delegation_rate = Default::default(); Self { @@ -91,6 +113,8 @@ impl ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, + inactive_validator_undelegation_delay, } } @@ -161,6 +185,16 @@ impl ValidatorBid { self.inactive } + /// Gets the delegation rate of the provided bid + pub fn eviction_era(&self) -> Option { + self.eviction_era + } + + /// Gets the delegation rate of the provided bid + pub fn inactive_validator_undelegation_delay(&self) -> u64 { + self.inactive_validator_undelegation_delay + } + /// Decreases the stake of the provided bid pub fn decrease_stake( &mut self, @@ -217,12 +251,15 @@ impl ValidatorBid { /// Sets given bid's `inactive` field to `false` pub fn activate(&mut self) -> bool { self.inactive = false; + self.eviction_era = None; false } /// Sets given bid's `inactive` field to `true` - pub fn deactivate(&mut self) -> bool { + /// and store eviction era id + pub fn deactivate(&mut self, era_id: EraId) -> bool { self.inactive = true; + self.eviction_era = Some(era_id); true } @@ -248,6 +285,9 @@ impl ToBytes for ValidatorBid { self.delegation_rate.write_bytes(&mut result)?; self.vesting_schedule.write_bytes(&mut result)?; self.inactive.write_bytes(&mut result)?; + self.eviction_era.write_bytes(&mut result)?; + self.inactive_validator_undelegation_delay + .write_bytes(&mut result)?; Ok(result) } @@ -258,6 +298,10 @@ impl ToBytes for ValidatorBid { + self.delegation_rate.serialized_length() + self.vesting_schedule.serialized_length() + self.inactive.serialized_length() + + self.eviction_era.serialized_length() + + self + .inactive_validator_undelegation_delay + .serialized_length() } fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { @@ -267,6 +311,9 @@ impl ToBytes for ValidatorBid { self.delegation_rate.write_bytes(writer)?; self.vesting_schedule.write_bytes(writer)?; self.inactive.write_bytes(writer)?; + self.eviction_era.write_bytes(writer)?; + self.inactive_validator_undelegation_delay + .write_bytes(writer)?; Ok(()) } } @@ -279,6 +326,8 @@ impl FromBytes for ValidatorBid { let (delegation_rate, bytes) = FromBytes::from_bytes(bytes)?; let (vesting_schedule, bytes) = FromBytes::from_bytes(bytes)?; let (inactive, bytes) = FromBytes::from_bytes(bytes)?; + let (eviction_era, bytes) = FromBytes::from_bytes(bytes)?; + let (inactive_validator_undelegation_delay, bytes) = FromBytes::from_bytes(bytes)?; Ok(( ValidatorBid { validator_public_key, @@ -287,6 +336,8 @@ impl FromBytes for ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, + inactive_validator_undelegation_delay, }, bytes, )) @@ -302,6 +353,8 @@ impl From for ValidatorBid { delegation_rate: *bid.delegation_rate(), vesting_schedule: bid.vesting_schedule().cloned(), inactive: bid.inactive(), + eviction_era: None, + inactive_validator_undelegation_delay: DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, } } } @@ -310,8 +363,11 @@ impl From for ValidatorBid { mod tests { use crate::{ bytesrepr, - system::auction::{bid::VestingSchedule, DelegationRate, ValidatorBid}, - AccessRights, PublicKey, SecretKey, URef, U512, + system::auction::{ + bid::VestingSchedule, validator_bid::DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + DelegationRate, ValidatorBid, + }, + AccessRights, EraId, PublicKey, SecretKey, URef, U512, }; #[test] @@ -325,6 +381,8 @@ mod tests { delegation_rate: DelegationRate::MAX, vesting_schedule: Some(VestingSchedule::default()), inactive: false, + eviction_era: None, + inactive_validator_undelegation_delay: DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, }; bytesrepr::test_serialization_roundtrip(&founding_validator); } @@ -340,6 +398,8 @@ mod tests { delegation_rate: DelegationRate::max_value(), vesting_schedule: Some(VestingSchedule::default()), inactive: true, + eviction_era: Some(EraId::default()), + inactive_validator_undelegation_delay: DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, }; bytesrepr::test_serialization_roundtrip(&founding_validator); } @@ -355,6 +415,7 @@ mod tests { let validator_bonding_purse = URef::new([42; 32], AccessRights::ADD); let validator_staked_amount = U512::from(1000); let validator_delegation_rate = 0; + let inactive_validator_undelegation_delay = 36; let bid = ValidatorBid::locked( validator_pk, @@ -362,6 +423,7 @@ mod tests { validator_staked_amount, validator_delegation_rate, validator_release_timestamp, + inactive_validator_undelegation_delay, ); assert!(!bid.is_locked_with_vesting_schedule( diff --git a/types/src/transaction/transaction_v1/transaction_v1_body.rs b/types/src/transaction/transaction_v1/transaction_v1_body.rs index 40f2c66a7c..7936fe1c0a 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_body.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_body.rs @@ -307,7 +307,14 @@ impl TransactionV1Body { let public_key = PublicKey::random(rng); let delegation_rate = rng.gen(); let amount = rng.gen::(); - let args = arg_handling::new_add_bid_args(public_key, delegation_rate, amount).unwrap(); + let inactive_validator_undelegation_delay = Some(rng.gen::()); + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + inactive_validator_undelegation_delay, + ) + .unwrap(); TransactionV1Body::new( args, TransactionTarget::Native, @@ -340,8 +347,14 @@ impl TransactionV1Body { let public_key = PublicKey::random(rng); let delegation_rate = rng.gen(); let amount = rng.gen::(); - let args = - arg_handling::new_add_bid_args(public_key, delegation_rate, amount).unwrap(); + let inactive_validator_undelegation_delay = Some(rng.gen::()); + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + inactive_validator_undelegation_delay, + ) + .unwrap(); TransactionV1Body::new( args, TransactionTarget::Native, diff --git a/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs b/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs index 7d69fe50ab..ef961b6a83 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs @@ -18,6 +18,8 @@ const TRANSFER_ARG_ID: OptionalArg = OptionalArg::new("id"); const ADD_BID_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); const ADD_BID_ARG_DELEGATION_RATE: RequiredArg = RequiredArg::new("delegation_rate"); const ADD_BID_ARG_AMOUNT: RequiredArg = RequiredArg::new("amount"); +const ADD_BID_ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY: OptionalArg = + OptionalArg::new("inactive_validator_undelegation_delay"); const WITHDRAW_BID_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); const WITHDRAW_BID_ARG_AMOUNT: RequiredArg = RequiredArg::new("amount"); @@ -218,11 +220,19 @@ pub(in crate::transaction::transaction_v1) fn new_add_bid_args>( public_key: PublicKey, delegation_rate: u8, amount: A, + maybe_inactive_delegator_undelegation_delay: Option, ) -> Result { let mut args = RuntimeArgs::new(); ADD_BID_ARG_PUBLIC_KEY.insert(&mut args, public_key)?; ADD_BID_ARG_DELEGATION_RATE.insert(&mut args, delegation_rate)?; ADD_BID_ARG_AMOUNT.insert(&mut args, amount.into())?; + + if let Some(inactive_delegator_undelegation_delay) = maybe_inactive_delegator_undelegation_delay + { + ADD_BID_ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY + .insert(&mut args, inactive_delegator_undelegation_delay)?; + } + Ok(args) } @@ -233,6 +243,8 @@ pub(in crate::transaction::transaction_v1) fn has_valid_add_bid_args( let _public_key = ADD_BID_ARG_PUBLIC_KEY.get(args)?; let _delegation_rate = ADD_BID_ARG_DELEGATION_RATE.get(args)?; let _amount = ADD_BID_ARG_AMOUNT.get(args)?; + let _maybe_inactive_validator_undelegation_delay = + ADD_BID_ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY.get(args)?; Ok(()) } @@ -247,7 +259,7 @@ pub(in crate::transaction::transaction_v1) fn new_withdraw_bid_args Result<(), InvalidTransactionV1> { @@ -501,8 +513,13 @@ mod tests { let rng = &mut TestRng::new(); // Check random args. - let mut args = - new_add_bid_args(PublicKey::random(rng), rng.gen(), rng.gen::()).unwrap(); + let mut args = new_add_bid_args( + PublicKey::random(rng), + rng.gen(), + rng.gen::(), + Some(rng.gen::()), + ) + .unwrap(); has_valid_add_bid_args(&args).unwrap(); // Check with extra arg. diff --git a/types/src/transaction/transaction_v1/transaction_v1_builder.rs b/types/src/transaction/transaction_v1/transaction_v1_builder.rs index 41516d2fba..7d0ea333dc 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_builder.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_builder.rs @@ -102,8 +102,14 @@ impl<'a> TransactionV1Builder<'a> { public_key: PublicKey, delegation_rate: u8, amount: A, + inactive_delegator_undelegation_delay: Option, ) -> Result { - let args = arg_handling::new_add_bid_args(public_key, delegation_rate, amount)?; + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + inactive_delegator_undelegation_delay, + )?; let body = TransactionV1Body::new( args, TransactionTarget::Native, diff --git a/utils/global-state-update-gen/src/generic.rs b/utils/global-state-update-gen/src/generic.rs index b9302df064..11803b8d7f 100644 --- a/utils/global-state-update-gen/src/generic.rs +++ b/utils/global-state-update-gen/src/generic.rs @@ -14,7 +14,9 @@ use clap::ArgMatches; use itertools::Itertools; use casper_engine_test_support::LmdbWasmTestBuilder; -use casper_execution_engine::engine_state::engine_config::DEFAULT_PROTOCOL_VERSION; +use casper_execution_engine::engine_state::engine_config::{ + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, DEFAULT_PROTOCOL_VERSION, +}; use casper_types::{ system::auction::{ Bid, BidKind, BidsExt, Delegator, SeigniorageRecipient, SeigniorageRecipientsSnapshot, @@ -308,9 +310,13 @@ pub fn add_and_remove_bids( public_key.clone(), *bid.bonding_purse(), ))), - BidKind::Validator(validator_bid) => BidKind::Validator(Box::new( - ValidatorBid::empty(public_key.clone(), *validator_bid.bonding_purse()), - )), + BidKind::Validator(validator_bid) => { + BidKind::Validator(Box::new(ValidatorBid::empty( + public_key.clone(), + *validator_bid.bonding_purse(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ))) + } BidKind::Delegator(delegator_bid) => { BidKind::Delegator(Box::new(Delegator::empty( public_key.clone(), @@ -514,6 +520,7 @@ fn create_or_update_bid( *bonding_purse, *updated_recipient.stake(), *updated_recipient.delegation_rate(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); state.set_bid( @@ -550,6 +557,7 @@ fn create_or_update_bid( bonding_purse, stake, *updated_recipient.delegation_rate(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); state.set_bid( BidKind::Validator(Box::new(validator_bid)), diff --git a/utils/global-state-update-gen/src/generic/testing.rs b/utils/global-state-update-gen/src/generic/testing.rs index 0284029305..d3f21e547c 100644 --- a/utils/global-state-update-gen/src/generic/testing.rs +++ b/utils/global-state-update-gen/src/generic/testing.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use casper_execution_engine::engine_state::engine_config::DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY; use itertools::Itertools; use rand::Rng; @@ -126,8 +127,13 @@ impl MockStateReader { self.bids.push(BidKind::Delegator(Box::new(delegator))); } - let validator_bid = - ValidatorBid::unlocked(public_key.clone(), bonding_purse, stake, delegation_rate); + let validator_bid = ValidatorBid::unlocked( + public_key.clone(), + bonding_purse, + stake, + delegation_rate, + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ); self.bids.push(BidKind::Validator(Box::new(validator_bid))); } @@ -535,6 +541,7 @@ fn should_change_one_validator() { bid_purse, validator3_new_staked, Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); update.assert_written_bid(account3_hash, BidKind::Validator(Box::new(expected_bid))); @@ -622,8 +629,13 @@ fn should_change_only_stake_of_one_validator() { update.assert_written_balance(bid_purse, 104); // check bid overwrite - let expected_bid = - ValidatorBid::unlocked(validator3, bid_purse, U512::from(104), Default::default()); + let expected_bid = ValidatorBid::unlocked( + validator3, + bid_purse, + U512::from(104), + Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ); update.assert_written_bid(account3_hash, BidKind::Validator(Box::new(expected_bid))); // 4 keys should be written: @@ -757,9 +769,14 @@ fn should_replace_one_validator() { // check bid overwrite let account1_hash = validator1.to_account_hash(); - let mut expected_bid_1 = - ValidatorBid::unlocked(validator1, bid_purse, U512::zero(), Default::default()); - expected_bid_1.deactivate(); + let mut expected_bid_1 = ValidatorBid::unlocked( + validator1, + bid_purse, + U512::zero(), + Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // check writes for validator2 @@ -857,9 +874,14 @@ fn should_replace_one_validator_with_unbonding() { // check bid overwrite let account1_hash = validator1.to_account_hash(); - let mut expected_bid_1 = - ValidatorBid::unlocked(validator1, bid_purse, U512::zero(), Default::default()); - expected_bid_1.deactivate(); + let mut expected_bid_1 = ValidatorBid::unlocked( + validator1, + bid_purse, + U512::zero(), + Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // check writes for validator2 @@ -1970,9 +1992,14 @@ fn should_handle_unbonding_to_oneself_correctly() { // Check bid overwrite let account1_hash = old_validator.to_account_hash(); - let mut expected_bid_1 = - ValidatorBid::unlocked(old_validator, bid_purse, U512::zero(), Default::default()); - expected_bid_1.deactivate(); + let mut expected_bid_1 = ValidatorBid::unlocked( + old_validator, + bid_purse, + U512::zero(), + Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // Check writes for validator2 @@ -2112,8 +2139,9 @@ fn should_handle_unbonding_to_a_delegator_correctly() { validator_purse, U512::zero(), Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); - expected_bid_1.deactivate(); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // Check writes for validator2 @@ -2233,9 +2261,14 @@ fn should_handle_legacy_unbonding_to_oneself_correctly() { // Check bid overwrite let account1_hash = old_validator.to_account_hash(); - let mut expected_bid_1 = - ValidatorBid::unlocked(old_validator, bid_purse, U512::zero(), Default::default()); - expected_bid_1.deactivate(); + let mut expected_bid_1 = ValidatorBid::unlocked( + old_validator, + bid_purse, + U512::zero(), + Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + ); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // Check writes for validator2 @@ -2408,8 +2441,9 @@ fn should_handle_legacy_unbonding_to_a_delegator_correctly() { validator_purse, U512::zero(), Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); - expected_bid_1.deactivate(); + expected_bid_1.deactivate(WITHDRAW_ERA); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // Check writes for validator2 diff --git a/utils/validation/src/generators.rs b/utils/validation/src/generators.rs index 73a1d0e7b8..fe6df5f141 100644 --- a/utils/validation/src/generators.rs +++ b/utils/validation/src/generators.rs @@ -121,6 +121,7 @@ pub fn make_abi_test_fixtures() -> Result { U512::from(50_000_000_000u64), 100, u64::MAX, + 36, ); let validator_bid_kind = BidKind::Validator(Box::new(validator_bid)); let delegator_public_key = PublicKey::from(&delegator_secret_key); diff --git a/utils/validation/tests/fixtures/ABI/stored_value.json b/utils/validation/tests/fixtures/ABI/stored_value.json index a7fde9ca7e..8b0497151e 100644 --- a/utils/validation/tests/fixtures/ABI/stored_value.json +++ b/utils/validation/tests/fixtures/ABI/stored_value.json @@ -370,13 +370,15 @@ "initial_release_timestamp_millis": 18446744073709551615, "locked_amounts": null }, - "inactive": false + "inactive": false, + "eviction_era": null, + "inactive_validator_undelegation_delay": 36 } } } } ], - "output": "0b0101197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d610a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a070500743ba40b6401ffffffffffffffff0000" + "output": "0b0101197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d610a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a070500743ba40b6401ffffffffffffffff0000002400000000000000" }, "Withdraw": { "input": [