Select Git revision
-
Millicent Billette authoredMillicent Billette authored
current_frame.rs 12.58 KiB
// Copyright (C) 2017-2019 The AXIOM TEAM Association.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Current frame : Interval of blocks taken for the calculation of the personalized difficulty.
//use crate::constants::*;
use crate::*;
extern crate num;
use crate::current_meta_datas::get_current_blockstamp_;
use current_meta_datas::CurrentMetaDataKey;
use dubp_common_doc::BlockNumber;
use durs_dbs_tools::DbError;
use durs_wot::WotId;
use num::Float;
use rkv::store::integer::IntegerStore;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
/// Describe a member in current frame
pub struct MemberInCurrentFrame {
/// Number of blocks forged by the member in the current frame.
pub forged_blocks: usize,
/// Personal difficulty of the member.
pub difficulty: PersonalDifficulty,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
/// Personal difficulty of a member.
pub struct PersonalDifficulty {
/// Exclusion factor
pub exclusion_factor: usize,
/// handicap
pub handicap: usize,
}
impl Default for PersonalDifficulty {
fn default() -> Self {
PersonalDifficulty {
exclusion_factor: 1,
handicap: 0,
}
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
/// in frame member info data store
pub struct MemberFrameInfo {
last_personal_block_number: BlockNumber,
last_personal_members_in_frame: usize,
nb_personal_blocks_in_frame: usize,
personal_difficulty: PersonalDifficulty,
}
//impl Default for MemberFrameInfo {
// fn default() -> Self {
// MemberFrameInfo {
// last_personal_block_number: BlockNumber,
// last_personal_members_in_frame: usize,
// nb_personal_blocks_in_frame: usize,
// personal_difficulty: PersonalDifficulty,
// }
// }
//}
/// Get current frame datas
pub fn get_current_frame<DB: DbReadable>(
_db: &DB,
) -> Result<Vec<(WotId, MemberInCurrentFrame)>, DbError> {
unimplemented!();
}
// TODO: tester ça
pub fn get_required_u64_in_int_store<DB: DbReadable, R: DbReader>(
db: &DB,
r: &R,
store_name: &str,
key: u32,
) -> Result<u64, DbError> {
if let Some(value) = db.get_int_store(store_name).get(r, key)? {
if let DbValue::U64(value_u64) = value {
Ok(value_u64)
} else {
Err(DbError::DBCorrupted)
}
} else {
Err(DbError::DBCorrupted)
}
}
// TODO: tester ça
//pub fn get_required_bin_in_int_store<DB: DbReadable, R: DbReader, V: DeserializeOwned>(
// db: &DB,
// r: &R,
// store_name: &str,
// key: u32,
//) -> Result<V, DbError> {
// if let Some(value) = db.get_int_store(store_name).get(r, key)? {
// Ok(BcDbRo::from_db_value(value)?)
// } else {
// Err(DbError::DBCorrupted)
// }
//}
// TODO: tester ça
pub fn get_bin_in_int_store<DB: DbReadable, R: DbReader, V: DeserializeOwned>(
db: &DB,
r: &R,
store_name: &str,
key: u32,
) -> Result<Option<V>, DbError> {
if let Some(value) = db.get_int_store(store_name).get(r, key)? {
Ok(Some(BcDbRo::from_db_value(value)?))
} else {
Ok(None)
}
}
/// Get the personal difficulty of a member.
/// If the member is not in the current window, returns `pow_min`.
pub fn get_member_diffi<DB: DbReadable, R: DbReader>(
db: &DB,
r: &R,
wot_id: WotId,
) -> Result<PersonalDifficulty, DbError> {
// si membre absent du store return personnal;
// difficulty par défaut
// sinon récupérer le résultat et continuer
let optional_member_info: Option<MemberFrameInfo> =
get_bin_in_int_store(db, r, CURRENT_FRAME_MEMBERS, wot_id.0 as u32)?;
if optional_member_info.is_none() {
Ok(PersonalDifficulty {
exclusion_factor: 1,
handicap: 0,
})
} else {
let current_blockstamp =
get_current_blockstamp_(db, r)?.ok_or(DbError::DBCorrupted)?;
let median_frame_member = get_required_u64_in_int_store(
db,
r,
CURRENT_METAS_DATAS,
CurrentMetaDataKey::MedianFrameMember.to_u32(),
)?;
Ok(PersonalDifficulty {
exclusion_factor: 2,
handicap: 13,
})
}
}
/// calcule la difficulté personnalisée à partir des données prêtes à l'emploi
/// reference dans le protocol : https://github.com/duniter/duniter/blob/master/doc/Protocol.md#br_g18---headpowzeros-and-headpowremainder
pub fn compute_personal_difficulty(
nb_member_in_frame: usize,
last_personal_block_number: BlockNumber,
current_block_number: BlockNumber,
nb_personal_blocks_in_frame: usize,
median_of_blocks_in_frame: usize,
) -> PersonalDifficulty {
PersonalDifficulty {
exclusion_factor: exclusion_factor(
nb_member_in_frame,
current_block_number.0 - last_personal_block_number.0,
),
handicap: handicap(nb_personal_blocks_in_frame, median_of_blocks_in_frame),
}
}
/// calcule le facteur d'exclusion à partir des membres de la fenêtres courante et du dernier block du membre concerné
/// ne gère pas le cas où le membre n'est pas dans la fenêtre courante
///
/// nb_member_in_frame : la valeur du champ issuersCount du dernier bloc trouvé par le membre
/// nb_blocks_since : le nombre de blocs trouvés par le reste du réseau depuis que le membre considéré a trouvé son dernier bloc
/// exclusion_factor = MAX [ 1 ; FLOOR (0.67 * nb_member_in_frame / (1 + nb_blocks_since)) ]
///
/// reference dans le protocol : https://github.com/duniter/duniter/blob/master/doc/Protocol.md#br_g18---headpowzeros-and-headpowremainder
pub fn exclusion_factor(nb_member_in_frame: usize, nb_blocks_since: u32) -> usize {
std::cmp::max(
1,
(0.67 * (nb_member_in_frame as f64 / (1 + nb_blocks_since) as f64)).floor() as usize,
)
}
/// handicap calcule le handicap d'un membre à partir du nombre de blocs qu'il a caclulé dans la fenêtre courante et du
/// nombre median de blocs écrits par les membres dans la fenêtre courante.
/// ne gère pas le cas où le membre n'est pas dans la fenêtre courante
///
/// nb_personal_blocks_in_frame : le nombre de blocs écrits par le membre considéré dans la fenêtre courante
/// median_of_blocks_in_frame : le nombre médian de blocs écrits par les membres au sein de la fenêtre courante.
/// handicap = FLOOR(LN(MAX(1;(nb_personal_blocks_in_frame + 1) / median_of_blocks_in_frame)) / LN(1.189))
///
/// reference dans le protocol : https://github.com/duniter/duniter/blob/master/doc/Protocol.md#br_g18---headpowzeros-and-headpowremainder
pub fn handicap(nb_personal_blocks_in_frame: usize, median_of_blocks_in_frame: usize) -> usize {
(((std::cmp::max(
1,
(nb_personal_blocks_in_frame + 1) / median_of_blocks_in_frame,
)) as f64)
.ln()
/ 1.189.ln())
.floor() as usize
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
use durs_dbs_tools::kv_db::KvFileDbHandler;
use dubp_common_doc::Blockstamp;
//TODO: déplacer la fonction métier dans blockchain ou bc-db-writer
#[test]
fn test_exclusion_factor() {
assert_eq!(1, exclusion_factor(1, 0));
assert_eq!(2, exclusion_factor(15, 4));
}
#[test]
fn test_handicap() {
assert_eq!(13, handicap(500, 50));
assert_eq!(0, handicap(1, 50));
}
#[test]
fn test_default_personal_difficulty() {
assert_eq!(
PersonalDifficulty {
exclusion_factor: 1,
handicap: 0,
},
PersonalDifficulty::default()
);
}
#[test]
fn test_compute_personal_difficulty_no_penality() {
assert_eq!(
PersonalDifficulty {
exclusion_factor: 1,
handicap: 0,
},
compute_personal_difficulty(100, BlockNumber(1000), BlockNumber(1100), 2, 5)
);
}
#[test]
fn test_compute_personal_difficulty_double_penality() {
assert_eq!(
PersonalDifficulty {
exclusion_factor: 3,
handicap: 10,
},
compute_personal_difficulty(50, BlockNumber(1000), BlockNumber(1010), 5, 1)
);
assert_eq!(
PersonalDifficulty {
exclusion_factor: 2,
handicap: 13,
},
compute_personal_difficulty(6, BlockNumber(99), BlockNumber(100), 19, 2)
);
}
// test avec db mockée
fn factory_member_frame_info(
last_bn: u32,
last_mf: usize,
nbr_pbf: usize,
) -> Result<Vec<u8>, DbError> {
durs_dbs_tools::to_bytes(&MemberFrameInfo {
last_personal_block_number: BlockNumber(last_bn),
last_personal_members_in_frame: last_mf,
nb_personal_blocks_in_frame: nbr_pbf,
personal_difficulty: PersonalDifficulty::default(),
})
}
fn put_member_frame_info_in_db(
db: &KvFileDbHandler,
w: &mut DbWriter,
wot_id: u32,
last_bn: u32,
last_mf: usize,
nbr_pbf: usize,
) -> Result<(), DbError> {
db.get_int_store(CURRENT_FRAME_MEMBERS).put(
w.as_mut(),
wot_id,
&DbValue::Blob(&factory_member_frame_info(last_bn, last_mf, nbr_pbf)?),
)?;
Ok(())
}
/// cré un store CURRENT_FRAME_MEMBERS avec comme
/// |- clef les wot_id des membres de la fenêtre courante
/// |- valeurs :
/// |- le block_number du dernier block de ce membre
/// |- le nombre de membre dans la fenetre courante au dernier block de ce membre
/// |- le nombre de block de ce membre dans la fenêtre courante
fn init_mocked_db() -> Result<KvFileDbHandler, DbError> {
let db = open_tmp_db()?;
db.write(|mut w| {
put_member_frame_info_in_db(&db, &mut w, 1, 99, 6, 15)?;
put_member_frame_info_in_db(&db, &mut w, 3, 80, 6, 5)?;
put_member_frame_info_in_db(&db, &mut w, 4, 97, 6, 3)?;
put_member_frame_info_in_db(&db, &mut w, 5, 98, 6, 1)?;
put_member_frame_info_in_db(&db, &mut w, 6, 90, 6, 1)?;
put_member_frame_info_in_db(&db, &mut w, 8, 71, 6, 1)?;
let blockstamp_bytes: Vec<u8> = Blockstamp{
id:BlockNumber(100),
..Default::default()
}.into();
db.get_int_store(CURRENT_METAS_DATAS).put(
w.as_mut(),
CurrentMetaDataKey::CurrentBlockstamp.to_u32(),
&DbValue::Blob(&blockstamp_bytes),
)?;
// db.get_int_store(CURRENT_METAS_DATAS).put(
// w.as_mut(),
// CurrentMetaDataKey::CurrentFrameMembersSize.to_u32(),
// &DbValue::U64(6),
// )?;
db.get_int_store(CURRENT_METAS_DATAS).put(
w.as_mut(),
CurrentMetaDataKey::MedianFrameMember.to_u32(),
&DbValue::U64(2),
)?;
//CurrentBlockNumber : BlockNumber
//CurrentFrameMembersSize : u32
//MedianFrameMember : bloc_count
Ok(w)
})?;
Ok(db)
}
#[test]
fn test_personal_difficulty_member_not_in_frame() -> Result<(), DbError> {
let db = init_mocked_db()?;
assert_eq!(
PersonalDifficulty {
exclusion_factor: 1,
handicap: 0,
},
db.read(|r| get_member_diffi(&db, r, WotId(0)))?
);
Ok(())
}
#[test]
fn test_personal_difficulty_member_supercalculator() -> Result<(), DbError> {
let db = init_mocked_db()?;
assert_eq!(
PersonalDifficulty {
exclusion_factor: 2,
handicap: 13,
},
db.read(|r| get_member_diffi(&db, r, WotId(1)))?
);
Ok(())
}
}