From 507782b191c230477c1d1eedb5e2aa9969b83bc1 Mon Sep 17 00:00:00 2001 From: drcpu Date: Sat, 11 Jan 2025 19:11:23 +0000 Subject: [PATCH] feat(staking): increment nonce only when processing stake / unstake transactions --- data_structures/benches/staking.rs | 24 ++++++- data_structures/src/staking/mod.rs | 16 +++-- data_structures/src/staking/stake.rs | 10 ++- data_structures/src/staking/stakes.rs | 90 +++++++++++++++++--------- node/src/actors/chain_manager/mod.rs | 2 + validations/src/eligibility/current.rs | 14 ++-- 6 files changed, 107 insertions(+), 49 deletions(-) diff --git a/data_structures/benches/staking.rs b/data_structures/benches/staking.rs index 19e3d5be9..7ccf1e376 100644 --- a/data_structures/benches/staking.rs +++ b/data_structures/benches/staking.rs @@ -15,7 +15,13 @@ fn populate(b: &mut Bencher) { let coins = i; let epoch = i; stakes - .add_stake(address.as_str(), coins, epoch, MINIMUM_VALIDATOR_STAKE) + .add_stake( + address.as_str(), + coins, + epoch, + true, + MINIMUM_VALIDATOR_STAKE, + ) .unwrap(); i += 1; @@ -37,7 +43,13 @@ fn rank(b: &mut Bencher) { let address = format!("{}", rng.gen::()); stakes - .add_stake(address.as_str(), coins, epoch, MINIMUM_VALIDATOR_STAKE) + .add_stake( + address.as_str(), + coins, + epoch, + true, + MINIMUM_VALIDATOR_STAKE, + ) .unwrap(); i += 1; @@ -69,7 +81,13 @@ fn query_power(b: &mut Bencher) { let address = format!("{i}"); stakes - .add_stake(address.as_str(), coins, epoch, MINIMUM_VALIDATOR_STAKE) + .add_stake( + address.as_str(), + coins, + epoch, + true, + MINIMUM_VALIDATOR_STAKE, + ) .unwrap(); i += 1; diff --git a/data_structures/src/staking/mod.rs b/data_structures/src/staking/mod.rs index 314ba667f..57d300358 100644 --- a/data_structures/src/staking/mod.rs +++ b/data_structures/src/staking/mod.rs @@ -31,7 +31,9 @@ pub mod test { let mut stakes = StakesTester::default(); // Alpha stakes 2 @ epoch 0 - stakes.add_stake("Alpha", 2, 0, MIN_STAKE_NANOWITS).unwrap(); + stakes + .add_stake("Alpha", 2, 0, true, MIN_STAKE_NANOWITS) + .unwrap(); // Nobody holds any power just yet let rank = stakes.by_rank(Capability::Mining, 0).collect::>(); @@ -42,7 +44,9 @@ pub mod test { assert_eq!(rank, vec![("Alpha".into(), 2)]); // Beta stakes 5 @ epoch 10 - stakes.add_stake("Beta", 5, 10, MIN_STAKE_NANOWITS).unwrap(); + stakes + .add_stake("Beta", 5, 10, true, MIN_STAKE_NANOWITS) + .unwrap(); // Alpha is still leading, but Beta has scheduled its takeover let rank = stakes.by_rank(Capability::Mining, 10).collect::>(); @@ -56,7 +60,7 @@ pub mod test { // Gamma should never take over, even in a million epochs, because it has only 1 coin stakes - .add_stake("Gamma", 1, 30, MIN_STAKE_NANOWITS) + .add_stake("Gamma", 1, 30, true, MIN_STAKE_NANOWITS) .unwrap(); let rank = stakes .by_rank(Capability::Mining, 1_000_000) @@ -72,7 +76,7 @@ pub mod test { // But Delta is here to change it all stakes - .add_stake("Delta", 1_000, 50, MIN_STAKE_NANOWITS) + .add_stake("Delta", 1_000, 50, true, MIN_STAKE_NANOWITS) .unwrap(); let rank = stakes.by_rank(Capability::Mining, 50).collect::>(); assert_eq!( @@ -96,7 +100,9 @@ pub mod test { ); // If Alpha removes all of its stake, it should immediately disappear - stakes.remove_stake("Alpha", 2, MIN_STAKE_NANOWITS).unwrap(); + stakes + .remove_stake("Alpha", 2, true, MIN_STAKE_NANOWITS) + .unwrap(); let rank = stakes.by_rank(Capability::Mining, 51).collect::>(); assert_eq!( rank, diff --git a/data_structures/src/staking/stake.rs b/data_structures/src/staking/stake.rs index 7ddc55460..351f88b47 100644 --- a/data_structures/src/staking/stake.rs +++ b/data_structures/src/staking/stake.rs @@ -84,6 +84,7 @@ where &mut self, coins: Coins, epoch: Epoch, + increment_nonce: bool, minimum_stakeable: Coins, ) -> StakesResult { // Make sure that the amount to be staked is equal or greater than the minimum @@ -112,7 +113,9 @@ where self.epochs.update(capability, epoch_after); } - self.nonce += Nonce::from(1); + if increment_nonce { + self.nonce += Nonce::from(1); + } Ok(coins_after) } @@ -141,6 +144,7 @@ where pub fn remove_stake( &mut self, coins: Coins, + increment_nonce: bool, minimum_stakeable: Coins, ) -> StakesResult { let coins_after = self.coins.sub(coins); @@ -154,7 +158,9 @@ where self.coins = coins_after; - self.nonce += Nonce::from(1); + if increment_nonce { + self.nonce += Nonce::from(1); + } Ok(self.coins) } diff --git a/data_structures/src/staking/stakes.rs b/data_structures/src/staking/stakes.rs index d161a5a1a..a638a6ec5 100644 --- a/data_structures/src/staking/stakes.rs +++ b/data_structures/src/staking/stakes.rs @@ -214,6 +214,7 @@ where key: ISK, coins: Coins, epoch: Epoch, + increment_nonce: bool, minimum_stakeable: Coins, ) -> StakesResult, Address, Coins, Epoch> where @@ -240,7 +241,7 @@ where stake .value .write()? - .add_stake(coins, epoch, minimum_stakeable)?; + .add_stake(coins, epoch, increment_nonce, minimum_stakeable)?; // Update all indexes if needed (only when the stake entry didn't exist before) if !stake_found { @@ -368,6 +369,7 @@ where &mut self, key: ISK, coins: Coins, + increment_nonce: bool, minimum_stakeable: Coins, ) -> StakesResult where @@ -380,7 +382,7 @@ where let final_coins = { let mut stake = by_address_entry.get_mut().value.write()?; - stake.remove_stake(coins, minimum_stakeable)? + stake.remove_stake(coins, increment_nonce, minimum_stakeable)? }; // No need to keep the entry if the stake has gone to zero @@ -521,7 +523,7 @@ where .value .write() .unwrap() - .add_stake(coins, current_epoch, 0.into()); + .add_stake(coins, current_epoch, false, 0.into()); Ok(()) } @@ -548,7 +550,7 @@ where .value .write() .unwrap() - .remove_stake(coins, minimum_stakeable); + .remove_stake(coins, false, minimum_stakeable); Ok(()) } @@ -807,7 +809,7 @@ where key.validator.bech32(environment) ); - stakes.add_stake(key, coins, epoch, minimum_stakeable.into())?; + stakes.add_stake(key, coins, epoch, true, minimum_stakeable.into())?; log::debug!("Current state of the stakes tracker: {:#?}", stakes); @@ -866,7 +868,7 @@ where coins.wits_and_nanowits().0, ); - stakes.remove_stake(key, coins, minimum_stakeable.into())?; + stakes.remove_stake(key, coins, true, minimum_stakeable.into())?; log::debug!("Current state of the stakes tracker: {:#?}", stakes); @@ -1004,7 +1006,7 @@ mod tests { // Let's make Alice stake 100 Wit at epoch 100 assert_eq!( stakes - .add_stake(alice_charlie, 100, 100, MIN_STAKE_NANOWITS) + .add_stake(alice_charlie, 100, 100, true, MIN_STAKE_NANOWITS) .unwrap(), Stake::from_parts( 100, @@ -1028,7 +1030,7 @@ mod tests { // Let's make Alice stake 50 Wits at epoch 150 this time assert_eq!( stakes - .add_stake(alice_charlie, 50, 300, MIN_STAKE_NANOWITS) + .add_stake(alice_charlie, 50, 300, true, MIN_STAKE_NANOWITS) .unwrap(), Stake::from_parts( 150, @@ -1059,7 +1061,7 @@ mod tests { // Now let's make Bob stake 500 Wits at epoch 1000 this time assert_eq!( stakes - .add_stake(bob_david, 500, 1_000, MIN_STAKE_NANOWITS) + .add_stake(bob_david, 500, 1_000, true, MIN_STAKE_NANOWITS) .unwrap(), Stake::from_parts( 500, @@ -1118,13 +1120,13 @@ mod tests { let charlie_erin = (charlie, erin); stakes - .add_stake(alice_charlie, 10, 0, MIN_STAKE_NANOWITS) + .add_stake(alice_charlie, 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(bob_david, 20, 20, MIN_STAKE_NANOWITS) + .add_stake(bob_david, 20, 20, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(charlie_erin, 30, 30, MIN_STAKE_NANOWITS) + .add_stake(charlie_erin, 30, 30, true, MIN_STAKE_NANOWITS) .unwrap(); // Let's really start our test at epoch 100 @@ -1270,19 +1272,19 @@ mod tests { let erin_alice = (erin, alice); stakes - .add_stake(alice_bob, 10, 0, MIN_STAKE_NANOWITS) + .add_stake(alice_bob, 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(bob_charlie, 20, 10, MIN_STAKE_NANOWITS) + .add_stake(bob_charlie, 20, 10, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(charlie_david, 30, 20, MIN_STAKE_NANOWITS) + .add_stake(charlie_david, 30, 20, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(david_erin, 40, 30, MIN_STAKE_NANOWITS) + .add_stake(david_erin, 40, 30, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(erin_alice, 50, 40, MIN_STAKE_NANOWITS) + .add_stake(erin_alice, 50, 40, true, MIN_STAKE_NANOWITS) .unwrap(); // Power of validators at epoch 90: @@ -1331,13 +1333,13 @@ mod tests { let charlie_erin = (charlie, erin); stakes - .add_stake(alice_charlie, 10, 0, MIN_STAKE_NANOWITS) + .add_stake(alice_charlie, 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(bob_david, 20, 30, MIN_STAKE_NANOWITS) + .add_stake(bob_david, 20, 30, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(charlie_erin, 40, 50, MIN_STAKE_NANOWITS) + .add_stake(charlie_erin, 40, 50, true, MIN_STAKE_NANOWITS) .unwrap(); let result = stakes.query_stakes(QueryStakesKey::Key(bob_david.into())); @@ -1415,7 +1417,7 @@ mod tests { let alice_bob = (alice.clone(), bob.clone()); stakes - .add_stake(alice_bob, 123, 456, MIN_STAKE_NANOWITS) + .add_stake(alice_bob, 123, 456, true, MIN_STAKE_NANOWITS) .ok(); let serialized = bincode::serialize(&stakes).unwrap().clone(); @@ -1448,7 +1450,7 @@ mod tests { // Use the validator with a (validator, withdrawer) pair stakes - .add_stake((alice, bob), 10, 0, MIN_STAKE_NANOWITS) + .add_stake((alice, bob), 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); // The validator is used, we can still stake as long as the correct withdrawer is used @@ -1476,26 +1478,50 @@ mod tests { let alice_charlie = (alice, charlie); let bob_david = (bob, david); + // Test nonces increasing stakes - .add_stake(alice_charlie, 10, 0, MIN_STAKE_NANOWITS) + .add_stake(alice_charlie, 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(bob_david, 20, 10, MIN_STAKE_NANOWITS) + .add_stake(bob_david, 20, 10, true, MIN_STAKE_NANOWITS) .unwrap(); assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); assert_eq!(stakes.query_nonce(bob_david), Ok(1)); stakes - .remove_stake(bob_david, 10, MIN_STAKE_NANOWITS) + .remove_stake(bob_david, 10, true, MIN_STAKE_NANOWITS) .unwrap(); assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); assert_eq!(stakes.query_nonce(bob_david), Ok(2)); stakes - .add_stake(bob_david, 40, 30, MIN_STAKE_NANOWITS) + .add_stake(bob_david, 40, 30, true, MIN_STAKE_NANOWITS) .unwrap(); assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); assert_eq!(stakes.query_nonce(bob_david), Ok(3)); + + // Test nonces not increasing + stakes + .remove_stake(bob_david, 10, false, MIN_STAKE_NANOWITS) + .unwrap(); + assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); + assert_eq!(stakes.query_nonce(bob_david), Ok(3)); + + stakes + .reserve_collateral(bob, 10, MIN_STAKE_NANOWITS) + .unwrap(); + assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); + assert_eq!(stakes.query_nonce(bob_david), Ok(3)); + + stakes + .add_stake(bob_david, 40, 30, false, MIN_STAKE_NANOWITS) + .unwrap(); + assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); + assert_eq!(stakes.query_nonce(bob_david), Ok(3)); + + stakes.add_reward(bob, 40, 30).unwrap(); + assert_eq!(stakes.query_nonce(alice_charlie), Ok(1)); + assert_eq!(stakes.query_nonce(bob_david), Ok(3)); } #[test] @@ -1509,10 +1535,10 @@ mod tests { // Add some stake and verify the power stakes - .add_stake((alice, charlie), 10, 0, MIN_STAKE_NANOWITS) + .add_stake((alice, charlie), 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake((bob, david), 20, 10, MIN_STAKE_NANOWITS) + .add_stake((bob, david), 20, 10, true, MIN_STAKE_NANOWITS) .unwrap(); assert_eq!(stakes.query_power(alice, Capability::Mining, 30), Ok(300)); assert_eq!(stakes.query_power(bob, Capability::Mining, 30), Ok(400)); @@ -1563,16 +1589,16 @@ mod tests { // Add some stake and verify the power stakes - .add_stake(alice_alice, 10, 0, MIN_STAKE_NANOWITS) + .add_stake(alice_alice, 10, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(bob_alice, 20, 0, MIN_STAKE_NANOWITS) + .add_stake(bob_alice, 20, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(charlie_charlie, 30, 10, MIN_STAKE_NANOWITS) + .add_stake(charlie_charlie, 30, 10, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(david_charlie, 40, 10, MIN_STAKE_NANOWITS) + .add_stake(david_charlie, 40, 10, true, MIN_STAKE_NANOWITS) .unwrap(); assert_eq!( stakes.by_rank(Capability::Mining, 30).collect::>(), diff --git a/node/src/actors/chain_manager/mod.rs b/node/src/actors/chain_manager/mod.rs index 9bb2356a4..e463bbdb5 100644 --- a/node/src/actors/chain_manager/mod.rs +++ b/node/src/actors/chain_manager/mod.rs @@ -4613,6 +4613,7 @@ mod tests { }, 100_000_000_000.into(), 100, + true, 1_000_000_000.into(), ) .unwrap(); @@ -4784,6 +4785,7 @@ mod tests { validator.clone(), 100_000_000_000_000.into(), 100, + true, 10_000_000_000_000.into(), ) .unwrap(); diff --git a/validations/src/eligibility/current.rs b/validations/src/eligibility/current.rs index baa2d70d2..22c8b3247 100644 --- a/validations/src/eligibility/current.rs +++ b/validations/src/eligibility/current.rs @@ -302,10 +302,10 @@ mod tests { let isk = "validator_1"; stakes - .add_stake(isk, 10_000_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake(isk, 10_000_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake("validator_2", 10_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake("validator_2", 10_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); assert_eq!( @@ -344,7 +344,7 @@ mod tests { let isk = "validator"; stakes - .add_stake(isk, 10_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake(isk, 10_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); let eligibility = stakes.witnessing_eligibility(isk, 0, 10, 0, 4); @@ -368,16 +368,16 @@ mod tests { let isk_4 = "validator_4"; stakes - .add_stake(isk_1, 10_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake(isk_1, 10_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(isk_2, 20_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake(isk_2, 20_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(isk_3, 30_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake(isk_3, 30_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); stakes - .add_stake(isk_4, 40_000_000_000, 0, MIN_STAKE_NANOWITS) + .add_stake(isk_4, 40_000_000_000, 0, true, MIN_STAKE_NANOWITS) .unwrap(); let eligibility = stakes.witnessing_eligibility(isk_1, 0, 2, 0, 4);