espresso_types/v0/impls/
reward.rs

1use std::{borrow::Borrow, collections::HashSet, iter::once, str::FromStr};
2
3use alloy::primitives::{
4    utils::{parse_units, ParseUnits},
5    Address, B256, U256,
6};
7use anyhow::{bail, ensure, Context};
8use ark_serialize::{
9    CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate,
10};
11use hotshot::types::BLSPubKey;
12use hotshot_contract_adapter::reward::RewardProofSiblings;
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_compat::{
19    prelude::MerkleNode, ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme,
20    LookupResult, 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};
27use vbs::version::StaticVersionType;
28
29use super::{
30    v0_3::{RewardAmount, Validator, COMMISSION_BASIS_POINTS},
31    v0_4::{
32        RewardAccountProofV2, RewardAccountQueryDataV2, RewardAccountV2, RewardMerkleCommitmentV2,
33        RewardMerkleProofV2, RewardMerkleTreeV2,
34    },
35    Leaf2, NodeState, ValidatedState,
36};
37use crate::{
38    eth_signature_key::EthKeyPair,
39    v0_3::{
40        RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1, RewardMerkleProofV1,
41        RewardMerkleTreeV1,
42    },
43    v0_4::{Delta, REWARD_MERKLE_TREE_V2_ARITY, REWARD_MERKLE_TREE_V2_HEIGHT},
44    DrbAndHeaderUpgradeVersion, EpochVersion, FeeAccount,
45};
46
47impl_serde_from_string_or_integer!(RewardAmount);
48impl_to_fixed_bytes!(RewardAmount, U256);
49
50impl From<u64> for RewardAmount {
51    fn from(amt: u64) -> Self {
52        Self(U256::from(amt))
53    }
54}
55
56impl CheckedSub for RewardAmount {
57    fn checked_sub(&self, v: &Self) -> Option<Self> {
58        self.0.checked_sub(v.0).map(RewardAmount)
59    }
60}
61
62impl FromStr for RewardAmount {
63    type Err = <U256 as FromStr>::Err;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        Ok(Self(s.parse()?))
67    }
68}
69
70impl FromStringOrInteger for RewardAmount {
71    type Binary = U256;
72    type Integer = u64;
73
74    fn from_binary(b: Self::Binary) -> anyhow::Result<Self> {
75        Ok(Self(b))
76    }
77
78    fn from_integer(i: Self::Integer) -> anyhow::Result<Self> {
79        Ok(i.into())
80    }
81
82    fn from_string(s: String) -> anyhow::Result<Self> {
83        // For backwards compatibility, we have an ad hoc parser for WEI amounts
84        // represented as hex strings.
85        if let Some(s) = s.strip_prefix("0x") {
86            return Ok(Self(s.parse()?));
87        }
88
89        // Strip an optional non-numeric suffix, which will be interpreted as a unit.
90        let (base, unit) = s
91            .split_once(char::is_whitespace)
92            .unwrap_or((s.as_str(), "wei"));
93        match parse_units(base, unit)? {
94            ParseUnits::U256(n) => Ok(Self(n)),
95            ParseUnits::I256(_) => bail!("amount cannot be negative"),
96        }
97    }
98
99    fn to_binary(&self) -> anyhow::Result<Self::Binary> {
100        Ok(self.0)
101    }
102
103    fn to_string(&self) -> anyhow::Result<String> {
104        Ok(format!("{self}"))
105    }
106}
107
108impl RewardAmount {
109    pub fn as_u64(&self) -> Option<u64> {
110        if self.0 <= U256::from(u64::MAX) {
111            Some(self.0.to::<u64>())
112        } else {
113            None
114        }
115    }
116}
117
118impl From<[u8; 20]> for RewardAccountV1 {
119    fn from(bytes: [u8; 20]) -> Self {
120        Self(Address::from(bytes))
121    }
122}
123
124impl AsRef<[u8]> for RewardAccountV1 {
125    fn as_ref(&self) -> &[u8] {
126        self.0.as_slice()
127    }
128}
129
130impl<const ARITY: usize> ToTraversalPath<ARITY> for RewardAccountV1 {
131    fn to_traversal_path(&self, height: usize) -> Vec<usize> {
132        self.0
133            .as_slice()
134            .iter()
135            .take(height)
136            .map(|i| *i as usize)
137            .collect()
138    }
139}
140
141impl RewardAccountV2 {
142    /// Return inner `Address`
143    pub fn address(&self) -> Address {
144        self.0
145    }
146    /// Return byte slice representation of inner `Address` type
147    pub fn as_bytes(&self) -> &[u8] {
148        self.0.as_slice()
149    }
150    /// Return array containing underlying bytes of inner `Address` type
151    pub fn to_fixed_bytes(self) -> [u8; 20] {
152        self.0.into_array()
153    }
154    pub fn test_key_pair() -> EthKeyPair {
155        EthKeyPair::from_mnemonic(
156            "test test test test test test test test test test test junk",
157            0u32,
158        )
159        .unwrap()
160    }
161}
162
163impl RewardAccountV1 {
164    /// Return inner `Address`
165    pub fn address(&self) -> Address {
166        self.0
167    }
168    /// Return byte slice representation of inner `Address` type
169    pub fn as_bytes(&self) -> &[u8] {
170        self.0.as_slice()
171    }
172    /// Return array containing underlying bytes of inner `Address` type
173    pub fn to_fixed_bytes(self) -> [u8; 20] {
174        self.0.into_array()
175    }
176    pub fn test_key_pair() -> EthKeyPair {
177        EthKeyPair::from_mnemonic(
178            "test test test test test test test test test test test junk",
179            0u32,
180        )
181        .unwrap()
182    }
183}
184
185impl FromStr for RewardAccountV2 {
186    type Err = anyhow::Error;
187
188    fn from_str(s: &str) -> Result<Self, Self::Err> {
189        Ok(Self(s.parse()?))
190    }
191}
192
193impl FromStr for RewardAccountV1 {
194    type Err = anyhow::Error;
195
196    fn from_str(s: &str) -> Result<Self, Self::Err> {
197        Ok(Self(s.parse()?))
198    }
199}
200
201impl Valid for RewardAmount {
202    fn check(&self) -> Result<(), SerializationError> {
203        Ok(())
204    }
205}
206
207impl Valid for RewardAccountV2 {
208    fn check(&self) -> Result<(), SerializationError> {
209        Ok(())
210    }
211}
212
213impl Valid for RewardAccountV1 {
214    fn check(&self) -> Result<(), SerializationError> {
215        Ok(())
216    }
217}
218
219impl CanonicalSerialize for RewardAmount {
220    fn serialize_with_mode<W: std::io::prelude::Write>(
221        &self,
222        mut writer: W,
223        _compress: Compress,
224    ) -> Result<(), SerializationError> {
225        Ok(writer.write_all(&self.to_fixed_bytes())?)
226    }
227
228    fn serialized_size(&self, _compress: Compress) -> usize {
229        core::mem::size_of::<U256>()
230    }
231}
232impl CanonicalDeserialize for RewardAmount {
233    fn deserialize_with_mode<R: Read>(
234        mut reader: R,
235        _compress: Compress,
236        _validate: Validate,
237    ) -> Result<Self, SerializationError> {
238        let mut bytes = [0u8; core::mem::size_of::<U256>()];
239        reader.read_exact(&mut bytes)?;
240        let value = U256::from_le_slice(&bytes);
241        Ok(Self(value))
242    }
243}
244
245impl CanonicalSerialize for RewardAccountV2 {
246    fn serialize_with_mode<W: std::io::prelude::Write>(
247        &self,
248        mut writer: W,
249        _compress: Compress,
250    ) -> Result<(), SerializationError> {
251        Ok(writer.write_all(self.0.as_slice())?)
252    }
253
254    fn serialized_size(&self, _compress: Compress) -> usize {
255        core::mem::size_of::<Address>()
256    }
257}
258impl CanonicalDeserialize for RewardAccountV2 {
259    fn deserialize_with_mode<R: Read>(
260        mut reader: R,
261        _compress: Compress,
262        _validate: Validate,
263    ) -> Result<Self, SerializationError> {
264        let mut bytes = [0u8; core::mem::size_of::<Address>()];
265        reader.read_exact(&mut bytes)?;
266        let value = Address::from_slice(&bytes);
267        Ok(Self(value))
268    }
269}
270
271impl CanonicalSerialize for RewardAccountV1 {
272    fn serialize_with_mode<W: std::io::prelude::Write>(
273        &self,
274        mut writer: W,
275        _compress: Compress,
276    ) -> Result<(), SerializationError> {
277        Ok(writer.write_all(self.0.as_slice())?)
278    }
279
280    fn serialized_size(&self, _compress: Compress) -> usize {
281        core::mem::size_of::<Address>()
282    }
283}
284impl CanonicalDeserialize for RewardAccountV1 {
285    fn deserialize_with_mode<R: Read>(
286        mut reader: R,
287        _compress: Compress,
288        _validate: Validate,
289    ) -> Result<Self, SerializationError> {
290        let mut bytes = [0u8; core::mem::size_of::<Address>()];
291        reader.read_exact(&mut bytes)?;
292        let value = Address::from_slice(&bytes);
293        Ok(Self(value))
294    }
295}
296
297impl From<[u8; 20]> for RewardAccountV2 {
298    fn from(bytes: [u8; 20]) -> Self {
299        Self(Address::from(bytes))
300    }
301}
302
303impl AsRef<[u8]> for RewardAccountV2 {
304    fn as_ref(&self) -> &[u8] {
305        self.0.as_slice()
306    }
307}
308
309impl<const ARITY: usize> ToTraversalPath<ARITY> for RewardAccountV2 {
310    fn to_traversal_path(&self, height: usize) -> Vec<usize> {
311        let mut result = vec![0; height];
312
313        // Convert 20-byte address to U256
314        let mut value = U256::from_be_slice(self.0.as_slice());
315
316        // Extract digits using modulo and division (LSB first)
317        for item in result.iter_mut().take(height) {
318            let digit = (value % U256::from(ARITY)).to::<usize>();
319            *item = digit;
320            value /= U256::from(ARITY);
321        }
322
323        result
324    }
325}
326
327impl RewardAccountProofV2 {
328    pub fn presence(
329        pos: FeeAccount,
330        proof: <RewardMerkleTreeV2 as MerkleTreeScheme>::MembershipProof,
331    ) -> Self {
332        Self {
333            account: pos.into(),
334            proof: RewardMerkleProofV2::Presence(proof),
335        }
336    }
337
338    pub fn absence(
339        pos: RewardAccountV2,
340        proof: <RewardMerkleTreeV2 as UniversalMerkleTreeScheme>::NonMembershipProof,
341    ) -> Self {
342        Self {
343            account: pos.into(),
344            proof: RewardMerkleProofV2::Absence(proof),
345        }
346    }
347
348    pub fn prove(tree: &RewardMerkleTreeV2, account: Address) -> Option<(Self, U256)> {
349        match tree.universal_lookup(RewardAccountV2(account)) {
350            LookupResult::Ok(balance, proof) => Some((
351                Self {
352                    account,
353                    proof: RewardMerkleProofV2::Presence(proof),
354                },
355                balance.0,
356            )),
357            LookupResult::NotFound(proof) => Some((
358                Self {
359                    account,
360                    proof: RewardMerkleProofV2::Absence(proof),
361                },
362                U256::ZERO,
363            )),
364            LookupResult::NotInMemory => None,
365        }
366    }
367
368    pub fn verify(&self, comm: &RewardMerkleCommitmentV2) -> anyhow::Result<U256> {
369        match &self.proof {
370            RewardMerkleProofV2::Presence(proof) => {
371                ensure!(
372                    RewardMerkleTreeV2::verify(comm, RewardAccountV2(self.account), proof)?.is_ok(),
373                    "invalid proof"
374                );
375                Ok(proof
376                    .elem()
377                    .context("presence proof is missing account balance")?
378                    .0)
379            },
380            RewardMerkleProofV2::Absence(proof) => {
381                let tree = RewardMerkleTreeV2::from_commitment(comm);
382                ensure!(
383                    RewardMerkleTreeV2::non_membership_verify(
384                        tree.commitment(),
385                        RewardAccountV2(self.account),
386                        proof
387                    )?,
388                    "invalid proof"
389                );
390                Ok(U256::ZERO)
391            },
392        }
393    }
394
395    pub fn remember(&self, tree: &mut RewardMerkleTreeV2) -> anyhow::Result<()> {
396        match &self.proof {
397            RewardMerkleProofV2::Presence(proof) => {
398                tree.remember(
399                    RewardAccountV2(self.account),
400                    proof
401                        .elem()
402                        .context("presence proof is missing account balance")?,
403                    proof,
404                )?;
405                Ok(())
406            },
407            RewardMerkleProofV2::Absence(proof) => {
408                tree.non_membership_remember(RewardAccountV2(self.account), proof)?;
409                Ok(())
410            },
411        }
412    }
413}
414
415impl TryInto<RewardProofSiblings> for RewardAccountProofV2 {
416    type Error = anyhow::Error;
417
418    /// Generate a Solidity-compatible proof for this account
419    ///
420    /// The proof is returned without leaf value. The caller is expected to
421    /// obtain the leaf value from the jellyfish proof (Self).
422    fn try_into(self) -> anyhow::Result<RewardProofSiblings> {
423        // NOTE: rustfmt fails to format this file if the nesting is too deep.
424        let proof = if let RewardMerkleProofV2::Presence(proof) = &self.proof {
425            proof
426        } else {
427            bail!("only presence proofs supported")
428        };
429
430        let path = ToTraversalPath::<REWARD_MERKLE_TREE_V2_ARITY>::to_traversal_path(
431            &RewardAccountV2(self.account),
432            REWARD_MERKLE_TREE_V2_HEIGHT,
433        );
434
435        if path.len() != REWARD_MERKLE_TREE_V2_HEIGHT {
436            bail!("Invalid proof: unexpected path length: {}", path.len());
437        };
438
439        let siblings: [B256; REWARD_MERKLE_TREE_V2_HEIGHT] = proof
440            .proof
441            .iter()
442            .enumerate()
443            .skip(1) // Skip the leaf node (first element)
444            .filter_map(|(level_idx, node)| match node {
445                MerkleNode::Branch { children, .. } => {
446                    // Use the path to determine which sibling we need
447                    let path_direction = path
448                        .get(level_idx - 1)
449                        .copied()
450                        .expect("exists");
451                    let sibling_idx = if path_direction == 0 { 1 } else { 0 };
452                    if sibling_idx >= children.len() {
453                        panic!(
454                            "Invalid proof: index={sibling_idx} length={}",
455                            children.len()
456                        );
457                    };
458
459                    match children[sibling_idx].as_ref() {
460                        MerkleNode::Empty => Some(B256::ZERO),
461                        MerkleNode::Leaf { value, .. } => {
462                            let bytes = value.as_ref();
463                            Some(B256::from_slice(bytes))
464                        }
465                        MerkleNode::Branch { value, .. } => {
466                            let bytes = value.as_ref();
467                            Some(B256::from_slice(bytes))
468                        }
469                        MerkleNode::ForgettenSubtree { value } => {
470                            let bytes = value.as_ref();
471                            Some(B256::from_slice(bytes))
472                        }
473                    }
474                }
475                _ => None,
476            })
477            .collect::<Vec<B256>>().try_into().map_err(|err: Vec<_>| {
478                panic!("Invalid proof length: {:?}, this should never happen", err.len())
479            })
480            .unwrap();
481
482        Ok(siblings.into())
483    }
484}
485
486impl RewardAccountProofV1 {
487    pub fn presence(
488        pos: FeeAccount,
489        proof: <RewardMerkleTreeV1 as MerkleTreeScheme>::MembershipProof,
490    ) -> Self {
491        Self {
492            account: pos.into(),
493            proof: RewardMerkleProofV1::Presence(proof),
494        }
495    }
496
497    pub fn absence(
498        pos: RewardAccountV1,
499        proof: <RewardMerkleTreeV1 as UniversalMerkleTreeScheme>::NonMembershipProof,
500    ) -> Self {
501        Self {
502            account: pos.into(),
503            proof: RewardMerkleProofV1::Absence(proof),
504        }
505    }
506
507    pub fn prove(tree: &RewardMerkleTreeV1, account: Address) -> Option<(Self, U256)> {
508        match tree.universal_lookup(RewardAccountV1(account)) {
509            LookupResult::Ok(balance, proof) => Some((
510                Self {
511                    account,
512                    proof: RewardMerkleProofV1::Presence(proof),
513                },
514                balance.0,
515            )),
516            LookupResult::NotFound(proof) => Some((
517                Self {
518                    account,
519                    proof: RewardMerkleProofV1::Absence(proof),
520                },
521                U256::ZERO,
522            )),
523            LookupResult::NotInMemory => None,
524        }
525    }
526
527    pub fn verify(&self, comm: &RewardMerkleCommitmentV1) -> anyhow::Result<U256> {
528        match &self.proof {
529            RewardMerkleProofV1::Presence(proof) => {
530                ensure!(
531                    RewardMerkleTreeV1::verify(comm, RewardAccountV1(self.account), proof)?.is_ok(),
532                    "invalid proof"
533                );
534                Ok(proof
535                    .elem()
536                    .context("presence proof is missing account balance")?
537                    .0)
538            },
539            RewardMerkleProofV1::Absence(proof) => {
540                let tree = RewardMerkleTreeV1::from_commitment(comm);
541                ensure!(
542                    RewardMerkleTreeV1::non_membership_verify(
543                        tree.commitment(),
544                        RewardAccountV1(self.account),
545                        proof
546                    )?,
547                    "invalid proof"
548                );
549                Ok(U256::ZERO)
550            },
551        }
552    }
553
554    pub fn remember(&self, tree: &mut RewardMerkleTreeV1) -> anyhow::Result<()> {
555        match &self.proof {
556            RewardMerkleProofV1::Presence(proof) => {
557                tree.remember(
558                    RewardAccountV1(self.account),
559                    proof
560                        .elem()
561                        .context("presence proof is missing account balance")?,
562                    proof,
563                )?;
564                Ok(())
565            },
566            RewardMerkleProofV1::Absence(proof) => {
567                tree.non_membership_remember(RewardAccountV1(self.account), proof)?;
568                Ok(())
569            },
570        }
571    }
572}
573
574impl From<(RewardAccountProofV2, U256)> for RewardAccountQueryDataV2 {
575    fn from((proof, balance): (RewardAccountProofV2, U256)) -> Self {
576        Self { balance, proof }
577    }
578}
579
580#[derive(Clone, Debug)]
581pub struct ComputedRewards {
582    leader_address: Address,
583    // leader commission reward
584    leader_commission: RewardAmount,
585    // delegator rewards
586    delegators: Vec<(Address, RewardAmount)>,
587}
588
589impl ComputedRewards {
590    pub fn new(
591        delegators: Vec<(Address, RewardAmount)>,
592        leader_address: Address,
593        leader_commission: RewardAmount,
594    ) -> Self {
595        Self {
596            delegators,
597            leader_address,
598            leader_commission,
599        }
600    }
601
602    pub fn leader_commission(&self) -> &RewardAmount {
603        &self.leader_commission
604    }
605
606    pub fn delegators(&self) -> &Vec<(Address, RewardAmount)> {
607        &self.delegators
608    }
609
610    // chains delegation rewards and leader commission reward
611    pub fn all_rewards(self) -> Vec<(Address, RewardAmount)> {
612        self.delegators
613            .into_iter()
614            .chain(once((self.leader_address, self.leader_commission)))
615            .collect()
616    }
617}
618
619pub struct RewardDistributor {
620    validator: Validator<BLSPubKey>,
621    block_reward: RewardAmount,
622    total_distributed: RewardAmount,
623}
624
625impl RewardDistributor {
626    pub fn new(
627        validator: Validator<BLSPubKey>,
628        block_reward: RewardAmount,
629        total_distributed: RewardAmount,
630    ) -> Self {
631        Self {
632            validator,
633            block_reward,
634            total_distributed,
635        }
636    }
637
638    pub fn validator(&self) -> Validator<BLSPubKey> {
639        self.validator.clone()
640    }
641
642    pub fn block_reward(&self) -> RewardAmount {
643        self.block_reward
644    }
645
646    pub fn total_distributed(&self) -> RewardAmount {
647        self.total_distributed
648    }
649
650    pub fn update_rewards_delta(&self, delta: &mut Delta) -> anyhow::Result<()> {
651        // Update delta rewards
652        delta
653            .rewards_delta
654            .insert(RewardAccountV2(self.validator().account));
655        delta.rewards_delta.extend(
656            self.validator()
657                .delegators
658                .keys()
659                .map(|d| RewardAccountV2(*d)),
660        );
661
662        Ok(())
663    }
664
665    fn update_reward_balance<P>(
666        tree: &mut P,
667        account: &P::Index,
668        amount: P::Element,
669    ) -> anyhow::Result<()>
670    where
671        P: PersistentUniversalMerkleTreeScheme,
672        P: MerkleTreeScheme<Element = RewardAmount>,
673        P::Index: Borrow<<P as MerkleTreeScheme>::Index> + std::fmt::Display,
674    {
675        let mut err = None;
676        *tree = tree.persistent_update_with(account.clone(), |balance| {
677            let balance = balance.copied();
678            match balance.unwrap_or_default().0.checked_add(amount.0) {
679                Some(updated) => Some(updated.into()),
680                None => {
681                    err = Some(format!("overflowed reward balance for account {account}"));
682                    balance
683                },
684            }
685        })?;
686
687        if let Some(error) = err {
688            tracing::warn!(error);
689            bail!(error)
690        }
691
692        Ok(())
693    }
694
695    pub fn apply_rewards(
696        &mut self,
697        version: vbs::version::Version,
698        state: &mut ValidatedState,
699    ) -> anyhow::Result<()> {
700        let computed_rewards = self.compute_rewards()?;
701
702        if version <= EpochVersion::version() {
703            for (address, reward) in computed_rewards.all_rewards() {
704                Self::update_reward_balance(
705                    &mut state.reward_merkle_tree_v1,
706                    &RewardAccountV1(address),
707                    reward,
708                )?;
709                tracing::debug!(%address, %reward, "applied v1 rewards");
710            }
711        } else {
712            for (address, reward) in computed_rewards.all_rewards() {
713                Self::update_reward_balance(
714                    &mut state.reward_merkle_tree_v2,
715                    &RewardAccountV2(address),
716                    reward,
717                )?;
718                tracing::debug!(%address, %reward, "applied v2 rewards");
719            }
720        }
721
722        self.total_distributed += self.block_reward();
723
724        Ok(())
725    }
726
727    /// Computes the reward in a block for the validator and its delegators
728    /// based on the commission rate, individual delegator stake, and total block reward.
729    ///
730    /// The block reward is distributed among the delegators first based on their stake,
731    /// with the remaining amount from the block reward given to the validator as the commission.
732    /// Any minor discrepancies due to rounding off errors are adjusted in the leader reward
733    /// to ensure the total reward is exactly equal to block reward.
734    pub fn compute_rewards(&self) -> anyhow::Result<ComputedRewards> {
735        ensure!(
736            self.validator.commission <= COMMISSION_BASIS_POINTS,
737            "commission must not exceed {COMMISSION_BASIS_POINTS}"
738        );
739
740        let mut rewards = Vec::new();
741
742        let total_reward = self.block_reward.0;
743        let delegators_ratio_basis_points = U256::from(COMMISSION_BASIS_POINTS)
744            .checked_sub(U256::from(self.validator.commission))
745            .context("overflow")?;
746        let delegators_reward = delegators_ratio_basis_points
747            .checked_mul(total_reward)
748            .context("overflow")?;
749
750        // Distribute delegator rewards
751        let total_stake = self.validator.stake;
752        let mut delegators_total_reward_distributed = U256::from(0);
753        for (delegator_address, delegator_stake) in &self.validator.delegators {
754            let delegator_reward = RewardAmount::from(
755                (delegator_stake
756                    .checked_mul(delegators_reward)
757                    .context("overflow")?
758                    .checked_div(total_stake)
759                    .context("overflow")?)
760                .checked_div(U256::from(COMMISSION_BASIS_POINTS))
761                .context("overflow")?,
762            );
763
764            delegators_total_reward_distributed += delegator_reward.0;
765
766            rewards.push((*delegator_address, delegator_reward));
767        }
768
769        let leader_commission = total_reward
770            .checked_sub(delegators_total_reward_distributed)
771            .context("overflow")?;
772
773        Ok(ComputedRewards::new(
774            rewards,
775            self.validator.account,
776            leader_commission.into(),
777        ))
778    }
779}
780
781/// Distributes the block reward for a given block height
782///
783/// Rewards are only distributed if the block belongs to an epoch beyond the second epoch.
784///
785/// The function also calculates the appropriate reward (fixed or dynamic) based
786/// on the protocol version.
787pub async fn distribute_block_reward(
788    instance_state: &NodeState,
789    validated_state: &mut ValidatedState,
790    parent_leaf: &Leaf2,
791    view_number: ViewNumber,
792    version: vbs::version::Version,
793) -> anyhow::Result<Option<RewardDistributor>> {
794    let height = parent_leaf.height() + 1;
795
796    let epoch_height = instance_state
797        .epoch_height
798        .context("epoch height not found")?;
799    let epoch = EpochNumber::new(epoch_from_block_number(height, epoch_height));
800    let coordinator = instance_state.coordinator.clone();
801    let first_epoch = {
802        coordinator
803            .membership()
804            .read()
805            .await
806            .first_epoch()
807            .context("The first epoch was not set.")?
808    };
809
810    // Rewards are distributed only if the current epoch is not the first or second epoch
811    // this is because we don't have stake table from the contract for the first two epochs
812    if epoch <= first_epoch + 1 {
813        return Ok(None);
814    }
815
816    // Determine who the block leader is for this view and ensure missing block
817    // rewards are fetched from peers if needed.
818
819    let leader = get_leader_and_fetch_missing_rewards(
820        instance_state,
821        validated_state,
822        parent_leaf,
823        view_number,
824    )
825    .await?;
826
827    let parent_header = parent_leaf.block_header();
828
829    // Initialize the total rewards distributed so far in this block.
830    let mut previously_distributed = parent_header.total_reward_distributed().unwrap_or_default();
831
832    // Decide whether to use a fixed or dynamic block reward.
833    let block_reward = if version >= DrbAndHeaderUpgradeVersion::version() {
834        instance_state
835            .block_reward(EpochNumber::new(*epoch))
836            .await
837            .with_context(|| format!("block reward is None for epoch {epoch}"))?
838    } else {
839        instance_state.fixed_block_reward().await?
840    };
841
842    // If we are in the DRB + header upgrade
843    // and the parent block is from V3 (which does not have a previously distributed reward field),
844    // we need to recompute the previously distributed rewards
845    // using the fixed block reward and the number of blocks in which fixed reward was distributed
846    if version >= DrbAndHeaderUpgradeVersion::version()
847        && parent_header.version() == EpochVersion::version()
848    {
849        ensure!(
850            instance_state.epoch_start_block != 0,
851            "epoch_start_block is zero"
852        );
853
854        let fixed_block_reward = instance_state.fixed_block_reward().await?;
855
856        // Compute the first block where rewards start being distributed.
857        // Rewards begin only after the first two epochs
858        // Example:
859        //   epoch_height = 10, first_epoch = 1
860        // first_reward_block = 21
861        let first_reward_block = (*first_epoch + 1) * epoch_height + 1;
862        // We only compute fixed reward distribured so far
863        // once the current block
864        // is beyond the first rewardable block.
865        if height > first_reward_block {
866            // If v4 upgrade started at block 101, and first_reward_block is 21:
867            // total_distributed = (101 - 21) * fixed_block_reward
868            let blocks = height.checked_sub(first_reward_block).with_context(|| {
869                format!("height ({height}) - first_reward_block ({first_reward_block}) underflowed")
870            })?;
871            previously_distributed = U256::from(blocks)
872                .checked_mul(fixed_block_reward.0)
873                .with_context(|| {
874                    format!(
875                        "overflow during total_distributed calculation: blocks={blocks}, \
876                         fixed_block_reward={}",
877                        fixed_block_reward.0
878                    )
879                })?
880                .into();
881        }
882    }
883
884    if block_reward.0.is_zero() {
885        tracing::info!("block reward is zero. height={height}. epoch={epoch}");
886        return Ok(None);
887    }
888
889    let mut reward_distributor =
890        RewardDistributor::new(leader, block_reward, previously_distributed);
891
892    reward_distributor.apply_rewards(version, validated_state)?;
893
894    Ok(Some(reward_distributor))
895}
896
897pub async fn get_leader_and_fetch_missing_rewards(
898    instance_state: &NodeState,
899    validated_state: &mut ValidatedState,
900    parent_leaf: &Leaf2,
901    view: ViewNumber,
902) -> anyhow::Result<Validator<BLSPubKey>> {
903    let parent_height = parent_leaf.height();
904    let parent_view = parent_leaf.view_number();
905    let new_height = parent_height + 1;
906
907    let epoch_height = instance_state
908        .epoch_height
909        .context("epoch height not found")?;
910    if epoch_height == 0 {
911        bail!("epoch height is 0. can not catchup reward accounts");
912    }
913    let epoch = EpochNumber::new(epoch_from_block_number(new_height, epoch_height));
914
915    let coordinator = instance_state.coordinator.clone();
916
917    let epoch_membership = coordinator.membership_for_epoch(Some(epoch)).await?;
918    let membership = epoch_membership.coordinator.membership().read().await;
919
920    let leader: BLSPubKey = membership
921        .leader(view, Some(epoch))
922        .context(format!("leader for epoch {epoch:?} not found"))?;
923
924    let validator = membership
925        .get_validator_config(&epoch, leader)
926        .context("validator not found")?;
927    drop(membership);
928
929    let mut reward_accounts = HashSet::new();
930    reward_accounts.insert(validator.account.into());
931    let delegators = validator
932        .delegators
933        .keys()
934        .cloned()
935        .map(|a| a.into())
936        .collect::<Vec<RewardAccountV2>>();
937
938    reward_accounts.extend(delegators.clone());
939
940    let parent_header = parent_leaf.block_header();
941
942    if parent_header.version() <= EpochVersion::version() {
943        let accts: HashSet<_> = reward_accounts
944            .into_iter()
945            .map(RewardAccountV1::from)
946            .collect();
947        let missing_reward_accts = validated_state.forgotten_reward_accounts_v1(accts);
948
949        if !missing_reward_accts.is_empty() {
950            tracing::warn!(
951                parent_height,
952                ?parent_view,
953                ?missing_reward_accts,
954                "fetching missing v1 reward accounts from peers"
955            );
956
957            let missing_account_proofs = instance_state
958                .state_catchup
959                .fetch_reward_accounts_v1(
960                    instance_state,
961                    parent_height,
962                    parent_view,
963                    validated_state.reward_merkle_tree_v1.commitment(),
964                    missing_reward_accts,
965                )
966                .await?;
967
968            for proof in missing_account_proofs.iter() {
969                proof
970                    .remember(&mut validated_state.reward_merkle_tree_v1)
971                    .expect("proof previously verified");
972            }
973        }
974    } else {
975        let missing_reward_accts = validated_state.forgotten_reward_accounts_v2(reward_accounts);
976
977        if !missing_reward_accts.is_empty() {
978            tracing::warn!(
979                parent_height,
980                ?parent_view,
981                ?missing_reward_accts,
982                "fetching missing reward accounts from peers"
983            );
984
985            let missing_account_proofs = instance_state
986                .state_catchup
987                .fetch_reward_accounts_v2(
988                    instance_state,
989                    parent_height,
990                    parent_view,
991                    validated_state.reward_merkle_tree_v2.commitment(),
992                    missing_reward_accts,
993                )
994                .await?;
995
996            for proof in missing_account_proofs.iter() {
997                proof
998                    .remember(&mut validated_state.reward_merkle_tree_v2)
999                    .expect("proof previously verified");
1000            }
1001        }
1002    }
1003
1004    Ok(validator)
1005}
1006
1007#[cfg(test)]
1008pub mod tests {
1009
1010    use super::*;
1011
1012    // TODO: current tests are just sanity checks, we need more.
1013
1014    #[test]
1015    fn test_reward_calculation_sanity_checks() {
1016        // This test verifies that the total rewards distributed match the block reward. Due to
1017        // rounding effects in distribution, the validator may receive a slightly higher amount
1018        // because the remainder after delegator distribution is sent to the validator.
1019
1020        let validator = Validator::mock();
1021        let mut distributor = RewardDistributor::new(
1022            validator,
1023            RewardAmount(U256::from(1902000000000000000_u128)),
1024            U256::ZERO.into(),
1025        );
1026        let rewards = distributor.compute_rewards().unwrap();
1027        let total = |rewards: ComputedRewards| {
1028            rewards
1029                .all_rewards()
1030                .iter()
1031                .fold(U256::ZERO, |acc, (_, r)| acc + r.0)
1032        };
1033        assert_eq!(total(rewards.clone()), distributor.block_reward.0);
1034
1035        distributor.validator.commission = 0;
1036        let rewards = distributor.compute_rewards().unwrap();
1037        assert_eq!(total(rewards.clone()), distributor.block_reward.0);
1038
1039        distributor.validator.commission = 10000;
1040        let rewards = distributor.compute_rewards().unwrap();
1041        assert_eq!(total(rewards.clone()), distributor.block_reward.0);
1042        let leader_commission = rewards.leader_commission();
1043        assert_eq!(*leader_commission, distributor.block_reward);
1044
1045        distributor.validator.commission = 10001;
1046        assert!(distributor
1047            .compute_rewards()
1048            .err()
1049            .unwrap()
1050            .to_string()
1051            .contains("must not exceed"));
1052    }
1053
1054    #[test]
1055    fn test_compute_rewards_validator_commission() {
1056        let validator = Validator::mock();
1057        let mut distributor = RewardDistributor::new(
1058            validator.clone(),
1059            RewardAmount(U256::from(1902000000000000000_u128)),
1060            U256::ZERO.into(),
1061        );
1062        distributor.validator.commission = 0;
1063
1064        let rewards = distributor.compute_rewards().unwrap();
1065
1066        let leader_commission = rewards.leader_commission();
1067        let percentage =
1068            leader_commission.0 * U256::from(COMMISSION_BASIS_POINTS) / distributor.block_reward.0;
1069        assert_eq!(percentage, U256::ZERO);
1070
1071        // 3%
1072        distributor.validator.commission = 300;
1073
1074        let rewards = distributor.compute_rewards().unwrap();
1075        let leader_commission = rewards.leader_commission();
1076        let percentage =
1077            leader_commission.0 * U256::from(COMMISSION_BASIS_POINTS) / distributor.block_reward.0;
1078        println!("percentage: {percentage:?}");
1079        assert_eq!(percentage, U256::from(300));
1080
1081        //100%
1082        distributor.validator.commission = 10000;
1083
1084        let rewards = distributor.compute_rewards().unwrap();
1085        let leader_commission = rewards.leader_commission();
1086        assert_eq!(*leader_commission, distributor.block_reward);
1087    }
1088}