Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat/monotonic stake nonce #2583

Merged
merged 13 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,4 +563,14 @@ impl Defaults for Testnet {
fn consensus_constants_minimum_difficulty(&self) -> u32 {
0
}

fn protocol_versions(&self) -> HashMap<ProtocolVersion, (Epoch, u16)> {
[
(ProtocolVersion::V1_7, (0, 45)),
(ProtocolVersion::V1_8, (101, 45)),
(ProtocolVersion::V2_0, (281, 20)),
]
.into_iter()
.collect()
}
}
23 changes: 14 additions & 9 deletions data_structures/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3202,6 +3202,19 @@ impl TransactionsPool {
}
}

/// Remove unstake transactions that would result in understaking on a validator
pub fn remove_understake_transactions(&mut self, transactions: Vec<Hash>) {
for ut_tx_hash in transactions.iter() {
if let Some(ut_tx) = self
.ut_transactions
.get(ut_tx_hash)
.map(|(_, ut)| ut.clone())
{
self.ut_remove(&ut_tx);
}
}
}

/// Remove an unstake transaction from the pool.
///
/// This should be used to remove transactions that got included in a consolidated block.
Expand Down Expand Up @@ -3450,15 +3463,7 @@ impl TransactionsPool {
if fee < self.minimum_ut_fee {
return vec![Transaction::Unstake(ut_tx)];
} else {
self.total_st_weight += u64::from(ut_tx.weight());

// TODO
// for input in &ut_tx.body.inputs {
// self.output_pointer_map
// .entry(input.output_pointer)
// .or_insert_with(Vec::new)
// .push(ut_tx.hash());
// }
self.total_ut_weight += u64::from(ut_tx.weight());

self.ut_transactions.insert(key, (priority, ut_tx));
self.sorted_ut_index.insert((priority, key));
Expand Down
6 changes: 6 additions & 0 deletions data_structures/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,12 @@ pub enum BlockError {
pkh
)]
RepeatedStakeOperator { pkh: PublicKeyHash },
/// Repeated operator Unstake
#[fail(
display = "A single operator is withdrawing stake more than once in a block: ({}) ",
pkh
)]
RepeatedUnstakeOperator { pkh: PublicKeyHash },
/// Missing expected tallies
#[fail(
display = "{} expected tally transactions are missing in block candidate {}",
Expand Down
18 changes: 18 additions & 0 deletions data_structures/src/staking/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ where
+ Default
+ DeserializeOwned
+ Display
+ From<Epoch>
+ From<u32>
+ Saturating
+ Send
Expand Down Expand Up @@ -457,6 +458,7 @@ where
+ Default
+ DeserializeOwned
+ Display
+ From<Epoch>
+ From<u32>
+ Saturating
+ Send
Expand Down Expand Up @@ -497,6 +499,22 @@ where
}
}

/// Tells the stakes tracker what to do with the nonce associated to the entry or entries being
/// updated.
///
/// This allows customizing the behavior of the nonce to be different when updating a stake entry
/// when processing a stake or unstake transaction vs. when adding rewards or enforcing slashing.
///
/// Generally speaking, we want to update the nonce when we are processing a stake or unstake
/// transaction, but we want to keep the nonce the same if it is a reward or slashing act.
#[derive(Debug, PartialEq)]
pub enum NoncePolicy<Epoch> {
/// Update the value of the nonce field by deriving it from this epoch.
SetFromEpoch(Epoch),
/// Leave the value of the nonce field as is.
KeepAsIs,
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion data_structures/src/staking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub mod test {

// If Alpha removes all of its stake, it should immediately disappear
stakes
.remove_stake("Alpha", 2, true, MIN_STAKE_NANOWITS)
.remove_stake("Alpha", 2, 52, true, MIN_STAKE_NANOWITS)
.unwrap();
let rank = stakes.by_rank(Capability::Mining, 51).collect::<Vec<_>>();
assert_eq!(
Expand Down
28 changes: 18 additions & 10 deletions data_structures/src/staking/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ where
pub coins: Coins,
/// The average epoch used to derive coin age for different capabilities.
pub epochs: CapabilityMap<Epoch>,
/// The amount of stake and unstake actions.
/// A versioning number that gets updated upon unstaking, to guarantee resistance to replay
/// attacks and other potential issues that may arise from the lack of inputs in unstake
/// transactions.
pub nonce: Nonce,
// These two phantom fields are here just for the sake of specifying generics.
/// This phantom field is here just for the sake of specifying generics.
#[serde(skip)]
phantom_address: PhantomData<Address>,
pub phantom_address: PhantomData<Address>,
/// This phantom field is here just for the sake of specifying generics.
#[serde(skip)]
phantom_power: PhantomData<Power>,
pub phantom_power: PhantomData<Power>,
}

impl<const UNIT: u8, Address, Coins, Epoch, Nonce, Power>
Expand Down Expand Up @@ -64,6 +67,7 @@ where
+ Default
+ num_traits::Saturating
+ AddAssign
+ From<Epoch>
+ From<u32>
+ Debug
+ Display
Expand All @@ -77,14 +81,15 @@ where
/// When adding stake:
/// - Amounts are added together.
/// - Epochs are weight-averaged, using the amounts as the weight.
/// - Nonces are overwritten.
///
/// This type of averaging makes the entry equivalent to an unbounded record of all stake
/// additions and removals, without the overhead in memory and computation.
pub fn add_stake(
&mut self,
coins: Coins,
epoch: Epoch,
increment_nonce: bool,
nonce_policy: NoncePolicy<Epoch>,
minimum_stakeable: Coins,
) -> StakesResult<Coins, Address, Coins, Epoch> {
// Make sure that the amount to be staked is equal or greater than the minimum
Expand Down Expand Up @@ -113,8 +118,11 @@ where
self.epochs.update(capability, epoch_after);
}

if increment_nonce {
self.nonce += Nonce::from(1);
// Nonces are updated following the "keep the latest epoch where this stake was updated
// manually" logic, where "manually" means by means of staking or unstaking, but not through
// rewards nor slashing.
if let NoncePolicy::SetFromEpoch(epoch) = nonce_policy {
self.nonce = Nonce::from(epoch);
}

Ok(coins_after)
Expand Down Expand Up @@ -144,7 +152,7 @@ where
pub fn remove_stake(
&mut self,
coins: Coins,
increment_nonce: bool,
nonce_policy: NoncePolicy<Epoch>,
minimum_stakeable: Coins,
) -> StakesResult<Coins, Address, Coins, Epoch> {
let coins_after = self.coins.sub(coins);
Expand All @@ -158,8 +166,8 @@ where

self.coins = coins_after;

if increment_nonce {
self.nonce += Nonce::from(1);
if let NoncePolicy::SetFromEpoch(epoch) = nonce_policy {
self.nonce = Nonce::from(epoch);
}

Ok(self.coins)
Expand Down
Loading
Loading