diff --git a/lib/modules/blockchain/blockchain/src/dubp/check/global/mod.rs b/lib/modules/blockchain/blockchain/src/dubp/check/global/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f9be5665a148f5eec45b77cecc2be5b11a14c54b --- /dev/null +++ b/lib/modules/blockchain/blockchain/src/dubp/check/global/mod.rs @@ -0,0 +1,233 @@ +// 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/>. + +//! Sub-module checking if a block complies with all the rules of the (DUBP DUniter Blockchain Protocol). + +use super::InvalidBlockError; +use crate::dubp::BlockError; +use dubp_documents::documents::block::BlockDocument; +use dubp_documents::*; +use dup_crypto::keys::PubKey; +use durs_blockchain_dal::*; +use durs_wot::*; +use std::cmp::Ordering; +use std::collections::HashMap; + +#[derive(Fail, Debug, Copy, Clone)] +pub enum InvalidRuleError { + #[fail(display = "BR_G99: different currency")] + DifferentCurrency, + #[fail(display = "BR_G03: wrong previous issuer")] + WrongPreviousIssuer, + #[fail(display = "BR_G100: issuer is not a member")] + NotMemberIssuer, + #[fail(display = "BR_G04: wrong issuers count")] + WrongIssuersCount, + #[fail(display = "BR_G05: genesis: issuers frame must be 1")] + WrongIssuersFrame, +} + +#[inline] +fn invalid_rule_error(error: InvalidRuleError) -> BlockError { + BlockError::InvalidBlock(InvalidBlockError::InvalidRule(error)) +} + +pub fn verify_genesis_block_validity(block: &BlockDocument) -> Result<(), BlockError> { + // BR_G03 - previous issuer + if block.previous_issuer != None { + return Err(invalid_rule_error(InvalidRuleError::WrongPreviousIssuer)); + } + + // BR_G04 - issuers count + if block.issuers_count != 0 { + return Err(invalid_rule_error(InvalidRuleError::WrongIssuersCount)); + } + + // BR_G05 - issuers count + if block.issuers_frame != 1 { + return Err(invalid_rule_error(InvalidRuleError::WrongIssuersFrame)); + } + + Ok(()) +} + +pub fn verify_block_validity<W: WebOfTrust>( + block: &BlockDocument, + blockchain_db: &BinDB<LocalBlockchainV10Datas>, + identities_db: &BinDB<IdentitiesV10Datas>, + _certs_db: &BinDB<CertsExpirV10Datas>, + _wot_index: &HashMap<PubKey, NodeId>, + _wot_db: &BinDB<W>, +) -> Result<(), BlockError> { + // Get previous block + let previous_block_opt = readers::block::get_block_in_local_blockchain( + blockchain_db, + BlockNumber(block.number.0 - 1), + )?; + + // Previous block must exist + if previous_block_opt.is_none() { + return Err(BlockError::InvalidBlock(InvalidBlockError::NoPreviousBlock)); + } + let previous_block = previous_block_opt.expect("safe unwrap"); + + // Block version must not decrease + if previous_block.version > block.version { + return Err(BlockError::InvalidBlock(InvalidBlockError::VersionDecrease)); + } + + // BR_G99 - same currency + if previous_block.currency != block.currency { + return Err(invalid_rule_error(InvalidRuleError::DifferentCurrency)); + } + + // BR_G03 - previous issuer + if Some(previous_block.issuers[0]) != block.previous_issuer { + return Err(invalid_rule_error(InvalidRuleError::WrongPreviousIssuer)); + } + + // BR_G100 - issuer is member + match readers::identity::get_identity(identities_db, &block.issuers[0])? + .expect("safe unwrap") + .state + { + entities::identity::DALIdentityState::Member(_) => {} + _ => return Err(invalid_rule_error(InvalidRuleError::NotMemberIssuer)), + } + + // BR_G04 - issuers count + let issuers_frame = readers::block::get_current_frame( + &entities::block::DALBlock { + block: block.clone(), + expire_certs: None, + }, + blockchain_db, + )?; + if issuers_frame.len() != block.issuers_count { + return Err(invalid_rule_error(InvalidRuleError::WrongIssuersCount)); + } + + //BR_G05 - issuers frame + if block.issuers_frame + != previous_block.issuers_frame + + match previous_block.issuers_frame_var.cmp(&0) { + Ordering::Less => -1, + Ordering::Greater => 1, + Ordering::Equal => 0, + } + { + return Err(invalid_rule_error(InvalidRuleError::WrongIssuersFrame)); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use dubp_documents_tests_tools::mocks::*; + use dup_crypto_tests_tools::mocks::*; + use durs_blockchain_dal::entities::block::DALBlock; + use durs_blockchain_dal::entities::identity::*; + + #[test] + fn test_verify_blocks() { + let blocks_dbs = BlocksV10DBs::open(None); + let wot_dbs = WotsV10DBs::open(None); + let wot_index: HashMap<PubKey, NodeId> = HashMap::new(); + + let idty_a = identity::gen_mock_idty(pubkey('A'), BlockNumber(0)); + let pubkey_a = idty_a.issuers()[0]; + + let idty_b = identity::gen_mock_idty(pubkey('B'), BlockNumber(0)); + let pubkey_b = idty_b.issuers()[0]; + + let mut blocks = gen_empty_timed_blocks(2, 300); + + // Valid block 0 + blocks[0].issuers_frame = 1; + blocks[0].issuers.push(pubkey_a); + blocks[0].identities.push(idty_a.clone()); + blocks[0].identities.push(idty_b.clone()); + + verify_genesis_block_validity(&blocks[0]).expect("Fail to valid genesis block"); + + blocks_dbs + .blockchain_db + .write(|db| { + db.insert( + blocks[0].number, + DALBlock { + block: blocks[0].clone(), + expire_certs: None, + }, + ); + }) + .expect("Fail write to blockchain db"); + + wot_dbs + .identities_db + .write(|db| { + db.insert( + pubkey_a, + DALIdentity { + hash: String::new(), + state: DALIdentityState::Member(vec![]), + joined_on: blocks[0].blockstamp(), + expired_on: None, + revoked_on: None, + idty_doc: idty_a, + wot_id: NodeId(0), + ms_created_block_id: blocks[0].number, + ms_chainable_on: vec![0], + cert_chainable_on: vec![0], + }, + ); + db.insert( + pubkey_b, + DALIdentity { + hash: String::new(), + state: DALIdentityState::Member(vec![]), + joined_on: blocks[0].blockstamp(), + expired_on: None, + revoked_on: None, + idty_doc: idty_b, + wot_id: NodeId(1), + ms_created_block_id: blocks[0].number, + ms_chainable_on: vec![0], + cert_chainable_on: vec![0], + }, + ); + }) + .expect("Fail write to idty db"); + + // Valid block 1 + blocks[1].issuers_count = 1; + blocks[1].issuers_frame = 1; + blocks[1].issuers_frame_var = 5; + blocks[1].issuers.push(pubkey_b); + blocks[1].previous_issuer = Some(pubkey_a); + + verify_block_validity( + &blocks[1], + &blocks_dbs.blockchain_db, + &wot_dbs.identities_db, + &wot_dbs.certs_db, + &wot_index, + &wot_dbs.wot_db, + ) + .expect("Fail to valid block"); + } +} diff --git a/lib/modules/blockchain/blockchain/src/dubp/check/hashs.rs b/lib/modules/blockchain/blockchain/src/dubp/check/local/hashs.rs similarity index 100% rename from lib/modules/blockchain/blockchain/src/dubp/check/hashs.rs rename to lib/modules/blockchain/blockchain/src/dubp/check/local/hashs.rs diff --git a/lib/modules/blockchain/blockchain/src/dubp/check/local/mod.rs b/lib/modules/blockchain/blockchain/src/dubp/check/local/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..5316ca2a91bbeec65d206f485faa47c4e6542ff9 --- /dev/null +++ b/lib/modules/blockchain/blockchain/src/dubp/check/local/mod.rs @@ -0,0 +1,18 @@ +// 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/>. + +//! Sub-module checking "local validation" of a block. + +pub mod hashs; diff --git a/lib/modules/blockchain/blockchain/src/dubp/check/mod.rs b/lib/modules/blockchain/blockchain/src/dubp/check/mod.rs index f0d924281c97a0f84975d020860567cf55d89a82..1588a50b575447b4e85aedc02350500c231fd573 100644 --- a/lib/modules/blockchain/blockchain/src/dubp/check/mod.rs +++ b/lib/modules/blockchain/blockchain/src/dubp/check/mod.rs @@ -15,30 +15,10 @@ //! Sub-module checking if a block complies with all the rules of the (DUBP DUniter Blockchain Protocol). -pub mod hashs; +pub mod global; +pub mod local; -use crate::dubp::BlockError; -use dubp_documents::documents::block::BlockDocument; -use dubp_documents::*; -use dup_crypto::keys::PubKey; -use durs_blockchain_dal::*; -use durs_wot::*; -use std::cmp::Ordering; -use std::collections::HashMap; - -#[derive(Fail, Debug, Copy, Clone)] -pub enum InvalidRuleError { - #[fail(display = "BR_G99: different currency")] - DifferentCurrency, - #[fail(display = "BR_G03: wrong previous issuer")] - WrongPreviousIssuer, - #[fail(display = "BR_G100: issuer is not a member")] - NotMemberIssuer, - #[fail(display = "BR_G04: wrong issuers count")] - WrongIssuersCount, - #[fail(display = "BR_G05: genesis: issuers frame must be 1")] - WrongIssuersFrame, -} +use global::InvalidRuleError; #[derive(Fail, Debug, Copy, Clone)] pub enum InvalidBlockError { @@ -49,196 +29,3 @@ pub enum InvalidBlockError { #[fail(display = "Error at {}", _0)] InvalidRule(InvalidRuleError), } - -#[inline] -fn invalid_rule_error(error: InvalidRuleError) -> BlockError { - BlockError::InvalidBlock(InvalidBlockError::InvalidRule(error)) -} - -pub fn verify_genesis_block_validity(block: &BlockDocument) -> Result<(), BlockError> { - // BR_G03 - previous issuer - if block.previous_issuer != None { - return Err(invalid_rule_error(InvalidRuleError::WrongPreviousIssuer)); - } - - // BR_G04 - issuers count - if block.issuers_count != 0 { - return Err(invalid_rule_error(InvalidRuleError::WrongIssuersCount)); - } - - // BR_G05 - issuers count - if block.issuers_frame != 1 { - return Err(invalid_rule_error(InvalidRuleError::WrongIssuersFrame)); - } - - Ok(()) -} - -pub fn verify_block_validity<W: WebOfTrust>( - block: &BlockDocument, - blockchain_db: &BinDB<LocalBlockchainV10Datas>, - identities_db: &BinDB<IdentitiesV10Datas>, - _certs_db: &BinDB<CertsExpirV10Datas>, - _wot_index: &HashMap<PubKey, NodeId>, - _wot_db: &BinDB<W>, -) -> Result<(), BlockError> { - // Get previous block - let previous_block_opt = readers::block::get_block_in_local_blockchain( - blockchain_db, - BlockNumber(block.number.0 - 1), - )?; - - // Previous block must exist - if previous_block_opt.is_none() { - return Err(BlockError::InvalidBlock(InvalidBlockError::NoPreviousBlock)); - } - let previous_block = previous_block_opt.expect("safe unwrap"); - - // Block version must not decrease - if previous_block.version > block.version { - return Err(BlockError::InvalidBlock(InvalidBlockError::VersionDecrease)); - } - - // BR_G99 - same currency - if previous_block.currency != block.currency { - return Err(invalid_rule_error(InvalidRuleError::DifferentCurrency)); - } - - // BR_G03 - previous issuer - if Some(previous_block.issuers[0]) != block.previous_issuer { - return Err(invalid_rule_error(InvalidRuleError::WrongPreviousIssuer)); - } - - // BR_G100 - issuer is member - match readers::identity::get_identity(identities_db, &block.issuers[0])? - .expect("safe unwrap") - .state - { - entities::identity::DALIdentityState::Member(_) => {} - _ => return Err(invalid_rule_error(InvalidRuleError::NotMemberIssuer)), - } - - // BR_G04 - issuers count - let issuers_frame = readers::block::get_current_frame( - &entities::block::DALBlock { - block: block.clone(), - expire_certs: None, - }, - blockchain_db, - )?; - if issuers_frame.len() != block.issuers_count { - return Err(invalid_rule_error(InvalidRuleError::WrongIssuersCount)); - } - - //BR_G05 - issuers frame - if block.issuers_frame - != previous_block.issuers_frame - + match previous_block.issuers_frame_var.cmp(&0) { - Ordering::Less => -1, - Ordering::Greater => 1, - Ordering::Equal => 0, - } - { - return Err(invalid_rule_error(InvalidRuleError::WrongIssuersFrame)); - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use dubp_documents_tests_tools::mocks::*; - use dup_crypto_tests_tools::mocks::*; - use durs_blockchain_dal::entities::block::DALBlock; - use durs_blockchain_dal::entities::identity::*; - - #[test] - fn test_verify_blocks() { - let blocks_dbs = BlocksV10DBs::open(None); - let wot_dbs = WotsV10DBs::open(None); - let wot_index: HashMap<PubKey, NodeId> = HashMap::new(); - - let idty_a = identity::gen_mock_idty(pubkey('A'), BlockNumber(0)); - let pubkey_a = idty_a.issuers()[0]; - - let idty_b = identity::gen_mock_idty(pubkey('B'), BlockNumber(0)); - let pubkey_b = idty_b.issuers()[0]; - - let mut blocks = gen_empty_timed_blocks(2, 300); - - // Valid block 0 - blocks[0].issuers_frame = 1; - blocks[0].issuers.push(pubkey_a); - blocks[0].identities.push(idty_a.clone()); - blocks[0].identities.push(idty_b.clone()); - - verify_genesis_block_validity(&blocks[0]).expect("Fail to valid genesis block"); - - blocks_dbs - .blockchain_db - .write(|db| { - db.insert( - blocks[0].number, - DALBlock { - block: blocks[0].clone(), - expire_certs: None, - }, - ); - }) - .expect("Fail write to blockchain db"); - - wot_dbs - .identities_db - .write(|db| { - db.insert( - pubkey_a, - DALIdentity { - hash: String::new(), - state: DALIdentityState::Member(vec![]), - joined_on: blocks[0].blockstamp(), - expired_on: None, - revoked_on: None, - idty_doc: idty_a, - wot_id: NodeId(0), - ms_created_block_id: blocks[0].number, - ms_chainable_on: vec![0], - cert_chainable_on: vec![0], - }, - ); - db.insert( - pubkey_b, - DALIdentity { - hash: String::new(), - state: DALIdentityState::Member(vec![]), - joined_on: blocks[0].blockstamp(), - expired_on: None, - revoked_on: None, - idty_doc: idty_b, - wot_id: NodeId(1), - ms_created_block_id: blocks[0].number, - ms_chainable_on: vec![0], - cert_chainable_on: vec![0], - }, - ); - }) - .expect("Fail write to idty db"); - - // Valid block 1 - blocks[1].issuers_count = 1; - blocks[1].issuers_frame = 1; - blocks[1].issuers_frame_var = 5; - blocks[1].issuers.push(pubkey_b); - blocks[1].previous_issuer = Some(pubkey_a); - - verify_block_validity( - &blocks[1], - &blocks_dbs.blockchain_db, - &wot_dbs.identities_db, - &wot_dbs.certs_db, - &wot_index, - &wot_dbs.wot_db, - ) - .expect("Fail to valid block"); - } -} diff --git a/lib/modules/blockchain/blockchain/src/dubp/mod.rs b/lib/modules/blockchain/blockchain/src/dubp/mod.rs index 4fbf68fdd3c965f0543d9949246ddaf556b4a0cc..14931cf3cec289091411a7a9ca739dd86a47e16e 100644 --- a/lib/modules/blockchain/blockchain/src/dubp/mod.rs +++ b/lib/modules/blockchain/blockchain/src/dubp/mod.rs @@ -73,7 +73,7 @@ pub fn check_and_apply_block( )?; // Verify block hashs - dubp::check::hashs::verify_block_hashs(&block_doc)?; + dubp::check::local::hashs::verify_block_hashs(&block_doc)?; // Check block chainability if (block_doc.number.0 == bc.current_blockstamp.id.0 + 1 @@ -93,9 +93,9 @@ pub fn check_and_apply_block( // Verify block validity (check all protocol rule, very long !) if block_doc.number.0 == 0 { - verify_genesis_block_validity(&block_doc)?; + check::global::verify_genesis_block_validity(&block_doc)?; } else { - verify_block_validity( + check::global::verify_block_validity( &block_doc, &bc.blocks_databases.blockchain_db, &bc.wot_databases.identities_db, diff --git a/lib/modules/blockchain/blockchain/src/sync/mod.rs b/lib/modules/blockchain/blockchain/src/sync/mod.rs index 2f82bdfc8955e96f12ef24ad629a1e0839520792..6b4f9e999354f62853a283365ab66dd57d541e72 100644 --- a/lib/modules/blockchain/blockchain/src/sync/mod.rs +++ b/lib/modules/blockchain/blockchain/src/sync/mod.rs @@ -290,7 +290,7 @@ pub fn local_sync<DC: DuniterConf>(profile: &str, conf: &DC, sync_opts: SyncOpt) // Verify block hashs let verif_block_hashs_begin = SystemTime::now(); if verif_inner_hash { - dubp::check::hashs::verify_block_hashs(&block_doc) + dubp::check::local::hashs::verify_block_hashs(&block_doc) .expect("Receive wrong block, please reset data and resync !"); } all_verif_block_hashs_duration += SystemTime::now()