Commit d63807b6 authored by Éloïs's avatar Éloïs

wip: #76

parent 23233c41
This diff is collapsed.
......@@ -18,9 +18,12 @@ duniter-module = { path = "../module" }
duniter-network = { path = "../network" }
duniter-wotb = { path = "../wotb" }
log = "0.4.1"
num_cpus = "1.8.0"
pbr = "1.0.1"
rand = "0.4.2"
rustbreak = {version = "2.0.0-rc2", features = ["bin_enc"]}
serde = "1.0.57"
serde_derive = "1.0.57"
serde_json = "1.0.17"
sqlite = "0.23.9"
\ No newline at end of file
sqlite = "0.23.9"
threadpool = "1.7.1"
\ No newline at end of file
// 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/>.
use std::collections::HashMap;
use apply_valid_block::*;
use duniter_crypto::keys::*;
use duniter_dal::block::DALBlock;
use duniter_dal::*;
use duniter_documents::blockchain::Document;
use duniter_documents::{BlockHash, BlockId, Blockstamp, PreviousBlockstamp};
use duniter_network::NetworkBlock;
use *;
#[derive(Debug, Copy, Clone)]
pub enum BlockError {
BlockVersionNotSupported(),
CompletedBlockError(CompletedBlockError),
DALError(DALError),
//CheckBlockError(),
ApplyValidBlockError(ApplyValidBlockError),
NoForkAvailable(),
UnknowError(),
}
impl From<CompletedBlockError> for BlockError {
fn from(err: CompletedBlockError) -> Self {
BlockError::CompletedBlockError(err)
}
}
impl From<DALError> for BlockError {
fn from(err: DALError) -> Self {
BlockError::DALError(err)
}
}
impl From<ApplyValidBlockError> for BlockError {
fn from(err: ApplyValidBlockError) -> Self {
BlockError::ApplyValidBlockError(err)
}
}
pub fn check_and_apply_block<W: WebOfTrust + Sync>(
blocks_databases: &BlocksV10DBs,
certs_db: &BinFileDB<CertsExpirV10Datas>,
block: &Block,
current_blockstamp: &Blockstamp,
wotb_index: &mut HashMap<PubKey, NodeId>,
wot: &mut W,
forks_states: &[ForkStatus],
) -> Result<ValidBlockApplyReqs, BlockError> {
let (block_doc, already_have_block) = match *block {
Block::NetworkBlock(network_block) => match *network_block {
NetworkBlock::V10(ref network_block_v10) => {
let already_have_block = DALBlock::already_have_block(
&blocks_databases.blockchain_db,
&blocks_databases.forks_blocks_db,
network_block_v10.uncompleted_block_doc.blockstamp(),
)?;
(&network_block_v10.uncompleted_block_doc, already_have_block)
}
_ => return Err(BlockError::BlockVersionNotSupported()),
},
Block::LocalBlock(block_doc) => (block_doc, true),
};
if (block_doc.number.0 == current_blockstamp.id.0 + 1
&& block_doc.previous_hash.to_string() == current_blockstamp.hash.0.to_string())
|| (block_doc.number.0 == 0 && *current_blockstamp == Blockstamp::default())
{
debug!(
"stackable_block : block {} chainable !",
block_doc.blockstamp()
);
// Detect expire_certs
let blocks_expiring = Vec::with_capacity(0);
let expire_certs = duniter_dal::certs::find_expire_certs(certs_db, blocks_expiring)?;
// Try stack up block
let mut old_fork_id = None;
let block_doc = match *block {
Block::NetworkBlock(network_block) => complete_network_block(network_block)?,
Block::LocalBlock(block_doc) => {
old_fork_id = duniter_dal::block::get_fork_id_of_blockstamp(
&blocks_databases.forks_blocks_db,
&block_doc.blockstamp(),
)?;
block_doc.clone()
}
};
return Ok(apply_valid_block(
&block_doc,
wotb_index,
wot,
&expire_certs,
old_fork_id,
)?);
} else if !already_have_block
&& (block_doc.number.0 >= current_blockstamp.id.0
|| (current_blockstamp.id.0 - block_doc.number.0) < 100)
{
debug!(
"stackable_block : block {} not chainable, store this for future !",
block_doc.blockstamp()
);
let (fork_id, new_fork) = DALBlock::assign_fork_to_new_block(
&blocks_databases.forks_db,
&PreviousBlockstamp {
id: BlockId(block_doc.number.0 - 1),
hash: BlockHash(block_doc.previous_hash),
},
&block_doc
.hash
.expect("Try to get hash of an uncompleted or reduce block"),
)?;
if let Some(fork_id) = fork_id {
let mut isolate = true;
let fork_state = if new_fork {
ForkStatus::Isolate()
} else {
forks_states[fork_id.0]
};
match fork_state {
ForkStatus::Stackable(_) | ForkStatus::RollBack(_, _) | ForkStatus::TooOld(_) => {
isolate = false
}
_ => {}
}
match *block {
Block::NetworkBlock(network_block) => {
// Completed network block
let block_doc = complete_network_block(network_block)?;
let dal_block = DALBlock {
fork_id,
isolate,
block: block_doc,
expire_certs: None,
};
duniter_dal::writers::block::write(
&blocks_databases.blockchain_db,
&blocks_databases.forks_db,
&blocks_databases.forks_blocks_db,
&dal_block,
None,
false,
).expect("duniter_dal::writers::block::write() : DALError")
}
Block::LocalBlock(block_doc) => {
let old_fork_id = None;
let dal_block = DALBlock {
fork_id,
isolate,
block: block_doc.clone(),
expire_certs: None,
};
duniter_dal::writers::block::write(
&blocks_databases.blockchain_db,
&blocks_databases.forks_db,
&blocks_databases.forks_blocks_db,
&dal_block,
old_fork_id,
false,
).expect("duniter_dal::writers::block::write() : DALError")
}
};
} else {
return Err(BlockError::NoForkAvailable());
}
} else {
debug!(
"stackable_block : block {} not chainable and already stored !",
block_doc.blockstamp()
);
}
Err(BlockError::UnknowError())
}
cyclomatic-complexity-threshold = 36
\ No newline at end of file
cyclomatic-complexity-threshold = 37
\ No newline at end of file
// 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/>.
use duniter_crypto::keys::*;
use duniter_dal::identity::DALIdentity;
use duniter_documents::blockchain::v10::documents::transaction::*;
use duniter_documents::Blockstamp;
use duniter_module::DuniterConf;
use duniter_wotb::data::rusty::RustyWebOfTrust;
use std::time::*;
use *;
#[derive(Debug, Clone)]
/// Query for wot databases explorer
pub enum DBExWotQuery {
/// Ask member datas
MemberDatas(String),
}
#[derive(Debug, Clone)]
/// Query for tx databases explorer
pub enum DBExTxQuery {
/// Ask balance of an address (pubkey or uid)
Balance(String),
}
#[derive(Debug, Clone)]
/// Query for databases explorer
pub enum DBExQuery {
/// Wot query
WotQuery(DBExWotQuery),
/// Tx query
TxQuery(DBExTxQuery),
}
pub fn dbex(conf: &DuniterConf, query: &DBExQuery) {
match *query {
DBExQuery::WotQuery(ref wot_query) => dbex_wot(conf, wot_query),
DBExQuery::TxQuery(ref tx_query) => dbex_tx(conf, tx_query),
}
}
pub fn dbex_tx(conf: &DuniterConf, query: &DBExTxQuery) {
// Get db path
let db_path = duniter_conf::get_blockchain_db_path(conf.profile().as_str(), &conf.currency());
// Open databases
let load_dbs_begin = SystemTime::now();
//let blocks_databases = BlocksV10DBs::open(&db_path, false);
let currency_databases = CurrencyV10DBs::open(&db_path, false);
let wot_databases = WotsV10DBs::open(&db_path, false);
let load_dbs_duration = SystemTime::now()
.duration_since(load_dbs_begin)
.expect("duration_since error !");
println!(
"Databases loaded in {}.{:03} seconds.",
load_dbs_duration.as_secs(),
load_dbs_duration.subsec_nanos() / 1_000_000
);
let req_process_begin = SystemTime::now();
match *query {
DBExTxQuery::Balance(ref address_str) => {
let pubkey = if let Ok(ed25519_pubkey) = ed25519::PublicKey::from_base58(address_str) {
PubKey::Ed25519(ed25519_pubkey)
} else if let Some(pubkey) = duniter_dal::identity::get_pubkey_from_uid(
&wot_databases.identities_db,
address_str,
).expect("get_uid : DALError")
{
pubkey
} else {
println!("This address doesn't exist !");
return;
};
let address =
TransactionOutputConditionGroup::Single(TransactionOutputCondition::Sig(pubkey));
let address_balance = duniter_dal::balance::get_address_balance(
&currency_databases.balances_db,
&address,
).expect("get_address_balance : DALError")
.expect("Address not found in balances DB.");
println!(
"Balance={},{} Ğ1",
(address_balance.0).0 / 100,
(address_balance.0).0 % 100
);
}
}
let req_process_duration = SystemTime::now()
.duration_since(req_process_begin)
.expect("duration_since error");
println!(
"Request processed in {}.{:06} seconds.",
req_process_duration.as_secs(),
req_process_duration.subsec_nanos() / 1_000
);
}
pub fn dbex_wot(conf: &DuniterConf, query: &DBExWotQuery) {
// Get db path
let db_path = duniter_conf::get_blockchain_db_path(conf.profile().as_str(), &conf.currency());
// Open databases
let load_dbs_begin = SystemTime::now();
//let blocks_databases = BlocksV10DBs::open(&db_path, false);
let wot_databases = WotsV10DBs::open(&db_path, false);
let load_dbs_duration = SystemTime::now()
.duration_since(load_dbs_begin)
.expect("duration_since error");
println!(
"Databases loaded in {}.{:03} seconds.",
load_dbs_duration.as_secs(),
load_dbs_duration.subsec_nanos() / 1_000_000
);
let req_process_begin = SystemTime::now();
// get wot_index
let wot_index = DALIdentity::get_wotb_index(&wot_databases.identities_db).expect("DALError");
// get wot_reverse_index
let wot_reverse_index: HashMap<NodeId, &PubKey> =
wot_index.iter().map(|(p, id)| (*id, p)).collect();
// Get wot path
let wot_path = duniter_conf::get_wot_path(conf.profile().clone().to_string(), &conf.currency());
// Open wot file
let (wot, wot_blockstamp): (RustyWebOfTrust, Blockstamp) =
open_wot_file(&WOT_FILE_FORMATER, &wot_path, *INFINITE_SIG_STOCK);
// Print wot blockstamp
println!("Wot : Current blockstamp = {}.", wot_blockstamp);
// Print members count
let members_count = wot.get_enabled().len();
println!(" Members count = {}.", members_count);
match *query {
DBExWotQuery::MemberDatas(ref uid) => {
if let Some(pubkey) =
duniter_dal::identity::get_pubkey_from_uid(&wot_databases.identities_db, uid)
.expect("get_pubkey_from_uid() : DALError !")
{
let wot_id = wot_index[&pubkey];
println!(
"{} : wot_id={}, pubkey={}.",
uid,
wot_id.0,
pubkey.to_string()
);
let sources = wot
.get_links_source(wot_id)
.expect("Fail to get links source !");
println!("Certifiers : {}", sources.len());
for (i, source) in sources.iter().enumerate() {
let source_uid = duniter_dal::identity::get_uid(
&wot_databases.identities_db,
*(wot_reverse_index[&source]),
).expect("get_uid() : DALError")
.expect("Not found source_uid !");
println!("{}: {}", i + 1, source_uid);
}
} else {
println!("Uid \"{}\" not found !", uid);
}
}
}
let req_process_duration = SystemTime::now()
.duration_since(req_process_begin)
.expect("duration_since error");
println!(
"Request processed in {}.{:06} seconds.",
req_process_duration.as_secs(),
req_process_duration.subsec_nanos() / 1_000
);
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -327,19 +327,13 @@ pub fn write_conf_file(file_path: &PathBuf, conf: &DuniterConf) -> Result<(), st
}
/// Returns the path to the database containing the blockchain
pub fn get_db_path(profile: &str, currency: &Currency, sync: bool) -> PathBuf {
if sync {
let mut db_path = PathBuf::new();
let mut db_name = String::from(profile);
db_name.push_str("_durs.db");
db_path.push("/dev/shm");
db_path.push(db_name);
db_path
} else {
let mut db_path = datas_path(profile, &currency);
db_path.push("blockchain.db");
db_path
pub fn get_blockchain_db_path(profile: &str, currency: &Currency) -> PathBuf {
let mut db_path = datas_path(profile, &currency);
db_path.push("blockchain/");
if !db_path.as_path().exists() {
fs::create_dir(db_path.as_path()).expect("Impossible to create blockchain dir !");
}
db_path
}
/// Returns the path to the binary file containing the state of the web of trust
......
......@@ -39,18 +39,29 @@ subcommands:
short: c
long: cautious
help: cautious mode (check all protocol rules, very slow)
- msync_ts:
about: synchronization in memory mode via a duniter-ts database
- dbex:
about: durs databases explorer
version: "0.1.0"
author: Elois L. <elois@duniter.org>
args:
- TS_PROFILE:
help: Set the ts profile to use
index: 1
- cautious:
short: c
long: cautious
help: cautious mode (check all protocol rules, very slow)
subcommands:
- member:
about: durs databases explorer (wot members datas)
version: "0.1.0"
author: Elois L. <elois@duniter.org>
args:
- UID:
help : choose member uid
index: 1
required: true
- balance:
about: durs databases explorer (balances datas)
version: "0.1.0"
author: Elois L. <elois@duniter.org>
args:
- ADDRESS:
help : public key or uid
index: 1
required: true
- reset:
about: reset data or conf or all
version: "0.1.0"
......
......@@ -38,9 +38,8 @@ extern crate simplelog;
extern crate sqlite;
extern crate threadpool;
use self::threadpool::ThreadPool;
use clap::{App, ArgMatches};
use duniter_blockchain::BlockchainModule;
use duniter_blockchain::{BlockchainModule, DBExQuery, DBExTxQuery, DBExWotQuery};
use duniter_conf::DuniterKeyPairs;
use duniter_message::DuniterMessage;
use duniter_module::*;
......@@ -52,6 +51,7 @@ use std::fs::{File, OpenOptions};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use threadpool::ThreadPool;
#[derive(Debug)]
/// Duniter Core Datas
......@@ -124,6 +124,21 @@ impl DuniterCore {
let ts_profile = matches.value_of("TS_PROFILE").unwrap_or("duniter_default");
sync_ts(&conf, ts_profile, matches.is_present("cautious"));
None
} else if let Some(matches) = cli_args.subcommand_matches("dbex") {
if let Some(member_matches) = matches.subcommand_matches("member") {
let uid = member_matches.value_of("UID").unwrap_or("");
dbex(
&conf,
&DBExQuery::WotQuery(DBExWotQuery::MemberDatas(String::from(uid))),
);
} else if let Some(balance_matches) = matches.subcommand_matches("balance") {
let address = balance_matches.value_of("ADDRESS").unwrap_or("");
dbex(
&conf,
&DBExQuery::TxQuery(DBExTxQuery::Balance(String::from(address))),
);
}
None
} else if let Some(matches) = cli_args.subcommand_matches("reset") {
let mut profile_path = match env::home_dir() {
Some(path) => path,
......@@ -135,7 +150,10 @@ impl DuniterCore {
if !profile_path.as_path().exists() {
panic!(format!("Error : {} profile don't exist !", profile));
}
match matches.value_of("DATAS_TYPE").unwrap() {
match matches
.value_of("DATAS_TYPE")
.expect("cli param DATAS_TYPE is missing !")
{
"data" => {
let mut currency_datas_path = profile_path.clone();
currency_datas_path.push("g1");
......@@ -189,7 +207,6 @@ impl DuniterCore {
let mut blockchain_module = BlockchainModule::load_blockchain_conf(
&self.conf,
RequiredKeysContent::MemberKeyPair(None),
false,
);
info!("Success to load Blockchain module.");
......@@ -265,7 +282,7 @@ pub fn start(
let mut modules_senders: Vec<mpsc::Sender<DuniterMessage>> = Vec::new();
let mut modules_count_expected = None;
while modules_count_expected.is_none()
|| modules_senders.len() < modules_count_expected.unwrap() + 1
|| modules_senders.len() < modules_count_expected.expect("safe unwrap") + 1
{
match main_receiver.recv_timeout(Duration::from_secs(20)) {
Ok(mess) => {
......@@ -363,7 +380,13 @@ pub fn start(
/// Launch synchronisation from a duniter-ts database
pub fn sync_ts(conf: &DuniterConf, ts_profile: &str, cautious: bool) {
// Launch sync-ts
BlockchainModule::sync_ts(&conf, ts_profile, cautious);
BlockchainModule::sync_ts(conf, ts_profile, cautious);
}
/// Launch databases explorer
pub fn dbex(conf: &DuniterConf, query: &DBExQuery) {
// Launch databases explorer
BlockchainModule::dbex(conf, query);
}
/// Initialize logger
......@@ -418,20 +441,17 @@ pub fn init_logger(profile: &str, soft_name: &'static str, cli_args: &ArgMatches
).expect("Fatal error : fail to create log file path !");
}
CombinedLogger::init(vec![
TermLogger::new(LevelFilter::Error, logger_config).unwrap(),
WriteLogger::new(
log_level.to_level_filter(),
logger_config,
OpenOptions::new()
.write(true)
.append(true)
.open(
log_file_path
.to_str()
.expect("Fatal error : fail to get log file path !"),
)
.expect("Fatal error : fail to open log file !"),
),
]).expect("Fatal error : fail to init logger !");
CombinedLogger::init(vec![WriteLogger::new(
log_level.to_level_filter(),
logger_config,
OpenOptions::new()
.write(true)
.append(true)
.open(
log_file_path
.to_str()
.expect("Fatal error : fail to get log file path !"),
)
.expect("Fatal error : fail to open log file !"),
)]).expect("Fatal error : fail to init logger !");
}
......@@ -16,6 +16,7 @@ base58 = "0.1.0"
base64 = "0.9.1"
rust-crypto = "0.2.36"
serde = "1.0.57"
serde_derive = "1.0.57"
[features]
# Treat warnings as a build error.
......
......@@ -21,20 +21,17 @@
extern crate serde;
use std::collections::hash_map::DefaultHasher;
use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Error;
use std::fmt::Formatter;
use std::hash::{Hash, Hasher};
use self::serde::ser::{Serialize, Serializer};
use self::serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use self::serde::ser::{Serialize, SerializeSeq, Serializer};
use super::{BaseConvertionError, PrivateKey as PrivateKeyMethods, PublicKey as PublicKeyMethods};
use base58::{FromBase58, FromBase58Error, ToBase58};
use base64;
use base64::DecodeError;
use crypto;
use super::{BaseConvertionError, PrivateKey as PrivateKeyMethods, PublicKey as PublicKeyMethods};
use std::collections::hash_map::DefaultHasher;
use std::fmt::{self, Debug, Display, Error, Formatter};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
/// Store a ed25519 signature.
#[derive(Clone, Copy)]
......@@ -47,6 +44,73 @@ impl Hash for Signature {
}
}
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(64))?;
for e in self.0.iter() {
seq.serialize_element(e)?;
}
seq.end()
}
}
struct SignatureVisitor {
marker: PhantomData<fn() -> Signature>,
}
impl SignatureVisitor {
fn new() -> Self {
SignatureVisitor {
marker: PhantomData,
}
}
}
impl<'de> Visitor<'de> for SignatureVisitor {
// The type that our Visitor is going to produce.
type Value = Signature;
// Format a message stating what data this Visitor expects to receive.
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("Signature datas")
}
// Deserialize Signature from an abstract "map" provided by the
// Deserializer. The MapAccess input is a callback provided by
// the Deserializer to let us see each entry in the map.
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: SeqAccess<'de>,
{
let mut map = Vec::with_capacity(access.size_hint().unwrap_or(0));
// While there are entries remaining in the input, add them
// into our map.
while let Some(value) = access.next_element()? {
map.push(value);
}
let mut sig_datas: [u8; 64] = [0; 64];
sig_datas.copy_from_slice(&map[0..64]);
Ok(Signature(sig_datas))
}
}
// This is the trait that informs Serde how to deserialize Signature.
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Instantiate our Visitor and ask the Deserializer to drive
// it over the input data, resulting in an instance of Signature.
deserializer.deserialize_seq(SignatureVisitor::new())
}
}
impl super::Signature for Signature {
fn from_base64(base64_data: &str) -> Result<Signature, BaseConvertionError> {
match base64::decode(base64_data) {
......@@ -104,7 +168,7 @@ impl Eq for Signature {}
/// Can be generated with [`KeyPairGenerator`].
///
/// [`KeyPairGenerator`]: struct.KeyPairGenerator.html
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Deserialize, PartialEq, Eq, Hash, Serialize)]