From bf4b0223394e0c7157718030917d2192082e36e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 26 Mar 2024 09:36:53 +0100 Subject: [PATCH 01/11] store era id when validator is marked as inactive --- storage/src/system/auction.rs | 3 ++- types/src/system/auction/validator_bid.rs | 25 ++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index 4ad6c08d3a..bb7794fc8e 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -500,8 +500,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); } } diff --git a/types/src/system/auction/validator_bid.rs b/types/src/system/auction/validator_bid.rs index a90b725b6e..99a0dcdd04 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; @@ -36,6 +36,8 @@ pub struct ValidatorBid { vesting_schedule: Option, /// `true` if validator has been "evicted" inactive: bool, + /// era when validator has been "evicted" + eviction_era: Option, } impl ValidatorBid { @@ -49,6 +51,7 @@ impl ValidatorBid { ) -> 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 +59,7 @@ impl ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, } } @@ -68,6 +72,7 @@ impl ValidatorBid { ) -> Self { let vesting_schedule = None; let inactive = false; + let eviction_era = None; Self { validator_public_key, bonding_purse, @@ -75,6 +80,7 @@ impl ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, } } @@ -82,6 +88,7 @@ impl ValidatorBid { pub fn empty(validator_public_key: PublicKey, bonding_purse: URef) -> Self { let vesting_schedule = None; let inactive = true; + let eviction_era = None; let staked_amount = 0.into(); let delegation_rate = Default::default(); Self { @@ -91,6 +98,7 @@ impl ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, } } @@ -217,12 +225,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 } } @@ -242,6 +253,7 @@ 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)?; Ok(result) } @@ -252,6 +264,7 @@ impl ToBytes for ValidatorBid { + self.delegation_rate.serialized_length() + self.vesting_schedule.serialized_length() + self.inactive.serialized_length() + + self.eviction_era.serialized_length() } fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { @@ -261,6 +274,7 @@ 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)?; Ok(()) } } @@ -273,6 +287,7 @@ 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)?; Ok(( ValidatorBid { validator_public_key, @@ -281,6 +296,7 @@ impl FromBytes for ValidatorBid { delegation_rate, vesting_schedule, inactive, + eviction_era, }, bytes, )) @@ -296,6 +312,7 @@ impl From for ValidatorBid { delegation_rate: *bid.delegation_rate(), vesting_schedule: bid.vesting_schedule().cloned(), inactive: bid.inactive(), + eviction_era: None, } } } @@ -305,7 +322,7 @@ mod tests { use crate::{ bytesrepr, system::auction::{bid::VestingSchedule, DelegationRate, ValidatorBid}, - AccessRights, PublicKey, SecretKey, URef, U512, + AccessRights, EraId, PublicKey, SecretKey, URef, U512, }; #[test] @@ -319,6 +336,7 @@ mod tests { delegation_rate: DelegationRate::MAX, vesting_schedule: Some(VestingSchedule::default()), inactive: false, + eviction_era: None, }; bytesrepr::test_serialization_roundtrip(&founding_validator); } @@ -334,6 +352,7 @@ mod tests { delegation_rate: DelegationRate::max_value(), vesting_schedule: Some(VestingSchedule::default()), inactive: true, + eviction_era: Some(EraId::default()), }; bytesrepr::test_serialization_roundtrip(&founding_validator); } From e77a023f36f2cd0359dfc32b2864385f109a53e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 26 Mar 2024 10:25:37 +0100 Subject: [PATCH 02/11] add new option to chainspec --- resources/local/chainspec.toml.in | 2 ++ resources/production/chainspec.toml | 2 ++ resources/test/valid/0_9_0/chainspec.toml | 1 + resources/test/valid/0_9_0_unordered/chainspec.toml | 1 + resources/test/valid/1_0_0/chainspec.toml | 1 + types/src/chainspec/core_config.rs | 13 +++++++++++++ 6 files changed, 20 insertions(+) diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index c3a8a959a3..f24beaf5ba 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -53,6 +53,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 d1ddb3e7bb..44590b7608 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -53,6 +53,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/valid/0_9_0/chainspec.toml b/resources/test/valid/0_9_0/chainspec.toml index 37870bc709..ced1fe0341 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 a996d46776..e2d13991db 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 329d2e5dca..fbfa020dfc 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/types/src/chainspec/core_config.rs b/types/src/chainspec/core_config.rs index 946d70a397..d0e3825d7e 100644 --- a/types/src/chainspec/core_config.rs +++ b/types/src/chainspec/core_config.rs @@ -90,6 +90,9 @@ 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, @@ -193,6 +196,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), @@ -247,6 +252,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, @@ -287,6 +293,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, @@ -329,6 +336,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()?); @@ -367,6 +375,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() @@ -405,6 +416,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)?; @@ -438,6 +450,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, From aeeeacb2087c4c4e5826d88ccaee72d86d7c8635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Thu, 28 Mar 2024 10:53:17 +0100 Subject: [PATCH 03/11] update existing tests --- resources/test/sse_data_schema.json | 11 +++++++++++ types/src/system/auction/validator_bid.rs | 2 +- utils/global-state-update-gen/src/generic/testing.rs | 12 ++++++------ .../validation/tests/fixtures/ABI/stored_value.json | 3 ++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 566e6b3096..ba8d91c2fb 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -3302,6 +3302,17 @@ "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" + } + ] } }, "additionalProperties": false diff --git a/types/src/system/auction/validator_bid.rs b/types/src/system/auction/validator_bid.rs index 99a0dcdd04..31b7dc79b3 100644 --- a/types/src/system/auction/validator_bid.rs +++ b/types/src/system/auction/validator_bid.rs @@ -88,7 +88,7 @@ impl ValidatorBid { pub fn empty(validator_public_key: PublicKey, bonding_purse: URef) -> Self { let vesting_schedule = None; let inactive = true; - let eviction_era = None; + let eviction_era = Some(EraId::default()); let staked_amount = 0.into(); let delegation_rate = Default::default(); Self { diff --git a/utils/global-state-update-gen/src/generic/testing.rs b/utils/global-state-update-gen/src/generic/testing.rs index 2b5e60f0d9..ef18677e4d 100644 --- a/utils/global-state-update-gen/src/generic/testing.rs +++ b/utils/global-state-update-gen/src/generic/testing.rs @@ -759,7 +759,7 @@ fn should_replace_one_validator() { 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(); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // check writes for validator2 @@ -859,7 +859,7 @@ fn should_replace_one_validator_with_unbonding() { 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(); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // check writes for validator2 @@ -1969,7 +1969,7 @@ fn should_handle_unbonding_to_oneself_correctly() { 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(); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // Check writes for validator2 @@ -2110,7 +2110,7 @@ fn should_handle_unbonding_to_a_delegator_correctly() { U512::zero(), Default::default(), ); - 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 @@ -2232,7 +2232,7 @@ fn should_handle_legacy_unbonding_to_oneself_correctly() { 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(); + expected_bid_1.deactivate(EraId::default()); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); // Check writes for validator2 @@ -2406,7 +2406,7 @@ fn should_handle_legacy_unbonding_to_a_delegator_correctly() { U512::zero(), Default::default(), ); - 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/tests/fixtures/ABI/stored_value.json b/utils/validation/tests/fixtures/ABI/stored_value.json index 8aedc86b66..9996d0ba89 100644 --- a/utils/validation/tests/fixtures/ABI/stored_value.json +++ b/utils/validation/tests/fixtures/ABI/stored_value.json @@ -361,7 +361,8 @@ "initial_release_timestamp_millis": 18446744073709551615, "locked_amounts": null }, - "inactive": false + "inactive": false, + "eviction_era": null } } } From e9ee849d8c1d48b1dd3888f73dbee2dd7812ea5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 2 Apr 2024 14:10:49 +0200 Subject: [PATCH 04/11] add inactive validator undelegation delay to `ValidatorBid` --- .../src/engine_state/engine_config.rs | 20 +++++++ execution_engine/src/runtime/mod.rs | 13 ++++- storage/src/system/auction.rs | 57 ++++++++++++++++++- storage/src/system/genesis.rs | 1 + types/src/auction_state.rs | 12 +++- types/src/chainspec/core_config.rs | 3 +- types/src/chainspec/genesis_config.rs | 30 ++++++++++ types/src/gens.rs | 14 ++++- types/src/system/auction/constants.rs | 2 + types/src/system/auction/validator_bid.rs | 40 ++++++++++++- utils/global-state-update-gen/src/generic.rs | 14 +++-- utils/validation/src/generators.rs | 1 + 12 files changed, 193 insertions(+), 14 deletions(-) diff --git a/execution_engine/src/engine_state/engine_config.rs b/execution_engine/src/engine_state/engine_config.rs index f362aced80..3daa04143a 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, @@ -94,6 +97,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(), @@ -197,6 +201,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`]. @@ -212,6 +221,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, @@ -269,6 +279,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); @@ -400,6 +416,9 @@ impl EngineConfigBuilder { let balance_hold_interval = self .balance_hold_interval .unwrap_or(DEFAULT_BALANCE_HOLD_INTERVAL); + let inactive_validator_undelegation_delay = self + .inactive_validator_undelegation_delay + .unwrap_or(DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY); EngineConfig { max_associated_keys, @@ -418,6 +437,7 @@ impl EngineConfigBuilder { max_delegators_per_validator, compute_rewards, balance_hold_interval, + inactive_validator_undelegation_delay, } } } diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 965a42fba3..f23e222799 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -833,9 +833,20 @@ where Self::get_named_argument(runtime_args, auction::ARG_DELEGATION_RATE)?; let amount = Self::get_named_argument(runtime_args, auction::ARG_AMOUNT)?; let holds_epoch = self.holds_epoch(); + let inactive_validator_undelegation_delay: Option = Self::get_named_argument( + runtime_args, + auction::ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + )?; + + let inactive_validator_undelegation_delay = inactive_validator_undelegation_delay + .unwrap_or( + self.context + .engine_config() + .inactive_validator_undelegation_delay(), + ); let result = runtime - .add_bid(account_hash, delegation_rate, amount, holds_epoch) + .add_bid(account_hash, delegation_rate, amount, holds_epoch, inactive_validator_undelegation_delay) .map_err(Self::reverter)?; CLValue::from_t(result).map_err(Self::reverter) diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index bb7794fc8e..6709628108 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -70,6 +70,7 @@ pub trait Auction: delegation_rate: DelegationRate, amount: U512, holds_epoch: HoldsEpoch, + inactive_validator_undelegation_delay: u64, ) -> Result { if !self.allow_auction_bids() { // The validator set may be closed on some side chains, @@ -103,8 +104,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)) }; @@ -690,6 +696,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 7b1e735ad0..9827c420c5 100644 --- a/storage/src/system/genesis.rs +++ b/storage/src/system/genesis.rs @@ -371,6 +371,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 d0e3825d7e..53f4d187f7 100644 --- a/types/src/chainspec/core_config.rs +++ b/types/src/chainspec/core_config.rs @@ -90,7 +90,8 @@ 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 + /// 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. diff --git a/types/src/chainspec/genesis_config.rs b/types/src/chainspec/genesis_config.rs index 480e35c1fa..3329807da4 100644 --- a/types/src/chainspec/genesis_config.rs +++ b/types/src/chainspec/genesis_config.rs @@ -23,6 +23,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% @@ -45,6 +48,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, } @@ -67,6 +71,7 @@ impl GenesisConfig { locked_funds_period_millis: u64, round_seigniorage_rate: Ratio, unbonding_delay: u64, + inactive_validator_undelegation_delay: u64, genesis_timestamp_millis: u64, ) -> GenesisConfig { GenesisConfig { @@ -78,6 +83,7 @@ impl GenesisConfig { locked_funds_period_millis, round_seigniorage_rate, unbonding_delay, + inactive_validator_undelegation_delay, genesis_timestamp_millis, } } @@ -154,6 +160,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 @@ -184,6 +195,8 @@ impl Distribution for Standard { let unbonding_delay = rng.gen(); + let inactive_validator_undelegation_delay = rng.gen(); + let genesis_timestamp_millis = rng.gen(); GenesisConfig { @@ -195,6 +208,7 @@ impl Distribution for Standard { locked_funds_period_millis, round_seigniorage_rate, unbonding_delay, + inactive_validator_undelegation_delay, genesis_timestamp_millis, } } @@ -214,6 +228,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, } @@ -271,6 +286,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); @@ -292,6 +316,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), @@ -316,6 +343,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) .build() } diff --git a/types/src/gens.rs b/types/src/gens.rs index 63f9462c80..025f4dbe34 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, }; @@ -617,9 +617,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, @@ -627,6 +635,7 @@ pub(crate) fn validator_bid_arb() -> impl Strategy { staked_amount, delegation_rate, 1u64, + inactive_validator_undelegation_delay, ) } else { ValidatorBid::unlocked( @@ -634,6 +643,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 63b414bfab..da2813f6d9 100644 --- a/types/src/system/auction/constants.rs +++ b/types/src/system/auction/constants.rs @@ -54,6 +54,8 @@ pub const ARG_ERA_END_TIMESTAMP_MILLIS: &str = "era_end_timestamp_millis"; pub const ARG_EVICTED_VALIDATORS: &str = "evicted_validators"; /// Named constant for `rewards_map`; pub const ARG_REWARDS_MAP: &str = "rewards_map"; +/// Named constant for `rewards_map`; +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/validator_bid.rs b/types/src/system/auction/validator_bid.rs index 31b7dc79b3..19ba872b93 100644 --- a/types/src/system/auction/validator_bid.rs +++ b/types/src/system/auction/validator_bid.rs @@ -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,8 +40,11 @@ pub struct ValidatorBid { vesting_schedule: Option, /// `true` if validator has been "evicted" inactive: bool, - /// era when validator has been "evicted" + /// 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 { @@ -48,6 +55,7 @@ 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; @@ -60,6 +68,7 @@ impl ValidatorBid { vesting_schedule, inactive, eviction_era, + inactive_validator_undelegation_delay, } } @@ -69,6 +78,7 @@ impl ValidatorBid { bonding_purse: URef, staked_amount: U512, delegation_rate: DelegationRate, + inactive_validator_undelegation_delay: u64, ) -> Self { let vesting_schedule = None; let inactive = false; @@ -81,11 +91,16 @@ impl ValidatorBid { 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()); @@ -99,6 +114,7 @@ impl ValidatorBid { vesting_schedule, inactive, eviction_era, + inactive_validator_undelegation_delay, } } @@ -169,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, @@ -288,6 +314,7 @@ impl FromBytes for ValidatorBid { 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, @@ -297,6 +324,7 @@ impl FromBytes for ValidatorBid { vesting_schedule, inactive, eviction_era, + inactive_validator_undelegation_delay, }, bytes, )) @@ -313,6 +341,7 @@ impl From for ValidatorBid { vesting_schedule: bid.vesting_schedule().cloned(), inactive: bid.inactive(), eviction_era: None, + inactive_validator_undelegation_delay: DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, } } } @@ -321,7 +350,10 @@ impl From for ValidatorBid { mod tests { use crate::{ bytesrepr, - system::auction::{bid::VestingSchedule, DelegationRate, ValidatorBid}, + system::auction::{ + bid::VestingSchedule, validator_bid::DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, + DelegationRate, ValidatorBid, + }, AccessRights, EraId, PublicKey, SecretKey, URef, U512, }; @@ -337,6 +369,7 @@ mod tests { 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); } @@ -353,6 +386,7 @@ mod tests { 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); } diff --git a/utils/global-state-update-gen/src/generic.rs b/utils/global-state-update-gen/src/generic.rs index 075d4bba46..d61e8e0b26 100644 --- a/utils/global-state-update-gen/src/generic.rs +++ b/utils/global-state-update-gen/src/generic.rs @@ -14,7 +14,7 @@ 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_PROTOCOL_VERSION, DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY}; use casper_types::{ system::auction::{ Bid, BidKind, BidsExt, Delegator, SeigniorageRecipient, SeigniorageRecipientsSnapshot, @@ -308,9 +308,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(), @@ -511,6 +515,7 @@ fn create_or_update_bid( *bonding_purse, *updated_recipient.stake(), *updated_recipient.delegation_rate(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); state.set_bid( @@ -547,6 +552,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/validation/src/generators.rs b/utils/validation/src/generators.rs index 5f4cbcc888..58a44db834 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); From dd1d9dbac14b560f1c73f37dead1faa8d7a4456f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 2 Apr 2024 14:12:47 +0200 Subject: [PATCH 05/11] add method to handle undelegation from inactive validators --- node/src/components/contract_runtime/error.rs | 11 ++- .../components/contract_runtime/operations.rs | 25 +++++ storage/src/data_access_layer.rs | 1 + .../data_access_layer/inactive_validators.rs | 85 +++++++++++++++++ storage/src/global_state/state/mod.rs | 92 +++++++++++++++++++ 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 storage/src/data_access_layer/inactive_validators.rs diff --git a/node/src/components/contract_runtime/error.rs b/node/src/components/contract_runtime/error.rs index f983d68c74..b884e19f43 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 ed669cd89b..c8fd0a2259 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -13,6 +13,9 @@ use casper_storage::{ FeeResult, FlushRequest, HandleFeeMode, HandleFeeRequest, HandleRefundMode, HandleRefundRequest, InsufficientBalanceHandling, ProofHandling, PruneRequest, PruneResult, StepRequest, StepResult, TransferRequest, + inactive_validators::{ + InactiveValidatorsUndelegationRequest, InactiveValidatorsUndelegationResult, + }, }, global_state::state::{ lmdb::LmdbGlobalState, scratch::ScratchGlobalState, CommitProvider, ScratchProvider, @@ -694,6 +697,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/storage/src/data_access_layer.rs b/storage/src/data_access_layer.rs index 3ef83ab6e3..dc1848294f 100644 --- a/storage/src/data_access_layer.rs +++ b/storage/src/data_access_layer.rs @@ -20,6 +20,7 @@ mod genesis; pub mod handle_fee; mod handle_refund; pub mod mint; +pub mod inactive_validators; mod protocol_upgrade; pub mod prune; pub mod query; 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..7fde085233 --- /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, 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: u64, +} + +impl InactiveValidatorsUndelegationRequest { + pub fn new( + config: Config, + state_hash: Digest, + protocol_version: ProtocolVersion, + block_time: u64, + ) -> 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) -> u64 { + 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 7e9c6fb24b..9e085e9b29 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -46,6 +46,10 @@ use crate::{ era_validators::EraValidatorsResult, handle_fee::{HandleFeeMode, HandleFeeRequest, HandleFeeResult}, mint::{TransferRequest, TransferRequestArgs, TransferResult}, + inactive_validators::{ + InactiveValidatorsUndelegationError, InactiveValidatorsUndelegationRequest, + InactiveValidatorsUndelegationResult, + }, tagged_values::{TaggedValuesRequest, TaggedValuesResult}, AddressableEntityRequest, AddressableEntityResult, AuctionMethod, BalanceHoldError, BalanceHoldKind, BalanceHoldMode, BalanceHoldRequest, BalanceHoldResult, BalanceIdentifier, @@ -505,6 +509,94 @@ pub trait CommitProvider: StateProvider { )), } } + + /// 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. From ac222eb4838c221e91af3db83a6c86d2c9f9a570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 2 Apr 2024 15:20:21 +0200 Subject: [PATCH 06/11] fix JSON schema test --- resources/test/sse_data_schema.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index ba8d91c2fb..b749a800e7 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -3254,6 +3254,7 @@ "bonding_purse", "delegation_rate", "inactive", + "inactive_validator_undelegation_delay", "staked_amount", "validator_public_key" ], @@ -3304,7 +3305,7 @@ "type": "boolean" }, "eviction_era": { - "description": "era when validator has been \"evicted\"", + "description": "Era when validator has been \"evicted\"", "anyOf": [ { "$ref": "#/definitions/EraId" @@ -3313,6 +3314,12 @@ "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 From bab3796e2fcc1fc9092a16a7d7c9f1ff8a4749f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 3 Apr 2024 12:10:38 +0200 Subject: [PATCH 07/11] fix missing serialization --- types/src/system/auction/validator_bid.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/types/src/system/auction/validator_bid.rs b/types/src/system/auction/validator_bid.rs index 19ba872b93..aff595ec02 100644 --- a/types/src/system/auction/validator_bid.rs +++ b/types/src/system/auction/validator_bid.rs @@ -280,6 +280,8 @@ impl ToBytes for ValidatorBid { 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) } @@ -291,6 +293,9 @@ impl ToBytes for ValidatorBid { + 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> { @@ -301,6 +306,8 @@ impl ToBytes for ValidatorBid { 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(()) } } @@ -402,6 +409,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, @@ -409,6 +417,7 @@ mod tests { validator_staked_amount, validator_delegation_rate, validator_release_timestamp, + inactive_validator_undelegation_delay, ); assert!(!bid.is_locked_with_vesting_schedule( From 7a6b35870691c1441110af04b9b58a1aa30ff418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 3 Apr 2024 12:10:48 +0200 Subject: [PATCH 08/11] fix existing tests --- .../src/generic/testing.rs | 58 +++++++++++++++---- .../tests/fixtures/ABI/stored_value.json | 5 +- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/utils/global-state-update-gen/src/generic/testing.rs b/utils/global-state-update-gen/src/generic/testing.rs index ef18677e4d..e56fcde586 100644 --- a/utils/global-state-update-gen/src/generic/testing.rs +++ b/utils/global-state-update-gen/src/generic/testing.rs @@ -1,6 +1,7 @@ use itertools::Itertools; use std::collections::BTreeMap; +use casper_execution_engine::engine_state::engine_config::DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY; use rand::Rng; use casper_types::{ @@ -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,8 +769,13 @@ 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()); + 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))); @@ -857,8 +874,13 @@ 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()); + 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))); @@ -1967,8 +1989,13 @@ 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()); + 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))); @@ -2109,6 +2136,7 @@ fn should_handle_unbonding_to_a_delegator_correctly() { validator_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))); @@ -2230,8 +2258,13 @@ 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()); + 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))); @@ -2405,6 +2438,7 @@ fn should_handle_legacy_unbonding_to_a_delegator_correctly() { validator_purse, U512::zero(), Default::default(), + DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ); expected_bid_1.deactivate(WITHDRAW_ERA); update.assert_written_bid(account1_hash, BidKind::Validator(Box::new(expected_bid_1))); diff --git a/utils/validation/tests/fixtures/ABI/stored_value.json b/utils/validation/tests/fixtures/ABI/stored_value.json index 9996d0ba89..58276e7e07 100644 --- a/utils/validation/tests/fixtures/ABI/stored_value.json +++ b/utils/validation/tests/fixtures/ABI/stored_value.json @@ -362,13 +362,14 @@ "locked_amounts": null }, "inactive": false, - "eviction_era": null + "eviction_era": null, + "inactive_validator_undelegation_delay": 36 } } } } ], - "output": "0b0101197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d610a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a070500743ba40b6401ffffffffffffffff0000" + "output": "0b0101197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d610a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a070500743ba40b6401ffffffffffffffff0000002400000000000000" }, "Withdraw": { "input": [ From 716cd584f1ea60ec517e8aee12fd686ea62aad4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Tue, 9 Apr 2024 12:36:51 +0200 Subject: [PATCH 09/11] test undelegation from inactive validators --- execution_engine/src/runtime/mod.rs | 15 +- .../test_support/src/wasm_test_builder.rs | 33 +++ .../src/test/system_contracts/auction/bids.rs | 209 +++++++++++++++++- .../contracts/client/add-bid/src/main.rs | 32 ++- types/src/system/auction/constants.rs | 2 +- types/src/system/auction/error.rs | 10 + 6 files changed, 287 insertions(+), 14 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index f23e222799..93059cd5f2 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -44,7 +44,7 @@ use casper_types::{ crypto, system::{ self, - auction::{self, EraInfo}, + auction::{self, EraInfo, Error}, handle_payment, mint, Caller, SystemEntityType, AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT, }, @@ -845,6 +845,19 @@ where .inactive_validator_undelegation_delay(), ); + let global_inactive_validator_undelegation_delay = self + .context() + .engine_config() + .inactive_validator_undelegation_delay(); + + if inactive_validator_undelegation_delay + > global_inactive_validator_undelegation_delay + { + return Err(ExecError::Revert(ApiError::from( + Error::InactiveValidatorUndelegationDelayTooLarge, + ))); + } + let result = runtime .add_bid(account_hash, delegation_rate, amount, holds_epoch, inactive_validator_undelegation_delay) .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 e1ee008140..87eb2207d0 100644 --- a/execution_engine_testing/test_support/src/wasm_test_builder.rs +++ b/execution_engine_testing/test_support/src/wasm_test_builder.rs @@ -23,6 +23,10 @@ use casper_storage::{ data_access_layer::{ balance::BalanceHandling, AuctionMethod, BalanceIdentifier, BalanceRequest, BalanceResult, BiddingRequest, BiddingResult, BidsRequest, BlockRewardsRequest, BlockRewardsResult, + inactive_validators::{ + InactiveValidatorsUndelegationRequest, InactiveValidatorsUndelegationResult, + }, + BalanceRequest, BalanceResult, BidsRequest, BlockRewardsRequest, BlockRewardsResult, BlockStore, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, FeeRequest, FeeResult, FlushRequest, FlushResult, GenesisRequest, GenesisResult, ProofHandling, ProtocolUpgradeRequest, ProtocolUpgradeResult, PruneRequest, PruneResult, QueryRequest, @@ -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, + 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 7ec37cf620..b357e43a97 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 @@ -29,8 +29,9 @@ use casper_types::{ self, auction::{ self, BidsExt, DelegationRate, EraValidators, Error as AuctionError, UnbondingPurses, - ValidatorWeights, ARG_AMOUNT, ARG_DELEGATION_RATE, ARG_DELEGATOR, ARG_NEW_VALIDATOR, - ARG_PUBLIC_KEY, ARG_VALIDATOR, ERA_ID_KEY, INITIAL_ERA_ID, + ValidatorWeights, ARG_AMOUNT, ARG_DELEGATION_RATE, ARG_DELEGATOR, + ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, ARG_NEW_VALIDATOR, ARG_PUBLIC_KEY, + ARG_VALIDATOR, ERA_ID_KEY, INITIAL_ERA_ID, }, }, EntityAddr, EraId, GenesisAccount, GenesisConfigBuilder, GenesisValidator, Key, Motes, @@ -4323,3 +4324,207 @@ fn should_increase_existing_delegation_when_limit_exceeded() { .expect_success() .commit(); } + +#[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(PRODUCTION_RUN_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/smart_contracts/contracts/client/add-bid/src/main.rs b/smart_contracts/contracts/client/add-bid/src/main.rs index 206c49b741..3c3971e846 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::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/types/src/system/auction/constants.rs b/types/src/system/auction/constants.rs index da2813f6d9..b09bdeeaea 100644 --- a/types/src/system/auction/constants.rs +++ b/types/src/system/auction/constants.rs @@ -54,7 +54,7 @@ pub const ARG_ERA_END_TIMESTAMP_MILLIS: &str = "era_end_timestamp_millis"; pub const ARG_EVICTED_VALIDATORS: &str = "evicted_validators"; /// Named constant for `rewards_map`; pub const ARG_REWARDS_MAP: &str = "rewards_map"; -/// Named constant for `rewards_map`; +/// 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`. diff --git a/types/src/system/auction/error.rs b/types/src/system/auction/error.rs index 1fb8bff3e0..e7799b6769 100644 --- a/types/src/system/auction/error.rs +++ b/types/src/system/auction/error.rs @@ -339,6 +339,12 @@ pub enum Error { /// assert_eq!(51, Error::MissingPurse as u8); /// ``` MissingPurse = 51, + /// Inactive validator undelegation delay exceeds global limit. + /// ``` + /// # use casper_types::system::auction::Error; + /// assert_eq!(52, Error::DelegationRateTooLarge as u8); + /// ``` + InactiveValidatorUndelegationDelayTooLarge = 52, } impl Display for Error { @@ -396,6 +402,7 @@ impl Display for Error { Error::TransferToAdministrator => formatter.write_str("Transfer to administrator error"), Error::ForgedReference => formatter.write_str("Forged reference"), Error::MissingPurse => formatter.write_str("Missing purse"), + Error::InactiveValidatorUndelegationDelayTooLarge => formatter.write_str("Inactive validator undelegation delay exceeds global limit"), } } } @@ -479,6 +486,9 @@ impl TryFrom for Error { d if d == Error::TransferToAdministrator as u8 => Ok(Error::TransferToAdministrator), d if d == Error::ForgedReference as u8 => Ok(Error::ForgedReference), d if d == Error::MissingPurse as u8 => Ok(Error::MissingPurse), + d if d == Error::InactiveValidatorUndelegationDelayTooLarge as u8 => { + Ok(Error::InactiveValidatorUndelegationDelayTooLarge) + } _ => Err(TryFromU8ForError(())), } } From 42b58bebfd28a3e5a5ec22834bbfb6ab42093e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Wed, 10 Apr 2024 08:34:31 +0200 Subject: [PATCH 10/11] post-rebase fixes --- execution_engine/src/runtime/mod.rs | 8 ++- .../test_support/src/wasm_test_builder.rs | 12 ++--- .../src/test/system_contracts/auction/bids.rs | 6 +-- .../system_contracts/auction/distribute.rs | 1 + .../components/contract_runtime/operations.rs | 51 ++++++++++--------- storage/src/data_access_layer.rs | 2 +- storage/src/data_access_layer/auction.rs | 16 +++++- .../data_access_layer/inactive_validators.rs | 8 +-- storage/src/global_state/state/mod.rs | 11 +++- storage/src/system/auction.rs | 2 +- types/src/system/auction/error.rs | 2 +- utils/global-state-update-gen/src/generic.rs | 4 +- 12 files changed, 77 insertions(+), 46 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 93059cd5f2..98e49713e7 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -859,7 +859,13 @@ where } let result = runtime - .add_bid(account_hash, delegation_rate, amount, holds_epoch, inactive_validator_undelegation_delay) + .add_bid( + account_hash, + delegation_rate, + amount, + inactive_validator_undelegation_delay, + holds_epoch, + ) .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 87eb2207d0..87813be91c 100644 --- a/execution_engine_testing/test_support/src/wasm_test_builder.rs +++ b/execution_engine_testing/test_support/src/wasm_test_builder.rs @@ -21,14 +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, + balance::BalanceHandling, inactive_validators::{ InactiveValidatorsUndelegationRequest, InactiveValidatorsUndelegationResult, }, - BalanceRequest, BalanceResult, BidsRequest, BlockRewardsRequest, BlockRewardsResult, - BlockStore, DataAccessLayer, EraValidatorsRequest, EraValidatorsResult, FeeRequest, - FeeResult, FlushRequest, FlushResult, GenesisRequest, GenesisResult, ProofHandling, + 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, @@ -1036,7 +1036,7 @@ where native_runtime_config, pre_state_hash, protocol_version, - time, + BlockTime::new(time), ); let inactive_validators_result = self .data_access_layer 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 b357e43a97..886c315a5c 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 @@ -4441,7 +4441,7 @@ fn should_undelegate_from_inactive_validator_after_delay() { let mut builder = LmdbWasmTestBuilder::default(); - builder.run_genesis(PRODUCTION_RUN_GENESIS_REQUEST.clone()); + builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()); for request in post_genesis_requests { builder.exec(request).commit().expect_success(); @@ -4485,7 +4485,7 @@ fn should_undelegate_from_inactive_validator_after_delay() { builder.advance_eras_by(global_inactive_validator_undelegation_delay - 1); // undelegate from inactive validators - builder.undelegate_from_inactive_validators(None, *DEFAULT_PROTOCOL_VERSION, timestamp); + builder.undelegate_from_inactive_validators(None, DEFAULT_PROTOCOL_VERSION, timestamp); let bids = builder.get_bids(); assert_eq!(bids.len(), 3); @@ -4516,7 +4516,7 @@ fn should_undelegate_from_inactive_validator_after_delay() { builder.advance_eras_by(4); // undelegate from inactive validators - builder.undelegate_from_inactive_validators(None, *DEFAULT_PROTOCOL_VERSION, timestamp); + builder.undelegate_from_inactive_validators(None, DEFAULT_PROTOCOL_VERSION, timestamp); let bids = builder.get_bids(); assert_eq!(bids.len(), 2); 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 8148e5087a..43f8097cbb 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 @@ -1028,6 +1028,7 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { public_key: VALIDATOR_1.clone(), amount, delegation_rate: 0, + inactive_validator_undelegation_delay: 36, holds_epoch: HoldsEpoch::NOT_APPLICABLE, } } else { diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index c8fd0a2259..f3fa7495a5 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -7,15 +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, 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, 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, @@ -698,26 +699,26 @@ pub fn execute_finalized_block( // 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; + // 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(); diff --git a/storage/src/data_access_layer.rs b/storage/src/data_access_layer.rs index dc1848294f..8f36e59660 100644 --- a/storage/src/data_access_layer.rs +++ b/storage/src/data_access_layer.rs @@ -19,8 +19,8 @@ mod flush; mod genesis; pub mod handle_fee; mod handle_refund; -pub mod mint; pub mod inactive_validators; +pub mod mint; mod protocol_upgrade; pub mod prune; pub mod query; diff --git a/storage/src/data_access_layer/auction.rs b/storage/src/data_access_layer/auction.rs index 3970b3429a..d387f519a6 100644 --- a/storage/src/data_access_layer/auction.rs +++ b/storage/src/data_access_layer/auction.rs @@ -45,6 +45,7 @@ pub enum AuctionMethod { public_key: PublicKey, delegation_rate: DelegationRate, amount: U512, + inactive_validator_undelegation_delay: u64, holds_epoch: HoldsEpoch, }, WithdrawBid { @@ -85,7 +86,11 @@ impl AuctionMethod { Err(AuctionMethodError::InvalidEntryPoint(entry_point)) } TransactionEntryPoint::ActivateBid => Self::new_activate_bid(runtime_args), - TransactionEntryPoint::AddBid => Self::new_add_bid(runtime_args, holds_epoch), + TransactionEntryPoint::AddBid => Self::new_add_bid( + runtime_args, + chainspec.core_config.inactive_validator_undelegation_delay, + holds_epoch, + ), TransactionEntryPoint::WithdrawBid => Self::new_withdraw_bid(runtime_args), TransactionEntryPoint::Delegate => Self::new_delegate( runtime_args, @@ -108,15 +113,24 @@ impl AuctionMethod { fn new_add_bid( runtime_args: &RuntimeArgs, + global_inactive_validator_undelegation_delay: u64, holds_epoch: HoldsEpoch, ) -> 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, + )?; + let inactive_validator_undelegation_delay = inactive_validator_undelegation_delay + .unwrap_or(global_inactive_validator_undelegation_delay); + Ok(Self::AddBid { public_key, delegation_rate, amount, + inactive_validator_undelegation_delay, holds_epoch, }) } diff --git a/storage/src/data_access_layer/inactive_validators.rs b/storage/src/data_access_layer/inactive_validators.rs index 7fde085233..c86d0294b6 100644 --- a/storage/src/data_access_layer/inactive_validators.rs +++ b/storage/src/data_access_layer/inactive_validators.rs @@ -1,5 +1,5 @@ use casper_types::{ - execution::Effects, system::auction::Error as AuctionError, Digest, ProtocolVersion, + execution::Effects, system::auction::Error as AuctionError, BlockTime, Digest, ProtocolVersion, }; use thiserror::Error; @@ -13,7 +13,7 @@ pub struct InactiveValidatorsUndelegationRequest { config: Config, state_hash: Digest, protocol_version: ProtocolVersion, - block_time: u64, + block_time: BlockTime, } impl InactiveValidatorsUndelegationRequest { @@ -21,7 +21,7 @@ impl InactiveValidatorsUndelegationRequest { config: Config, state_hash: Digest, protocol_version: ProtocolVersion, - block_time: u64, + block_time: BlockTime, ) -> Self { InactiveValidatorsUndelegationRequest { config, @@ -47,7 +47,7 @@ impl InactiveValidatorsUndelegationRequest { } /// Returns block time. - pub fn block_time(&self) -> u64 { + pub fn block_time(&self) -> BlockTime { self.block_time } } diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 9e085e9b29..6445e11296 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -45,11 +45,11 @@ use crate::{ balance::BalanceHandling, era_validators::EraValidatorsResult, handle_fee::{HandleFeeMode, HandleFeeRequest, HandleFeeResult}, - mint::{TransferRequest, TransferRequestArgs, TransferResult}, inactive_validators::{ InactiveValidatorsUndelegationError, InactiveValidatorsUndelegationRequest, InactiveValidatorsUndelegationResult, }, + mint::{TransferRequest, TransferRequestArgs, TransferResult}, tagged_values::{TaggedValuesRequest, TaggedValuesResult}, AddressableEntityRequest, AddressableEntityResult, AuctionMethod, BalanceHoldError, BalanceHoldKind, BalanceHoldMode, BalanceHoldRequest, BalanceHoldResult, BalanceIdentifier, @@ -1085,9 +1085,16 @@ pub trait StateProvider { public_key, delegation_rate, amount, + inactive_validator_undelegation_delay, holds_epoch, } => runtime - .add_bid(public_key, delegation_rate, amount, holds_epoch) + .add_bid( + public_key, + delegation_rate, + amount, + inactive_validator_undelegation_delay, + holds_epoch, + ) .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 6709628108..f7cd7b5931 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -69,8 +69,8 @@ pub trait Auction: public_key: PublicKey, delegation_rate: DelegationRate, amount: U512, - holds_epoch: HoldsEpoch, inactive_validator_undelegation_delay: u64, + holds_epoch: HoldsEpoch, ) -> Result { if !self.allow_auction_bids() { // The validator set may be closed on some side chains, diff --git a/types/src/system/auction/error.rs b/types/src/system/auction/error.rs index e7799b6769..555b305569 100644 --- a/types/src/system/auction/error.rs +++ b/types/src/system/auction/error.rs @@ -342,7 +342,7 @@ pub enum Error { /// Inactive validator undelegation delay exceeds global limit. /// ``` /// # use casper_types::system::auction::Error; - /// assert_eq!(52, Error::DelegationRateTooLarge as u8); + /// assert_eq!(52, Error::InactiveValidatorUndelegationDelayTooLarge as u8); /// ``` InactiveValidatorUndelegationDelayTooLarge = 52, } diff --git a/utils/global-state-update-gen/src/generic.rs b/utils/global-state-update-gen/src/generic.rs index d61e8e0b26..69a435527c 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, DEFAULT_INACTIVE_VALIDATOR_UNDELEGATION_DELAY}; +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, From 081087385cca1cdf1be57df1279307ca8908fb53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20W=C3=B3jcik?= Date: Mon, 15 Apr 2024 18:02:10 +0200 Subject: [PATCH 11/11] handle optional `add_bid` arg validation --- execution_engine/src/runtime/mod.rs | 20 ++---------- .../system_contracts/auction/distribute.rs | 3 +- .../contract/src/contract_api/runtime.rs | 31 +++++++++++++++++++ .../contracts/client/add-bid/src/main.rs | 2 +- storage/src/data_access_layer/auction.rs | 8 ++--- storage/src/global_state/state/mod.rs | 2 ++ storage/src/system/auction.rs | 9 +++++- .../transaction_v1/transaction_v1_body.rs | 19 ++++++++++-- .../transaction_v1_body/arg_handling.rs | 23 ++++++++++++-- .../transaction_v1/transaction_v1_builder.rs | 8 ++++- 10 files changed, 94 insertions(+), 31 deletions(-) diff --git a/execution_engine/src/runtime/mod.rs b/execution_engine/src/runtime/mod.rs index 98e49713e7..86c2dc2614 100644 --- a/execution_engine/src/runtime/mod.rs +++ b/execution_engine/src/runtime/mod.rs @@ -44,7 +44,7 @@ use casper_types::{ crypto, system::{ self, - auction::{self, EraInfo, Error}, + auction::{self, EraInfo}, handle_payment, mint, Caller, SystemEntityType, AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT, }, @@ -838,32 +838,18 @@ where auction::ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, )?; - let inactive_validator_undelegation_delay = inactive_validator_undelegation_delay - .unwrap_or( - self.context - .engine_config() - .inactive_validator_undelegation_delay(), - ); - - let global_inactive_validator_undelegation_delay = self + let maximum_inactive_validator_undelegation_delay = self .context() .engine_config() .inactive_validator_undelegation_delay(); - if inactive_validator_undelegation_delay - > global_inactive_validator_undelegation_delay - { - return Err(ExecError::Revert(ApiError::from( - Error::InactiveValidatorUndelegationDelayTooLarge, - ))); - } - let result = runtime .add_bid( account_hash, delegation_rate, amount, inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, holds_epoch, ) .map_err(Self::reverter)?; 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 43f8097cbb..f1399e8bb2 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 @@ -1028,7 +1028,8 @@ fn should_distribute_rewards_after_restaking_delegated_funds() { public_key: VALIDATOR_1.clone(), amount, delegation_rate: 0, - inactive_validator_undelegation_delay: 36, + inactive_validator_undelegation_delay: None, + maximum_inactive_validator_undelegation_delay: 36, holds_epoch: HoldsEpoch::NOT_APPLICABLE, } } else { 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 3c3971e846..d3d697e42b 100644 --- a/smart_contracts/contracts/client/add-bid/src/main.rs +++ b/smart_contracts/contracts/client/add-bid/src/main.rs @@ -39,7 +39,7 @@ pub extern "C" fn call() { 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::get_named_arg(ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY); + runtime::try_get_named_arg(ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY); add_bid( public_key, diff --git a/storage/src/data_access_layer/auction.rs b/storage/src/data_access_layer/auction.rs index d387f519a6..290557555f 100644 --- a/storage/src/data_access_layer/auction.rs +++ b/storage/src/data_access_layer/auction.rs @@ -45,7 +45,8 @@ pub enum AuctionMethod { public_key: PublicKey, delegation_rate: DelegationRate, amount: U512, - inactive_validator_undelegation_delay: u64, + inactive_validator_undelegation_delay: Option, + maximum_inactive_validator_undelegation_delay: u64, holds_epoch: HoldsEpoch, }, WithdrawBid { @@ -113,7 +114,7 @@ impl AuctionMethod { fn new_add_bid( runtime_args: &RuntimeArgs, - global_inactive_validator_undelegation_delay: u64, + maximum_inactive_validator_undelegation_delay: u64, holds_epoch: HoldsEpoch, ) -> Result { let public_key = Self::get_named_argument(runtime_args, auction::ARG_PUBLIC_KEY)?; @@ -123,14 +124,13 @@ impl AuctionMethod { runtime_args, auction::ARG_INACTIVE_VALIDATOR_UNDELEGATION_DELAY, )?; - let inactive_validator_undelegation_delay = inactive_validator_undelegation_delay - .unwrap_or(global_inactive_validator_undelegation_delay); Ok(Self::AddBid { public_key, delegation_rate, amount, inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, holds_epoch, }) } diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 6445e11296..5472e0776b 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -1086,6 +1086,7 @@ pub trait StateProvider { delegation_rate, amount, inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, holds_epoch, } => runtime .add_bid( @@ -1093,6 +1094,7 @@ pub trait StateProvider { delegation_rate, amount, inactive_validator_undelegation_delay, + maximum_inactive_validator_undelegation_delay, holds_epoch, ) .map(AuctionMethodRet::UpdatedAmount) diff --git a/storage/src/system/auction.rs b/storage/src/system/auction.rs index f7cd7b5931..69f7139d17 100644 --- a/storage/src/system/auction.rs +++ b/storage/src/system/auction.rs @@ -69,7 +69,8 @@ pub trait Auction: public_key: PublicKey, delegation_rate: DelegationRate, amount: U512, - inactive_validator_undelegation_delay: u64, + inactive_validator_undelegation_delay: Option, + maximum_inactive_validator_undelegation_delay: u64, holds_epoch: HoldsEpoch, ) -> Result { if !self.allow_auction_bids() { @@ -86,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) { diff --git a/types/src/transaction/transaction_v1/transaction_v1_body.rs b/types/src/transaction/transaction_v1/transaction_v1_body.rs index 81295c4830..5a0dfa9c05 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_body.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_body.rs @@ -301,7 +301,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, @@ -334,8 +341,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 7e4cbcaa1a..435e27e0b5 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"); @@ -214,11 +216,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) } @@ -229,6 +239,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(()) } @@ -243,7 +255,7 @@ pub(in crate::transaction::transaction_v1) fn new_withdraw_bid_args Result<(), InvalidTransactionV1> { @@ -488,8 +500,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,