1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
// This file is part of the HotShot repository.

// You should have received a copy of the MIT License
// along with the HotShot repository. If not, see <https://mit-license.org/>.

//! Trait for stake table data structures

use ark_std::{rand::SeedableRng, vec::Vec};
use digest::crypto_common::rand_core::CryptoRngCore;
use displaydoc::Display;

/// Snapshots of the stake table
pub enum SnapshotVersion {
    /// the latest "Head" where all new changes are applied to
    Head,
    /// marks the snapshot at the beginning of the current epoch
    EpochStart,
    /// marks the beginning of the last epoch
    LastEpochStart,
    /// at arbitrary block height
    BlockNum(u64),
}

/// Common interfaces required for a stake table used in `HotShot` System.
/// APIs that doesn't take `version: SnapshotVersion` as an input by default works on the head/latest version.
pub trait StakeTableScheme {
    /// type for stake key
    type Key: Clone;
    /// type for the staked amount
    type Amount: Clone + Copy;
    /// type for the commitment to the current stake table
    type Commitment;
    /// type for the proof associated with the lookup result (if any)
    type LookupProof;
    /// type for the iterator over (key, value) entries
    type IntoIter: Iterator<Item = (Self::Key, Self::Amount, Self::Aux)>;
    /// Auxiliary information associated with the key
    type Aux: Clone;

    /// Register a new key into the stake table.
    ///
    /// # Errors
    ///
    /// Return err if key is already registered.
    fn register(
        &mut self,
        new_key: Self::Key,
        amount: Self::Amount,
        aux: Self::Aux,
    ) -> Result<(), StakeTableError>;

    /// Batch register a list of new keys. A default implementation is provided
    /// w/o batch optimization.
    ///
    /// # Errors
    ///
    /// Return err if any of `new_keys` fails to register.
    fn batch_register<I, J, K>(
        &mut self,
        new_keys: I,
        amounts: J,
        auxs: K,
    ) -> Result<(), StakeTableError>
    where
        I: IntoIterator<Item = Self::Key>,
        J: IntoIterator<Item = Self::Amount>,
        K: IntoIterator<Item = Self::Aux>,
    {
        let _ = new_keys
            .into_iter()
            .zip(amounts)
            .zip(auxs)
            .try_for_each(|((key, amount), aux)| Self::register(self, key, amount, aux));
        Ok(())
    }

    /// Deregister an existing key from the stake table.
    /// Returns error if some keys are not found.
    ///
    /// # Errors
    /// Return err if `existing_key` wasn't registered.
    fn deregister(&mut self, existing_key: &Self::Key) -> Result<(), StakeTableError>;

    /// Batch deregister a list of keys. A default implementation is provided
    /// w/o batch optimization.
    ///
    /// # Errors
    /// Return err if any of `existing_keys` fail to deregister.
    fn batch_deregister<'a, I>(&mut self, existing_keys: I) -> Result<(), StakeTableError>
    where
        I: IntoIterator<Item = &'a <Self as StakeTableScheme>::Key>,
        <Self as StakeTableScheme>::Key: 'a,
    {
        let _ = existing_keys
            .into_iter()
            .try_for_each(|key| Self::deregister(self, key));
        Ok(())
    }

    /// Returns the commitment to the `version` of stake table.
    ///
    /// # Errors
    /// Return err if the `version` is not supported.
    fn commitment(&self, version: SnapshotVersion) -> Result<Self::Commitment, StakeTableError>;

    /// Returns the accumulated stakes of all registered keys of the `version`
    /// of stake table.
    ///
    /// # Errors
    /// Return err if the `version` is not supported.
    fn total_stake(&self, version: SnapshotVersion) -> Result<Self::Amount, StakeTableError>;

    /// Returns the number of keys in the `version` of the table.
    ///
    /// # Errors
    /// Return err if the `version` is not supported.
    fn len(&self, version: SnapshotVersion) -> Result<usize, StakeTableError>;

    /// Returns true if `key` is currently registered, else returns false.
    fn contains_key(&self, key: &Self::Key) -> bool;

    /// Returns the stakes withhelded by a public key.
    ///
    /// # Errors
    /// Return err if the `version` is not supported or `key` doesn't exist.
    fn lookup(
        &self,
        version: SnapshotVersion,
        key: &Self::Key,
    ) -> Result<Self::Amount, StakeTableError>;

    /// Returns the stakes withhelded by a public key along with a membership proof.
    ///
    /// # Errors
    /// Return err if the `version` is not supported or `key` doesn't exist.
    fn lookup_with_proof(
        &self,
        version: SnapshotVersion,
        key: &Self::Key,
    ) -> Result<(Self::Amount, Self::LookupProof), StakeTableError>;

    /// Return the associated stake amount and auxiliary information of a public key,
    /// along with a membership proof.
    ///
    /// # Errors
    /// Return err if the `version` is not supported or `key` doesn't exist.
    #[allow(clippy::type_complexity)]
    fn lookup_with_aux_and_proof(
        &self,
        version: SnapshotVersion,
        key: &Self::Key,
    ) -> Result<(Self::Amount, Self::Aux, Self::LookupProof), StakeTableError>;

    /// Update the stake of the `key` with `(negative ? -1 : 1) * delta`.
    /// Return the updated stake or error.
    ///
    /// # Errors
    /// Return err if the `key` doesn't exist of if the update overflow/underflow.
    fn update(
        &mut self,
        key: &Self::Key,
        delta: Self::Amount,
        negative: bool,
    ) -> Result<Self::Amount, StakeTableError>;

    /// Batch update the stake balance of `keys`. Read documentation about
    /// [`Self::update()`]. By default, we call `Self::update()` on each
    /// (key, amount, negative) tuple.
    ///
    /// # Errors
    /// Return err if any one of the `update` failed.
    fn batch_update(
        &mut self,
        keys: &[Self::Key],
        amounts: &[Self::Amount],
        negative_flags: Vec<bool>,
    ) -> Result<Vec<Self::Amount>, StakeTableError> {
        let updated_amounts = keys
            .iter()
            .zip(amounts.iter())
            .zip(negative_flags.iter())
            .map(|((key, &amount), negative)| Self::update(self, key, amount, *negative))
            .collect::<Result<Vec<_>, _>>()?;

        Ok(updated_amounts)
    }

    /// Randomly sample a (key, stake amount) pair proportional to the stake distributions,
    /// given a fixed seed for `rng`, this sampling should be deterministic.
    fn sample(
        &self,
        rng: &mut (impl SeedableRng + CryptoRngCore),
    ) -> Option<(&Self::Key, &Self::Amount)>;

    /// Returns an iterator over all (key, value) entries of the `version` of the table
    ///
    /// # Errors
    /// Return err if the `version` is not supported.
    fn try_iter(&self, version: SnapshotVersion) -> Result<Self::IntoIter, StakeTableError>;
}

/// Error type for [`StakeTableScheme`]
#[derive(Debug, Display)]
pub enum StakeTableError {
    /// Internal error caused by Rescue
    RescueError,
    /// Key mismatched
    MismatchedKey,
    /// Key not found
    KeyNotFound,
    /// Key already exists
    ExistingKey,
    /// Malformed Merkle proof
    MalformedProof,
    /// Verification Error
    VerificationError,
    /// Insufficient fund: the number of stake cannot be negative
    InsufficientFund,
    /// The number of stake exceed U256
    StakeOverflow,
    /// The historical snapshot requested is not supported.
    SnapshotUnsupported,
}

impl ark_std::error::Error for StakeTableError {}