use super::{AccountAdded, AccountId, AccumulationEvent, RewardsAccumulated, RewardsClaimed};
use sn_data_types::{Error, Money, Result, RewardCounter, Work};
use std::collections::{HashMap, HashSet};
#[derive(Clone)]
pub struct Accumulation {
idempotency: HashSet<Id>,
accumulated: HashMap<AccountId, RewardCounter>,
}
pub type Id = Vec<u8>;
impl Accumulation {
pub fn new(idempotency: HashSet<Id>, accumulated: HashMap<AccountId, RewardCounter>) -> Self {
Self {
idempotency,
accumulated,
}
}
pub fn get(&self, account: &AccountId) -> Option<&RewardCounter> {
self.accumulated.get(account)
}
pub fn get_all(&self) -> &HashMap<AccountId, RewardCounter> {
&self.accumulated
}
pub fn add_account(&self, id: AccountId, work: Work) -> Result<AccountAdded> {
if self.accumulated.contains_key(&id) {
return Err(Error::BalanceExists);
}
Ok(AccountAdded { id, work })
}
pub fn accumulate(
&self,
id: Id,
distribution: HashMap<AccountId, Money>,
) -> Result<RewardsAccumulated> {
if self.idempotency.contains(&id) {
return Err(Error::DataExists);
}
for (id, amount) in &distribution {
if let Some(existing) = self.accumulated.get(&id) {
if existing.add(*amount).is_none() {
return Err(Error::ExcessiveValue);
}
};
}
Ok(RewardsAccumulated { id, distribution })
}
pub fn claim(&self, account: AccountId) -> Result<RewardsClaimed> {
let result = self.accumulated.get(&account);
match result {
None => Err(Error::NoSuchKey),
Some(rewards) => Ok(RewardsClaimed {
account,
rewards: rewards.clone(),
}),
}
}
pub fn apply(&mut self, event: AccumulationEvent) {
use AccumulationEvent::*;
match event {
AccountAdded(e) => {
let _ = self.accumulated.insert(
e.id,
RewardCounter {
reward: Money::zero(),
work: e.work,
},
);
}
RewardsAccumulated(e) => {
for (id, amount) in e.distribution {
let existing = match self.accumulated.get(&id) {
None => Default::default(),
Some(acc) => acc.clone(),
};
let accumulated = existing.add(amount).unwrap(); let _ = self.idempotency.insert(e.id.clone());
let _ = self.accumulated.insert(id, accumulated);
}
}
RewardsClaimed(e) => {
let _ = self.accumulated.remove(&e.account);
}
}
}
}
#[cfg(test)]
mod test {
use super::{Accumulation, AccumulationEvent};
use sn_data_types::{Error, Money, PublicKey};
use threshold_crypto::SecretKey;
macro_rules! hashmap {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( let _ = map.insert($key, $val); )*
map
}}
}
#[test]
fn when_data_was_not_previously_rewarded_reward_accumulates() -> Result<(), Error> {
let mut acc = Accumulation::new(Default::default(), Default::default());
let account = get_random_pk();
let data_hash = vec![1, 2, 3];
let reward = Money::from_nano(10);
let distribution = hashmap![account => reward];
let e = acc.accumulate(data_hash, distribution)?;
assert!(e.distribution.len() == 1);
assert!(e.distribution.contains_key(&account));
assert_eq!(&reward, e.distribution.get(&account).unwrap());
acc.apply(AccumulationEvent::RewardsAccumulated(e));
if let Some(accumulated) = acc.get(&account) {
assert_eq!(accumulated.reward, reward);
}
Ok(())
}
#[test]
fn when_data_is_already_rewarded_accumulation_is_rejected() -> Result<(), Error> {
let mut acc = Accumulation::new(Default::default(), Default::default());
let account = get_random_pk();
let data_hash = vec![1, 2, 3];
let reward = Money::from_nano(10);
let distribution = hashmap![account => reward];
let reward = acc.accumulate(data_hash.clone(), distribution.clone())?;
acc.apply(AccumulationEvent::RewardsAccumulated(reward));
assert_eq!(
acc.accumulate(data_hash, distribution),
Err(Error::DataExists)
);
Ok(())
}
#[test]
fn when_account_has_reward_it_can_claim() -> Result<(), Error> {
let mut acc = Accumulation::new(Default::default(), Default::default());
let account = get_random_pk();
let data_hash = vec![1, 2, 3];
let reward = Money::from_nano(10);
let distribution = hashmap![account => reward];
let accumulation = acc.accumulate(data_hash, distribution)?;
acc.apply(AccumulationEvent::RewardsAccumulated(accumulation));
let e = acc.claim(account)?;
assert!(e.account == account);
assert!(e.rewards.reward == reward);
acc.apply(AccumulationEvent::RewardsClaimed(e));
Ok(())
}
#[test]
fn when_reward_was_claimed_it_can_not_be_claimed_again() {
let mut acc = Accumulation::new(Default::default(), Default::default());
let account = get_random_pk();
let data_hash = vec![1, 2, 3];
let reward = Money::from_nano(10);
let distribution = hashmap![account => reward];
let accumulation = acc.accumulate(data_hash, distribution).unwrap();
acc.apply(AccumulationEvent::RewardsAccumulated(accumulation));
let claim = acc.claim(account).unwrap();
acc.apply(AccumulationEvent::RewardsClaimed(claim));
let result = acc.claim(account);
assert_eq!(result, Err(Error::NoSuchKey))
}
#[test]
fn when_account_has_no_reward_it_can_not_claim() {
let acc = Accumulation::new(Default::default(), Default::default());
let account = get_random_pk();
let result = acc.claim(account);
match result {
Ok(_) => panic!(),
Err(err) => assert_eq!(err, Error::NoSuchKey),
}
}
#[test]
fn when_reward_was_claimed_get_returns_none() {
let mut acc = Accumulation::new(Default::default(), Default::default());
let account = get_random_pk();
let data_hash = vec![1, 2, 3];
let reward = Money::from_nano(10);
let distribution = hashmap![account => reward];
let accumulation = acc.accumulate(data_hash, distribution).unwrap();
acc.apply(AccumulationEvent::RewardsAccumulated(accumulation));
let claim = acc.claim(account).unwrap();
acc.apply(AccumulationEvent::RewardsClaimed(claim));
let result = acc.get(&account);
assert!(result.is_none());
}
fn get_random_pk() -> PublicKey {
PublicKey::from(SecretKey::random().public_key())
}
}