tip911_stakeset/
lib.rs

1use melstructs::{CoinID, CoinValue, StakeDoc, TxHash};
2use novasmt::{dense::DenseMerkleTree, Database, InMemoryCas};
3use stdcode::StdcodeSerializeExt;
4use tmelcrypt::{Ed25519PK, Hashable};
5
6/// Keeps track of all the active stakes in the blockchain. Abstracts over the pre-TIP911 and post-TIP911 representations.
7#[derive(Clone, Debug)]
8pub struct StakeSet {
9    stakes: imbl::HashMap<TxHash, StakeDoc>,
10}
11
12impl StakeSet {
13    /// Creates a new StakeSet, from a mapping from the [TxHash] that created a stake, to the [StakeDoc] describing the stake.
14    pub fn new(stakes: impl Iterator<Item = (TxHash, StakeDoc)>) -> Self {
15        Self {
16            stakes: stakes.collect(),
17        }
18    }
19
20    /// Returns stakes as an iterator
21    pub fn iter(&self) -> impl Iterator<Item = (&TxHash, &StakeDoc)> {
22        self.stakes.iter()
23    }
24
25    /// Adds another stake to the set. The transaction hash of the staking transaction, as well as the parsed [StakeDoc], must be given.
26    pub fn add_stake(&mut self, txhash: TxHash, stake: StakeDoc) {
27        self.stakes.insert(txhash, stake);
28    }
29
30    /// Gets information about a particular stake, identified by the [TxHash] of the transaction that created it.
31    pub fn get_stake(&self, txhash: TxHash) -> Option<StakeDoc> {
32        self.stakes.get(&txhash).cloned()
33    }
34
35    /// Checks whether a particular CoinID is frozen due to it containing an active stake.
36    pub fn is_frozen(&self, coin: CoinID) -> bool {
37        self.stakes.contains_key(&coin.txhash) && coin.index == 0
38    }
39
40    /// Checks how many votes a particular staker has, in the given epoch.
41    pub fn votes(&self, epoch: u64, key: Ed25519PK) -> u128 {
42        self.stakes
43            .values()
44            .filter(|v| v.e_start <= epoch && v.e_post_end > epoch && v.pubkey == key)
45            .map(|v| v.syms_staked.0)
46            .sum()
47    }
48
49    /// Obtains the number of votes in total for the given epoch.
50    pub fn total_votes(&self, epoch: u64) -> u128 {
51        self.stakes
52            .values()
53            .filter(|v| v.e_start <= epoch && v.e_post_end > epoch)
54            .map(|v| v.syms_staked.0)
55            .sum()
56    }
57
58    /// Removes all the stakes that have expired by this epoch.
59    pub fn unlock_old(&mut self, epoch: u64) {
60        self.stakes.retain(|_, v| v.e_post_end >= epoch);
61    }
62
63    /// Obtain an old-style (pre-TIP-911) sparse merkle tree that maps the **hash of the stdcode encoding of** the transaction hash of the transaction that created the stake, to the stdcode-encoded stake.
64    pub fn pre_tip911(&self) -> novasmt::Tree<InMemoryCas> {
65        let mut tree = Database::new(InMemoryCas::default())
66            .get_tree([0u8; 32])
67            .unwrap();
68        for (k, v) in self.stakes.iter() {
69            tree.insert(k.stdcode().hash().0, &v.stdcode());
70        }
71        tree
72    }
73
74    /// Obtain a TIP-911-style object.
75    pub fn post_tip911(&self, epoch: u64) -> Tip911 {
76        let mut stakes: Vec<(TxHash, StakeDoc)> = self.stakes.clone().into_iter().collect();
77        // sort by txhash
78        stakes.sort_unstable_by_key(|s| s.0);
79        // then, sort *stably* by stake size.
80        stakes.sort_by_key(|s| s.1.syms_staked);
81        Tip911 {
82            current_total: self.total_votes(epoch).into(),
83            next_total: self.total_votes(epoch + 1).into(),
84            stakes,
85        }
86    }
87}
88
89#[derive(Clone, Debug)]
90pub struct Tip911 {
91    /// Tally of all the SYM that can vote in this epoch.
92    pub current_total: CoinValue,
93    /// Tally of all the SYM that can vote in the next epoch.
94    pub next_total: CoinValue,
95    /// Vector of all the stakedocs, sorted first by stake size, and then by txhash.
96    pub stakes: Vec<(TxHash, StakeDoc)>,
97}
98
99impl Tip911 {
100    /// Calculates a dense merkle tree where the kth element is the hash of a stdcode'ed vector of the first k(current_total, next_total, txhash, stakedoc).
101    pub fn calculate_merkle(&self) -> DenseMerkleTree {
102        let upto_vec: Vec<Vec<u8>> = (0..self.stakes.len())
103            .map(|k| {
104                (
105                    self.current_total,
106                    self.next_total,
107                    self.stakes[..=k].to_vec(),
108                )
109                    .stdcode()
110            })
111            .collect();
112        DenseMerkleTree::new(&upto_vec)
113    }
114}