use crate::core::block::feijoada::{
get_bottles_default, next_block_bottles, Deterministic, Feijoada, Policy,
};
use crate::core::block::HeaderVersion;
use crate::core::hash::{Hash, ZERO_HASH};
use crate::global;
use crate::pow::{Difficulty, PoWType};
use std::cmp::{max, min};
pub const EPIC_BASE: u64 = 100_000_000;
pub const MILLI_EPIC: u64 = EPIC_BASE / 1_000;
pub const MICRO_EPIC: u64 = MILLI_EPIC / 1_000;
pub const FREEMAN: u64 = 1;
pub const BLOCK_TIME_SEC: u64 = 60;
pub const BLOCK_ERA_1: u64 = DAY_HEIGHT * 334;
pub const BLOCK_ERA_2: u64 = BLOCK_ERA_1 + (DAY_HEIGHT * 470);
pub const BLOCK_ERA_3: u64 = BLOCK_ERA_2 + (DAY_HEIGHT * 601);
pub const BLOCK_ERA_4: u64 = BLOCK_ERA_3 + (DAY_HEIGHT * 800);
pub const BLOCK_ERA_5: u64 = BLOCK_ERA_4 + (DAY_HEIGHT * 1019);
pub const BLOCK_ERA_6_ONWARDS: u64 = DAY_HEIGHT * 1460;
pub const BASE_REWARD_ERA_6_ONWARDS: u64 = (0.15625 * EPIC_BASE as f64) as u64;
pub fn mainnet_block_total_reward_at_height(height: u64) -> u64 {
if height <= BLOCK_ERA_1 {
16 * EPIC_BASE
} else if height <= BLOCK_ERA_2 {
8 * EPIC_BASE
} else if height <= BLOCK_ERA_3 {
4 * EPIC_BASE
} else if height <= BLOCK_ERA_4 {
2 * EPIC_BASE
} else if height <= BLOCK_ERA_5 {
1 * EPIC_BASE
} else {
let height_with_offset = height - BLOCK_ERA_5 - 1;
let exp = height_with_offset / BLOCK_ERA_6_ONWARDS;
BASE_REWARD_ERA_6_ONWARDS / (1 << exp)
}
}
pub const FLOONET_BLOCK_ERA_1: u64 = DAY_HEIGHT * 334;
pub fn floonet_block_total_reward_at_height(height: u64) -> u64 {
if height <= FLOONET_BLOCK_ERA_1 {
16 * EPIC_BASE
} else {
8 * EPIC_BASE
}
}
pub fn block_total_reward_at_height(height: u64) -> u64 {
let param_ref = global::CHAIN_TYPE.read();
match *param_ref {
global::ChainTypes::Floonet => floonet_block_total_reward_at_height(height),
_ => mainnet_block_total_reward_at_height(height),
}
}
pub const MAINNET_FOUNDATION_HEIGHT: u64 = DAY_HEIGHT;
pub const AUTOMATEDTEST_FOUNDATION_HEIGHT: u64 = 5;
pub const USERNET_FOUNDATION_HEIGHT: u64 = DAY_HEIGHT;
pub const FLOONET_FOUNDATION_HEIGHT: u64 = DAY_HEIGHT;
pub fn foundation_height() -> u64 {
let param_ref = global::CHAIN_TYPE.read();
match *param_ref {
global::ChainTypes::AutomatedTesting => AUTOMATEDTEST_FOUNDATION_HEIGHT,
global::ChainTypes::UserTesting => USERNET_FOUNDATION_HEIGHT,
global::ChainTypes::Floonet => FLOONET_FOUNDATION_HEIGHT,
_ => MAINNET_FOUNDATION_HEIGHT,
}
}
pub fn is_foundation_height(height: u64) -> bool {
height > 0 && height % foundation_height() == 0 && reward_foundation_at_height(height) != 0
}
pub fn foundation_index(height: u64) -> u64 {
if height > 0 {
(height / foundation_height()) - 1
} else {
panic!("Error to get the correct index in the foundation.json file! It was expected a height > 0, it got a height of {:?}", height);
}
}
pub fn add_reward_foundation(height: u64) -> u64 {
if is_foundation_height(height) {
cumulative_reward_foundation(height)
} else {
0
}
}
pub fn cumulative_reward_foundation(height: u64) -> u64 {
assert!(is_foundation_height(height), "To compute the cumulative foundation reward the height needs to be a foundation height multiple");
let mut sum: u64 = 0;
let f_height: u64 = foundation_height();
let n: u64 = (height - f_height) + 1;
for iter_height in n..=height {
sum += reward_foundation_at_height(iter_height);
}
sum
}
pub const FOUNDATION_LEVY_ERA_1: u64 = DAY_HEIGHT * 120;
pub const FOUNDATION_LEVY_ERA_2_ONWARDS: u64 = DAY_HEIGHT * 365;
pub const FOUNDATION_LEVY_RATIO: u64 = 10000;
pub const FOUNDATION_LEVY: [u64; 9] = [888, 777, 666, 555, 444, 333, 222, 111, 111];
pub fn reward_foundation_at_height(height: u64) -> u64 {
if height == 0 {
return 0;
} else if height <= FOUNDATION_LEVY_ERA_1 {
let block_total_reward = block_total_reward_at_height(height);
return (block_total_reward * FOUNDATION_LEVY[0]) / FOUNDATION_LEVY_RATIO;
} else {
let height_with_offset = height - FOUNDATION_LEVY_ERA_1 - 1;
let index: u64 = (height_with_offset / FOUNDATION_LEVY_ERA_2_ONWARDS) + 1;
assert!(
index < std::u32::MAX.into(),
"Couldn't convert index u64 to usize without lost information!"
);
let index = index as usize;
if index < FOUNDATION_LEVY.len() {
let block_total_reward = block_total_reward_at_height(height);
return (block_total_reward * FOUNDATION_LEVY[index]) / FOUNDATION_LEVY_RATIO;
} else {
return 0;
}
}
}
pub fn reward(fee: u64, height: u64) -> u64 {
let reward = reward_at_height(height);
return reward.saturating_add(fee);
}
pub fn reward_at_height(height: u64) -> u64 {
let total_reward = block_total_reward_at_height(height);
return total_reward - reward_foundation_at_height(height);
}
pub fn reward_foundation(fees: u64, height: u64) -> u64 {
reward(fees, height) + add_reward_foundation(height)
}
pub fn total_overage_at_height(height: u64, genesis_had_reward: bool) -> i64 {
let mut sum: i64 = 0;
if genesis_had_reward {
sum += reward_at_height(0) as i64;
}
for i in 1..=height {
let reward = reward_at_height(i as u64) as i64;
sum += reward + add_reward_foundation(i as u64) as i64;
}
return sum;
}
pub const HOUR_HEIGHT: u64 = 3600 / BLOCK_TIME_SEC;
pub const DAY_HEIGHT: u64 = 24 * HOUR_HEIGHT;
pub const WEEK_HEIGHT: u64 = 7 * DAY_HEIGHT;
pub const YEAR_HEIGHT: u64 = 52 * WEEK_HEIGHT;
pub const COINBASE_MATURITY: u64 = DAY_HEIGHT;
pub fn secondary_pow_ratio(height: u64) -> u64 {
90u64.saturating_sub(height / (2 * YEAR_HEIGHT / 90))
}
fn ar_scale_damp_factor(_height: u64) -> u64 {
AR_SCALE_DAMP_FACTOR
}
pub const PROOFSIZE: usize = 42;
pub const DEFAULT_MIN_EDGE_BITS: u8 = 19;
pub const SECOND_POW_EDGE_BITS: u8 = 31;
pub const BASE_EDGE_BITS: u8 = 24;
pub const CUT_THROUGH_HORIZON: u32 = WEEK_HEIGHT as u32;
pub const STATE_SYNC_THRESHOLD: u32 = 2 * DAY_HEIGHT as u32;
pub const BLOCK_INPUT_WEIGHT: usize = 1;
pub const BLOCK_OUTPUT_WEIGHT: usize = 21;
pub const BLOCK_KERNEL_WEIGHT: usize = 3;
pub const MAX_BLOCK_WEIGHT: usize = 40_000;
pub const MAINNET_FIRST_HARD_FORK: u64 = 9000000;
pub const FLOONET_FIRST_HARD_FORK: u64 = 25800;
pub const TESTING_FIRST_HARD_FORK: u64 = 6;
pub fn first_fork_height() -> u64 {
match global::CHAIN_TYPE.read().clone() {
global::ChainTypes::Mainnet => MAINNET_FIRST_HARD_FORK,
global::ChainTypes::Floonet => FLOONET_FIRST_HARD_FORK,
global::ChainTypes::AutomatedTesting | global::ChainTypes::UserTesting => {
TESTING_FIRST_HARD_FORK
}
}
}
pub fn header_version(height: u64) -> HeaderVersion {
if height < first_fork_height() {
HeaderVersion(6)
} else {
HeaderVersion(7)
}
}
pub fn valid_header_version(height: u64, version: HeaderVersion) -> bool {
version == header_version(height)
}
pub const TESTING_DIFFICULTY_ERA: u64 = 50;
pub const FLOONET_DIFFICULTY_ERA: u64 = 200;
pub const MAINNET_DIFFICULTY_ERA: u64 = 501160;
pub const DIFFICULTY_ADJUST_WINDOW: u64 = HOUR_HEIGHT;
pub const BLOCK_TIME_WINDOW: u64 = DIFFICULTY_ADJUST_WINDOW * BLOCK_TIME_SEC;
pub const CLAMP_FACTOR: u64 = 2;
pub const DIFFICULTY_DAMP_FACTOR: u64 = 3;
pub const AR_SCALE_DAMP_FACTOR: u64 = 13;
pub fn difficultyfix_height() -> u64 {
let param_ref = global::CHAIN_TYPE.read();
match *param_ref {
global::ChainTypes::AutomatedTesting => TESTING_DIFFICULTY_ERA,
global::ChainTypes::UserTesting => TESTING_DIFFICULTY_ERA,
global::ChainTypes::Floonet => FLOONET_DIFFICULTY_ERA,
_ => MAINNET_DIFFICULTY_ERA,
}
}
pub fn graph_weight(height: u64, edge_bits: u8) -> u64 {
let mut xpr_edge_bits = edge_bits as u64;
let mut min_edge_bits = global::min_edge_bits();
if min_edge_bits == 19 {
min_edge_bits += 12
}
let bits_over_min = edge_bits.saturating_sub(min_edge_bits);
let expiry_height = if height > 880000 {
(1 << bits_over_min) * (YEAR_HEIGHT * 100)
} else {
(1 << bits_over_min) * (YEAR_HEIGHT)
};
if height >= expiry_height {
xpr_edge_bits = xpr_edge_bits.saturating_sub(1 + (height - expiry_height) / WEEK_HEIGHT);
}
let graph_weight: u64 =
(2 << (if edge_bits > global::base_edge_bits() {
edge_bits - global::base_edge_bits()
} else {
global::base_edge_bits() - edge_bits
}) as u64) * xpr_edge_bits;
graph_weight
}
pub const MIN_DIFFICULTY: u64 = DIFFICULTY_DAMP_FACTOR;
pub const MIN_DIFFICULTY_RANDOMX: u64 = 4000;
pub const MIN_DIFFICULTY_RANDOMX_TESTING: u64 = 1;
pub const OLD_MIN_DIFFICULTY_RANDOMX: u64 = 5000;
pub const MIN_DIFFICULTY_PROGPOW: u64 = 200000;
pub const OLD_MIN_DIFFICULTY_PROGPOW: u64 = 100000;
pub const BLOCK_DIFF_FACTOR_RANDOMX: u64 = 64;
pub const BLOCK_DIFF_FACTOR_PROGPOW: u64 = 64;
pub const MIN_AR_SCALE: u64 = AR_SCALE_DAMP_FACTOR;
pub const RX_CLAMP_FACTOR: u64 = 2;
pub const RX_DIFFICULTY_DAMP_FACTOR: u64 = 3;
pub const PP_CLAMP_FACTOR: u64 = 2;
pub const PP_DIFFICULTY_DAMP_FACTOR: u64 = 3;
pub const UNIT_DIFFICULTY: u64 =
((2 as u64) << (SECOND_POW_EDGE_BITS - BASE_EDGE_BITS)) * (SECOND_POW_EDGE_BITS as u64);
pub const INITIAL_DIFFICULTY: u64 = 1_000_000 * UNIT_DIFFICULTY;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HeaderInfo {
pub block_hash: Hash,
pub timestamp: u64,
pub difficulty: Difficulty,
pub secondary_scaling: u32,
pub is_secondary: bool,
pub prev_timespan: u64,
}
impl HeaderInfo {
pub fn new(
block_hash: Hash,
timestamp: u64,
difficulty: Difficulty,
secondary_scaling: u32,
is_secondary: bool,
prev_timespan: u64,
) -> HeaderInfo {
HeaderInfo {
block_hash,
timestamp,
difficulty,
secondary_scaling,
is_secondary,
prev_timespan,
}
}
pub fn from_ts_diff(timestamp: u64, difficulty: Difficulty) -> HeaderInfo {
HeaderInfo {
block_hash: ZERO_HASH,
timestamp,
difficulty,
secondary_scaling: global::initial_graph_weight(),
is_secondary: true,
prev_timespan: 0,
}
}
pub fn from_diff_scaling(difficulty: Difficulty, secondary_scaling: u32) -> HeaderInfo {
HeaderInfo {
block_hash: ZERO_HASH,
timestamp: 1,
difficulty,
secondary_scaling,
is_secondary: true,
prev_timespan: 0,
}
}
}
pub fn damp(actual: u64, goal: u64, damp_factor: u64) -> u64 {
(actual + (damp_factor - 1) * goal) / damp_factor
}
pub fn clamp(actual: u64, goal: u64, clamp_factor: u64) -> u64 {
max(goal / clamp_factor, min(actual, goal * clamp_factor))
}
pub fn next_policy<T>(policy: u8, cursor: T) -> (PoWType, Policy)
where
T: IntoIterator<Item = Policy>,
{
let prev_bottles: Vec<Policy> = cursor.into_iter().take(1).collect();
let bottles = if let Some(p_bottles) = prev_bottles.first() {
p_bottles.clone()
} else {
get_bottles_default()
};
let pow_type = Deterministic::choose_algo(&global::get_policies(policy).unwrap(), &bottles);
let b = next_block_bottles(pow_type, &bottles);
(pow_type, b)
}
pub fn next_difficulty<T>(height: u64, prev_algo: PoWType, cursor: T) -> HeaderInfo
where
T: IntoIterator<Item = HeaderInfo>,
{
if crate::global::is_floonet() || crate::global::is_user_testing_mode() {
return HeaderInfo::from_diff_scaling(Difficulty::from_num(1), 1);
}
let diff_data = match prev_algo.clone() {
PoWType::Cuckatoo => global::difficulty_data_to_vector(cursor, DIFFICULTY_ADJUST_WINDOW),
PoWType::Cuckaroo => global::difficulty_data_to_vector(cursor, DIFFICULTY_ADJUST_WINDOW),
PoWType::RandomX => global::difficulty_data_to_vector(cursor, 1),
PoWType::ProgPow => global::difficulty_data_to_vector(cursor, 1),
};
let sec_pow_scaling = secondary_pow_scaling(height, &diff_data[1..]);
let mut diff = diff_data.last().unwrap().difficulty.num.clone();
match prev_algo {
PoWType::Cuckatoo => {
diff.insert(
PoWType::Cuckatoo,
next_cuckoo_difficulty(height, PoWType::Cuckatoo, &diff_data),
);
}
PoWType::Cuckaroo => {
diff.insert(
PoWType::Cuckaroo,
next_cuckoo_difficulty(height, PoWType::Cuckaroo, &diff_data),
);
}
PoWType::RandomX => {
diff.insert(
PoWType::RandomX,
next_hash_difficulty(PoWType::RandomX, &diff_data),
);
}
PoWType::ProgPow => {
diff.insert(
PoWType::ProgPow,
next_hash_difficulty(PoWType::ProgPow, &diff_data),
);
}
};
HeaderInfo::from_diff_scaling(Difficulty::from_dic_number(diff), sec_pow_scaling)
}
fn next_cuckoo_difficulty(_height: u64, pow: PoWType, diff_data: &Vec<HeaderInfo>) -> u64 {
let ts_delta: u64 =
diff_data[DIFFICULTY_ADJUST_WINDOW as usize].timestamp - diff_data[0].timestamp;
let diff_sum: u64 = diff_data
.iter()
.skip(1)
.map(|dd| dd.difficulty.to_num(pow))
.sum();
let adj_ts = clamp(
damp(ts_delta, BLOCK_TIME_WINDOW, DIFFICULTY_DAMP_FACTOR),
BLOCK_TIME_WINDOW,
CLAMP_FACTOR,
);
max(MIN_DIFFICULTY, diff_sum * BLOCK_TIME_SEC / adj_ts)
}
pub fn timestamp_median<T>(header_ts: u64, prev_algo: PoWType, cursor: T) -> u64
where
T: IntoIterator<Item = HeaderInfo>,
{
let diff_data = match prev_algo.clone() {
PoWType::Cuckatoo => global::ts_data_to_vector(cursor, 6),
PoWType::Cuckaroo => global::ts_data_to_vector(cursor, 6),
PoWType::RandomX => global::ts_data_to_vector(cursor, 6),
PoWType::ProgPow => global::ts_data_to_vector(cursor, 6),
};
let mut ts: Vec<u64> = vec![];
for i in 0..diff_data.len() {
ts.push(diff_data[i].timestamp);
}
ts.push(header_ts);
ts.sort();
let half = ts.len() / 2;
let median_ts: u64 = if (ts.len() % 2) == 0 {
ts[half]
} else {
(ts[half - 1] + ts[half]) / 2
};
median_ts
}
pub fn next_difficulty_era1<T>(height: u64, prev_algo: PoWType, cursor: T) -> HeaderInfo
where
T: IntoIterator<Item = HeaderInfo>,
{
let diff_data = match prev_algo.clone() {
PoWType::Cuckatoo => global::difficulty_data_to_vector(cursor, DIFFICULTY_ADJUST_WINDOW),
PoWType::Cuckaroo => global::difficulty_data_to_vector(cursor, DIFFICULTY_ADJUST_WINDOW),
PoWType::RandomX => global::difficulty_data_to_vector(cursor, DIFFICULTY_ADJUST_WINDOW),
PoWType::ProgPow => global::difficulty_data_to_vector(cursor, DIFFICULTY_ADJUST_WINDOW),
};
let sec_pow_scaling = secondary_pow_scaling(height, &diff_data[1..]);
let mut diff = diff_data.last().unwrap().difficulty.num.clone();
match prev_algo {
PoWType::Cuckatoo => {
diff.insert(
PoWType::Cuckatoo,
next_cuckoo_difficulty_era1(PoWType::Cuckatoo, &diff_data),
);
}
PoWType::Cuckaroo => {
diff.insert(
PoWType::Cuckaroo,
next_cuckoo_difficulty_era1(PoWType::Cuckaroo, &diff_data),
);
}
PoWType::RandomX => {
diff.insert(
PoWType::RandomX,
next_randomx_difficulty_era1(PoWType::RandomX, &diff_data),
);
}
PoWType::ProgPow => {
diff.insert(
PoWType::ProgPow,
next_progpow_difficulty_era1(PoWType::ProgPow, &diff_data),
);
}
};
HeaderInfo::from_diff_scaling(Difficulty::from_dic_number(diff), sec_pow_scaling)
}
fn next_progpow_difficulty_era1(pow: PoWType, diff_data: &Vec<HeaderInfo>) -> u64 {
let mut ts_delta: u64 = 0;
for i in 1..diff_data.len() {
ts_delta += diff_data[i - 1].timestamp
- diff_data[i - 1]
.timestamp
.saturating_sub(diff_data[i - 1].prev_timespan);
}
let diff_sum: u64 = diff_data
.iter()
.skip(1)
.map(|dd| dd.difficulty.to_num(pow))
.sum();
let adj_ts = clamp(
damp(ts_delta, BLOCK_TIME_WINDOW, PP_DIFFICULTY_DAMP_FACTOR),
BLOCK_TIME_WINDOW,
PP_CLAMP_FACTOR,
);
max(MIN_DIFFICULTY_PROGPOW, diff_sum * BLOCK_TIME_SEC / adj_ts)
}
fn next_randomx_difficulty_era1(pow: PoWType, diff_data: &Vec<HeaderInfo>) -> u64 {
let mut ts_delta: u64 = 0;
for i in 1..diff_data.len() {
ts_delta += diff_data[i - 1].timestamp
- diff_data[i - 1]
.timestamp
.saturating_sub(diff_data[i - 1].prev_timespan);
}
let diff_sum: u64 = diff_data
.iter()
.skip(1)
.map(|dd| dd.difficulty.to_num(pow))
.sum();
let adj_ts = clamp(
damp(ts_delta, BLOCK_TIME_WINDOW, RX_DIFFICULTY_DAMP_FACTOR),
BLOCK_TIME_WINDOW,
RX_CLAMP_FACTOR,
);
let param_ref = global::CHAIN_TYPE.read();
match *param_ref {
global::ChainTypes::UserTesting => max(
MIN_DIFFICULTY_RANDOMX_TESTING,
diff_sum * BLOCK_TIME_SEC / adj_ts,
),
_ => max(MIN_DIFFICULTY_RANDOMX, diff_sum * BLOCK_TIME_SEC / adj_ts),
}
}
fn next_cuckoo_difficulty_era1(pow: PoWType, diff_data: &Vec<HeaderInfo>) -> u64 {
let mut ts_delta: u64 = 0;
for i in 1..diff_data.len() {
ts_delta += diff_data[i - 1].timestamp
- diff_data[i - 1]
.timestamp
.saturating_sub(diff_data[i - 1].prev_timespan);
}
let diff_sum: u64 = diff_data
.iter()
.skip(1)
.map(|dd| dd.difficulty.to_num(pow))
.sum();
let adj_ts = clamp(
damp(ts_delta, BLOCK_TIME_WINDOW, DIFFICULTY_DAMP_FACTOR),
BLOCK_TIME_WINDOW,
CLAMP_FACTOR,
);
max(MIN_DIFFICULTY, diff_sum * BLOCK_TIME_SEC / adj_ts)
}
pub fn next_hash_difficulty(pow: PoWType, diff_data: &Vec<HeaderInfo>) -> u64 {
let diff_adjustment_cutoff = 60;
let block_diff_factor = match pow {
PoWType::RandomX => BLOCK_DIFF_FACTOR_RANDOMX,
PoWType::ProgPow => BLOCK_DIFF_FACTOR_PROGPOW,
_ => panic!("The function next_hash_difficulty is only used by Progpow and RandomX, but it got a {:?}", pow),
};
let current_diff = diff_data[1].difficulty.to_num(pow);
let current_timestamp = diff_data[1].timestamp;
let prev_timestamp = diff_data[0].timestamp;
let min_diff = match pow {
PoWType::RandomX => OLD_MIN_DIFFICULTY_RANDOMX,
PoWType::ProgPow => OLD_MIN_DIFFICULTY_PROGPOW,
_ => panic!("The function next_hash_difficulty is only used by Progpow and RandomX, but it got a {:?}", pow),
};
let ts_delta: u64 = current_timestamp - prev_timestamp;
let offset: i64 = (current_diff / block_diff_factor) as i64;
let sign: i64 = max(1 - 2 * (ts_delta as i64 / diff_adjustment_cutoff), -99);
max(
max(current_diff as i64 + offset * sign, 1) as u64,
min(current_diff, min_diff),
)
}
pub fn ar_count(_height: u64, diff_data: &[HeaderInfo]) -> u64 {
100 * diff_data.iter().filter(|n| n.is_secondary).count() as u64
}
pub fn secondary_pow_scaling(height: u64, diff_data: &[HeaderInfo]) -> u32 {
let scale_sum: u64 = diff_data.iter().map(|dd| dd.secondary_scaling as u64).sum();
let target_pct = secondary_pow_ratio(height);
let target_count = DIFFICULTY_ADJUST_WINDOW * target_pct;
let adj_count = clamp(
damp(
ar_count(height, diff_data),
target_count,
ar_scale_damp_factor(height),
),
target_count,
CLAMP_FACTOR,
);
let scale = scale_sum * target_pct / max(1, adj_count);
max(MIN_AR_SCALE, scale) as u32
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_graph_weight() {
assert_eq!(graph_weight(1, 31), 256 * 31);
assert_eq!(graph_weight(1, 32), 512 * 32);
assert_eq!(graph_weight(1, 33), 1024 * 33);
assert_eq!(graph_weight(YEAR_HEIGHT, 31), 256 * 30);
assert_eq!(graph_weight(YEAR_HEIGHT, 32), 512 * 32);
assert_eq!(graph_weight(YEAR_HEIGHT, 33), 1024 * 33);
assert_eq!(graph_weight(YEAR_HEIGHT + WEEK_HEIGHT, 31), 256 * 29);
assert_eq!(graph_weight(YEAR_HEIGHT + 2 * WEEK_HEIGHT, 31), 256 * 28);
assert_eq!(graph_weight(YEAR_HEIGHT + 32 * WEEK_HEIGHT, 31), 0);
assert_eq!(graph_weight(2 * YEAR_HEIGHT, 31), 0);
assert_eq!(graph_weight(2 * YEAR_HEIGHT, 32), 512 * 31);
assert_eq!(graph_weight(2 * YEAR_HEIGHT, 33), 1024 * 33);
assert_eq!(graph_weight(2 * YEAR_HEIGHT + WEEK_HEIGHT, 32), 512 * 30);
assert_eq!(graph_weight(2 * YEAR_HEIGHT + WEEK_HEIGHT, 31), 0);
assert_eq!(graph_weight(2 * YEAR_HEIGHT + 30 * WEEK_HEIGHT, 32), 512);
assert_eq!(graph_weight(2 * YEAR_HEIGHT + 31 * WEEK_HEIGHT, 32), 0);
assert_eq!(graph_weight(3 * YEAR_HEIGHT, 31), 0);
assert_eq!(graph_weight(3 * YEAR_HEIGHT, 32), 0);
assert_eq!(graph_weight(3 * YEAR_HEIGHT, 33), 1024 * 33);
assert_eq!(graph_weight(4 * YEAR_HEIGHT, 31), 0);
assert_eq!(graph_weight(4 * YEAR_HEIGHT, 32), 0);
assert_eq!(graph_weight(4 * YEAR_HEIGHT, 33), 1024 * 32);
}
}