espresso_types/v0/impls/
reward.rs

1use std::{collections::HashSet, str::FromStr};
2
3use alloy::primitives::{
4    utils::{parse_units, ParseUnits},
5    Address, U256,
6};
7use anyhow::{bail, ensure, Context};
8use ark_serialize::{
9    CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate,
10};
11use committable::{Commitment, Committable, RawCommitmentBuilder};
12use hotshot::types::BLSPubKey;
13use hotshot_types::{
14    data::{EpochNumber, ViewNumber},
15    traits::{election::Membership, node_implementation::ConsensusTime},
16    utils::epoch_from_block_number,
17};
18use jf_merkle_tree::{
19    ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult,
20    MerkleCommitment, MerkleTreeScheme, PersistentUniversalMerkleTreeScheme, ToTraversalPath,
21    UniversalMerkleTreeScheme,
22};
23use num_traits::CheckedSub;
24use sequencer_utils::{
25    impl_serde_from_string_or_integer, impl_to_fixed_bytes, ser::FromStringOrInteger,
26};
27
28use super::{
29    v0_1::{
30        block_reward, RewardAccount, RewardAccountProof, RewardAccountQueryData, RewardAmount,
31        RewardInfo, RewardMerkleCommitment, RewardMerkleProof, RewardMerkleTree,
32        COMMISSION_BASIS_POINTS,
33    },
34    v0_3::Validator,
35    Leaf2, NodeState, ValidatedState,
36};
37use crate::{eth_signature_key::EthKeyPair, FeeAccount};
38
39impl Committable for RewardInfo {
40    fn commit(&self) -> Commitment<Self> {
41        RawCommitmentBuilder::new(&Self::tag())
42            .fixed_size_field("account", &self.account.to_fixed_bytes())
43            .fixed_size_field("amount", &self.amount.to_fixed_bytes())
44            .finalize()
45    }
46    fn tag() -> String {
47        "REWARD_INFO".into()
48    }
49}
50
51impl_serde_from_string_or_integer!(RewardAmount);
52impl_to_fixed_bytes!(RewardAmount, U256);
53
54impl From<u64> for RewardAmount {
55    fn from(amt: u64) -> Self {
56        Self(U256::from(amt))
57    }
58}
59
60impl CheckedSub for RewardAmount {
61    fn checked_sub(&self, v: &Self) -> Option<Self> {
62        self.0.checked_sub(v.0).map(RewardAmount)
63    }
64}
65
66impl FromStr for RewardAmount {
67    type Err = <U256 as FromStr>::Err;
68
69    fn from_str(s: &str) -> Result<Self, Self::Err> {
70        Ok(Self(s.parse()?))
71    }
72}
73
74impl FromStringOrInteger for RewardAmount {
75    type Binary = U256;
76    type Integer = u64;
77
78    fn from_binary(b: Self::Binary) -> anyhow::Result<Self> {
79        Ok(Self(b))
80    }
81
82    fn from_integer(i: Self::Integer) -> anyhow::Result<Self> {
83        Ok(i.into())
84    }
85
86    fn from_string(s: String) -> anyhow::Result<Self> {
87        // For backwards compatibility, we have an ad hoc parser for WEI amounts represented as hex
88        // strings.
89        if let Some(s) = s.strip_prefix("0x") {
90            return Ok(Self(s.parse()?));
91        }
92
93        // Strip an optional non-numeric suffix, which will be interpreted as a unit.
94        let (base, unit) = s
95            .split_once(char::is_whitespace)
96            .unwrap_or((s.as_str(), "wei"));
97        match parse_units(base, unit)? {
98            ParseUnits::U256(n) => Ok(Self(n)),
99            ParseUnits::I256(_) => bail!("amount cannot be negative"),
100        }
101    }
102
103    fn to_binary(&self) -> anyhow::Result<Self::Binary> {
104        Ok(self.0)
105    }
106
107    fn to_string(&self) -> anyhow::Result<String> {
108        Ok(format!("{self}"))
109    }
110}
111
112impl RewardAmount {
113    pub fn as_u64(&self) -> Option<u64> {
114        if self.0 <= U256::from(u64::MAX) {
115            Some(self.0.to::<u64>())
116        } else {
117            None
118        }
119    }
120}
121impl RewardAccount {
122    /// Return inner `Address`
123    pub fn address(&self) -> Address {
124        self.0
125    }
126    /// Return byte slice representation of inner `Address` type
127    pub fn as_bytes(&self) -> &[u8] {
128        self.0.as_slice()
129    }
130    /// Return array containing underlying bytes of inner `Address` type
131    pub fn to_fixed_bytes(self) -> [u8; 20] {
132        self.0.into_array()
133    }
134    pub fn test_key_pair() -> EthKeyPair {
135        EthKeyPair::from_mnemonic(
136            "test test test test test test test test test test test junk",
137            0u32,
138        )
139        .unwrap()
140    }
141}
142
143impl FromStr for RewardAccount {
144    type Err = anyhow::Error;
145
146    fn from_str(s: &str) -> Result<Self, Self::Err> {
147        Ok(Self(s.parse()?))
148    }
149}
150
151impl Valid for RewardAmount {
152    fn check(&self) -> Result<(), SerializationError> {
153        Ok(())
154    }
155}
156
157impl Valid for RewardAccount {
158    fn check(&self) -> Result<(), SerializationError> {
159        Ok(())
160    }
161}
162
163impl CanonicalSerialize for RewardAmount {
164    fn serialize_with_mode<W: std::io::prelude::Write>(
165        &self,
166        mut writer: W,
167        _compress: Compress,
168    ) -> Result<(), SerializationError> {
169        Ok(writer.write_all(&self.to_fixed_bytes())?)
170    }
171
172    fn serialized_size(&self, _compress: Compress) -> usize {
173        core::mem::size_of::<U256>()
174    }
175}
176impl CanonicalDeserialize for RewardAmount {
177    fn deserialize_with_mode<R: Read>(
178        mut reader: R,
179        _compress: Compress,
180        _validate: Validate,
181    ) -> Result<Self, SerializationError> {
182        let mut bytes = [0u8; core::mem::size_of::<U256>()];
183        reader.read_exact(&mut bytes)?;
184        let value = U256::from_le_slice(&bytes);
185        Ok(Self(value))
186    }
187}
188impl CanonicalSerialize for RewardAccount {
189    fn serialize_with_mode<W: std::io::prelude::Write>(
190        &self,
191        mut writer: W,
192        _compress: Compress,
193    ) -> Result<(), SerializationError> {
194        Ok(writer.write_all(self.0.as_slice())?)
195    }
196
197    fn serialized_size(&self, _compress: Compress) -> usize {
198        core::mem::size_of::<Address>()
199    }
200}
201impl CanonicalDeserialize for RewardAccount {
202    fn deserialize_with_mode<R: Read>(
203        mut reader: R,
204        _compress: Compress,
205        _validate: Validate,
206    ) -> Result<Self, SerializationError> {
207        let mut bytes = [0u8; core::mem::size_of::<Address>()];
208        reader.read_exact(&mut bytes)?;
209        let value = Address::from_slice(&bytes);
210        Ok(Self(value))
211    }
212}
213
214impl ToTraversalPath<256> for RewardAccount {
215    fn to_traversal_path(&self, height: usize) -> Vec<usize> {
216        self.0
217            .as_slice()
218            .iter()
219            .take(height)
220            .map(|i| *i as usize)
221            .collect()
222    }
223}
224
225#[allow(dead_code)]
226impl RewardAccountProof {
227    pub fn presence(
228        pos: FeeAccount,
229        proof: <RewardMerkleTree as MerkleTreeScheme>::MembershipProof,
230    ) -> Self {
231        Self {
232            account: pos.into(),
233            proof: RewardMerkleProof::Presence(proof),
234        }
235    }
236
237    pub fn absence(
238        pos: RewardAccount,
239        proof: <RewardMerkleTree as UniversalMerkleTreeScheme>::NonMembershipProof,
240    ) -> Self {
241        Self {
242            account: pos.into(),
243            proof: RewardMerkleProof::Absence(proof),
244        }
245    }
246
247    pub fn prove(tree: &RewardMerkleTree, account: Address) -> Option<(Self, U256)> {
248        match tree.universal_lookup(RewardAccount(account)) {
249            LookupResult::Ok(balance, proof) => Some((
250                Self {
251                    account,
252                    proof: RewardMerkleProof::Presence(proof),
253                },
254                balance.0,
255            )),
256            LookupResult::NotFound(proof) => Some((
257                Self {
258                    account,
259                    proof: RewardMerkleProof::Absence(proof),
260                },
261                U256::ZERO,
262            )),
263            LookupResult::NotInMemory => None,
264        }
265    }
266
267    pub fn verify(&self, comm: &RewardMerkleCommitment) -> anyhow::Result<U256> {
268        match &self.proof {
269            RewardMerkleProof::Presence(proof) => {
270                ensure!(
271                    RewardMerkleTree::verify(comm.digest(), RewardAccount(self.account), proof)?
272                        .is_ok(),
273                    "invalid proof"
274                );
275                Ok(proof
276                    .elem()
277                    .context("presence proof is missing account balance")?
278                    .0)
279            },
280            RewardMerkleProof::Absence(proof) => {
281                let tree = RewardMerkleTree::from_commitment(comm);
282                ensure!(
283                    tree.non_membership_verify(RewardAccount(self.account), proof)?,
284                    "invalid proof"
285                );
286                Ok(U256::ZERO)
287            },
288        }
289    }
290
291    pub fn remember(&self, tree: &mut RewardMerkleTree) -> anyhow::Result<()> {
292        match &self.proof {
293            RewardMerkleProof::Presence(proof) => {
294                tree.remember(
295                    RewardAccount(self.account),
296                    proof
297                        .elem()
298                        .context("presence proof is missing account balance")?,
299                    proof,
300                )?;
301                Ok(())
302            },
303            RewardMerkleProof::Absence(proof) => {
304                tree.non_membership_remember(RewardAccount(self.account), proof)?;
305                Ok(())
306            },
307        }
308    }
309}
310
311impl From<(RewardAccountProof, U256)> for RewardAccountQueryData {
312    fn from((proof, balance): (RewardAccountProof, U256)) -> Self {
313        Self { balance, proof }
314    }
315}
316
317pub fn apply_rewards(
318    mut reward_state: RewardMerkleTree,
319    validator: Validator<BLSPubKey>,
320) -> anyhow::Result<RewardMerkleTree> {
321    let mut update_balance = |account: &RewardAccount, amount: RewardAmount| {
322        let mut err = None;
323        reward_state = reward_state.persistent_update_with(account, |balance| {
324            let balance = balance.copied();
325            match balance.unwrap_or_default().0.checked_add(amount.0) {
326                Some(updated) => Some(updated.into()),
327                None => {
328                    err = Some(format!("overflowed reward balance for account {}", account));
329                    balance
330                },
331            }
332        })?;
333
334        if let Some(error) = err {
335            tracing::warn!(error);
336            bail!(error)
337        }
338        Ok::<(), anyhow::Error>(())
339    };
340
341    let computed_rewards = compute_rewards(validator)?;
342    for (address, reward) in computed_rewards {
343        update_balance(&RewardAccount(address), reward)?;
344        tracing::debug!("applied rewards address={address} reward={reward}",);
345    }
346    Ok(reward_state)
347}
348
349pub fn compute_rewards(
350    validator: Validator<BLSPubKey>,
351) -> anyhow::Result<Vec<(alloy::primitives::Address, RewardAmount)>> {
352    ensure!(
353        validator.commission <= COMMISSION_BASIS_POINTS,
354        "commission must not exceed {COMMISSION_BASIS_POINTS}"
355    );
356
357    let mut rewards = Vec::new();
358
359    let total_reward = block_reward().0;
360    let delegators_ratio_basis_points = U256::from(COMMISSION_BASIS_POINTS)
361        .checked_sub(U256::from(validator.commission))
362        .context("overflow")?;
363    let delegators_reward = delegators_ratio_basis_points
364        .checked_mul(total_reward)
365        .context("overflow")?;
366
367    // Distribute delegator rewards
368    let total_stake = validator.stake;
369    let mut delegators_rewards_distributed = U256::from(0);
370    for (delegator_address, delegator_stake) in &validator.delegators {
371        let delegator_reward = RewardAmount::from(
372            (delegator_stake
373                .checked_mul(delegators_reward)
374                .context("overflow")?
375                .checked_div(total_stake)
376                .context("overflow")?)
377            .checked_div(U256::from(COMMISSION_BASIS_POINTS))
378            .context("overflow")?,
379        );
380
381        delegators_rewards_distributed += delegator_reward.0;
382
383        rewards.push((*delegator_address, delegator_reward));
384    }
385
386    let leader_reward = total_reward
387        .checked_sub(delegators_rewards_distributed)
388        .context("overflow")?;
389    rewards.push((validator.account, leader_reward.into()));
390
391    Ok(rewards)
392}
393/// Checks whether the given height belongs to the first or second epoch. or
394/// the Genesis epoch (EpochNumber::new(0))
395///
396/// Rewards are not distributed for these epochs because the stake table
397/// is built from the contract only when `add_epoch_root()` is called
398/// by HotShot, which happens starting from the third epoch.
399pub async fn first_two_epochs(height: u64, instance_state: &NodeState) -> anyhow::Result<bool> {
400    let epoch_height = instance_state
401        .epoch_height
402        .context("epoch height not found")?;
403    let epoch = EpochNumber::new(epoch_from_block_number(height, epoch_height));
404    let coordinator = instance_state.coordinator.clone();
405    let first_epoch = coordinator
406        .membership()
407        .read()
408        .await
409        .first_epoch()
410        .context("The first epoch was not set.")?;
411
412    Ok(epoch <= first_epoch + 1)
413}
414
415pub async fn find_validator_info(
416    instance_state: &NodeState,
417    validated_state: &mut ValidatedState,
418    parent_leaf: &Leaf2,
419    view: ViewNumber,
420) -> anyhow::Result<Validator<BLSPubKey>> {
421    let parent_height = parent_leaf.height();
422    let parent_view = parent_leaf.view_number();
423    let new_height = parent_height + 1;
424
425    let epoch_height = instance_state
426        .epoch_height
427        .context("epoch height not found")?;
428    if epoch_height == 0 {
429        bail!("epoch height is 0. can not catchup reward accounts");
430    }
431    let epoch = EpochNumber::new(epoch_from_block_number(new_height, epoch_height));
432
433    let coordinator = instance_state.coordinator.clone();
434
435    let epoch_membership = coordinator.membership_for_epoch(Some(epoch)).await?;
436    let membership = epoch_membership.coordinator.membership().read().await;
437
438    let leader: BLSPubKey = membership
439        .leader(view, Some(epoch))
440        .context(format!("leader for epoch {epoch:?} not found"))?;
441
442    let validator = membership
443        .get_validator_config(&epoch, leader)
444        .context("validator not found")?;
445    drop(membership);
446
447    let mut reward_accounts = HashSet::new();
448    reward_accounts.insert(validator.account.into());
449    let delegators = validator
450        .delegators
451        .keys()
452        .cloned()
453        .map(|a| a.into())
454        .collect::<Vec<RewardAccount>>();
455
456    reward_accounts.extend(delegators.clone());
457    let missing_reward_accts = validated_state.forgotten_reward_accounts(reward_accounts);
458
459    if !missing_reward_accts.is_empty() {
460        tracing::warn!(
461            parent_height,
462            ?parent_view,
463            ?missing_reward_accts,
464            "fetching missing reward accounts from peers"
465        );
466
467        let missing_account_proofs = instance_state
468            .state_catchup
469            .fetch_reward_accounts(
470                instance_state,
471                parent_height,
472                parent_view,
473                validated_state.reward_merkle_tree.commitment(),
474                missing_reward_accts,
475            )
476            .await?;
477
478        for proof in missing_account_proofs.iter() {
479            proof
480                .remember(&mut validated_state.reward_merkle_tree)
481                .expect("proof previously verified");
482        }
483    }
484    Ok(validator)
485}
486
487#[cfg(test)]
488pub mod tests {
489
490    use super::*;
491
492    // TODO: current tests are just sanity checks, we need more.
493
494    #[test]
495    fn test_reward_calculation_sanity_checks() {
496        // This test verifies that the total rewards distributed match the block reward.
497        // Due to rounding effects in distribution, the validator may receive a slightly higher amount
498        // because the remainder after delegator distribution is sent to the validator.
499
500        let validator = Validator::mock();
501        let rewards = compute_rewards(validator).unwrap();
502        let total = |rewards: Vec<(_, RewardAmount)>| {
503            rewards.iter().fold(U256::ZERO, |acc, (_, r)| acc + r.0)
504        };
505        assert_eq!(total(rewards), block_reward().into());
506
507        let mut validator = Validator::mock();
508        validator.commission = 0;
509        let rewards = compute_rewards(validator.clone()).unwrap();
510        assert_eq!(total(rewards.clone()), block_reward().into());
511
512        let mut validator = Validator::mock();
513        validator.commission = 10000;
514        let rewards = compute_rewards(validator.clone()).unwrap();
515        assert_eq!(total(rewards.clone()), block_reward().into());
516        let validator_reward = rewards
517            .iter()
518            .find(|(a, _)| *a == validator.account)
519            .unwrap()
520            .1;
521        assert_eq!(validator_reward, block_reward());
522
523        let mut validator = Validator::mock();
524        validator.commission = 10001;
525        assert!(compute_rewards(validator.clone())
526            .err()
527            .unwrap()
528            .to_string()
529            .contains("must not exceed"));
530    }
531}