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

Add data structures and base logic for staking tracker #2404

Open
wants to merge 2 commits into
base: 2.0-base
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions data_structures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ rand_distr = "0.4.3"
[[bench]]
name = "sort_active_identities"
harness = false

[[bench]]
name = "staking"
harness = false
85 changes: 85 additions & 0 deletions data_structures/benches/staking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#[macro_use]
extern crate bencher;
use bencher::Bencher;
use rand::Rng;
use witnet_data_structures::staking::prelude::*;

fn populate(b: &mut Bencher) {
let mut stakes = Stakes::<String, u64, u64, u64>::default();
let mut i = 1;

b.iter(|| {
let address = format!("{i}");
let coins = i;
let epoch = i;
stakes.add_stake(address, coins, epoch).unwrap();

i += 1;
});
}

fn rank(b: &mut Bencher) {
let mut stakes = Stakes::<String, u64, u64, u64>::default();
let mut i = 1;

let stakers = 100_000;
let rf = 10;

let mut rng = rand::thread_rng();

loop {
let coins = i;
let epoch = i;
let address = format!("{}", rng.gen::<u64>());

stakes.add_stake(address, coins, epoch).unwrap();

i += 1;

if i == stakers {
break;
}
}

b.iter(|| {
let rank = stakes.rank(Capability::Mining, i);
let mut top = rank.take(usize::try_from(stakers / rf).unwrap());
let _first = top.next();
let _last = top.last();

i += 1;
})
}

fn query_power(b: &mut Bencher) {
let mut stakes = Stakes::<String, u64, u64, u64>::default();
let mut i = 1;

let stakers = 100_000;

loop {
let coins = i;
let epoch = i;
let address = format!("{i}");

stakes.add_stake(address, coins, epoch).unwrap();

i += 1;

if i == stakers {
break;
}
}

i = 1;

b.iter(|| {
let address = format!("{i}");
let _power = stakes.query_power(&address, Capability::Mining, i);

i += 1;
})
}

benchmark_main!(benches);
benchmark_group!(benches, populate, rank, query_power);
44 changes: 44 additions & 0 deletions data_structures/src/capabilities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Capability {
/// The base block mining and superblock voting capability
Mining = 0,
/// The universal HTTP GET / HTTP POST / WIP-0019 RNG capability
Witnessing = 1,
}

#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct CapabilityMap<T>
where
T: Default,
{
pub mining: T,
pub witnessing: T,
}

impl<T> CapabilityMap<T>
where
T: Copy + Default,
{
#[inline]
pub fn get(&self, capability: Capability) -> T {
match capability {
Capability::Mining => self.mining,
Capability::Witnessing => self.witnessing,
}
}

#[inline]
pub fn update(&mut self, capability: Capability, value: T) {
match capability {
Capability::Mining => self.mining = value,
Capability::Witnessing => self.witnessing = value,
}
}

#[inline]
pub fn update_all(&mut self, value: T) {
self.mining = value;
self.witnessing = value;
}
}
6 changes: 6 additions & 0 deletions data_structures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub mod fee;
/// Module containing data_request structures
pub mod data_request;

/// Module containing data structures for the staking functionality
pub mod staking;

/// Module containing superblock structures
pub mod superblock;

Expand Down Expand Up @@ -69,6 +72,9 @@ mod serialization_helpers;
/// Provides convenient constants, structs and methods for handling values denominated in Wit.
pub mod wit;

/// Provides support for segmented protocol capabilities.
pub mod capabilities;

