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 if let Some(s) = s.strip_prefix("0x") {
90 return Ok(Self(s.parse()?));
91 }
92
93 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 pub fn address(&self) -> Address {
124 self.0
125 }
126 pub fn as_bytes(&self) -> &[u8] {
128 self.0.as_slice()
129 }
130 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 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}
393pub 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 #[test]
495 fn test_reward_calculation_sanity_checks() {
496 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}