1use std::{borrow::Borrow, collections::HashSet, iter::once, str::FromStr, sync::Arc};
2
3use alloy::primitives::{
4 Address, B256, U256,
5 utils::{ParseUnits, parse_units},
6};
7use anyhow::{Context, bail, ensure};
8use ark_serialize::{
9 CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate,
10};
11use espresso_utils::{
12 impl_serde_from_string_or_integer, impl_to_fixed_bytes, ser::FromStringOrInteger,
13};
14use hotshot_contract_adapter::reward::RewardProofSiblings;
15use hotshot_types::{
16 data::{EpochNumber, ViewNumber},
17 epoch_membership::EpochMembershipCoordinator,
18 signature_key::BLSPubKey,
19 traits::election::{Membership, MembershipSnapshot},
20 utils::epoch_from_block_number,
21};
22use jf_merkle_tree_compat::{
23 ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult,
24 MerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme, prelude::MerkleNode,
25};
26use num_traits::CheckedSub;
27use tokio::task::JoinHandle;
28use vbs::version::Version;
29use versions::{DRB_AND_HEADER_UPGRADE_VERSION, EPOCH_REWARD_VERSION, EPOCH_VERSION};
30
31use super::{
32 Leaf2, NodeState, ValidatedState,
33 v0_3::{AuthenticatedValidator, COMMISSION_BASIS_POINTS, RewardAmount},
34 v0_4::{
35 RewardAccountProofV2, RewardAccountQueryDataV2, RewardAccountV2, RewardMerkleCommitmentV2,
36 RewardMerkleProofV2, RewardMerkleTreeV2, forgotten_accounts_include,
37 },
38};
39use crate::{
40 EpochSnapshot, FeeAccount, SeqTypes,
41 eth_signature_key::EthKeyPair,
42 v0_3::{
43 RewardAccountProofV1, RewardAccountV1, RewardMerkleCommitmentV1, RewardMerkleProofV1,
44 RewardMerkleTreeV1,
45 },
46 v0_4::{Delta, REWARD_MERKLE_TREE_V2_ARITY, REWARD_MERKLE_TREE_V2_HEIGHT},
47 v0_5::LeaderCounts,
48};
49
50impl_serde_from_string_or_integer!(RewardAmount);
51impl_to_fixed_bytes!(RewardAmount, U256);
52
53impl From<u64> for RewardAmount {
54 fn from(amt: u64) -> Self {
55 Self(U256::from(amt))
56 }
57}
58
59impl CheckedSub for RewardAmount {
60 fn checked_sub(&self, v: &Self) -> Option<Self> {
61 self.0.checked_sub(v.0).map(RewardAmount)
62 }
63}
64
65impl FromStr for RewardAmount {
66 type Err = <U256 as FromStr>::Err;
67
68 fn from_str(s: &str) -> Result<Self, Self::Err> {
69 Ok(Self(s.parse()?))
70 }
71}
72
73impl FromStringOrInteger for RewardAmount {
74 type Binary = U256;
75 type Integer = u64;
76
77 fn from_binary(b: Self::Binary) -> anyhow::Result<Self> {
78 Ok(Self(b))
79 }
80
81 fn from_integer(i: Self::Integer) -> anyhow::Result<Self> {
82 Ok(i.into())
83 }
84
85 fn from_string(s: String) -> anyhow::Result<Self> {
86 if let Some(s) = s.strip_prefix("0x") {
89 return Ok(Self(s.parse()?));
90 }
91
92 let (base, unit) = s
94 .split_once(char::is_whitespace)
95 .unwrap_or((s.as_str(), "wei"));
96 match parse_units(base, unit)? {
97 ParseUnits::U256(n) => Ok(Self(n)),
98 ParseUnits::I256(_) => bail!("amount cannot be negative"),
99 }
100 }
101
102 fn to_binary(&self) -> anyhow::Result<Self::Binary> {
103 Ok(self.0)
104 }
105
106 fn to_string(&self) -> anyhow::Result<String> {
107 Ok(format!("{self}"))
108 }
109}
110
111impl RewardAmount {
112 pub fn as_u64(&self) -> Option<u64> {
113 if self.0 <= U256::from(u64::MAX) {
114 Some(self.0.to::<u64>())
115 } else {
116 None
117 }
118 }
119}
120
121impl From<[u8; 20]> for RewardAccountV1 {
122 fn from(bytes: [u8; 20]) -> Self {
123 Self(Address::from(bytes))
124 }
125}
126
127impl AsRef<[u8]> for RewardAccountV1 {
128 fn as_ref(&self) -> &[u8] {
129 self.0.as_slice()
130 }
131}
132
133impl<const ARITY: usize> ToTraversalPath<ARITY> for RewardAccountV1 {
134 fn to_traversal_path(&self, height: usize) -> Vec<usize> {
135 self.0
136 .as_slice()
137 .iter()
138 .take(height)
139 .map(|i| *i as usize)
140 .collect()
141 }
142}
143
144impl RewardAccountV2 {
145 pub fn address(&self) -> Address {
147 self.0
148 }
149 pub fn as_bytes(&self) -> &[u8] {
151 self.0.as_slice()
152 }
153 pub fn to_fixed_bytes(self) -> [u8; 20] {
155 self.0.into_array()
156 }
157 pub fn test_key_pair() -> EthKeyPair {
158 EthKeyPair::from_mnemonic(
159 "test test test test test test test test test test test junk",
160 0u32,
161 )
162 .unwrap()
163 }
164}
165
166impl RewardAccountV1 {
167 pub fn address(&self) -> Address {
169 self.0
170 }
171 pub fn as_bytes(&self) -> &[u8] {
173 self.0.as_slice()
174 }
175 pub fn to_fixed_bytes(self) -> [u8; 20] {
177 self.0.into_array()
178 }
179 pub fn test_key_pair() -> EthKeyPair {
180 EthKeyPair::from_mnemonic(
181 "test test test test test test test test test test test junk",
182 0u32,
183 )
184 .unwrap()
185 }
186}
187
188impl FromStr for RewardAccountV2 {
189 type Err = anyhow::Error;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 Ok(Self(s.parse()?))
193 }
194}
195
196impl FromStr for RewardAccountV1 {
197 type Err = anyhow::Error;
198
199 fn from_str(s: &str) -> Result<Self, Self::Err> {
200 Ok(Self(s.parse()?))
201 }
202}
203
204impl Valid for RewardAmount {
205 fn check(&self) -> Result<(), SerializationError> {
206 Ok(())
207 }
208}
209
210impl Valid for RewardAccountV2 {
211 fn check(&self) -> Result<(), SerializationError> {
212 Ok(())
213 }
214}
215
216impl Valid for RewardAccountV1 {
217 fn check(&self) -> Result<(), SerializationError> {
218 Ok(())
219 }
220}
221
222impl CanonicalSerialize for RewardAmount {
223 fn serialize_with_mode<W: std::io::prelude::Write>(
224 &self,
225 mut writer: W,
226 _compress: Compress,
227 ) -> Result<(), SerializationError> {
228 Ok(writer.write_all(&self.to_fixed_bytes())?)
229 }
230
231 fn serialized_size(&self, _compress: Compress) -> usize {
232 core::mem::size_of::<U256>()
233 }
234}
235impl CanonicalDeserialize for RewardAmount {
236 fn deserialize_with_mode<R: Read>(
237 mut reader: R,
238 _compress: Compress,
239 _validate: Validate,
240 ) -> Result<Self, SerializationError> {
241 let mut bytes = [0u8; core::mem::size_of::<U256>()];
242 reader.read_exact(&mut bytes)?;
243 let value = U256::from_le_slice(&bytes);
244 Ok(Self(value))
245 }
246}
247
248impl CanonicalSerialize for RewardAccountV2 {
249 fn serialize_with_mode<W: std::io::prelude::Write>(
250 &self,
251 mut writer: W,
252 _compress: Compress,
253 ) -> Result<(), SerializationError> {
254 Ok(writer.write_all(self.0.as_slice())?)
255 }
256
257 fn serialized_size(&self, _compress: Compress) -> usize {
258 core::mem::size_of::<Address>()
259 }
260}
261impl CanonicalDeserialize for RewardAccountV2 {
262 fn deserialize_with_mode<R: Read>(
263 mut reader: R,
264 _compress: Compress,
265 _validate: Validate,
266 ) -> Result<Self, SerializationError> {
267 let mut bytes = [0u8; core::mem::size_of::<Address>()];
268 reader.read_exact(&mut bytes)?;
269 let value = Address::from_slice(&bytes);
270 Ok(Self(value))
271 }
272}
273
274impl CanonicalSerialize for RewardAccountV1 {
275 fn serialize_with_mode<W: std::io::prelude::Write>(
276 &self,
277 mut writer: W,
278 _compress: Compress,
279 ) -> Result<(), SerializationError> {
280 Ok(writer.write_all(self.0.as_slice())?)
281 }
282
283 fn serialized_size(&self, _compress: Compress) -> usize {
284 core::mem::size_of::<Address>()
285 }
286}
287impl CanonicalDeserialize for RewardAccountV1 {
288 fn deserialize_with_mode<R: Read>(
289 mut reader: R,
290 _compress: Compress,
291 _validate: Validate,
292 ) -> Result<Self, SerializationError> {
293 let mut bytes = [0u8; core::mem::size_of::<Address>()];
294 reader.read_exact(&mut bytes)?;
295 let value = Address::from_slice(&bytes);
296 Ok(Self(value))
297 }
298}
299
300impl From<[u8; 20]> for RewardAccountV2 {
301 fn from(bytes: [u8; 20]) -> Self {
302 Self(Address::from(bytes))
303 }
304}
305
306impl AsRef<[u8]> for RewardAccountV2 {
307 fn as_ref(&self) -> &[u8] {
308 self.0.as_slice()
309 }
310}
311
312impl<const ARITY: usize> ToTraversalPath<ARITY> for RewardAccountV2 {
313 fn to_traversal_path(&self, height: usize) -> Vec<usize> {
314 let mut result = vec![0; height];
315
316 let mut value = U256::from_be_slice(self.0.as_slice());
318
319 for item in result.iter_mut().take(height) {
321 let digit = (value % U256::from(ARITY)).to::<usize>();
322 *item = digit;
323 value /= U256::from(ARITY);
324 }
325
326 result
327 }
328}
329
330impl RewardAccountProofV2 {
331 pub fn presence(
332 pos: FeeAccount,
333 proof: <RewardMerkleTreeV2 as MerkleTreeScheme>::MembershipProof,
334 ) -> Self {
335 Self {
336 account: pos.into(),
337 proof: RewardMerkleProofV2::Presence(proof),
338 }
339 }
340
341 pub fn absence(
342 pos: RewardAccountV2,
343 proof: <RewardMerkleTreeV2 as UniversalMerkleTreeScheme>::NonMembershipProof,
344 ) -> Self {
345 Self {
346 account: pos.into(),
347 proof: RewardMerkleProofV2::Absence(proof),
348 }
349 }
350
351 pub fn prove(tree: &RewardMerkleTreeV2, account: Address) -> Option<(Self, U256)> {
352 match tree.universal_lookup(RewardAccountV2(account)) {
353 LookupResult::Ok(balance, proof) => Some((
354 Self {
355 account,
356 proof: RewardMerkleProofV2::Presence(proof),
357 },
358 balance.0,
359 )),
360 LookupResult::NotFound(proof) => Some((
361 Self {
362 account,
363 proof: RewardMerkleProofV2::Absence(proof),
364 },
365 U256::ZERO,
366 )),
367 LookupResult::NotInMemory => None,
368 }
369 }
370
371 pub fn verify(&self, comm: &RewardMerkleCommitmentV2) -> anyhow::Result<U256> {
372 match &self.proof {
373 RewardMerkleProofV2::Presence(proof) => {
374 ensure!(
375 RewardMerkleTreeV2::verify(comm, RewardAccountV2(self.account), proof)?.is_ok(),
376 "invalid proof"
377 );
378 Ok(proof
379 .elem()
380 .context("presence proof is missing account balance")?
381 .0)
382 },
383 RewardMerkleProofV2::Absence(proof) => {
384 let tree = RewardMerkleTreeV2::from_commitment(comm);
385 ensure!(
386 RewardMerkleTreeV2::non_membership_verify(
387 tree.commitment(),
388 RewardAccountV2(self.account),
389 proof
390 )?,
391 "invalid proof"
392 );
393 Ok(U256::ZERO)
394 },
395 }
396 }
397
398 pub fn remember(&self, tree: &mut RewardMerkleTreeV2) -> anyhow::Result<()> {
399 match &self.proof {
400 RewardMerkleProofV2::Presence(proof) => {
401 tree.remember(
402 RewardAccountV2(self.account),
403 proof
404 .elem()
405 .context("presence proof is missing account balance")?,
406 proof,
407 )?;
408 Ok(())
409 },
410 RewardMerkleProofV2::Absence(proof) => {
411 tree.non_membership_remember(RewardAccountV2(self.account), proof)?;
412 Ok(())
413 },
414 }
415 }
416}
417
418impl TryInto<RewardProofSiblings> for RewardAccountProofV2 {
419 type Error = anyhow::Error;
420
421 fn try_into(self) -> anyhow::Result<RewardProofSiblings> {
426 let proof = if let RewardMerkleProofV2::Presence(proof) = &self.proof {
428 proof
429 } else {
430 bail!("only presence proofs supported")
431 };
432
433 let path = ToTraversalPath::<{ REWARD_MERKLE_TREE_V2_ARITY }>::to_traversal_path(
434 &RewardAccountV2(self.account),
435 REWARD_MERKLE_TREE_V2_HEIGHT,
436 );
437
438 if path.len() != REWARD_MERKLE_TREE_V2_HEIGHT {
439 bail!("Invalid proof: unexpected path length: {}", path.len());
440 };
441
442 let siblings: [B256; REWARD_MERKLE_TREE_V2_HEIGHT] = proof
443 .proof
444 .iter()
445 .enumerate()
446 .skip(1) .filter_map(|(level_idx, node)| match node {
448 MerkleNode::Branch { children, .. } => {
449 let path_direction = path
451 .get(level_idx - 1)
452 .copied()
453 .expect("exists");
454 let sibling_idx = if path_direction == 0 { 1 } else { 0 };
455 if sibling_idx >= children.len() {
456 panic!(
457 "Invalid proof: index={sibling_idx} length={}",
458 children.len()
459 );
460 };
461
462 match children[sibling_idx].as_ref() {
463 MerkleNode::Empty => Some(B256::ZERO),
464 MerkleNode::Leaf { value, .. } => {
465 let bytes = value.as_ref();
466 Some(B256::from_slice(bytes))
467 }
468 MerkleNode::Branch { value, .. } => {
469 let bytes = value.as_ref();
470 Some(B256::from_slice(bytes))
471 }
472 MerkleNode::ForgettenSubtree { value } => {
473 let bytes = value.as_ref();
474 Some(B256::from_slice(bytes))
475 }
476 }
477 }
478 _ => None,
479 })
480 .collect::<Vec<B256>>().try_into().map_err(|err: Vec<_>| {
481 panic!("Invalid proof length: {:?}, this should never happen", err.len())
482 })
483 .unwrap();
484
485 Ok(siblings.into())
486 }
487}
488
489impl RewardAccountProofV1 {
490 pub fn presence(
491 pos: FeeAccount,
492 proof: <RewardMerkleTreeV1 as MerkleTreeScheme>::MembershipProof,
493 ) -> Self {
494 Self {
495 account: pos.into(),
496 proof: RewardMerkleProofV1::Presence(proof),
497 }
498 }
499
500 pub fn absence(
501 pos: RewardAccountV1,
502 proof: <RewardMerkleTreeV1 as UniversalMerkleTreeScheme>::NonMembershipProof,
503 ) -> Self {
504 Self {
505 account: pos.into(),
506 proof: RewardMerkleProofV1::Absence(proof),
507 }
508 }
509
510 pub fn prove(tree: &RewardMerkleTreeV1, account: Address) -> Option<(Self, U256)> {
511 match tree.universal_lookup(RewardAccountV1(account)) {
512 LookupResult::Ok(balance, proof) => Some((
513 Self {
514 account,
515 proof: RewardMerkleProofV1::Presence(proof),
516 },
517 balance.0,
518 )),
519 LookupResult::NotFound(proof) => Some((
520 Self {
521 account,
522 proof: RewardMerkleProofV1::Absence(proof),
523 },
524 U256::ZERO,
525 )),
526 LookupResult::NotInMemory => None,
527 }
528 }
529
530 pub fn verify(&self, comm: &RewardMerkleCommitmentV1) -> anyhow::Result<U256> {
531 match &self.proof {
532 RewardMerkleProofV1::Presence(proof) => {
533 ensure!(
534 RewardMerkleTreeV1::verify(comm, RewardAccountV1(self.account), proof)?.is_ok(),
535 "invalid proof"
536 );
537 Ok(proof
538 .elem()
539 .context("presence proof is missing account balance")?
540 .0)
541 },
542 RewardMerkleProofV1::Absence(proof) => {
543 let tree = RewardMerkleTreeV1::from_commitment(comm);
544 ensure!(
545 RewardMerkleTreeV1::non_membership_verify(
546 tree.commitment(),
547 RewardAccountV1(self.account),
548 proof
549 )?,
550 "invalid proof"
551 );
552 Ok(U256::ZERO)
553 },
554 }
555 }
556
557 pub fn remember(&self, tree: &mut RewardMerkleTreeV1) -> anyhow::Result<()> {
558 match &self.proof {
559 RewardMerkleProofV1::Presence(proof) => {
560 tree.remember(
561 RewardAccountV1(self.account),
562 proof
563 .elem()
564 .context("presence proof is missing account balance")?,
565 proof,
566 )?;
567 Ok(())
568 },
569 RewardMerkleProofV1::Absence(proof) => {
570 tree.non_membership_remember(RewardAccountV1(self.account), proof)?;
571 Ok(())
572 },
573 }
574 }
575}
576
577impl From<(RewardAccountProofV2, U256)> for RewardAccountQueryDataV2 {
578 fn from((proof, balance): (RewardAccountProofV2, U256)) -> Self {
579 Self { balance, proof }
580 }
581}
582
583#[derive(Clone, Debug)]
584pub struct ComputedRewards {
585 leader_address: Address,
586 leader_commission: RewardAmount,
588 delegators: Vec<(Address, RewardAmount)>,
590}
591
592impl ComputedRewards {
593 pub fn new(
594 delegators: Vec<(Address, RewardAmount)>,
595 leader_address: Address,
596 leader_commission: RewardAmount,
597 ) -> Self {
598 Self {
599 delegators,
600 leader_address,
601 leader_commission,
602 }
603 }
604
605 pub fn leader_commission(&self) -> &RewardAmount {
606 &self.leader_commission
607 }
608
609 pub fn delegators(&self) -> &Vec<(Address, RewardAmount)> {
610 &self.delegators
611 }
612
613 pub fn all_rewards(self) -> Vec<(Address, RewardAmount)> {
615 self.delegators
616 .into_iter()
617 .chain(once((self.leader_address, self.leader_commission)))
618 .collect()
619 }
620}
621
622pub struct ValidatorLeaderCounts(Vec<(AuthenticatedValidator<BLSPubKey>, u16)>);
624
625impl ValidatorLeaderCounts {
626 pub fn new(snapshot: &EpochSnapshot, leader_counts: LeaderCounts) -> anyhow::Result<Self> {
633 let entries: Vec<_> = snapshot
634 .stake_table()
635 .zip(leader_counts.iter().copied())
636 .map(|(entry, count)| {
637 let validator = snapshot.validator_config(&entry.stake_table_entry.stake_key)?;
638 Ok((validator.clone(), count))
639 })
640 .collect::<anyhow::Result<_>>()?;
641
642 Ok(Self(entries))
643 }
644
645 pub fn active_leaders(
647 &self,
648 ) -> impl Iterator<Item = (&AuthenticatedValidator<BLSPubKey>, u16)> {
649 self.0
650 .iter()
651 .filter(|(_, count)| *count > 0)
652 .map(|(v, count)| (v, *count))
653 }
654
655 fn all_reward_accounts(&self) -> Vec<RewardAccountV2> {
657 self.active_leaders()
658 .flat_map(|(v, _)| {
659 std::iter::once(RewardAccountV2(v.account))
660 .chain(v.delegators.keys().map(|d| RewardAccountV2(*d)))
661 })
662 .collect()
663 }
664}
665
666pub struct RewardDistributor {
667 validator: AuthenticatedValidator<BLSPubKey>,
668 block_reward: RewardAmount,
669 total_distributed: RewardAmount,
670}
671
672impl RewardDistributor {
673 pub fn new(
674 validator: AuthenticatedValidator<BLSPubKey>,
675 block_reward: RewardAmount,
676 total_distributed: RewardAmount,
677 ) -> Self {
678 Self {
679 validator,
680 block_reward,
681 total_distributed,
682 }
683 }
684
685 pub fn validator(&self) -> AuthenticatedValidator<BLSPubKey> {
686 self.validator.clone()
687 }
688
689 pub fn block_reward(&self) -> RewardAmount {
690 self.block_reward
691 }
692
693 pub fn total_distributed(&self) -> RewardAmount {
694 self.total_distributed
695 }
696
697 pub fn update_rewards_delta(&self, delta: &mut Delta) -> anyhow::Result<()> {
698 delta
700 .rewards_delta
701 .insert(RewardAccountV2(self.validator().account));
702 delta.rewards_delta.extend(
703 self.validator()
704 .delegators
705 .keys()
706 .map(|d| RewardAccountV2(*d)),
707 );
708
709 Ok(())
710 }
711
712 pub fn update_reward_balance<P>(
713 tree: &mut P,
714 account: &P::Index,
715 amount: P::Element,
716 ) -> anyhow::Result<()>
717 where
718 P: UniversalMerkleTreeScheme<Element = RewardAmount>,
719 P::Index: Borrow<<P as MerkleTreeScheme>::Index> + std::fmt::Display,
720 {
721 let mut err = None;
722 tree.update_with(account.clone(), |balance| {
723 let balance = balance.copied();
724 match balance.unwrap_or_default().0.checked_add(amount.0) {
725 Some(updated) => Some(updated.into()),
726 None => {
727 err = Some(format!("overflowed reward balance for account {account}"));
728 balance
729 },
730 }
731 })?;
732
733 if let Some(error) = err {
734 tracing::warn!(error);
735 bail!(error)
736 }
737
738 Ok(())
739 }
740
741 pub fn apply_rewards(
742 &mut self,
743 version: Version,
744 state: &mut ValidatedState,
745 ) -> anyhow::Result<()> {
746 let computed_rewards = self.compute_rewards()?;
747
748 if version <= EPOCH_VERSION {
749 for (address, reward) in computed_rewards.all_rewards() {
750 Self::update_reward_balance(
751 &mut state.reward_merkle_tree_v1,
752 &RewardAccountV1(address),
753 reward,
754 )?;
755 tracing::debug!(%address, %reward, "applied v1 rewards");
756 }
757 } else {
758 for (address, reward) in computed_rewards.all_rewards() {
759 Self::update_reward_balance(
760 &mut state.reward_merkle_tree_v2,
761 &RewardAccountV2(address),
762 reward,
763 )?;
764 tracing::debug!(%address, %reward, "applied v2 rewards");
765 }
766 }
767
768 self.total_distributed += self.block_reward();
769
770 Ok(())
771 }
772
773 pub fn compute_rewards(&self) -> anyhow::Result<ComputedRewards> {
781 ensure!(
782 self.validator.commission <= COMMISSION_BASIS_POINTS,
783 "commission must not exceed {COMMISSION_BASIS_POINTS}"
784 );
785
786 let mut rewards = Vec::new();
787
788 let total_reward = self.block_reward.0;
789 let delegators_ratio_basis_points = U256::from(COMMISSION_BASIS_POINTS)
790 .checked_sub(U256::from(self.validator.commission))
791 .context("overflow")?;
792 let delegators_reward = delegators_ratio_basis_points
793 .checked_mul(total_reward)
794 .context("overflow")?;
795
796 let total_stake = self.validator.stake;
798 let mut delegators_total_reward_distributed = U256::from(0);
799 for (delegator_address, delegator_stake) in &self.validator.delegators {
800 let delegator_reward = RewardAmount::from(
801 (delegator_stake
802 .checked_mul(delegators_reward)
803 .context("overflow")?
804 .checked_div(total_stake)
805 .context("overflow")?)
806 .checked_div(U256::from(COMMISSION_BASIS_POINTS))
807 .context("overflow")?,
808 );
809
810 delegators_total_reward_distributed += delegator_reward.0;
811
812 rewards.push((*delegator_address, delegator_reward));
813 }
814
815 let leader_commission = total_reward
816 .checked_sub(delegators_total_reward_distributed)
817 .context("overflow")?;
818
819 Ok(ComputedRewards::new(
820 rewards,
821 self.validator.account,
822 leader_commission.into(),
823 ))
824 }
825}
826
827pub async fn distribute_block_reward(
834 instance_state: &NodeState,
835 validated_state: &mut ValidatedState,
836 parent_leaf: &Leaf2,
837 view_number: ViewNumber,
838 version: Version,
839) -> anyhow::Result<Option<RewardDistributor>> {
840 let height = parent_leaf.height() + 1;
841
842 let epoch_height = instance_state
843 .epoch_height
844 .context("epoch height not found")?;
845 let epoch = EpochNumber::new(epoch_from_block_number(height, epoch_height));
846 let coordinator = instance_state.coordinator.clone();
847 let first_epoch = {
848 coordinator
849 .membership()
850 .first_epoch()
851 .context("The first epoch was not set.")?
852 };
853
854 if epoch <= first_epoch + 1 {
857 return Ok(None);
858 }
859
860 let leader = get_leader_and_fetch_missing_rewards(
864 instance_state,
865 validated_state,
866 parent_leaf,
867 view_number,
868 )
869 .await?;
870
871 let parent_header = parent_leaf.block_header();
872
873 let mut previously_distributed = parent_header.total_reward_distributed().unwrap_or_default();
875
876 let block_reward = if version == DRB_AND_HEADER_UPGRADE_VERSION {
878 instance_state
879 .block_reward(EpochNumber::new(*epoch))
880 .await
881 .with_context(|| format!("block reward is None for epoch {epoch}"))?
882 } else {
883 instance_state.fixed_block_reward().await?
884 };
885
886 if version == DRB_AND_HEADER_UPGRADE_VERSION && parent_header.version() == EPOCH_VERSION {
891 ensure!(
892 instance_state.epoch_start_block != 0,
893 "epoch_start_block is zero"
894 );
895
896 let fixed_block_reward = instance_state.fixed_block_reward().await?;
897
898 let first_reward_block = (*first_epoch + 1) * epoch_height + 1;
904 if height > first_reward_block {
908 let blocks = height.checked_sub(first_reward_block).with_context(|| {
911 format!("height ({height}) - first_reward_block ({first_reward_block}) underflowed")
912 })?;
913 previously_distributed = U256::from(blocks)
914 .checked_mul(fixed_block_reward.0)
915 .with_context(|| {
916 format!(
917 "overflow during total_distributed calculation: blocks={blocks}, \
918 fixed_block_reward={}",
919 fixed_block_reward.0
920 )
921 })?
922 .into();
923 }
924 }
925
926 if block_reward.0.is_zero() {
927 tracing::info!("block reward is zero. height={height}. epoch={epoch}");
928 return Ok(None);
929 }
930
931 let mut reward_distributor =
932 RewardDistributor::new(leader, block_reward, previously_distributed);
933
934 reward_distributor.apply_rewards(version, validated_state)?;
935
936 Ok(Some(reward_distributor))
937}
938
939pub async fn get_leader_and_fetch_missing_rewards(
940 instance_state: &NodeState,
941 validated_state: &mut ValidatedState,
942 parent_leaf: &Leaf2,
943 view: ViewNumber,
944) -> anyhow::Result<AuthenticatedValidator<BLSPubKey>> {
945 let parent_height = parent_leaf.height();
946 let parent_view = parent_leaf.view_number();
947 let new_height = parent_height + 1;
948
949 let epoch_height = instance_state
950 .epoch_height
951 .context("epoch height not found")?;
952 if epoch_height == 0 {
953 bail!("epoch height is 0. can not catchup reward accounts");
954 }
955 let epoch = EpochNumber::new(epoch_from_block_number(new_height, epoch_height));
956
957 let coordinator = instance_state.coordinator.clone();
958
959 let membership = coordinator.membership_for_epoch(Some(epoch))?;
960
961 let snapshot = membership
962 .snapshot()
963 .context(format!("no committee for epoch {epoch:?}"))?;
964
965 let leader: BLSPubKey = snapshot
966 .leader(view)
967 .context(format!("leader for epoch {epoch:?} not found"))?;
968
969 tracing::debug!("Selected leader: {leader} for view {view} and epoch {epoch}");
970
971 let validator = snapshot
972 .validator_config(&leader)
973 .context("validator not found")?;
974
975 let parent_header = parent_leaf.block_header();
976
977 if parent_header.version() <= EPOCH_VERSION {
978 let mut reward_accounts = HashSet::new();
979 reward_accounts.insert(validator.account.into());
980 let delegators = validator
981 .delegators
982 .keys()
983 .cloned()
984 .map(|a| a.into())
985 .collect::<Vec<RewardAccountV2>>();
986
987 reward_accounts.extend(delegators.clone());
988
989 let accts: HashSet<_> = reward_accounts
990 .into_iter()
991 .map(RewardAccountV1::from)
992 .collect();
993 let missing_reward_accts = validated_state.forgotten_reward_accounts_v1(accts);
994
995 if !missing_reward_accts.is_empty() {
996 tracing::warn!(
997 parent_height,
998 ?parent_view,
999 ?missing_reward_accts,
1000 "fetching missing v1 reward accounts from peers"
1001 );
1002
1003 let missing_account_proofs = instance_state
1004 .state_catchup
1005 .fetch_reward_accounts_v1(
1006 instance_state,
1007 parent_height,
1008 parent_view,
1009 validated_state.reward_merkle_tree_v1.commitment(),
1010 missing_reward_accts,
1011 )
1012 .await?;
1013
1014 for proof in missing_account_proofs.iter() {
1015 proof
1016 .remember(&mut validated_state.reward_merkle_tree_v1)
1017 .expect("proof previously verified");
1018 }
1019 }
1020 } else {
1021 let reward_accounts = Arc::new(
1022 std::iter::once(validator.account.into())
1023 .chain(validator.delegators.keys().cloned().map(Into::into))
1024 .collect::<Vec<_>>(),
1025 );
1026
1027 let reward_merkle_tree_root = validated_state.reward_merkle_tree_v2.commitment();
1028 if forgotten_accounts_include(&validated_state.reward_merkle_tree_v2, &reward_accounts) {
1029 tracing::warn!(
1030 parent_height,
1031 ?parent_view,
1032 %reward_merkle_tree_root,
1033 "fetching reward merkle tree from peers"
1034 );
1035
1036 validated_state.reward_merkle_tree_v2 = instance_state
1037 .state_catchup
1038 .fetch_reward_merkle_tree_v2(
1039 parent_height,
1040 parent_view,
1041 reward_merkle_tree_root,
1042 reward_accounts,
1043 )
1044 .await?
1045 .tree;
1046
1047 tracing::warn!(
1048 parent_height,
1049 ?parent_view,
1050 %reward_merkle_tree_root,
1051 "successfully fetched reward merkle tree from peers"
1052 );
1053 }
1054 }
1055
1056 Ok(validator.clone())
1057}
1058
1059#[derive(Debug, Clone)]
1061pub struct EpochRewardsResult {
1062 pub epoch: EpochNumber,
1064 pub reward_tree: RewardMerkleTreeV2,
1066 pub total_distributed: RewardAmount,
1068 pub changed_accounts: HashSet<RewardAccountV2>,
1070}
1071
1072#[derive(Debug, Default)]
1075pub struct EpochRewardsCalculator {
1076 pending: Option<(EpochNumber, JoinHandle<anyhow::Result<EpochRewardsResult>>)>,
1078}
1079
1080impl EpochRewardsCalculator {
1081 pub fn new() -> Self {
1082 Self { pending: None }
1083 }
1084
1085 pub fn is_calculating(&self, epoch: EpochNumber) -> bool {
1087 self.pending.as_ref().is_some_and(|(e, _)| *e == epoch)
1088 }
1089
1090 pub async fn get_result(
1096 &mut self,
1097 epoch: EpochNumber,
1098 ) -> Option<anyhow::Result<EpochRewardsResult>> {
1099 let (pending_epoch, handle) = self.pending.take()?;
1100 if pending_epoch != epoch {
1101 self.pending = Some((pending_epoch, handle));
1103 return None;
1104 }
1105
1106 let result = match handle.await {
1107 Ok(Ok(result)) => {
1108 tracing::info!(%epoch, total = %result.total_distributed.0, "epoch rewards calculation completed");
1109 Ok(result)
1110 },
1111 Ok(Err(e)) => {
1112 tracing::error!(%epoch, error = %e, "epoch rewards calculation failed");
1113 Err(e)
1114 },
1115 Err(e) => {
1116 tracing::error!(%epoch, error = %e, "epoch rewards task panicked");
1117 Err(anyhow::Error::new(e).context("epoch rewards task panicked"))
1118 },
1119 };
1120 Some(result)
1121 }
1122
1123 pub fn spawn_background_task(
1127 &mut self,
1128 epoch: EpochNumber,
1129 epoch_height: u64,
1130 reward_tree: RewardMerkleTreeV2,
1131 instance_state: NodeState,
1132 coordinator: EpochMembershipCoordinator<SeqTypes>,
1133 leader_counts: Option<LeaderCounts>,
1134 ) {
1135 if self.is_calculating(epoch) {
1136 tracing::debug!(%epoch, "calculation already in progress, skipping");
1137 return;
1138 }
1139
1140 if let Some((stale_epoch, handle)) = self.pending.take() {
1142 tracing::info!(%stale_epoch, %epoch, "aborting stale epoch rewards task");
1143 handle.abort();
1144 }
1145
1146 tracing::info!(
1147 %epoch,
1148 has_leader_counts = leader_counts.is_some(),
1149 "starting background epoch rewards task"
1150 );
1151
1152 let handle = tokio::spawn(async move {
1153 Self::fetch_and_calculate(
1154 epoch,
1155 epoch_height,
1156 reward_tree,
1157 instance_state,
1158 coordinator,
1159 leader_counts,
1160 )
1161 .await
1162 });
1163 self.pending = Some((epoch, handle));
1164 }
1165
1166 async fn fetch_and_calculate(
1167 epoch: EpochNumber,
1168 epoch_height: u64,
1169 mut reward_tree: RewardMerkleTreeV2,
1170 instance_state: NodeState,
1171 coordinator: EpochMembershipCoordinator<SeqTypes>,
1172 leader_counts: Option<LeaderCounts>,
1173 ) -> anyhow::Result<EpochRewardsResult> {
1174 let epoch_last_block_height = (*epoch) * epoch_height;
1175
1176 tracing::info!(
1177 %epoch,
1178 epoch_last_block_height,
1179 has_leader_counts = leader_counts.is_some(),
1180 "fetch_and_calculate: starting"
1181 );
1182
1183 if let Err(err) = coordinator.membership_for_epoch(Some(epoch)) {
1185 tracing::info!(%epoch, "stake table missing for epoch, triggering catchup: {err:#}");
1186 coordinator
1187 .wait_for_catchup(epoch)
1188 .await
1189 .context(format!("failed to catch up for epoch={epoch}"))?;
1190 }
1191
1192 let leader_counts = if let Some(lc) = leader_counts {
1194 lc
1195 } else {
1196 let leaf = instance_state
1199 .state_catchup
1200 .as_ref()
1201 .fetch_leaf(coordinator.clone(), epoch_last_block_height)
1202 .await
1203 .with_context(|| {
1204 format!(
1205 "failed to fetch leaf at height {epoch_last_block_height} for epoch \
1206 {epoch}"
1207 )
1208 })?;
1209 let header = leaf.block_header();
1210
1211 tracing::info!(
1212 %epoch,
1213 header_height = header.height(),
1214 header_version = %header.version(),
1215 header_reward_merkle_tree_root = %header.reward_merkle_tree_root(),
1216 "fetch_and_calculate: fetched leaf"
1217 );
1218
1219 if header.version() < EPOCH_REWARD_VERSION {
1220 tracing::info!(
1221 %epoch,
1222 header_version = %header.version(),
1223 "no rewards to distribute"
1224 );
1225 return Ok(EpochRewardsResult {
1226 epoch,
1227 reward_tree,
1228 total_distributed: RewardAmount::default(),
1229 changed_accounts: HashSet::new(),
1230 });
1231 }
1232
1233 let expected_root = header.reward_merkle_tree_root().right();
1239 let actual_root = reward_tree.commitment();
1240 if expected_root != Some(actual_root) {
1241 tracing::warn!(
1242 %epoch,
1243 ?expected_root,
1244 ?actual_root,
1245 "reward merkle tree root mismatch, using empty tree"
1246 );
1247 reward_tree = RewardMerkleTreeV2::new(REWARD_MERKLE_TREE_V2_HEIGHT);
1248 }
1249
1250 *header
1251 .leader_counts()
1252 .expect("V6+ header must have leader_counts")
1253 };
1254
1255 let snapshot = coordinator
1256 .membership()
1257 .snapshot(epoch)
1258 .with_context(|| format!("no committee for epoch={epoch}"))?;
1259 let validator_leader_counts = ValidatorLeaderCounts::new(&snapshot, leader_counts)?;
1260 let block_reward = snapshot
1261 .epoch_block_reward()
1262 .context("block reward not found for epoch")?;
1263
1264 tracing::info!(
1265 %epoch,
1266 %block_reward,
1267 "fetch_and_calculate: got block_reward"
1268 );
1269
1270 let accounts_to_update = validator_leader_counts.all_reward_accounts();
1272
1273 let missing_accounts: Vec<_> = accounts_to_update
1274 .iter()
1275 .filter(|account| reward_tree.lookup(**account).expect_not_in_memory().is_ok())
1276 .cloned()
1277 .collect();
1278
1279 if !missing_accounts.is_empty() {
1282 tracing::info!(
1283 %epoch,
1284 num_missing = missing_accounts.len(),
1285 "missing accounts detected, fetching reward merkle tree from peers"
1286 );
1287
1288 let reward_merkle_tree_root = reward_tree.commitment();
1289 reward_tree = instance_state
1290 .state_catchup
1291 .as_ref()
1292 .fetch_reward_merkle_tree_v2(
1293 epoch_last_block_height,
1294 ViewNumber::new(0),
1295 reward_merkle_tree_root,
1296 Arc::new(missing_accounts),
1297 )
1298 .await
1299 .with_context(|| {
1300 format!(
1301 "failed to fetch reward merkle tree at height {epoch_last_block_height} \
1302 for epoch {epoch}"
1303 )
1304 })?
1305 .tree;
1306
1307 tracing::info!(
1308 %epoch,
1309 reward_tree_commitment = %reward_tree.commitment(),
1310 "reward tree fetched successfully"
1311 );
1312 }
1313
1314 tracing::info!(
1315 %epoch,
1316 reward_tree_commitment = %reward_tree.commitment(),
1317 "starting final epoch calculation"
1318 );
1319
1320 Self::calculate_all_rewards(epoch, validator_leader_counts, reward_tree, block_reward).await
1321 }
1322
1323 async fn calculate_all_rewards(
1325 epoch: EpochNumber,
1326 validator_leader_counts: ValidatorLeaderCounts,
1327 mut reward_tree: RewardMerkleTreeV2,
1328 block_reward: RewardAmount,
1329 ) -> anyhow::Result<EpochRewardsResult> {
1330 let mut total_distributed = U256::ZERO;
1331 let mut changed_accounts = HashSet::new();
1332
1333 for (validator, count) in validator_leader_counts.active_leaders() {
1334 let validator_reward = block_reward
1336 .0
1337 .checked_mul(U256::from(count))
1338 .context("overflow in validator reward calculation")?;
1339
1340 if validator_reward.is_zero() {
1341 continue;
1342 }
1343
1344 changed_accounts.insert(RewardAccountV2(validator.account));
1345 changed_accounts.extend(validator.delegators.keys().map(|d| RewardAccountV2(*d)));
1346
1347 let distributor = RewardDistributor::new(
1348 validator.clone(),
1349 RewardAmount(validator_reward),
1350 Default::default(),
1351 );
1352
1353 let computed_rewards = distributor.compute_rewards()?;
1354
1355 for (address, reward) in computed_rewards.all_rewards() {
1356 RewardDistributor::update_reward_balance(
1357 &mut reward_tree,
1358 &RewardAccountV2(address),
1359 reward,
1360 )?;
1361 tracing::debug!(%epoch, %address, %reward, "applied epoch reward");
1362 }
1363
1364 total_distributed += validator_reward;
1365 }
1366
1367 tracing::info!(
1368 %epoch,
1369 total_distributed = %total_distributed,
1370 num_changed_accounts = changed_accounts.len(),
1371 "epoch rewards calculation complete"
1372 );
1373
1374 Ok(EpochRewardsResult {
1375 epoch,
1376 reward_tree,
1377 total_distributed: RewardAmount(total_distributed),
1378 changed_accounts,
1379 })
1380 }
1381}
1382
1383#[cfg(test)]
1384pub mod tests {
1385
1386 use super::*;
1387
1388 fn make_distributor(commission: u16) -> RewardDistributor {
1389 RewardDistributor::new(
1390 AuthenticatedValidator::mock_with_commission(commission),
1391 RewardAmount(U256::from(1902000000000000000_u128)),
1392 U256::ZERO.into(),
1393 )
1394 }
1395
1396 fn total_rewards(rewards: ComputedRewards) -> U256 {
1397 rewards
1398 .all_rewards()
1399 .iter()
1400 .fold(U256::ZERO, |acc, (_, r)| acc + r.0)
1401 }
1402
1403 #[test]
1406 fn test_reward_calculation_sanity_checks() {
1407 let distributor = make_distributor(500);
1412 let rewards = distributor.compute_rewards().unwrap();
1413 assert_eq!(total_rewards(rewards.clone()), distributor.block_reward.0);
1414
1415 let distributor = make_distributor(0);
1416 let rewards = distributor.compute_rewards().unwrap();
1417 assert_eq!(total_rewards(rewards.clone()), distributor.block_reward.0);
1418
1419 let distributor = make_distributor(10000);
1420 let rewards = distributor.compute_rewards().unwrap();
1421 assert_eq!(total_rewards(rewards.clone()), distributor.block_reward.0);
1422 let leader_commission = rewards.leader_commission();
1423 assert_eq!(*leader_commission, distributor.block_reward);
1424
1425 let distributor = make_distributor(10001);
1426 assert!(
1427 distributor
1428 .compute_rewards()
1429 .err()
1430 .unwrap()
1431 .to_string()
1432 .contains("must not exceed")
1433 );
1434 }
1435
1436 #[test]
1437 fn test_compute_rewards_validator_commission() {
1438 let distributor = make_distributor(0);
1439 let rewards = distributor.compute_rewards().unwrap();
1440 let leader_commission = rewards.leader_commission();
1441 let percentage =
1442 leader_commission.0 * U256::from(COMMISSION_BASIS_POINTS) / distributor.block_reward.0;
1443 assert_eq!(percentage, U256::ZERO);
1444
1445 let distributor = make_distributor(300);
1447 let rewards = distributor.compute_rewards().unwrap();
1448 let leader_commission = rewards.leader_commission();
1449 let percentage =
1450 leader_commission.0 * U256::from(COMMISSION_BASIS_POINTS) / distributor.block_reward.0;
1451 println!("percentage: {percentage:?}");
1452 assert_eq!(percentage, U256::from(300));
1453
1454 let distributor = make_distributor(10000);
1456 let rewards = distributor.compute_rewards().unwrap();
1457 let leader_commission = rewards.leader_commission();
1458 assert_eq!(*leader_commission, distributor.block_reward);
1459 }
1460}