lazy_static! {
/// Environment in which we are running: mainnet or testnet.
/// This is used for Bech32 serialization.
Expand Down
37 changes: 37 additions & 0 deletions data_structures/src/staking/aux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::rc::Rc;
use std::sync::RwLock;

use super::prelude::*;

/// Type alias for a reference-counted and read-write-locked instance of `Stake`.
pub type SyncStake<Address, Coins, Epoch, Power> = Rc<RwLock<Stake<Address, Coins, Epoch, Power>>>;

/// The resulting type for all the fallible functions in this module.
pub type Result<T, Address, Coins, Epoch> =
std::result::Result<T, StakesError<Address, Coins, Epoch>>;

/// Couples an amount of coins and an address together. This is to be used in `Stakes` as the index
/// of the `by_coins` index..
#[derive(Eq, Ord, PartialEq, PartialOrd)]
pub struct CoinsAndAddress<Coins, Address> {
/// An amount of coins.
pub coins: Coins,
/// The address of a staker.
pub address: Address,
}

/// Allows telling the `census` method in `Stakes` to source addresses from its internal `by_coins`
/// following different strategies.
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum CensusStrategy {
/// Retrieve all addresses, ordered by decreasing power.
All = 0,
/// Retrieve every Nth address, ordered by decreasing power.
StepBy(usize) = 1,
/// Retrieve the most powerful N addresses, ordered by decreasing power.
Take(usize) = 2,
/// Retrieve a total of N addresses, evenly distributed from the index, ordered by decreasing
/// power.
Evenly(usize) = 3,
}
2 changes: 2 additions & 0 deletions data_structures/src/staking/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// A minimum stakeable amount needs to exist to prevent spamming of the tracker.
pub const MINIMUM_STAKEABLE_AMOUNT_WITS: u64 = 10_000;
41 changes: 41 additions & 0 deletions data_structures/src/staking/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::sync::PoisonError;

/// All errors related to the staking functionality.
#[derive(Debug, PartialEq)]
pub enum StakesError<Address, Coins, Epoch> {
/// The amount of coins being staked or the amount that remains after unstaking is below the
/// minimum stakeable amount.
AmountIsBelowMinimum {
/// The number of coins being staked or remaining after staking.
amount: Coins,
/// The minimum stakeable amount.
minimum: Coins,
},
/// Tried to query `Stakes` for information that belongs to the past.
EpochInThePast {
/// The Epoch being referred.
epoch: Epoch,
/// The latest Epoch.
latest: Epoch,
},
/// An operation thrown an Epoch value that overflows.
EpochOverflow {
/// The computed Epoch value.
computed: u64,
/// The maximum Epoch.
maximum: Epoch,
},
/// Tried to query `Stakes` for the address of a staker that is not registered in `Stakes`.
IdentityNotFound {
/// The unknown address.
identity: Address,
},
/// Tried to obtain a lock on a write-locked piece of data that is already locked.
PoisonedLock,
}

impl<T, Address, Coins, Epoch> From<PoisonError<T>> for StakesError<Address, Coins, Epoch> {
fn from(_value: PoisonError<T>) -> Self {
StakesError::PoisonedLock
}
}
107 changes: 107 additions & 0 deletions data_structures/src/staking/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#![deny(missing_docs)]

/// Auxiliary convenience types and data structures.
pub mod aux;
/// Constants related to the staking functionality.
pub mod constants;
/// Errors related to the staking functionality.
pub mod errors;
/// The data structure and related logic for stake entries.
pub mod stake;
/// The data structure and related logic for keeping track of multiple stake entries.
pub mod stakes;

/// Module re-exporting virtually every submodule on a single level to ease importing of everything
/// staking-related.
pub mod prelude {
pub use crate::capabilities::*;

pub use super::aux::*;
pub use super::constants::*;
pub use super::errors::*;
pub use super::stake::*;
pub use super::stakes::*;
}

#[cfg(test)]
pub mod test {
use super::prelude::*;

#[test]
fn test_e2e() {
let mut stakes = Stakes::<String, u64, u64, u64>::with_minimum(1);

// Alpha stakes 2 @ epoch 0
stakes.add_stake("Alpha", 2, 0).unwrap();

// Nobody holds any power just yet
let rank = stakes.rank(Capability::Mining, 0).collect::<Vec<_>>();
assert_eq!(rank, vec![("Alpha".into(), 0)]);

// One epoch later, Alpha starts to hold power
let rank = stakes.rank(Capability::Mining, 1).collect::<Vec<_>>();
assert_eq!(rank, vec![("Alpha".into(), 2)]);

// Beta stakes 5 @ epoch 10
stakes.add_stake("Beta", 5, 10).unwrap();

// Alpha is still leading, but Beta has scheduled its takeover
let rank = stakes.rank(Capability::Mining, 10).collect::<Vec<_>>();
assert_eq!(rank, vec![("Alpha".into(), 20), ("Beta".into(), 0)]);

// Beta eventually takes over after epoch 16
let rank = stakes.rank(Capability::Mining, 16).collect::<Vec<_>>();
assert_eq!(rank, vec![("Alpha".into(), 32), ("Beta".into(), 30)]);
let rank = stakes.rank(Capability::Mining, 17).collect::<Vec<_>>();
assert_eq!(rank, vec![("Beta".into(), 35), ("Alpha".into(), 34)]);

// Gamma should never take over, even in a million epochs, because it has only 1 coin
stakes.add_stake("Gamma", 1, 30).unwrap();
let rank = stakes
.rank(Capability::Mining, 1_000_000)
.collect::<Vec<_>>();
assert_eq!(
rank,
vec![
("Beta".into(), 4_999_950),
("Alpha".into(), 2_000_000),
("Gamma".into(), 999_970)
]
);

// But Delta is here to change it all
stakes.add_stake("Delta", 1_000, 50).unwrap();
let rank = stakes.rank(Capability::Mining, 50).collect::<Vec<_>>();
assert_eq!(
rank,
vec![
("Beta".into(), 200),
("Alpha".into(), 100),
("Gamma".into(), 20),
("Delta".into(), 0)
]
);
let rank = stakes.rank(Capability::Mining, 51).collect::<Vec<_>>();
assert_eq!(
rank,
vec![
("Delta".into(), 1_000),
("Beta".into(), 205),
("Alpha".into(), 102),
("Gamma".into(), 21)
]
);

// If Alpha removes all of its stake, it should immediately disappear
stakes.remove_stake("Alpha", 2).unwrap();
let rank = stakes.rank(Capability::Mining, 51).collect::<Vec<_>>();
assert_eq!(
rank,
vec![
("Delta".into(), 1_000),
("Beta".into(), 205),
("Gamma".into(), 21),
]
);
}
}
Empty file.
Loading
Loading