diff --git a/dal/Cargo.toml b/dal/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a3ecdaa1528d5fe2d814221abafe3abf69e91eb7 --- /dev/null +++ b/dal/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "duniter-dal" +version = "0.1.0" +authors = ["librelois <elois@ifee.fr>"] +description = "Data Access Layer for the Duniter project." +license = "AGPL-3.0" + +[lib] +path = "lib.rs" + +[dependencies] +duniter-crypto = { path = "../crypto" } +duniter-documents = { path = "../documents" } +duniter-module = { path = "../module" } +duniter-network = { path = "../network" } +duniter-wotb = { path = "../wotb" } +lazy_static = "1.0.0" +log = "0.4.1" +rand = "0.4.2" +rust-crypto = "0.2.36" +regex = "0.2.6" +sqlite = "0.23.9" +serde = "1.0.24" +serde_derive = "1.0.24" +serde_json = "1.0.9" +websocket = "0.20.2" + +[features] +exp = [] \ No newline at end of file diff --git a/dal/block.rs b/dal/block.rs new file mode 100644 index 0000000000000000000000000000000000000000..92d5cefda333ccb34e1662288f3c3530ef86a741 --- /dev/null +++ b/dal/block.rs @@ -0,0 +1,523 @@ +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_wotb; +extern crate serde; +extern crate serde_json; +extern crate sqlite; + +use self::duniter_crypto::keys; +use self::duniter_crypto::keys::{ed25519, PublicKey, Signature}; +use self::duniter_documents::blockchain::v10::documents::identity::IdentityDocument; +use self::duniter_documents::blockchain::v10::documents::membership::MembershipType; +use self::duniter_documents::blockchain::v10::documents::BlockDocument; +use self::duniter_documents::blockchain::Document; +use self::duniter_documents::{BlockHash, BlockId, Blockstamp, Hash}; +use self::duniter_wotb::NodeId; +use super::constants::MAX_FORKS; +use super::parsers::certifications::parse_certifications; +use super::parsers::excluded::parse_exclusions; +use super::parsers::identities::parse_identities; +use super::parsers::memberships::parse_memberships; +use super::parsers::revoked::parse_revocations; +use super::parsers::transactions::parse_compact_transactions; +use super::{DuniterDB, ForkState}; +use std::collections::HashMap; + +pub fn blockstamp_to_timestamp(blockstamp: &Blockstamp, db: &DuniterDB) -> Option<u64> { + if blockstamp.id.0 == 0 { + return Some(1_488_987_127); + } + let mut cursor = db + .0 + .prepare("SELECT median_time FROM blocks WHERE number=? AND hash=? LIMIT 1;") + .expect("convert blockstamp to timestamp failure at step 0 !") + .cursor(); + + cursor + .bind(&[ + sqlite::Value::Integer(blockstamp.id.0 as i64), + sqlite::Value::String(blockstamp.hash.0.to_hex()), + ]) + .expect("convert blockstamp to timestamp failure at step 1 !"); + + if let Some(row) = cursor + .next() + .expect("convert blockstamp to timestamp failure at step 2 !") + { + return Some(row[0] + .as_integer() + .expect("convert blockstamp to timestamp failure at step 3 !") + as u64); + } + None +} + +#[derive(Debug, Copy, Clone)] +pub enum WotEvent { + AddNode(ed25519::PublicKey, NodeId), + RemNode(ed25519::PublicKey), + AddLink(NodeId, NodeId), + RemLink(NodeId, NodeId), + EnableNode(NodeId), + DisableNode(NodeId), +} + +#[derive(Debug, Clone)] +pub struct BlockContext { + pub blockstamp: Blockstamp, + pub wot_events: Vec<WotEvent>, +} + +#[derive(Debug, Clone)] +pub struct BlockContextV2 { + pub blockstamp: Blockstamp, + pub wot_events: Vec<WotEvent>, +} + +#[derive(Debug, Clone)] +pub struct DALBlock { + pub fork: usize, + pub isolate: bool, + pub block: BlockDocument, + pub median_frame: usize, + pub second_tiercile_frame: usize, +} + +impl DALBlock { + pub fn blockstamp(&self) -> Blockstamp { + self.block.blockstamp() + } +} + +pub fn get_forks(db: &DuniterDB) -> Vec<ForkState> { + let mut forks = Vec::new(); + forks.push(ForkState::Full()); + for fork in 1..*MAX_FORKS { + let mut cursor = db + .0 + .prepare("SELECT isolate FROM blocks WHERE fork=? ORDER BY median_time DESC LIMIT 1;") + .expect("Fail to get block !") + .cursor(); + + cursor + .bind(&[sqlite::Value::Integer(fork as i64)]) + .expect("Fail to get block !"); + + if let Some(row) = cursor.next().unwrap() { + if row[0].as_integer().unwrap() == 0 { + forks.push(ForkState::Full()) + } else { + forks.push(ForkState::Isolate()) + } + } else { + forks.push(ForkState::Free()); + } + } + forks +} + +impl DALBlock { + pub fn unisolate_fork(db: &DuniterDB, fork: usize) { + db.0 + .execute(format!("UPDATE blocks SET isolate=0 WHERE fork={};", fork)) + .unwrap(); + } + pub fn delete_fork(db: &DuniterDB, fork: usize) { + db.0 + .execute(format!("DELETE FROM blocks WHERE fork={};", fork)) + .unwrap(); + } + pub fn get_block_fork(db: &DuniterDB, blockstamp: &Blockstamp) -> Option<usize> { + let mut cursor = db + .0 + .prepare("SELECT fork FROM blocks WHERE number=? AND hash=?;") + .expect("Fail to get block !") + .cursor(); + + cursor + .bind(&[ + sqlite::Value::Integer(blockstamp.id.0 as i64), + sqlite::Value::String(blockstamp.hash.0.to_string()), + ]) + .expect("Fail to get block !"); + + if let Some(row) = cursor.next().unwrap() { + Some(row[0].as_integer().unwrap() as usize) + } else { + None + } + } + pub fn get_block_hash(db: &DuniterDB, block_number: &BlockId) -> Option<BlockHash> { + let mut cursor = db + .0 + .prepare("SELECT hash FROM blocks WHERE number=? AND fork=0;") + .expect("Fail to get block !") + .cursor(); + + cursor + .bind(&[sqlite::Value::Integer(block_number.0 as i64)]) + .expect("Fail to get block !"); + + if let Some(row) = cursor.next().unwrap() { + Some(BlockHash( + Hash::from_hex(row[0].as_string().unwrap()).unwrap(), + )) + } else { + None + } + } + + pub fn get_blocks_hashs_all_forks( + db: &DuniterDB, + block_number: &BlockId, + ) -> (Vec<BlockHash>, Vec<Hash>) { + let mut cursor = db + .0 + .prepare("SELECT hash, previous_hash FROM blocks WHERE number=?;") + .expect("Fail to get block !") + .cursor(); + + cursor + .bind(&[sqlite::Value::Integer(block_number.0 as i64)]) + .expect("Fail to get block !"); + + let mut hashs = Vec::new(); + let mut previous_hashs = Vec::new(); + while let Some(row) = cursor.next().unwrap() { + hashs.push(BlockHash( + Hash::from_hex(row[0].as_string().unwrap()).unwrap(), + )); + previous_hashs.push(Hash::from_hex(row[1].as_string().unwrap()).unwrap()); + } + (hashs, previous_hashs) + } + + pub fn get_stackables_blocks( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + current_blockstamp: &Blockstamp, + ) -> Vec<DALBlock> { + debug!("get_stackables_blocks() after {}", current_blockstamp); + let mut stackables_blocks = Vec::new(); + let block_id = BlockId(current_blockstamp.id.0 + 1); + let (hashs, previous_hashs) = DALBlock::get_blocks_hashs_all_forks(db, &block_id); + for (hash, previous_hash) in hashs.into_iter().zip(previous_hashs) { + if previous_hash == current_blockstamp.hash.0 { + if let Some(dal_block) = DALBlock::get_block( + currency, + db, + wotb_index, + &Blockstamp { id: block_id, hash }, + ) { + stackables_blocks.push(dal_block); + } else { + panic!(format!( + "Fail to get stackable block {} !", + Blockstamp { id: block_id, hash } + )); + } + } + } + stackables_blocks + } + pub fn get_stackables_forks(db: &DuniterDB, current_blockstamp: &Blockstamp) -> Vec<usize> { + let mut stackables_forks = Vec::new(); + let block_id = BlockId(current_blockstamp.id.0 + 1); + let (hashs, previous_hashs) = DALBlock::get_blocks_hashs_all_forks(db, &block_id); + for (hash, previous_hash) in hashs.into_iter().zip(previous_hashs) { + if previous_hash == current_blockstamp.hash.0 { + if let Some(fork) = DALBlock::get_block_fork(db, &Blockstamp { id: block_id, hash }) + { + if fork > 0 { + stackables_forks.push(fork); + } + } + } + } + stackables_forks + } + pub fn get_block( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + blockstamp: &Blockstamp, + ) -> Option<DALBlock> { + let mut cursor = db + .0 + .prepare( + "SELECT fork, isolate, nonce, number, + pow_min, time, median_time, members_count, + monetary_mass, unit_base, issuers_count, issuers_frame, + issuers_frame_var, median_frame, second_tiercile_frame, + currency, issuer, signature, hash, previous_hash, dividend, identities, joiners, + actives, leavers, revoked, excluded, certifications, + transactions FROM blocks WHERE number=? AND hash=?;", + ) + .expect("Fail to get block !") + .cursor(); + + cursor + .bind(&[ + sqlite::Value::Integer(blockstamp.id.0 as i64), + sqlite::Value::String(blockstamp.hash.0.to_string()), + ]) + .expect("Fail to get block !"); + + if let Some(row) = cursor.next().expect("block not found in bdd !") { + let dividend_amount = row[20] + .as_integer() + .expect("dal::get_block() : fail to parse dividend !"); + let dividend = if dividend_amount > 0 { + Some(dividend_amount as usize) + } else if dividend_amount == 0 { + None + } else { + return None; + }; + let nonce = row[2] + .as_integer() + .expect("dal::get_block() : fail to parse nonce !") as u64; + let inner_hash = Hash::from_hex( + row[18] + .as_string() + .expect("dal::get_block() : fail to parse inner_hash !"), + ).expect("dal::get_block() : fail to parse inner_hash (2) !"); + let identities = parse_identities( + currency, + row[21] + .as_string() + .expect("dal::get_block() : fail to parse identities !"), + ).expect("dal::get_block() : fail to parse identities (2) !"); + let hashmap_identities = identities + .iter() + .map(|i| (i.issuers()[0], i.clone())) + .collect::<HashMap<ed25519::PublicKey, IdentityDocument>>(); + Some(DALBlock { + fork: row[0] + .as_integer() + .expect("dal::get_block() : fail to parse fork !") + as usize, + isolate: if row[1] + .as_integer() + .expect("dal::get_block() : fail to parse isolate !") + == 0 + { + false + } else { + true + }, + block: BlockDocument { + nonce, + number: BlockId(row[3] + .as_integer() + .expect("dal::get_block() : fail to parse number !") + as u32), + pow_min: row[4] + .as_integer() + .expect("dal::get_block() : fail to parse pow min !") + as usize, + time: row[5] + .as_integer() + .expect("dal::get_block() : fail to parse time !") + as u64, + median_time: row[6] + .as_integer() + .expect("dal::get_block() : fail to parse median_time !") + as u64, + members_count: row[7] + .as_integer() + .expect("dal::get_block() : fail to parse members_count !") + as usize, + monetary_mass: row[8] + .as_integer() + .expect("dal::get_block() : fail to parse monetary_mass !") + as usize, + unit_base: row[9] + .as_integer() + .expect("dal::get_block() : fail to parse unit_base !") + as usize, + issuers_count: row[10] + .as_integer() + .expect("dal::get_block() : fail to parse issuers_count !") + as usize, + issuers_frame: row[11] + .as_integer() + .expect("dal::get_block() : fail to parse issuers_frame !") + as isize, + issuers_frame_var: row[12] + .as_integer() + .expect("dal::get_block() : fail to parse issuers_frame_var !") + as isize, + currency: row[15] + .as_string() + .expect("dal::get_block() : fail to parse currency !") + .to_string(), + issuers: vec![ + PublicKey::from_base58( + row[16] + .as_string() + .expect("dal::get_block() : fail to parse issuer !"), + ).expect("dal::get_block() : fail to parse pubkey !"), + ], + signatures: vec![ + Signature::from_base64( + row[17] + .as_string() + .expect("dal::get_block() : fail to parse signature !"), + ).expect("dal::get_block() : fail to parse signature (2) !"), + ], + hash: Some(BlockHash( + Hash::from_hex( + row[18] + .as_string() + .expect("dal::get_block() : fail to parse hash !"), + ).expect("dal::get_block() : fail to parse hash (2) !"), + )), + parameters: None, + previous_hash: Hash::from_hex( + row[19] + .as_string() + .expect("dal::get_block() : fail to parse previous_hash !"), + ).expect( + "dal::get_block() : fail to parse previous_hash (2) !", + ), + previous_issuer: None, + inner_hash: Some(inner_hash), + dividend, + identities: identities.clone(), + joiners: parse_memberships( + currency, + MembershipType::In(), + row[22] + .as_string() + .expect("dal::get_block() : fail to parse joiners !"), + ).expect("dal::get_block() : fail to parse joiners (2) !"), + actives: parse_memberships( + currency, + MembershipType::In(), + row[23] + .as_string() + .expect("dal::get_block() : fail to parse actives !"), + ).expect("dal::get_block() : fail to parse actives (2) !"), + leavers: parse_memberships( + currency, + MembershipType::Out(), + row[24] + .as_string() + .expect("dal::get_block() : fail to parse leavers !"), + ).expect("dal::get_block() : fail to parse leavers (2) !"), + revoked: parse_revocations( + currency, + db, + wotb_index, + &hashmap_identities, + row[25] + .as_string() + .expect("dal::get_block() : fail to parse revoked !"), + ).expect("dal::get_block() : fail to parse revoked (2) !"), + excluded: parse_exclusions( + row[26] + .as_string() + .expect("dal::get_block() : fail to parse excluded !"), + ).expect("dal::get_block() : fail to parse excluded (2) !"), + certifications: parse_certifications( + currency, + db, + wotb_index, + &hashmap_identities, + row[27] + .as_string() + .expect("dal::get_block() : fail to parse certifications !"), + ).expect( + "dal::get_block() : fail to parse certifications (2) !", + ), + transactions: parse_compact_transactions( + currency, + row[28] + .as_string() + .expect("dal::get_block() : fail to parse transactions !"), + ).expect("dal::get_block() : fail to parse transactions (2) !"), + inner_hash_and_nonce_str: format!( + "InnerHash: {}\nNonce: {}\n", + inner_hash.to_hex(), + nonce + ), + }, + median_frame: row[13].as_integer().unwrap_or(0) as usize, + second_tiercile_frame: row[14].as_integer().unwrap_or(0) as usize, + }) + } else { + None + } + } + + pub fn get_current_frame(&self, db: &DuniterDB) -> HashMap<keys::ed25519::PublicKey, usize> { + let frame_begin = self.block.number.0 as i64 - (self.block.issuers_frame as i64); + let mut current_frame: HashMap<keys::ed25519::PublicKey, usize> = HashMap::new(); + let mut cursor = db + .0 + .prepare("SELECT issuer FROM blocks WHERE fork=0 AND number>=? LIMIT ?;") + .expect("get current frame blocks failure at step 1 !") + .cursor(); + cursor + .bind(&[ + sqlite::Value::Integer(frame_begin), + sqlite::Value::Integer(self.block.issuers_frame as i64), + ]) + .expect("get current frame blocks failure at step 2 !"); + + while let Some(row) = cursor + .next() + .expect("get current frame blocks failure at step 3 !") + { + let current_frame_copy = current_frame.clone(); + match current_frame_copy + .get(&PublicKey::from_base58(row[0].as_string().unwrap()).unwrap()) + { + Some(blocks_count) => { + if let Some(new_blocks_count) = current_frame + .get_mut(&PublicKey::from_base58(row[0].as_string().unwrap()).unwrap()) + { + *new_blocks_count = *blocks_count + 1; + } + } + None => { + current_frame.insert( + PublicKey::from_base58(row[0].as_string().unwrap()).unwrap(), + 0, + ); + } + } + } + current_frame + } + + pub fn compute_median_issuers_frame(&mut self, db: &DuniterDB) -> () { + let current_frame = self.get_current_frame(db); + if !current_frame.is_empty() { + let mut current_frame_vec: Vec<_> = current_frame.values().cloned().collect(); + current_frame_vec.sort_unstable(); + + // Calculate median + let mut median_index = match self.block.issuers_count % 2 { + 1 => (self.block.issuers_count / 2) + 1, + _ => self.block.issuers_count / 2, + }; + if median_index >= self.block.issuers_count { + median_index = self.block.issuers_count - 1; + } + self.median_frame = current_frame_vec[median_index]; + + // Calculate second tiercile index + let mut second_tiercile_index = match self.block.issuers_count % 3 { + 1 | 2 => (self.block.issuers_count as f64 * (2.0 / 3.0)) as usize + 1, + _ => (self.block.issuers_count as f64 * (2.0 / 3.0)) as usize, + }; + if second_tiercile_index >= self.block.issuers_count { + second_tiercile_index = self.block.issuers_count - 1; + } + self.second_tiercile_frame = current_frame_vec[second_tiercile_index]; + } + } +} diff --git a/dal/constants.rs b/dal/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..a35986fcd9853f13f592c16643c3b7aae14f3b9b --- /dev/null +++ b/dal/constants.rs @@ -0,0 +1,48 @@ +#[derive(Debug, Copy, Clone)] +pub struct CurrencyParametersV10 { + pub c: f64, + pub dt: i64, + pub ud0: i64, + pub sig_period: u64, + pub sig_stock: i64, + pub sig_window: i64, + pub sig_validity: i64, + pub sig_qty: i64, + pub idty_window: i64, + pub ms_window: i64, + pub x_percent: f64, + pub ms_validity: u64, + pub step_max: u32, + pub median_time_blocks: i64, + pub avg_gen_time: i64, + pub dt_diff_eval: i64, + pub percent_rot: f64, + pub ud_time0: i64, + pub ud_reeval_time0: i64, + pub dt_reeval: i64, +} + +pub static G1_PARAMS: &'static CurrencyParametersV10 = &CurrencyParametersV10 { + c: 0.0488, + dt: 86_400, + ud0: 1_000, + sig_period: 432_000, + sig_stock: 100, + sig_window: 5_259_600, + sig_validity: 63_115_200, + sig_qty: 5, + idty_window: 5_259_600, + ms_window: 5_259_600, + x_percent: 0.8, + ms_validity: 31_557_600, + step_max: 5, + median_time_blocks: 24, + avg_gen_time: 300, + dt_diff_eval: 12, + percent_rot: 0.67, + ud_time0: 1_488_970_800, + ud_reeval_time0: 1_490_094_000, + dt_reeval: 15_778_800, +}; +pub static G1_CONNECTIVITY_MAX: &'static usize = &125; +pub static MAX_FORKS: &'static usize = &50; diff --git a/dal/dal_event.rs b/dal/dal_event.rs new file mode 100644 index 0000000000000000000000000000000000000000..99e41058ea4b33366263c6b7cfef62717866f303 --- /dev/null +++ b/dal/dal_event.rs @@ -0,0 +1,13 @@ +extern crate duniter_documents; +extern crate serde; + +use self::duniter_documents::blockchain::v10::documents::BlockDocument; +use self::duniter_documents::blockchain::BlockchainProtocol; + +#[derive(Debug, Clone)] +pub enum DALEvent { + StackUpValidBlock(Box<BlockDocument>), + RevertBlocks(Vec<Box<BlockDocument>>), + NewValidPendingDoc(BlockchainProtocol), + RefusedPendingDoc(BlockchainProtocol), +} diff --git a/dal/dal_requests.rs b/dal/dal_requests.rs new file mode 100644 index 0000000000000000000000000000000000000000..efd50dc2b7b821c160ccfb44552396f63b86d4b5 --- /dev/null +++ b/dal/dal_requests.rs @@ -0,0 +1,63 @@ +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_module; +extern crate serde; + +use self::duniter_crypto::keys::ed25519; +use self::duniter_documents::blockchain::v10::documents::{ + BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument, +}; +use self::duniter_documents::Hash; +use self::duniter_module::ModuleReqFullId; +use std::collections::HashMap; + +#[derive(Debug, Copy, Clone)] +pub enum DALReqPendings { + AllPendingIdentyties(ModuleReqFullId, usize), + AllPendingIdentytiesWithoutCerts(ModuleReqFullId, usize), + PendingWotDatasForPubkey(ModuleReqFullId, ed25519::PublicKey), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum DALReqBlockchain { + CurrentBlock(ModuleReqFullId), + BlockByNumber(ModuleReqFullId, u64), + Chunk(ModuleReqFullId, u64, usize), + UIDs(Vec<ed25519::PublicKey>), +} + +#[derive(Debug, Clone)] +pub enum DALRequest { + BlockchainRequest(DALReqBlockchain), + PendingsRequest(DALReqPendings), +} + +#[derive(Debug, Clone)] +pub struct PendingIdtyDatas { + pub idty: IdentityDocument, + pub memberships: Vec<MembershipDocument>, + pub certs_count: usize, + pub certs: Vec<CertificationDocument>, + pub revocation: Option<RevocationDocument>, +} + +#[derive(Debug, Clone)] +pub enum DALResPendings { + AllPendingIdentyties(HashMap<Hash, PendingIdtyDatas>), + AllPendingIdentytiesWithoutCerts(HashMap<Hash, PendingIdtyDatas>), + PendingWotDatasForPubkey(Vec<PendingIdtyDatas>), +} + +#[derive(Debug, Clone)] +pub enum DALResBlockchain { + CurrentBlock(ModuleReqFullId, BlockDocument), + BlockByNumber(ModuleReqFullId, BlockDocument), + Chunk(ModuleReqFullId, Vec<BlockDocument>), + UIDs(HashMap<ed25519::PublicKey, Option<String>>), +} + +#[derive(Debug, Clone)] +pub enum DALResponse { + Blockchain(DALResBlockchain), + Pendings(ModuleReqFullId, DALResPendings), +} diff --git a/dal/endpoint.rs b/dal/endpoint.rs new file mode 100644 index 0000000000000000000000000000000000000000..2bd5567ca2fa58578fdf66d4fe0b619aa41807b5 --- /dev/null +++ b/dal/endpoint.rs @@ -0,0 +1,166 @@ +extern crate crypto; +extern crate duniter_crypto; +extern crate sqlite; + +use std::time::Duration; + +use self::crypto::digest::Digest; +use self::crypto::sha2::Sha256; +use self::duniter_crypto::keys::PublicKey; +use self::duniter_crypto::keys::ed25519::PublicKey as ed25519PublicKey; +use super::DuniterDB; +use super::WriteToDuniterDB; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum DALEndpointApi { + WS2P, + //WS2PS, + //WS2PTOR, + //DASA, + //BMA, + //BMAS, +} + +impl From<u32> for DALEndpointApi { + fn from(integer: u32) -> Self { + match integer { + _ => DALEndpointApi::WS2P, + } + } +} + +pub fn string_to_api(api: &str) -> Option<DALEndpointApi> { + match api { + "WS2P" => Some(DALEndpointApi::WS2P), + //"WS2PS" => Some(DALEndpointApi::WS2PS), + //"WS2PTOR" => Some(DALEndpointApi::WS2PTOR), + //"DASA" => Some(DALEndpointApi::DASA), + //"BASIC_MERKLED_API" => Some(DALEndpointApi::BMA), + //"BMAS" => Some(DALEndpointApi::BMAS), + &_ => None, + } +} + +pub fn api_to_integer(api: &DALEndpointApi) -> i64 { + match *api { + DALEndpointApi::WS2P => 0, + //DALEndpointApi::WS2PS => 1, + //DALEndpointApi::WS2PTOR => 2, + //DALEndpointApi::DASA => 3, + //DALEndpointApi::BMA => 4, + //DALEndpointApi::BMAS => 5, + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DALEndpoint { + pub hash_full_id: String, + pub status: u32, + pub node_id: u32, + pub pubkey: ed25519PublicKey, + pub api: DALEndpointApi, + pub version: usize, + pub endpoint: String, + pub last_check: u64, +} + +impl DALEndpoint { + pub fn new( + status: u32, + node_id: u32, + pubkey: ed25519PublicKey, + api: DALEndpointApi, + version: usize, + endpoint: String, + last_check: Duration, + ) -> DALEndpoint { + let mut sha = Sha256::new(); + sha.input_str(&format!( + "{}{}{}{}", + node_id, + pubkey, + api_to_integer(&api), + version + )); + DALEndpoint { + hash_full_id: sha.result_str(), + status, + node_id, + pubkey, + api, + version, + endpoint, + last_check: last_check.as_secs(), + } + } + pub fn get_endpoints_for_api(db: &DuniterDB, api: DALEndpointApi) -> Vec<DALEndpoint> { + let mut cursor:sqlite::Cursor = db.0 + .prepare("SELECT hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check FROM endpoints WHERE api=? ORDER BY status DESC;") + .expect("get_endpoints_for_api() : Error in SQL request !") + .cursor(); + + cursor + .bind(&[sqlite::Value::Integer(api_to_integer(&api))]) + .expect("get_endpoints_for_api() : Error in cursor binding !"); + let mut endpoints = Vec::new(); + while let Some(row) = cursor + .next() + .expect("get_endpoints_for_api() : Error in cursor.next()") + { + endpoints.push(DALEndpoint { + hash_full_id: row[0].as_string().unwrap().to_string(), + status: row[1].as_integer().unwrap() as u32, + node_id: row[2].as_integer().unwrap() as u32, + pubkey: ed25519PublicKey::from_base58(row[3].as_string().unwrap()).unwrap(), + api: DALEndpointApi::from(row[4].as_integer().unwrap() as u32), + version: row[5].as_integer().unwrap() as usize, + endpoint: row[6].as_string().unwrap().to_string(), + last_check: row[7].as_integer().unwrap() as u64, + }); + } + endpoints + } +} + +impl WriteToDuniterDB for DALEndpoint { + fn write( + &self, + db: &DuniterDB, + _written_blockstamp: super::block_v10::BlockStampV10, + _written_timestamp: u64, + ) { + // Check if endpoint it's already written + let mut cursor: sqlite::Cursor = db.0 + .prepare("SELECT status FROM endpoints WHERE hash_full_id=? ORDER BY status DESC;") + .expect("get_endpoints_for_api() : Error in SQL request !") + .cursor(); + cursor + .bind(&[sqlite::Value::String(self.hash_full_id.clone())]) + .expect("get_endpoints_for_api() : Error in cursor binding !"); + + // If endpoint it's already written, update status + if let Some(row) = cursor + .next() + .expect("get_endpoints_for_api() : Error in cursor.next()") + { + if row[0].as_integer().unwrap() as u32 != self.status { + db.0 + .execute(format!( + "UPDATE endpoints SET status={} WHERE hash_full_id='{}'", + self.status, self.hash_full_id + )) + .unwrap(); + } + } else { + db.0 + .execute( + format!( + "INSERT INTO endpoints (hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check) VALUES ('{}', {}, {}, '{}', {}, {}, '{}', {});", + self.hash_full_id, self.status, self.node_id, self.pubkey.to_string(), + api_to_integer(&self.api), self.version, self.endpoint, self.last_check + ) + ) + .unwrap(); + } + } +} diff --git a/dal/identity.rs b/dal/identity.rs new file mode 100644 index 0000000000000000000000000000000000000000..b65dc0306157f0b8e9f1f715ac9bc387d51932cb --- /dev/null +++ b/dal/identity.rs @@ -0,0 +1,254 @@ +extern crate sqlite; + +use super::block::{blockstamp_to_timestamp, DALBlock}; +use super::DuniterDB; +use duniter_crypto::keys::{ed25519, PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::identity::IdentityDocumentBuilder; +use duniter_documents::blockchain::v10::documents::IdentityDocument; +use duniter_documents::blockchain::{Document, DocumentBuilder}; +use duniter_documents::Blockstamp; +use duniter_wotb::NodeId; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct DALIdentity { + pub wotb_id: NodeId, + pub hash: String, + pub state: isize, + pub joined_on: Blockstamp, + pub penultimate_renewed_on: Blockstamp, + pub last_renewed_on: Blockstamp, + pub expires_on: u64, + pub revokes_on: u64, + pub expired_on: Option<Blockstamp>, + pub revoked_on: Option<Blockstamp>, + pub idty_doc: IdentityDocument, +} + +impl DALIdentity { + pub fn exclude_identity( + db: &DuniterDB, + wotb_id: NodeId, + renewal_blockstamp: Blockstamp, + revert: bool, + ) { + let state = if revert { 0 } else { 1 }; + let expired_on = if revert { + None + } else { + Some(renewal_blockstamp) + }; + let mut cursor = db + .0 + .prepare("UPDATE identities SET state=?, expired_on=? WHERE wotb_id=?;") + .expect("Fail to exclude idty !") + .cursor(); + + cursor + .bind(&[ + sqlite::Value::Integer(i64::from(state)), + sqlite::Value::String( + expired_on + .clone() + .unwrap_or_else(Blockstamp::default) + .to_string(), + ), + sqlite::Value::Integer(wotb_id.0 as i64), + ]) + .expect("Fail to exclude idty !"); + } + + pub fn get_wotb_index(db: &DuniterDB) -> HashMap<ed25519::PublicKey, NodeId> { + let mut wotb_index: HashMap<ed25519::PublicKey, NodeId> = HashMap::new(); + + let mut cursor = db + .0 + .prepare("SELECT wotb_id, pubkey FROM identities ORDER BY wotb_id ASC;") + .unwrap() + .cursor(); + + while let Some(row) = cursor.next().unwrap() { + wotb_index.insert( + PublicKey::from_base58(row[1].as_string().unwrap()).unwrap(), + NodeId(row[0].as_integer().unwrap() as usize), + ); + } + wotb_index + } + + pub fn create_identity( + db: &DuniterDB, + wotb_id: NodeId, + idty_doc: &IdentityDocument, + current_blockstamp: Blockstamp, + ) -> DALIdentity { + let created_on = idty_doc.blockstamp(); + let created_time = blockstamp_to_timestamp(&created_on, &db) + .expect("convert blockstamp to timestamp failure !"); + + DALIdentity { + wotb_id, + hash: "0".to_string(), + state: 0, + joined_on: current_blockstamp, + penultimate_renewed_on: created_on.clone(), + last_renewed_on: created_on, + expires_on: created_time + super::constants::G1_PARAMS.ms_validity, + revokes_on: created_time + super::constants::G1_PARAMS.ms_validity, + expired_on: None, + revoked_on: None, + idty_doc: idty_doc.clone(), + } + } + + pub fn revoke_identity( + db: &DuniterDB, + wotb_id: NodeId, + renewal_blockstamp: &Blockstamp, + revert: bool, + ) { + let state = if revert { 2 } else { 1 }; + let revoked_on = if revert { + String::from("") + } else { + renewal_blockstamp.to_string() + }; + let mut cursor = db + .0 + .prepare("UPDATE identities SET state=?, revoked_on=? WHERE wotb_id=?;") + .expect("Fail to exclude idty !") + .cursor(); + + cursor + .bind(&[ + sqlite::Value::Integer(state), + sqlite::Value::String(revoked_on), + sqlite::Value::Integer(wotb_id.0 as i64), + ]) + .expect("Fail to exclude idty !"); + } + + pub fn renewal_identity( + &mut self, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + renewal_blockstamp: &Blockstamp, + renawal_timestamp: u64, + revert: bool, + ) { + let mut penultimate_renewed_block: Option<DALBlock> = None; + let revert_excluding = if revert { + penultimate_renewed_block = Some( + DALBlock::get_block( + self.idty_doc.currency(), + db, + wotb_index, + &self.penultimate_renewed_on.clone(), + ).unwrap(), + ); + penultimate_renewed_block.clone().unwrap().block.median_time + + super::constants::G1_PARAMS.ms_validity < renawal_timestamp + } else { + false + }; + self.state = if revert && revert_excluding { 1 } else { 0 }; + self.expires_on = if revert { + penultimate_renewed_block.unwrap().block.median_time + + super::constants::G1_PARAMS.ms_validity + } else { + renawal_timestamp + super::constants::G1_PARAMS.ms_validity + }; + let mut cursor = db.0 + .prepare( + "UPDATE identities SET state=?, last_renewed_on=?, expires_on=?, revokes_on=? WHERE wotb_id=?;", + ) + .expect("Fail to renewal idty !") + .cursor(); + + cursor + .bind(&[ + sqlite::Value::Integer(self.state as i64), + sqlite::Value::String(renewal_blockstamp.to_string()), + sqlite::Value::Integer(self.expires_on as i64), + sqlite::Value::Integer( + (renawal_timestamp + (super::constants::G1_PARAMS.ms_validity * 2)) as i64, + ), + sqlite::Value::Integer(self.wotb_id.0 as i64), + ]) + .expect("Fail to renewal idty !"); + } + + pub fn remove_identity(db: &DuniterDB, wotb_id: NodeId) -> () { + db.0 + .execute(format!( + "DELETE FROM identities WHERE wotb_id={}", + wotb_id.0 + )) + .unwrap(); + } + + pub fn get_identity(currency: &str, db: &DuniterDB, wotb_id: &NodeId) -> Option<DALIdentity> { + let mut cursor = db + .0 + .prepare( + "SELECT uid, pubkey, hash, sig, + state, created_on, joined_on, penultimate_renewed_on, last_renewed_on, + expires_on, revokes_on, expired_on, revoked_on FROM identities WHERE wotb_id=?;", + ) + .expect("Fail to get idty !") + .cursor(); + + cursor + .bind(&[sqlite::Value::Integer(wotb_id.0 as i64)]) + .expect("Fail to get idty !"); + + if let Some(row) = cursor.next().unwrap() { + let idty_doc_builder = IdentityDocumentBuilder { + currency, + username: row[0].as_string().unwrap(), + blockstamp: &Blockstamp::from_string( + row[5] + .as_string() + .expect("DB Error : idty created_on invalid !"), + ).expect("DB Error : idty created_on invalid (2) !"), + issuer: &PublicKey::from_base58(row[1].as_string().unwrap()).unwrap(), + }; + let idty_sig = Signature::from_base64(row[3].as_string().unwrap()).unwrap(); + let idty_doc = idty_doc_builder.build_with_signature(vec![idty_sig]); + + let expired_on = match Blockstamp::from_string(row[11].as_string().unwrap()) { + Ok(blockstamp) => Some(blockstamp), + Err(_) => None, + }; + let revoked_on = match Blockstamp::from_string(row[12].as_string().unwrap()) { + Ok(blockstamp) => Some(blockstamp), + Err(_) => None, + }; + Some(DALIdentity { + wotb_id: wotb_id.clone(), + hash: row[2].as_string().unwrap().to_string(), + state: row[4].as_integer().unwrap() as isize, + joined_on: Blockstamp::from_string( + row[6] + .as_string() + .expect("DB Error : idty joined_on invalid !"), + ).expect("DB Error : idty joined_on invalid !"), + penultimate_renewed_on: Blockstamp::from_string( + row[7] + .as_string() + .expect("DB Error : idty penultimate_renewed_on invalid !"), + ).expect( + "DB Error : idty penultimate_renewed_on invalid (2) !", + ), + last_renewed_on: Blockstamp::from_string(row[8].as_string().unwrap()).unwrap(), + expires_on: row[9].as_integer().unwrap() as u64, + revokes_on: row[10].as_integer().unwrap() as u64, + expired_on, + revoked_on, + idty_doc, + }) + } else { + None + } + } +} diff --git a/dal/lib.rs b/dal/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b6eb80e0d144737018cc3fd8ba683aa828fed35 --- /dev/null +++ b/dal/lib.rs @@ -0,0 +1,271 @@ +// Copyright (C) 2018 The Duniter Project Developers. +// +// 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/>. + +//! Defined the few global types used by all modules, +//! as well as the DuniterModule trait that all modules must implement. + +#![cfg_attr(feature = "strict", deny(warnings))] +#![cfg_attr(feature = "cargo-clippy", allow(implicit_hasher))] +#![cfg_attr(feature = "exp", allow(warnings))] +#![deny( + missing_debug_implementations, missing_copy_implementations, trivial_casts, + trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces +)] + +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_json; + +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_wotb; +extern crate serde; +extern crate sqlite; + +pub mod block; +pub mod constants; +pub mod dal_event; +pub mod dal_requests; +pub mod identity; +pub mod parsers; +pub mod tools; +pub mod writers; + +use duniter_crypto::keys::{ed25519, PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::BlockDocument; +use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash}; +use duniter_wotb::NodeId; +use std::collections::HashMap; +use std::fmt::Debug; +use std::marker; +use std::path::PathBuf; + +use self::block::DALBlock; + +pub struct DuniterDB(pub sqlite::Connection); + +impl Debug for DuniterDB { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "DuniterDB {{ }}") + } +} + +pub trait FromJsonValue +where + Self: marker::Sized, +{ + fn from_json_value(value: &serde_json::Value) -> Option<Self>; +} + +pub trait WriteToDuniterDB { + fn write(&self, db: &DuniterDB, written_blockstamp: Blockstamp, written_timestamp: u64); +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ForkState { + Free(), + Full(), + Isolate(), +} + +#[derive(Debug, Clone)] +pub struct WotState { + pub block_number: u32, + pub block_hash: String, + pub sentries_count: usize, + pub average_density: usize, + pub average_distance: usize, + pub distances: Vec<usize>, + pub average_connectivity: usize, + pub connectivities: Vec<usize>, + pub average_centrality: usize, + pub centralities: Vec<u64>, +} + +fn _use_json_macro() -> serde_json::Value { + json!({}) +} + +pub fn open_db(db_path: &PathBuf, memory_mode: bool) -> Result<DuniterDB, sqlite::Error> { + let conn: sqlite::Connection; + if memory_mode || !db_path.as_path().exists() { + if memory_mode { + conn = sqlite::open(":memory:")?; + } else { + conn = sqlite::open(db_path.as_path())?; + } + //conn.execute("PRAGMA synchronous = 0;") + //.expect("Fail to configure SQLite DB (PRAGMA) !"); + conn.execute( + " + CREATE TABLE wot_history (block_number INTEGER, block_hash TEXT, sentries_count INTEGER, + average_density INTEGER, average_distance INTEGER, + distances TEXT, average_connectivity INTEGER, connectivities TEXT, + average_centrality INTEGER, centralities TEXT); + CREATE TABLE blocks (fork INTEGER, isolate INTEGER, version INTEGER, nonce INTEGER, number INTEGER, + pow_min INTEGER, time INTEGER, median_time INTEGER, members_count INTEGER, + monetary_mass INTEGER, unit_base INTEGER, issuers_count INTEGER, issuers_frame INTEGER, + issuers_frame_var INTEGER, median_frame INTEGER, second_tiercile_frame INTEGER, + currency TEXT, issuer TEXT, signature TEXT, hash TEXT, previous_hash TEXT, inner_hash TEXT, dividend INTEGER, identities TEXT, joiners TEXT, + actives TEXT, leavers TEXT, revoked TEXT, excluded TEXT, certifications TEXT, + transactions TEXT); + CREATE TABLE identities (wotb_id INTEGER, uid TEXT, pubkey TEXT, hash TEXT, sig TEXT, + state INTEGER, created_on TEXT, joined_on TEXT, penultimate_renewed_on TEXT, last_renewed_on TEXT, + expires_on INTEGER, revokes_on INTEGER, expired_on TEXT, revoked_on TEXT); + CREATE TABLE certifications (pubkey_from TEXT, pubkey_to TEXT, created_on TEXT, + signature TEXT, written_on TEXT, expires_on INTEGER, chainable_on INTEGER); + ", + )?; + } else { + conn = sqlite::open(db_path.as_path())?; + } + Ok(DuniterDB(conn)) +} + +pub fn close_db(db: &DuniterDB) { + db.0 + .execute("PRAGMA optimize;") + .expect("Fail to optimize SQLite DB !"); +} + +pub fn get_uid(db: &DuniterDB, wotb_id: NodeId) -> Option<String> { + let mut cursor: sqlite::Cursor = db + .0 + .prepare("SELECT uid FROM identities WHERE wotb_id=? LIMIT 1;") + .expect("Request SQL get_current_block is wrong !") + .cursor(); + cursor + .bind(&[sqlite::Value::Integer(wotb_id.0 as i64)]) + .expect("0"); + if let Some(row) = cursor.next().expect("fait to get_uid() : cursor error") { + Some(String::from( + row[0] + .as_string() + .expect("get_uid: Fail to parse uid field in str !"), + )) + } else { + None + } +} + +pub fn new_get_current_block(db: &DuniterDB) -> Option<BlockDocument> { + let mut cursor: sqlite::Cursor = db.0 + .prepare( + "SELECT version, nonce, number, pow_min, time, median_time, members_count, monetary_mass, unit_base, issuers_count, issuers_frame, issuers_frame_var, median_frame, second_tiercile_frame, currency, issuer, signature, hash, dividend, joiners, actives, leavers, revoked, excluded, certifications, transactions FROM blocks + WHERE fork=0 ORDER BY median_time DESC LIMIT ?;", + ) + .expect("Request SQL get_current_block is wrong !") + .cursor(); + + cursor.bind(&[sqlite::Value::Integer(1)]).expect("0"); + if let Some(row) = cursor.next().expect("1") { + let dividend = row[18].as_integer().expect("dividend"); + let dividend = if dividend > 0 { + Some(dividend as usize) + } else { + None + }; + return Some(BlockDocument { + nonce: row[1].as_integer().expect("nonce") as u64, + number: BlockId(row[2].as_integer().expect("2") as u32), + pow_min: row[3].as_integer().expect("version") as usize, + time: row[4].as_integer().expect("time") as u64, + median_time: row[5].as_integer().expect("median_time") as u64, + members_count: row[6].as_integer().expect("7") as usize, + monetary_mass: row[7].as_integer().expect("8") as usize, + unit_base: row[8].as_integer().expect("unit_base") as usize, + issuers_count: row[9].as_integer().expect("issuers_count") as usize, + issuers_frame: row[10].as_integer().expect("issuers_frame") as isize, + issuers_frame_var: row[11].as_integer().expect("issuers_frame_var") as isize, + currency: row[14].as_string().expect("currency").to_string(), + issuers: vec![PublicKey::from_base58(row[15].as_string().expect("issuer")).unwrap()], + signatures: vec![ + Signature::from_base64(row[16].as_string().expect("signature")).unwrap(), + ], + hash: Some(BlockHash( + Hash::from_hex(row[17].as_string().expect("hash")).unwrap(), + )), + parameters: None, + previous_hash: Hash::default(), + previous_issuer: None, + inner_hash: None, + dividend, + identities: Vec::with_capacity(0), + joiners: Vec::with_capacity(0), + actives: Vec::with_capacity(0), + leavers: Vec::with_capacity(0), + revoked: Vec::with_capacity(0), + excluded: Vec::with_capacity(0), + certifications: Vec::with_capacity(0), + transactions: Vec::with_capacity(0), + inner_hash_and_nonce_str: String::new(), + }); + } + None +} + +pub fn get_current_block( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, +) -> Option<DALBlock> { + let mut cursor: sqlite::Cursor = db + .0 + .prepare("SELECT number, hash FROM blocks WHERE fork=0 ORDER BY median_time DESC LIMIT ?;") + .expect("Request SQL get_current_block is wrong !") + .cursor(); + + cursor.bind(&[sqlite::Value::Integer(1)]).expect("0"); + + if let Some(row) = cursor.next().unwrap() { + let blockstamp = Blockstamp { + id: BlockId(row[0].as_integer().unwrap() as u32), + hash: BlockHash(Hash::from_hex(row[1].as_string().unwrap()).unwrap()), + }; + DALBlock::get_block(currency, db, wotb_index, &blockstamp) + } else { + None + } +} + +pub fn register_wot_state(db: &DuniterDB, wot_state: &WotState) { + if wot_state.block_number != 1 { + db.0 + .execute(format!( + "INSERT INTO wot_history (block_number, block_hash, sentries_count, + average_density, average_distance, distances, + average_connectivity, connectivities, average_centrality, centralities) + VALUES ({}, '{}', {}, {}, {}, '{}', {}, '{}', {}, '{}');", + wot_state.block_number, + wot_state.block_hash, + wot_state.sentries_count, + wot_state.average_density, + wot_state.average_distance, + serde_json::to_string(&wot_state.distances).unwrap(), + wot_state.average_connectivity, + serde_json::to_string(&wot_state.connectivities).unwrap(), + wot_state.average_centrality, + serde_json::to_string(&wot_state.centralities).unwrap(), + )) + .unwrap(); + } +} + +#[derive(Debug, Copy, Clone)] +pub enum BlockchainError { + UnexpectedBlockNumber(), + UnknowError(), +} diff --git a/dal/parsers/blocks.rs b/dal/parsers/blocks.rs new file mode 100644 index 0000000000000000000000000000000000000000..e78ce51adb90701374d846cd4b3812839d117e45 --- /dev/null +++ b/dal/parsers/blocks.rs @@ -0,0 +1,110 @@ +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_network; +extern crate serde_json; + +use self::duniter_network::{NetworkBlock, NetworkBlockV10}; +use super::excluded::parse_exclusions_from_json_value; +use super::identities::parse_compact_identity; +use super::transactions::parse_transaction; +use duniter_crypto::keys::{PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::BlockDocument; +use duniter_documents::{BlockHash, BlockId, Hash}; + +pub fn parse_json_block(source: &serde_json::Value) -> Option<NetworkBlock> { + let number = BlockId(source.get("number")?.as_u64()? as u32); + let currency = source.get("currency")?.as_str()?.to_string(); + let issuer = match PublicKey::from_base58(source.get("issuer")?.as_str()?) { + Ok(pubkey) => pubkey, + Err(_) => return None, + }; + let sig = match Signature::from_base64(source.get("signature")?.as_str()?) { + Ok(sig) => sig, + Err(_) => return None, + }; + let hash = match Hash::from_hex(source.get("hash")?.as_str()?) { + Ok(hash) => hash, + Err(_) => return None, + }; + let previous_hash = match source.get("previousHash")?.as_str() { + Some(hash_str) => match Hash::from_hex(hash_str) { + Ok(hash) => hash, + Err(_) => return None, + }, + None => if number.0 > 0 { + return None; + } else { + Hash::default() + }, + }; + let previous_issuer = match source.get("previousIssuer")?.as_str() { + Some(pubkey_str) => match PublicKey::from_base58(pubkey_str) { + Ok(pubkey) => Some(pubkey), + Err(_) => return None, + }, + None => if number.0 > 0 { + return None; + } else { + None + }, + }; + let inner_hash = match Hash::from_hex(source.get("inner_hash")?.as_str()?) { + Ok(hash) => Some(hash), + Err(_) => return None, + }; + let dividend = match source.get("dividend")?.as_u64() { + Some(dividend) => Some(dividend as usize), + None => None, + }; + let mut identities = Vec::new(); + for raw_idty in source.get("identities")?.as_array()? { + identities.push(parse_compact_identity(¤cy, &raw_idty)?); + } + let mut transactions = Vec::new(); + for json_tx in source.get("transactions")?.as_array()? { + transactions.push(parse_transaction("g1", &json_tx)?); + } + let block_doc = BlockDocument { + nonce: source.get("nonce")?.as_i64()? as u64, + number: BlockId(source.get("number")?.as_u64()? as u32), + pow_min: source.get("powMin")?.as_u64()? as usize, + time: source.get("time")?.as_u64()?, + median_time: source.get("medianTime")?.as_u64()?, + members_count: source.get("membersCount")?.as_u64()? as usize, + monetary_mass: source.get("monetaryMass")?.as_u64()? as usize, + unit_base: source.get("unitbase")?.as_u64()? as usize, + issuers_count: source.get("issuersCount")?.as_u64()? as usize, + issuers_frame: source.get("issuersFrame")?.as_i64()? as isize, + issuers_frame_var: source.get("issuersFrameVar")?.as_i64()? as isize, + currency, + issuers: vec![issuer], + signatures: vec![sig], + hash: Some(BlockHash(hash)), + parameters: None, + previous_hash, + previous_issuer, + inner_hash, + dividend, + identities, + joiners: Vec::with_capacity(0), + actives: Vec::with_capacity(0), + leavers: Vec::with_capacity(0), + revoked: Vec::with_capacity(0), + excluded: parse_exclusions_from_json_value(&source.get("excluded")?.as_array()?), + certifications: Vec::with_capacity(0), + transactions, + inner_hash_and_nonce_str: format!( + "InnerHash: {}\nNonce: {}\n", + inner_hash.unwrap().to_hex(), + source.get("nonce")?.as_u64()? + ), + }; + Some(NetworkBlock::V10(Box::new(NetworkBlockV10 { + uncompleted_block_doc: block_doc, + joiners: source.get("joiners")?.as_array()?.clone(), + actives: source.get("actives")?.as_array()?.clone(), + leavers: source.get("leavers")?.as_array()?.clone(), + revoked: source.get("revoked")?.as_array()?.clone(), + certifications: source.get("certifications")?.as_array()?.clone(), + }))) +} diff --git a/dal/parsers/certifications.rs b/dal/parsers/certifications.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee395afd0df1ee54bc60ac4d8ee2ddb8eea020ab --- /dev/null +++ b/dal/parsers/certifications.rs @@ -0,0 +1,97 @@ +extern crate serde; +extern crate serde_json; +extern crate sqlite; + +use super::super::block::DALBlock; +use super::super::identity::DALIdentity; +use super::super::DuniterDB; +use duniter_crypto::keys::{ed25519, PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::certification::CertificationDocumentBuilder; +use duniter_documents::blockchain::v10::documents::{CertificationDocument, IdentityDocument}; +use duniter_documents::blockchain::{Document, DocumentBuilder}; +use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash}; +use duniter_wotb::NodeId; +use std::collections::HashMap; + +pub fn parse_certifications_from_json_value( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>, + array_certifications: &[serde_json::Value], +) -> Vec<CertificationDocument> { + let mut certifications: Vec<CertificationDocument> = Vec::new(); + for certification in array_certifications.iter() { + let certification_datas: Vec<&str> = certification.as_str().unwrap().split(':').collect(); + if certification_datas.len() == 4 { + let target = PublicKey::from_base58(certification_datas[1]) + .expect("Fail to parse cert target !"); + let target_idty_doc: IdentityDocument = match block_identities.get(&target) { + Some(idty_doc) => idty_doc.clone(), + None => { + let target_wotb_id = wotb_index.get(&target).expect(&format!( + "target identity {} not found in wotb index !", + target.to_string() + )); + let dal_idty = DALIdentity::get_identity(currency, db, target_wotb_id) + .expect("target identity not found in bdd !"); + dal_idty.idty_doc + } + }; + let cert_blockstamp_id = BlockId( + certification_datas[2] + .parse() + .expect("Fail to parse cert blockstamp !"), + ); + let cert_builder = + CertificationDocumentBuilder { + currency, + issuer: &PublicKey::from_base58(certification_datas[0]) + .expect("Fail to parse cert issuer !"), + blockstamp: &Blockstamp { + id: cert_blockstamp_id, + hash: if cert_blockstamp_id == BlockId(0) { + BlockHash(Hash::from_hex( + "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + ).unwrap()) + } else { + DALBlock::get_block_hash(db, &cert_blockstamp_id).expect(&format!( + "Fatal Error : Block {} not found in bdd !", + cert_blockstamp_id + )) + }, + }, + target: &target, + identity_username: target_idty_doc.username(), + identity_blockstamp: &target_idty_doc.blockstamp(), + identity_sig: &target_idty_doc.signatures()[0], + }; + let cert_sig = + Signature::from_base64(certification_datas[3]).expect("Fail to parse cert sig !"); + certifications.push(cert_builder.build_with_signature(vec![cert_sig])); + } + } + certifications +} + +pub fn parse_certifications( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>, + json_datas: &str, +) -> Option<Vec<CertificationDocument>> { + let raw_certifications: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + + if raw_certifications.is_array() { + Some(parse_certifications_from_json_value( + currency, + db, + wotb_index, + block_identities, + raw_certifications.as_array().unwrap(), + )) + } else { + None + } +} diff --git a/dal/parsers/excluded.rs b/dal/parsers/excluded.rs new file mode 100644 index 0000000000000000000000000000000000000000..329261da4fe5a876d6d751945b53ef1d3bd8d505 --- /dev/null +++ b/dal/parsers/excluded.rs @@ -0,0 +1,26 @@ +extern crate serde; +extern crate serde_json; + +use duniter_crypto::keys::{ed25519, PublicKey}; + +pub fn parse_exclusions(json_datas: &str) -> Option<Vec<ed25519::PublicKey>> { + let raw_exclusions: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + + if raw_exclusions.is_array() { + Some(parse_exclusions_from_json_value( + raw_exclusions.as_array().unwrap(), + )) + } else { + None + } +} + +pub fn parse_exclusions_from_json_value( + array_exclusions: &[serde_json::Value], +) -> Vec<ed25519::PublicKey> { + let mut exclusions: Vec<ed25519::PublicKey> = Vec::new(); + for exclusion in array_exclusions.iter() { + exclusions.push(PublicKey::from_base58(exclusion.as_str().unwrap()).unwrap()); + } + exclusions +} diff --git a/dal/parsers/identities.rs b/dal/parsers/identities.rs new file mode 100644 index 0000000000000000000000000000000000000000..9791d4c0f5045d08ce499709ee79857c1676f6c2 --- /dev/null +++ b/dal/parsers/identities.rs @@ -0,0 +1,102 @@ +extern crate serde_json; +extern crate sqlite; + +use duniter_crypto::keys::{PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::identity::IdentityDocumentBuilder; +use duniter_documents::blockchain::v10::documents::IdentityDocument; +use duniter_documents::blockchain::DocumentBuilder; +use duniter_documents::Blockstamp; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IdentityParseError { + WrongFormat(), +} + +pub fn parse_identities(currency: &str, json_datas: &str) -> Option<Vec<IdentityDocument>> { + let raw_idties: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + if raw_idties.is_array() { + return Some( + parse_identities_from_json_value(currency, raw_idties.as_array().unwrap()) + .iter() + .map(|i| { + i.clone() + .expect("Fatal error : Fail to parse identity from local DB !") + }) + .collect(), + ); + } + None +} + +pub fn parse_identities_from_json_value( + currency: &str, + array_identities: &[serde_json::Value], +) -> Vec<Result<IdentityDocument, IdentityParseError>> { + array_identities + .iter() + .map(|idty| { + let idty_datas: Vec<&str> = idty.as_str().unwrap().split(':').collect(); + if idty_datas.len() == 4 { + let idty_doc_builder = IdentityDocumentBuilder { + currency, + issuer: &PublicKey::from_base58(idty_datas[0]).unwrap(), + blockstamp: &Blockstamp::from_string(idty_datas[2]).unwrap(), + username: idty_datas[3], + }; + let idty_sig = Signature::from_base64(idty_datas[1]).unwrap(); + //memberships.push(membership_doc_builder.build_with_signature(vec![membership_sig])); + Ok(idty_doc_builder.build_with_signature(vec![idty_sig])) + } else { + Err(IdentityParseError::WrongFormat()) + } + }) + .collect() + + /*for membership in array_memberships.iter() { + let membership_datas: Vec<&str> = membership.as_str().unwrap().split(':').collect(); + if membership_datas.len() == 5 { + let membership_doc_builder = IdentityDocumentBuilder { + currency, + issuer: &PublicKey::from_base58(membership_datas[0]).unwrap(), + blockstamp: &Blockstamp::from_string(membership_datas[2]).unwrap(), + membership: membership_type, + identity_username: membership_datas[4], + identity_blockstamp: &Blockstamp::from_string(membership_datas[3]).unwrap(), + }; + let membership_sig = Signature::from_base64(membership_datas[1]).unwrap(); + memberships.push(membership_doc_builder.build_with_signature(vec![membership_sig])); + } + } + memberships*/ +} + +pub fn parse_compact_identity( + currency: &str, + source: &serde_json::Value, +) -> Option<IdentityDocument> { + if source.is_string() { + let idty_elements: Vec<&str> = source.as_str().unwrap().split(':').collect(); + let issuer = match PublicKey::from_base58(idty_elements[0]) { + Ok(pubkey) => pubkey, + Err(_) => return None, + }; + let signature = match Signature::from_base64(idty_elements[1]) { + Ok(sig) => sig, + Err(_) => return None, + }; + let blockstamp = match Blockstamp::from_string(idty_elements[2]) { + Ok(blockstamp) => blockstamp, + Err(_) => return None, + }; + let username = idty_elements[3]; + let idty_doc_builder = IdentityDocumentBuilder { + currency, + username, + blockstamp: &blockstamp, + issuer: &issuer, + }; + Some(idty_doc_builder.build_with_signature(vec![signature])) + } else { + None + } +} diff --git a/dal/parsers/memberships.rs b/dal/parsers/memberships.rs new file mode 100644 index 0000000000000000000000000000000000000000..8db1a052ec09720219c62fefef64f77f6529444a --- /dev/null +++ b/dal/parsers/memberships.rs @@ -0,0 +1,66 @@ +extern crate serde_json; +extern crate sqlite; + +use duniter_crypto::keys::{PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::membership::{ + MembershipDocumentBuilder, MembershipType, +}; +use duniter_documents::blockchain::v10::documents::MembershipDocument; +use duniter_documents::blockchain::DocumentBuilder; +use duniter_documents::Blockstamp; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MembershipParseError { + WrongFormat(), +} + +pub fn parse_memberships( + currency: &str, + membership_type: MembershipType, + json_datas: &str, +) -> Option<Vec<MembershipDocument>> { + let raw_memberships: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + if raw_memberships.is_array() { + return Some( + parse_memberships_from_json_value( + currency, + membership_type, + raw_memberships.as_array().unwrap(), + ).iter() + .map(|m| { + m.clone() + .expect("Fatal error : Fail to parse membership from local DB !") + }) + .collect(), + ); + } + None +} + +pub fn parse_memberships_from_json_value( + currency: &str, + membership_type: MembershipType, + array_memberships: &[serde_json::Value], +) -> Vec<Result<MembershipDocument, MembershipParseError>> { + //let memberships: Vec<MembershipDocument> = Vec::new(); + array_memberships + .iter() + .map(|membership| { + let membership_datas: Vec<&str> = membership.as_str().unwrap().split(':').collect(); + if membership_datas.len() == 5 { + let membership_doc_builder = MembershipDocumentBuilder { + currency, + issuer: &PublicKey::from_base58(membership_datas[0]).unwrap(), + blockstamp: &Blockstamp::from_string(membership_datas[2]).unwrap(), + membership: membership_type, + identity_username: membership_datas[4], + identity_blockstamp: &Blockstamp::from_string(membership_datas[3]).unwrap(), + }; + let membership_sig = Signature::from_base64(membership_datas[1]).unwrap(); + Ok(membership_doc_builder.build_with_signature(vec![membership_sig])) + } else { + Err(MembershipParseError::WrongFormat()) + } + }) + .collect() +} diff --git a/dal/parsers/mod.rs b/dal/parsers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..49a98a57d01502f01c3412b2cbd6e5643ef8e049 --- /dev/null +++ b/dal/parsers/mod.rs @@ -0,0 +1,132 @@ +pub mod blocks; +pub mod certifications; +pub mod excluded; +pub mod identities; +pub mod memberships; +pub mod revoked; +pub mod transactions; + +#[cfg(test)] +mod tests { + use super::transactions::*; + use duniter_crypto::keys::{PublicKey, Signature}; + use duniter_documents::blockchain::v10::documents::transaction::*; + use duniter_documents::blockchain::DocumentBuilder; + use duniter_documents::Blockstamp; + + #[test] + fn parse_json_tx() { + let tx_json = json!({ + "version": 10, + "currency": "g1", + "locktime": 0, + "hash": "3424206EF64C69E5F8C3906AAE571E378A498FCDAE0B85E9405A5205D7148EFE", + "blockstamp": "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1", + "blockstampTime": 0, + "issuers": [ + "51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2" + ], + "inputs": [ + "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496" + ], + "outputs": [ + "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)" + ], + "unlocks": [ + "0:SIG(0)" + ], + "signatures": [ + "5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==" + ], + "comment": "Merci pour la calligraphie ;) de Liam" + }); + + let tx_builder = TransactionDocumentBuilder { + currency: "g1", + blockstamp: &Blockstamp::from_string( + "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1", + ).unwrap(), + locktime: &0, + issuers: &vec![ + PublicKey::from_base58("51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2").unwrap(), + ], + inputs: &vec![ + TransactionInput::parse_from_str( + "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496", + ).unwrap(), + ], + outputs: &vec![ + TransactionOutput::parse_from_str( + "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)", + ).unwrap(), + ], + unlocks: &vec![TransactionInputUnlocks::parse_from_str("0:SIG(0)").unwrap()], + comment: "Merci pour la calligraphie ;) de Liam", + }; + + assert_eq!( + parse_transaction("g1", &tx_json).expect("Fail to parse transaction !"), + tx_builder.build_with_signature(vec![Signature::from_base64("5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==").unwrap()]) + ); + } + + #[test] + fn parse_json_tx2() { + let tx_json = json!({ + "version": 10, + "currency": "g1", + "locktime": 0, + "hash": "F98BF7A8BF82E76F5B69E70CEF0A07A08BFDB03561955EC57B254DB1E958529C", + "blockstamp": "58-00005B9167EBA1E32C6EAD42AE7F72D8F14B765D3C9E47D233B553D47C5AEE0C", + "blockstampTime": 1488990541, + "issuers": [ + "FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD" + ], + "inputs": [ + "1000:0:D:FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD:1" + ], + "outputs": [ + "3:0:SIG(7vU9BMDhN6fBuRa2iK3JRbC6pqQKb4qDMGsFcQuT5cz)", + "997:0:SIG(FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD)" + ], + "unlocks": [ + "0:SIG(0)" + ], + "signatures": [ + "VWbvsiybM4L2X5+o+6lIiuKNw5KrD1yGZqmV+lHtA28XoRUFzochSIgfoUqBsTAaYEHY45vSX917LDXudTEzBg==" + ], + "comment": "Un petit cafe ;-)" + }); + + let tx_builder = TransactionDocumentBuilder { + currency: "g1", + blockstamp: &Blockstamp::from_string( + "58-00005B9167EBA1E32C6EAD42AE7F72D8F14B765D3C9E47D233B553D47C5AEE0C", + ).unwrap(), + locktime: &0, + issuers: &vec![ + PublicKey::from_base58("FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD").unwrap(), + ], + inputs: &vec![ + TransactionInput::parse_from_str( + "1000:0:D:FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD:1", + ).unwrap(), + ], + outputs: &vec![ + TransactionOutput::parse_from_str( + "3:0:SIG(7vU9BMDhN6fBuRa2iK3JRbC6pqQKb4qDMGsFcQuT5cz)", + ).unwrap(), + TransactionOutput::parse_from_str( + "997:0:SIG(FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD)", + ).unwrap(), + ], + unlocks: &vec![TransactionInputUnlocks::parse_from_str("0:SIG(0)").unwrap()], + comment: "Un petit cafe ;-)", + }; + + assert_eq!( + parse_transaction("g1", &tx_json).expect("Fail to parse transaction !"), + tx_builder.build_with_signature(vec![Signature::from_base64("VWbvsiybM4L2X5+o+6lIiuKNw5KrD1yGZqmV+lHtA28XoRUFzochSIgfoUqBsTAaYEHY45vSX917LDXudTEzBg==").unwrap()]) + ); + } +} diff --git a/dal/parsers/revoked.rs b/dal/parsers/revoked.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc24d1b2d1df5a3cba065d6b8ea8baec13dedcd5 --- /dev/null +++ b/dal/parsers/revoked.rs @@ -0,0 +1,69 @@ +extern crate serde_json; + +use duniter_crypto::keys::{ed25519, PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::revocation::RevocationDocumentBuilder; +use duniter_documents::blockchain::v10::documents::{IdentityDocument, RevocationDocument}; +use duniter_documents::blockchain::{Document, DocumentBuilder}; +use duniter_wotb::NodeId; + +use super::super::identity::DALIdentity; +use super::super::DuniterDB; + +use std::collections::HashMap; + +pub fn parse_revocations( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>, + json_datas: &str, +) -> Option<Vec<RevocationDocument>> { + let raw_certifications: serde_json::Value = serde_json::from_str(json_datas).unwrap(); + + if raw_certifications.is_array() { + Some(parse_revocations_from_json_value( + currency, + db, + wotb_index, + block_identities, + raw_certifications.as_array().unwrap(), + )) + } else { + None + } +} + +pub fn parse_revocations_from_json_value( + currency: &str, + db: &DuniterDB, + wotb_index: &HashMap<ed25519::PublicKey, NodeId>, + block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>, + array_revocations: &[serde_json::Value], +) -> Vec<RevocationDocument> { + let mut revocations: Vec<RevocationDocument> = Vec::new(); + for revocation in array_revocations.iter() { + let revocations_datas: Vec<&str> = revocation.as_str().unwrap().split(':').collect(); + if revocations_datas.len() == 2 { + let idty_pubkey: ed25519::PublicKey = + PublicKey::from_base58(revocations_datas[0]).unwrap(); + let idty_doc: IdentityDocument = match block_identities.get(&idty_pubkey) { + Some(idty_doc) => idty_doc.clone(), + None => { + let idty_wotb_id = wotb_index.get(&idty_pubkey).unwrap(); + let dal_idty = DALIdentity::get_identity(currency, db, idty_wotb_id).unwrap(); + dal_idty.idty_doc + } + }; + let revoc_doc_builder = RevocationDocumentBuilder { + currency, + issuer: &idty_pubkey, + identity_username: idty_doc.username(), + identity_blockstamp: &idty_doc.blockstamp(), + identity_sig: &idty_doc.signatures()[0], + }; + let revoc_sig = Signature::from_base64(revocations_datas[1]).unwrap(); + revocations.push(revoc_doc_builder.build_with_signature(vec![revoc_sig])); + } + } + revocations +} diff --git a/dal/parsers/transactions.rs b/dal/parsers/transactions.rs new file mode 100644 index 0000000000000000000000000000000000000000..75146df4b1f32e6b842c6024616c1dd35dfbcb3d --- /dev/null +++ b/dal/parsers/transactions.rs @@ -0,0 +1,233 @@ +extern crate serde; +extern crate serde_json; + +use duniter_crypto::keys::{PublicKey, Signature}; +use duniter_documents::blockchain::v10::documents::transaction::{ + TransactionDocument, TransactionDocumentBuilder, TransactionInput, TransactionInputUnlocks, + TransactionOutput, +}; +use duniter_documents::blockchain::DocumentBuilder; +use duniter_documents::Blockstamp; + +pub fn parse_compact_transactions( + currency: &str, + json_datas: &str, +) -> Option<Vec<TransactionDocument>> { + let raw_transactions: serde_json::Value = + serde_json::from_str(json_datas).expect("Fatal error : fail to jsonifie tx from DB !"); + + if raw_transactions.is_array() { + let mut transactions = Vec::new(); + for transaction in raw_transactions.as_array().unwrap() { + let transaction_lines: Vec<&str> = transaction + .as_str() + .expect("Fail to parse tx from DB !") + .split('$') + .collect(); + let tx_headers: Vec<&str> = transaction_lines[0].split(':').collect(); + let issuers_count = tx_headers[2] + .parse() + .expect("Fail to parse tx header NB_ISSUERS !"); + let inputs_count = tx_headers[3] + .parse() + .expect("Fail to parse tx header NB_INPUTS !"); + let unlocks_count = tx_headers[4] + .parse() + .expect("Fail to parse tx header NB_UNLOCKS !"); + let outputs_count = tx_headers[5] + .parse() + .expect("Fail to parse tx header NB_OUTPUTS !"); + let has_comment: usize = tx_headers[6] + .parse() + .expect("Fail to parse tx header HAS_COMMENT !"); + let locktime = tx_headers[7] + .parse() + .expect("Fail to parse tx header LOCKTIME !"); + let blockstamp = Blockstamp::from_string(transaction_lines[1]) + .expect("Fail to parse tx BLOCKSTAMP !"); + let mut line = 2; + let mut issuers = Vec::new(); + for _ in 0..issuers_count { + issuers.push( + PublicKey::from_base58(transaction_lines[line]) + .expect("Fail to parse tx issuer !"), + ); + line += 1; + } + let mut inputs = Vec::new(); + for _ in 0..inputs_count { + inputs.push( + TransactionInput::parse_from_str(transaction_lines[line]) + .expect("Fail to parse tx issuer !"), + ); + line += 1; + } + let mut unlocks = Vec::new(); + for _ in 0..unlocks_count { + unlocks.push( + TransactionInputUnlocks::parse_from_str(transaction_lines[line]) + .expect("Fail to parse tx issuer !"), + ); + line += 1; + } + let mut outputs = Vec::new(); + for _ in 0..outputs_count { + outputs.push( + TransactionOutput::parse_from_str(transaction_lines[line]) + .expect("Fail to parse tx issuer !"), + ); + line += 1; + } + let mut comment = ""; + if has_comment == 1 { + comment = transaction_lines[line]; + line += 1; + } + let mut signatures = Vec::new(); + for _ in 0..issuers_count { + signatures.push( + Signature::from_base64(transaction_lines[line]) + .expect("Fail to parse tx signature !"), + ); + line += 1; + } + let tx_doc_builder = TransactionDocumentBuilder { + currency, + blockstamp: &blockstamp, + locktime: &locktime, + issuers: &issuers, + inputs: &inputs, + unlocks: &unlocks, + outputs: &outputs, + comment, + }; + transactions.push(tx_doc_builder.build_with_signature(signatures)); + } + Some(transactions) + } else { + None + } +} + +pub fn parse_transaction( + currency: &str, + source: &serde_json::Value, +) -> Option<TransactionDocument> { + //debug!("transaction={:#?}", source); + let blockstamp = match Blockstamp::from_string(source.get("blockstamp")?.as_str()?) { + Ok(blockstamp) => blockstamp, + Err(_) => { + return None; + } + }; + let locktime = source.get("locktime")?.as_i64()? as u64; + let issuers_array = source.get("issuers")?.as_array()?; + let mut issuers = Vec::with_capacity(issuers_array.len()); + for issuer in issuers_array { + match PublicKey::from_base58(issuer.as_str()?) { + Ok(pubkey) => issuers.push(pubkey), + Err(_) => { + return None; + } + } + } + let inputs_array = source.get("inputs")?.as_array()?; + let mut inputs = Vec::with_capacity(inputs_array.len()); + for input in inputs_array { + let input_str = input.as_str()?; + match TransactionInput::parse_from_str(input_str) { + Ok(input) => inputs.push(input), + Err(_) => { + return None; + } + } + } + let unlocks_array = source.get("unlocks")?.as_array()?; + let mut unlocks = Vec::with_capacity(unlocks_array.len()); + for unlock in unlocks_array { + match TransactionInputUnlocks::parse_from_str(unlock.as_str()?) { + Ok(unlock) => unlocks.push(unlock), + Err(_) => { + return None; + } + } + } + let outputs_array = source.get("outputs")?.as_array()?; + let mut outputs = Vec::with_capacity(outputs_array.len()); + for output in outputs_array { + match TransactionOutput::parse_from_str(output.as_str()?) { + Ok(output) => outputs.push(output), + Err(_) => { + return None; + } + } + } + let signatures_array = source.get("signatures")?.as_array()?; + let mut signatures = Vec::with_capacity(signatures_array.len()); + for signature in signatures_array { + match Signature::from_base64(signature.as_str()?) { + Ok(signature) => signatures.push(signature), + Err(_) => { + return None; + } + } + } + let comment = source.get("comment")?.as_str()?; + + let tx_doc_builder = TransactionDocumentBuilder { + currency, + blockstamp: &blockstamp, + locktime: &locktime, + issuers: &issuers, + inputs: &inputs, + unlocks: &unlocks, + outputs: &outputs, + comment, + }; + Some(tx_doc_builder.build_with_signature(signatures)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_compact_tx() { + let compact_txs = "[\"TX:10:1:1:1:1:1:0$\ +112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1$\ +51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2$\ +1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496$\ +0:SIG(0)$\ +1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)$\ +Merci pour la calligraphie ;) de Liam$\ +5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==\"]"; + + let tx_builder = TransactionDocumentBuilder { + currency: "g1", + blockstamp: &Blockstamp::from_string( + "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1", + ).unwrap(), + locktime: &0, + issuers: &vec![ + PublicKey::from_base58("51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2").unwrap(), + ], + inputs: &vec![ + TransactionInput::parse_from_str( + "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496", + ).unwrap(), + ], + outputs: &vec![ + TransactionOutput::parse_from_str( + "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)", + ).unwrap(), + ], + unlocks: &vec![TransactionInputUnlocks::parse_from_str("0:SIG(0)").unwrap()], + comment: "Merci pour la calligraphie ;) de Liam", + }; + + assert_eq!( + parse_compact_transactions("g1", compact_txs).expect("Fail to parse compact transactions !"), + vec![tx_builder.build_with_signature(vec![Signature::from_base64("5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==").unwrap()])] + ); + } +} diff --git a/dal/test.db b/dal/test.db new file mode 100644 index 0000000000000000000000000000000000000000..ab254abc64c4f280bdde96b863cdc6420fddff67 Binary files /dev/null and b/dal/test.db differ diff --git a/dal/tools.rs b/dal/tools.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a4ecee0aa5f649b94ccd172c0eee2d326659533 --- /dev/null +++ b/dal/tools.rs @@ -0,0 +1,137 @@ +extern crate duniter_wotb; + +use duniter_wotb::operations::centrality::{ + CentralitiesCalculator, UlrikBrandesCentralityCalculator, +}; +use duniter_wotb::operations::distance::{ + DistanceCalculator, RustyDistanceCalculator, WotDistance, WotDistanceParameters, +}; +use duniter_wotb::operations::path::{PathFinder, RustyPathFinder}; +use duniter_wotb::{NodeId, WebOfTrust}; + +pub static CENTRALITY_CALCULATOR: UlrikBrandesCentralityCalculator = + UlrikBrandesCentralityCalculator {}; +pub static DISTANCE_CALCULATOR: RustyDistanceCalculator = RustyDistanceCalculator {}; +pub static PATH_FINDER: RustyPathFinder = RustyPathFinder {}; + +pub fn get_sentry_requirement(members_count: usize, step_max: u32) -> u32 { + match step_max { + 5 => { + if members_count < 33 { + 2 + } else if members_count < 244 { + 3 + } else if members_count < 1025 { + 4 + } else if members_count < 3126 { + 5 + } else if members_count < 7777 { + 6 + } else { + panic!("get_sentry_requirement not define for members_count greater than 7777 !"); + } + } + _ => panic!("get_sentry_requirement not define for step_max != 5 !"), + } +} + +pub fn calculate_average_density<T: WebOfTrust>(wot: &T) -> usize { + let enabled_members = wot.get_enabled(); + let enabled_members_count = enabled_members.len(); + let mut count_actives_links: usize = 0; + for member in &enabled_members { + count_actives_links += wot.issued_count(*member).unwrap(); + } + ((count_actives_links as f32 / enabled_members_count as f32) * 1_000.0) as usize +} + +pub fn compute_distances<T: WebOfTrust + Sync>( + wot: &T, + sentry_requirement: u32, + step_max: u32, + x_percent: f64, +) -> (usize, Vec<usize>, usize, Vec<usize>) { + let members_count = wot.get_enabled().len(); + let mut distances = Vec::new(); + let mut average_distance: usize = 0; + let mut connectivities = Vec::new(); + let mut average_connectivity: usize = 0; + for i in 0..wot.size() { + let distance_datas: WotDistance = DISTANCE_CALCULATOR + .compute_distance( + wot, + WotDistanceParameters { + node: NodeId(i), + sentry_requirement, + step_max, + x_percent, + }, + ) + .expect("Fatal Error: compute_distance return None !"); + let mut distance = ((f64::from(distance_datas.success) + / (x_percent * f64::from(distance_datas.sentries))) * 100.0) + as usize; + distances.push(distance); + average_distance += distance; + let mut connectivity = + ((f64::from(distance_datas.success - distance_datas.success_at_border) + / (x_percent * f64::from(distance_datas.sentries))) * 100.0) as usize; + connectivities.push(connectivity); + average_connectivity += connectivity; + } + average_distance /= members_count; + average_connectivity /= members_count; + ( + average_distance, + distances, + average_connectivity, + connectivities, + ) +} + +pub fn calculate_distance_stress_centralities<T: WebOfTrust>(wot: &T, step_max: u32) -> Vec<u64> { + CENTRALITY_CALCULATOR.distance_stress_centralities(wot, step_max as usize) +} + +pub fn calculate_centralities_degree<T: WebOfTrust>(wot: &T, step_max: u32) -> Vec<usize> { + let wot_size = wot.size(); + let members_count = wot.get_enabled().len() as u64; + let oriented_couples_count: u64 = members_count * (members_count - 1); + let mut centralities: Vec<u64> = vec![0; wot_size]; + for i in 0..wot_size { + for j in 0..wot_size { + let mut paths = PATH_FINDER.find_paths(wot, NodeId(i), NodeId(j), step_max); + if paths.is_empty() { + break; + } + //paths.sort_unstable_by(|a, b| a.len().cmp(&b.len())); + let shortest_path_len = paths[0].len(); + let mut intermediate_members: Vec<NodeId> = Vec::new(); + if shortest_path_len > 2 { + for path in paths { + //if path.len() == shortest_path_len { + for node_id in &path { + if !intermediate_members.contains(node_id) { + intermediate_members.push(*node_id); + } + } + /*} else { + break; + }*/ + } + } + let centralities_copy = centralities.clone(); + for node_id in intermediate_members { + let centrality = ¢ralities_copy[node_id.0]; + if let Some(tmp) = centralities.get_mut(node_id.0) { + *tmp = *centrality + 1; + } + } + } + } + let mut relative_centralities = Vec::with_capacity(wot_size); + for centrality in centralities { + relative_centralities.push((centrality * 100_000 / oriented_couples_count) as usize); + } + relative_centralities +} diff --git a/dal/writers/block.rs b/dal/writers/block.rs new file mode 100644 index 0000000000000000000000000000000000000000..906dbdfe6a55b372548f6641753bb0de94548260 --- /dev/null +++ b/dal/writers/block.rs @@ -0,0 +1,77 @@ +extern crate duniter_crypto; +extern crate duniter_documents; +extern crate duniter_wotb; +extern crate serde; +extern crate serde_json; +extern crate sqlite; + +use self::duniter_documents::blockchain::v10::documents::BlockDocument; +use self::duniter_documents::blockchain::Document; +use super::super::block::DALBlock; +use super::super::DuniterDB; + +pub fn write_network_block( + db: &DuniterDB, + block: &BlockDocument, + fork: usize, + isolate: bool, + joiners: &Vec<serde_json::Value>, + actives: &Vec<serde_json::Value>, + leavers: &Vec<serde_json::Value>, + revoked: &Vec<serde_json::Value>, + certifications: &Vec<serde_json::Value>, +) { + db.0 + .execute( + format!("INSERT INTO blocks (fork, isolate, version, nonce, number, pow_min, time, median_time, members_count, monetary_mass, unit_base, issuers_count, issuers_frame, issuers_frame_var, currency, issuer, signature, hash, previous_hash, inner_hash, dividend, identities, joiners, actives, leavers, revoked, excluded, certifications, transactions) VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');", + fork, if isolate { 1 } else { 0 }, 10, + block.nonce, block.number, block.pow_min, block.time, block.median_time, + block.members_count, block.monetary_mass, block.unit_base, block.issuers_count, + block.issuers_frame, block.issuers_frame_var, block.currency, block.issuers[0], + block.signatures[0].to_string(), block.hash.unwrap().0.to_string(), + block.previous_hash.to_string(), block.inner_hash.unwrap().to_string(), + block.dividend.unwrap_or(0), + serde_json::to_string(&block.identities).unwrap(), + serde_json::to_string(joiners).unwrap(), serde_json::to_string(actives).unwrap(), + serde_json::to_string(leavers).unwrap(), serde_json::to_string(revoked).unwrap(), + serde_json::to_string(&block.excluded).unwrap(), serde_json::to_string(certifications).unwrap(), + serde_json::to_string(&block.transactions).unwrap() + )) + .unwrap(); +} + +pub fn write(db: &DuniterDB, block: &BlockDocument, fork: usize, isolate: bool) { + let mut insert = true; + if fork == 0 { + if let Some(_fork) = DALBlock::get_block_fork(db, &block.blockstamp()) { + insert = false; + db.0 + .execute(format!( + "UPDATE blocks SET fork=0 WHERE number={} AND hash='{}';", + block.number, + block.hash.unwrap().0.to_string() + )) + .unwrap(); + } + } + + if insert { + db.0 + .execute( + format!("INSERT INTO blocks (fork, isolate, version, nonce, number, pow_min, time, median_time, members_count, monetary_mass, unit_base, issuers_count, issuers_frame, issuers_frame_var, currency, issuer, signature, hash, previous_hash, inner_hash, dividend, identities, joiners, actives, leavers, revoked, excluded, certifications, transactions) VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');", + fork, if isolate { 1 } else { 0 }, 10, + block.nonce, block.number, block.pow_min, block.time, block.median_time, + block.members_count, block.monetary_mass, block.unit_base, block.issuers_count, + block.issuers_frame, block.issuers_frame_var, block.currency, block.issuers[0], + block.signatures[0].to_string(), block.hash.unwrap().0.to_string(), + block.previous_hash.to_string(), block.inner_hash.unwrap().to_string(), + block.dividend.unwrap_or(0), serde_json::to_string(&block.identities).unwrap(), + serde_json::to_string(&block.joiners).unwrap(), serde_json::to_string(&block.actives).unwrap(), + serde_json::to_string(&block.leavers).unwrap(), serde_json::to_string(&block.revoked).unwrap(), + serde_json::to_string(&block.excluded).unwrap(), serde_json::to_string(&block.certifications).unwrap(), + serde_json::to_string(&block.transactions).unwrap() + ), + ) + .unwrap(); + } +} diff --git a/dal/writers/certification.rs b/dal/writers/certification.rs new file mode 100644 index 0000000000000000000000000000000000000000..511c776612dba820ca59d7e7860580cba3b703a2 --- /dev/null +++ b/dal/writers/certification.rs @@ -0,0 +1,53 @@ +extern crate serde; +extern crate serde_json; +extern crate sqlite; + +use super::super::DuniterDB; +use duniter_crypto::keys::ed25519; +use duniter_documents::blockchain::v10::documents::CertificationDocument; +use duniter_documents::blockchain::Document; +use duniter_documents::Blockstamp; + +pub fn write_certification( + cert: &CertificationDocument, + db: &DuniterDB, + written_blockstamp: Blockstamp, + written_timestamp: u64, +) { + let mut cursor = db + .0 + .prepare("SELECT median_time FROM blocks WHERE number=? AND fork=0 LIMIT 1;") + .unwrap() + .cursor(); + + cursor + .bind(&[sqlite::Value::Integer(cert.blockstamp().id.0 as i64)]) + .expect("convert blockstamp to timestamp failure at step 1 !"); + + let mut created_timestamp: i64 = 0; + if let Some(row) = cursor + .next() + .expect("convert blockstamp to timestamp failure at step 2 !") + { + created_timestamp = row[0].as_integer().unwrap(); + } + + db.0 + .execute( + format!("INSERT INTO certifications (pubkey_from, pubkey_to, created_on, signature, written_on, expires_on, chainable_on) VALUES ('{}', '{}', '{}', '{}', '{}', {}, {});", + cert.issuers()[0], cert.target(), cert.blockstamp().id.0, cert.signatures()[0], + written_blockstamp.to_string(), + created_timestamp+super::super::constants::G1_PARAMS.sig_validity, + written_timestamp+super::super::constants::G1_PARAMS.sig_period + )) + .unwrap(); +} + +pub fn remove_certification(from: ed25519::PublicKey, to: ed25519::PublicKey, db: &DuniterDB) { + db.0 + .execute(format!( + "DELETE FROM certifications WHERE pubkey_from={} AND pubkey_to={}", + from, to + )) + .unwrap(); +} diff --git a/dal/writers/identity.rs b/dal/writers/identity.rs new file mode 100644 index 0000000000000000000000000000000000000000..c000d945cabfc3edf74f155dad765dad2b925d48 --- /dev/null +++ b/dal/writers/identity.rs @@ -0,0 +1,34 @@ +extern crate sqlite; + +use super::super::identity::DALIdentity; +use super::super::DuniterDB; +use duniter_documents::blockchain::Document; +use duniter_documents::Blockstamp; + +pub fn write( + idty: &DALIdentity, + db: &DuniterDB, + _written_blockstamp: Blockstamp, + _written_timestamp: u64, +) { + let expired_on = match idty.expired_on { + Some(ref tmp) => tmp.to_string(), + None => String::from(""), + }; + let revoked_on = match idty.revoked_on { + Some(ref tmp) => tmp.to_string(), + None => String::from(""), + }; + db.0 + .execute( + format!("INSERT INTO identities (wotb_id, uid, pubkey, hash, sig, state, created_on, joined_on, penultimate_renewed_on, last_renewed_on, expires_on, revokes_on, expired_on, revoked_on) VALUES ({}, '{}', '{}', '{}', '{}', {}, '{}', '{}', '{}', '{}', {}, {}, '{}', '{}');", + idty.wotb_id.0, idty.idty_doc.username(), idty.idty_doc.issuers()[0], idty.hash, + idty.idty_doc.signatures()[0], idty.state, + idty.idty_doc.blockstamp().to_string(), + idty.joined_on.to_string(), + idty.penultimate_renewed_on.to_string(), + idty.last_renewed_on.to_string(), + idty.expires_on, idty.revokes_on, expired_on, revoked_on + )) + .unwrap(); +} diff --git a/dal/writers/mod.rs b/dal/writers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..41219969b14c6d6fc9368ea0e3eb77513d41ebd6 --- /dev/null +++ b/dal/writers/mod.rs @@ -0,0 +1,3 @@ +pub mod block; +pub mod certification; +pub mod identity;