rewards.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 use alphavm_ledger_committee::{Committee, MIN_DELEGATOR_STAKE}; 17 use console::{account::Address, network::prelude::*}; 18 19 use indexmap::IndexMap; 20 use std::sync::atomic::AtomicU64; 21 22 #[cfg(feature = "locktick")] 23 use locktick::parking_lot::Mutex; 24 #[cfg(not(feature = "locktick"))] 25 use parking_lot::Mutex; 26 #[cfg(not(feature = "serial"))] 27 use rayon::prelude::*; 28 29 /// A safety bound (sanity-check) for the coinbase reward. 30 const MAX_COINBASE_REWARD: u64 = alphavm_ledger_block::MAX_COINBASE_REWARD; // Coinbase reward at block 1. 31 32 /// Returns the updated stakers reflecting the staking rewards for the given committee, block reward, 33 /// and validator commission rates. 34 /// 35 /// The staking reward for validators is defined as: `block_reward * stake / total_stake + commission_to_recieve`. 36 /// The commission to receive for validators is defined as: `block_reward * (total_stake_delegated / total_stake) * (rate / 100)`. 37 /// 38 /// The staking reward for delegators is defined as: `block_reward * stake / total_stake - commission_to_pay`. 39 /// The commission to pay for delegators is defined as: `block_reward * (stake / total_stake) * (rate / 100)` 40 /// 41 /// This method ensures that stakers who are bonded to validators with more than **25%** 42 /// of the total stake will not receive a staking reward. In addition, this method 43 /// ensures delegators who have less than 10,000 credits are not eligible for a staking reward. 44 /// 45 /// The choice of 25% is to ensure at least 4 validators are operational at any given time. 46 /// Our security model tolerates Byzantines behavior by validators staking up to f stake, 47 /// where f = max{m: integer | m < N/3}, N being the total amount staked. 48 /// Therefore, 1 Byzantine validator out of 4 equal-staked validators will be tolerated. 49 pub fn staking_rewards<N: Network>( 50 stakers: &IndexMap<Address<N>, (Address<N>, u64)>, 51 committee: &Committee<N>, 52 block_reward: u64, 53 ) -> IndexMap<Address<N>, (Address<N>, u64)> { 54 // If the list of stakers is empty, there is no stake, or the block reward is 0, return the stakers. 55 if stakers.is_empty() || committee.total_stake() == 0 || block_reward == 0 { 56 return stakers.clone(); 57 } 58 59 // First, validate all validators and create a map of valid validators. 60 let valid_validators: IndexMap<_, _> = committee 61 .members() 62 .iter() 63 .filter_map(|(validator, (validator_stake, _is_open, commission_rate))| { 64 // Skip validators with invalid commission rates. 65 if *commission_rate > 100 { 66 error!("Commission rate ({commission_rate}) is greater than 100 - skipping validator {validator}"); 67 return None; 68 } 69 // Skip validators with more than 25% of total stake. 70 if *validator_stake > committee.total_stake().saturating_div(4) { 71 trace!("Validator {validator} has more than 25% of the total stake - skipping all stakers"); 72 return None; 73 } 74 Some((*validator, (*validator_stake, *commission_rate))) 75 }) 76 .collect(); 77 78 // Track validators not in committee as well as their stake. 79 // Pre-allocating with an expected capacity prevents reallocation while the mutex is held. 80 let hashset_capacity = committee.members().len(); 81 let missing_validators = Mutex::new(std::collections::HashSet::<Address<N>>::with_capacity(hashset_capacity)); 82 let invalid_validator_stake = AtomicU64::new(0); 83 84 // Compute the updated stakers. 85 let staking_rewards = cfg_iter!(stakers) 86 .map(|(staker, (validator, stake))| { 87 // If the validator is not in the valid validators list, skip the staker. 88 let Some((validator_stake, commission_rate)) = valid_validators.get(validator) else { 89 // Log validator not in committee. 90 if tracing::enabled!(tracing::Level::TRACE) && !committee.members().contains_key(validator) { 91 let mut logged = missing_validators.lock(); 92 if logged.insert(*validator) { 93 trace!("Validator {validator} is not in the committee - skipping all stakers"); 94 } 95 invalid_validator_stake.fetch_add(*stake, std::sync::atomic::Ordering::Relaxed); 96 } 97 return (*staker, (*validator, *stake)); 98 }; 99 100 // If the staker has less than the minimum required stake, skip the staker, unless the staker is the validator. 101 if *stake < MIN_DELEGATOR_STAKE && *staker != *validator { 102 trace!("Staker has less than {MIN_DELEGATOR_STAKE} microcredits - skipping {staker}"); 103 return (*staker, (*validator, *stake)); 104 } 105 106 // Compute the numerator. 107 let numerator = (block_reward as u128).saturating_mul(*stake as u128); 108 // Compute the denominator. 109 // Note: We guarantee this denominator cannot be 0 (as we return early if the total stake is 0). 110 let denominator = committee.total_stake() as u128; 111 // Compute the quotient. 112 let quotient = numerator.saturating_div(denominator); 113 // Ensure the staking reward is within a safe bound. 114 if quotient > MAX_COINBASE_REWARD as u128 { 115 error!("Staking reward ({quotient}) is too large - skipping {staker}"); 116 return (*staker, (*validator, *stake)); 117 } 118 // Cast the staking reward as a u64. 119 // Note: This '.expect' is guaranteed to be safe, as we ensure the quotient is within a safe bound. 120 let staking_reward = u64::try_from(quotient).expect("Staking reward is too large"); 121 122 // Update the staking reward with the commission. 123 // 124 // Note: This approach to computing commissions is far more computationally-efficient, 125 // however it does introduce a small (deterministic) precision error that is accepted for the 126 // sake of performance. There is a negligible difference (at most 100 microcredits per delegator) 127 // between the validators (+) and the delegators (-) in the allocated commission difference. 128 let staking_reward_after_commission = match staker == validator { 129 // If the staker is the validator, add the total commission to the staking reward. 130 true => { 131 // Calculate the total stake delegated to the validator. 132 let total_delegated_stake = validator_stake.saturating_sub(*stake); 133 // Compute the numerator. 134 let numerator = (block_reward as u128).saturating_mul(total_delegated_stake as u128); 135 // Compute the quotient. This quotient is the total staking reward recieved by delegators. 136 let quotient = numerator.saturating_div(denominator); 137 // Compute the commission. 138 let total_commission_to_receive = 139 quotient.saturating_mul(*commission_rate as u128).saturating_div(100u128); 140 // Cast the commission as a u64. 141 // Note: This '.expect' is guaranteed to be safe, as we ensure the commission is within a safe bound. 142 let total_commission_to_receive = 143 u64::try_from(total_commission_to_receive).expect("Commission is too large"); 144 145 // Add the commission to the validator staking reward. 146 staking_reward.saturating_add(total_commission_to_receive) 147 } 148 // If the staker is a delegator, subtract the commission from the staking reward. 149 false => { 150 // Calculate the commission. 151 let commission = quotient.saturating_mul(*commission_rate as u128).saturating_div(100u128); 152 // Cast the commission as a u64. 153 // Note: This '.expect' is guaranteed to be safe, as we ensure the quotient is within a safe bound. 154 let commission_to_pay = u64::try_from(commission).expect("Commission is too large"); 155 156 // Subtract the commission from the delegator staking reward. 157 staking_reward.saturating_sub(commission_to_pay) 158 } 159 }; 160 // Return the staker and the updated stake. 161 (*staker, (*validator, stake.saturating_add(staking_reward_after_commission))) 162 }) 163 .collect(); 164 165 if tracing::enabled!(tracing::Level::TRACE) { 166 let invalid_validator_stake = invalid_validator_stake.load(std::sync::atomic::Ordering::Relaxed); 167 trace!("Total stake invalidated due to validator not in committee: {invalid_validator_stake}"); 168 } 169 170 // Return the result. 171 staking_rewards 172 } 173 174 /// Returns the proving rewards for a given coinbase reward and list of prover solutions. 175 /// The prover reward is defined as: `puzzle_reward * (proof_target / combined_proof_target)`. 176 pub fn proving_rewards<N: Network>( 177 proof_targets: Vec<(Address<N>, u64)>, 178 puzzle_reward: u64, 179 ) -> IndexMap<Address<N>, u64> { 180 // Compute the combined proof target. Using '.sum' here is safe because we sum u64s into a u128. 181 let combined_proof_target = proof_targets.iter().map(|(_, t)| *t as u128).sum::<u128>(); 182 183 // If the list of solutions is empty, the combined proof target is 0, or the puzzle reward is 0, return an empty map. 184 if proof_targets.is_empty() || combined_proof_target == 0 || puzzle_reward == 0 { 185 return Default::default(); 186 } 187 188 // Initialize a vector to store the proving rewards. 189 let mut rewards = IndexMap::<_, u64>::with_capacity(proof_targets.len()); 190 191 // Calculate the rewards for the individual provers. 192 for (address, proof_target) in proof_targets { 193 // Compute the numerator. 194 let numerator = (puzzle_reward as u128).saturating_mul(proof_target as u128); 195 // Compute the denominator. 196 // Note: We guarantee this denominator cannot be 0 (to prevent a div by 0). 197 let denominator = combined_proof_target.max(1); 198 // Compute the quotient. 199 let quotient = numerator.saturating_div(denominator); 200 // Ensure the proving reward is within a safe bound. 201 if quotient > MAX_COINBASE_REWARD as u128 { 202 error!("Prover reward ({quotient}) is too large - skipping solution from {address}"); 203 continue; 204 } 205 // Cast the proving reward as a u64. 206 // Note: This '.expect' is guaranteed to be safe, as we ensure the quotient is within a safe bound. 207 let prover_reward = u64::try_from(quotient).expect("Prover reward is too large"); 208 // If there is a proving reward, append it to the vector. 209 if prover_reward > 0 { 210 // Add the proving reward to the prover. 211 let entry = rewards.entry(address).or_default(); 212 *entry = entry.saturating_add(prover_reward); 213 } 214 } 215 216 // Return the proving rewards. 217 rewards 218 } 219 220 #[cfg(test)] 221 mod tests { 222 use super::*; 223 use console::prelude::TestRng; 224 225 use indexmap::indexmap; 226 227 type CurrentNetwork = console::network::MainnetV0; 228 229 const ITERATIONS: usize = 1000; 230 231 #[test] 232 fn test_staking_rewards() { 233 let rng = &mut TestRng::default(); 234 // Sample a random committee. 235 let committee = alphavm_ledger_committee::test_helpers::sample_committee_with_commissions(rng); 236 // Sample a random block reward. 237 let block_reward = rng.gen_range(0..MAX_COINBASE_REWARD); 238 // Retrieve an address. 239 let address = *committee.members().iter().next().unwrap().0; 240 241 for _ in 0..ITERATIONS { 242 // Sample a random stake. 243 let stake = rng.gen_range(MIN_DELEGATOR_STAKE..committee.total_stake()); 244 // Construct the stakers. 245 let stakers = indexmap! {address => (address, stake)}; 246 let next_stakers = staking_rewards::<CurrentNetwork>(&stakers, &committee, block_reward); 247 assert_eq!(next_stakers.len(), 1); 248 let (candidate_address, (candidate_validator, candidate_stake)) = next_stakers.into_iter().next().unwrap(); 249 assert_eq!(candidate_address, address); 250 assert_eq!(candidate_validator, address); 251 let reward = block_reward as u128 * stake as u128 / committee.total_stake() as u128; 252 assert_eq!(candidate_stake, stake + u64::try_from(reward).unwrap(), "stake: {stake}, reward: {reward}"); 253 } 254 } 255 256 #[test] 257 fn test_staking_rewards_to_validator_not_in_committee() { 258 let rng = &mut TestRng::default(); 259 // Sample a random committee. 260 let committee = alphavm_ledger_committee::test_helpers::sample_committee(rng); 261 let fake_committee = alphavm_ledger_committee::test_helpers::sample_committee(rng); 262 // Sample a random block reward. 263 let block_reward = rng.gen_range(0..MAX_COINBASE_REWARD); 264 265 // Generate the stakers 266 let stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng); 267 // Generate stakers for a non-existent committee, to ensure they are not rewarded. 268 let stakers_fake = crate::committee::test_helpers::to_stakers(fake_committee.members(), rng); 269 let all_stakers: IndexMap<Address<CurrentNetwork>, (Address<CurrentNetwork>, u64)> = 270 stakers.clone().into_iter().chain(stakers_fake.clone()).collect(); 271 272 // Start a timer. 273 let timer = std::time::Instant::now(); 274 275 let next_stakers = staking_rewards::<CurrentNetwork>(&all_stakers, &committee, block_reward); 276 println!("staking_rewards: {}ms", timer.elapsed().as_millis()); 277 assert_eq!(next_stakers.len(), all_stakers.len()); 278 for ((staker, (validator, stake)), (next_staker, (next_validator, next_stake))) in 279 all_stakers.into_iter().zip(next_stakers.into_iter()) 280 { 281 assert_eq!(staker, next_staker); 282 assert_eq!(validator, next_validator); 283 // If the validator is not in the committee, the stake should not change. 284 if !committee.members().contains_key(&validator) { 285 assert_eq!(stake, next_stake, "stake: {stake}, reward should be 0"); 286 } else { 287 // Otherwise, the stake should increase. 288 let reward = block_reward as u128 * stake as u128 / committee.total_stake() as u128; 289 assert_eq!(stake + u64::try_from(reward).unwrap(), next_stake, "stake: {stake}, reward: {reward}"); 290 } 291 } 292 } 293 294 #[test] 295 fn test_staking_rewards_commission() { 296 let rng = &mut TestRng::default(); 297 // Sample a random committee. 298 let committee = alphavm_ledger_committee::test_helpers::sample_committee_with_commissions(rng); 299 // Convert the committee into stakers. 300 let stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng); 301 // Sample a random block reward. 302 let block_reward = rng.gen_range(0..MAX_COINBASE_REWARD); 303 // Create a map of validators to commissions 304 let commissions: IndexMap<Address<CurrentNetwork>, u8> = 305 committee.members().iter().map(|(address, (_, _, commission))| (*address, *commission)).collect(); 306 // Print the commissions from the indexmap 307 println!("commissions: {commissions:?}"); 308 // Create a map of validators to the sum of their commissions 309 let mut total_commissions: IndexMap<Address<CurrentNetwork>, u64> = Default::default(); 310 311 // Start a timer. 312 let timer = std::time::Instant::now(); 313 // Compute the staking rewards. 314 let next_stakers = staking_rewards::<CurrentNetwork>(&stakers, &committee, block_reward); 315 println!("staking_rewards: {}ms", timer.elapsed().as_millis()); 316 assert_eq!(next_stakers.len(), stakers.len()); 317 for ((staker, (validator, stake)), (next_staker, (next_validator, next_stake))) in 318 stakers.clone().into_iter().zip(next_stakers.clone().into_iter()) 319 { 320 assert_eq!(staker, next_staker); 321 assert_eq!(validator, next_validator); 322 323 let commission_rate = commissions.get(&validator).copied().unwrap_or(0); 324 325 if staker == validator { 326 // Calculate the total stake delegated to the validator. 327 let total_stake = committee.get_stake(validator); 328 let total_delegated_stake = total_stake.saturating_sub(stake); 329 330 // Calculate the commission to receive. 331 let reward = block_reward as u128 * total_delegated_stake as u128 / committee.total_stake() as u128; 332 let commission_to_receive = reward * commission_rate as u128 / 100; 333 334 total_commissions.insert(validator, u64::try_from(commission_to_receive).unwrap()); 335 assert_eq!( 336 stake + u64::try_from(reward + commission_to_receive).unwrap(), 337 next_stake, 338 "stake: {stake}, reward: {reward}, commission_to_receive: {commission_to_receive}, commission_rate: {commission_rate}" 339 ); 340 } else { 341 // Calculate the commission to pay the validator. 342 let reward = block_reward as u128 * stake as u128 / committee.total_stake() as u128; 343 let commission_to_pay = reward * commission_rate as u128 / 100; 344 345 assert_eq!( 346 stake + u64::try_from(reward - commission_to_pay).unwrap(), 347 next_stake, 348 "stake: {stake}, reward: {reward}, commission_to_pay: {commission_to_pay}, commission_rate: {commission_rate}" 349 ); 350 } 351 } 352 353 assert_eq!( 354 total_commissions.len(), 355 committee.members().len(), 356 "total_commissions.len() != committee.members().len()" 357 ); 358 359 // For each staker that is a validator, ensure the next staker = reward + commission. 360 for (validator, commission) in total_commissions { 361 let (_, stake) = stakers.get(&validator).unwrap(); 362 let (_, next_stake) = next_stakers.get(&validator).unwrap(); 363 let reward = block_reward as u128 * *stake as u128 / committee.total_stake() as u128; 364 let expected_stake = stake + commission + u64::try_from(reward).unwrap(); 365 assert_eq!(*next_stake, expected_stake, "stake: {stake}, commission: {commission}"); 366 } 367 } 368 369 #[test] 370 fn test_staking_rewards_large() { 371 let rng = &mut TestRng::default(); 372 373 // Sample a random block reward. 374 let block_reward = rng.gen_range(0..MAX_COINBASE_REWARD); 375 // Sample a committee. 376 let committee = alphavm_ledger_committee::test_helpers::sample_committee_for_round_and_size(1, 25, rng); 377 // Convert the committee into stakers. 378 let stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng); 379 380 // Start a timer. 381 let timer = std::time::Instant::now(); 382 // Compute the staking rewards. 383 let next_stakers = staking_rewards::<CurrentNetwork>(&stakers, &committee, block_reward); 384 println!("staking_rewards: {}ms", timer.elapsed().as_millis()); 385 assert_eq!(next_stakers.len(), stakers.len()); 386 for ((staker, (validator, stake)), (next_staker, (next_validator, next_stake))) in 387 stakers.into_iter().zip(next_stakers.into_iter()) 388 { 389 assert_eq!(staker, next_staker); 390 assert_eq!(validator, next_validator); 391 let reward = block_reward as u128 * stake as u128 / committee.total_stake() as u128; 392 assert_eq!(stake + u64::try_from(reward).unwrap(), next_stake, "stake: {stake}, reward: {reward}"); 393 } 394 } 395 396 #[test] 397 fn test_staking_rewards_when_delegator_is_under_min_yields_no_reward() { 398 let rng = &mut TestRng::default(); 399 // Sample a random committee. 400 let committee = alphavm_ledger_committee::test_helpers::sample_committee(rng); 401 // Convert the committee into stakers. 402 let stakers = crate::committee::test_helpers::to_stakers(committee.members(), rng); 403 // Sample a random block reward. 404 let block_reward = rng.gen_range(0..MAX_COINBASE_REWARD); 405 // Retrieve an address of a delegator that isn't a validator 406 let address = *stakers.iter().find(|(address, _)| !committee.is_committee_member(**address)).unwrap().0; 407 408 for _ in 0..ITERATIONS { 409 // Sample a random stake. 410 let stake = rng.gen_range(0..MIN_DELEGATOR_STAKE); 411 // Construct the stakers. 412 let stakers = indexmap! {address => (address, stake)}; 413 let next_stakers = staking_rewards::<CurrentNetwork>(&stakers, &committee, block_reward); 414 assert_eq!(next_stakers.len(), 1); 415 let (candidate_address, (candidate_validator, candidate_stake)) = next_stakers.into_iter().next().unwrap(); 416 assert_eq!(candidate_address, address); 417 assert_eq!(candidate_validator, address); 418 assert_eq!(candidate_stake, stake); 419 } 420 } 421 422 #[test] 423 fn test_staking_rewards_cannot_exceed_coinbase_reward() { 424 let rng = &mut TestRng::default(); 425 // Sample a random committee. 426 let committee = alphavm_ledger_committee::test_helpers::sample_committee(rng); 427 // Retrieve an address. 428 let address = *committee.members().iter().next().unwrap().0; 429 430 // Construct the stakers. 431 let stakers = indexmap![address => (address, MIN_DELEGATOR_STAKE)]; 432 // Check that a maxed out coinbase reward, returns empty. 433 let next_stakers = staking_rewards::<CurrentNetwork>(&stakers, &committee, u64::MAX); 434 assert_eq!(stakers, next_stakers); 435 436 // Ensure a staking reward that is too large, renders no rewards. 437 for _ in 0..ITERATIONS { 438 // Sample a random overly-large block reward. 439 let block_reward = rng.gen_range(MAX_COINBASE_REWARD..u64::MAX); 440 // Sample a random stake. 441 let stake = rng.gen_range(MIN_DELEGATOR_STAKE..u64::MAX); 442 // Construct the stakers. 443 let stakers = indexmap![address => (address, stake)]; 444 // Check that an overly large block reward fails. 445 let next_stakers = staking_rewards::<CurrentNetwork>(&stakers, &committee, block_reward); 446 assert_eq!(stakers, next_stakers); 447 } 448 } 449 450 #[test] 451 fn test_staking_rewards_is_empty() { 452 let rng = &mut TestRng::default(); 453 // Sample a random committee. 454 let committee = alphavm_ledger_committee::test_helpers::sample_committee(rng); 455 456 // Compute the staking rewards (empty). 457 let rewards = staking_rewards::<CurrentNetwork>(&indexmap![], &committee, rng.r#gen()); 458 assert!(rewards.is_empty()); 459 } 460 461 #[test] 462 fn test_proving_rewards() { 463 let rng = &mut TestRng::default(); 464 465 for _ in 0..ITERATIONS { 466 // Sample a random address. 467 let address = Address::rand(rng); 468 // Sample a random puzzle reward. 469 let puzzle_reward = rng.gen_range(0..MAX_COINBASE_REWARD); 470 471 let rewards = proving_rewards::<CurrentNetwork>(vec![(address, u64::MAX)], puzzle_reward); 472 assert_eq!(rewards.len(), 1); 473 let (candidate_address, candidate_amount) = rewards.into_iter().next().unwrap(); 474 assert_eq!(candidate_address, address); 475 assert!(candidate_amount <= puzzle_reward); 476 } 477 } 478 479 #[test] 480 fn test_proving_rewards_cannot_exceed_coinbase_reward() { 481 let rng = &mut TestRng::default(); 482 483 // Ensure a proving reward that is too large, renders no rewards. 484 for _ in 0..ITERATIONS { 485 // Sample a random address. 486 let address = Address::rand(rng); 487 // Sample a random overly-large puzzle reward. 488 let puzzle_reward = rng.gen_range(MAX_COINBASE_REWARD..u64::MAX); 489 // Sample a random proof target. 490 let proof_target = rng.gen_range(0..u64::MAX); 491 // Check that a maxed out proof target fails. 492 let rewards = proving_rewards::<CurrentNetwork>(vec![(address, proof_target)], puzzle_reward); 493 assert!(rewards.is_empty()); 494 } 495 } 496 497 #[test] 498 fn test_proving_rewards_is_empty() { 499 let rng = &mut TestRng::default(); 500 // Sample a random address. 501 let address = Address::rand(rng); 502 503 // Compute the proving rewards (empty). 504 let rewards = proving_rewards::<CurrentNetwork>(vec![], rng.r#gen()); 505 assert!(rewards.is_empty()); 506 507 // Check that a maxed out coinbase reward, returns empty. 508 let rewards = proving_rewards::<CurrentNetwork>(vec![(address, 2)], u64::MAX); 509 assert!(rewards.is_empty()); 510 511 // Ensure a 0 coinbase reward case is empty. 512 let rewards = proving_rewards::<CurrentNetwork>(vec![(address, 2)], 0); 513 assert!(rewards.is_empty()); 514 } 515 }