Skip to content
Snippets Groups Projects

WIP: Resolve "DUBP local validation"

Closed jawaka requested to merge jawaka-146-dubp-local-validation into dev

Files

@@ -13,7 +13,7 @@
// 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).
//! Sub-module that checks and applies the content of a block according to the DUBP (DUBP DUniter Blockchain Protocol).
pub mod hashs;
@@ -25,12 +25,289 @@ use durs_blockchain_dal::*;
use durs_wot::*;
use std::collections::HashMap;
use dubp_documents::documents::block::TxDocOrTxHash;
use dubp_documents::text_document_traits::CompactTextDocument;
use dubp_documents::VerificationResult;
use durs_common_tools::fatal_error;
pub static ZERO_STRING: &'static str = "0";
#[derive(Debug, Copy, Clone)]
pub enum InvalidBlockError {
NoPreviousBlock,
VersionDecrease,
}
#[derive(Debug, Clone)]
pub enum TransactionDocumentError {
LengthError,
NumberSourcesError,
UnlocksError,
SignatureIssuerError,
VersionError,
SignatureCountError,
SignaturesOrderError,
SignatureConstructionError,
VerificationResult(VerificationResult),
}
/*
impl From<VerificationResult> for TransactionDocumentError {
fn from(err: VerificationResult) -> Self {
VerificationResult(err)
}
}
*/
#[derive(Debug, Clone)]
pub enum LocalVerifyBlockError {
VerifyBlockHashsError,
VersionBlockError,
PreviousIssuerRootBlockError,
ParametersRootBlockError,
UniversalDividendRootBlockError,
UnitBaseRootBlockError,
BlockTimeError,
ZerosHashBlockError,
SignaturesLenBlockError,
SignatureBlockError,
SignatureMembershipDocumentError,
SignatureRevocationDocumentError,
TextDocumentFormat,
TransactionDocumentError(TransactionDocumentError),
}
impl From<TransactionDocumentError> for LocalVerifyBlockError {
fn from(err: TransactionDocumentError) -> Self {
TransactionDocumentError(err)
}
}
use crate::dubp::check::LocalVerifyBlockError::*;
use crate::dubp::check::TransactionDocumentError::*;
pub fn local_verification(block: &BlockDocument) -> Result<(), LocalVerifyBlockError> {
// Version
if !(block.version == 10 || block.version == 11) {
return Err(VersionBlockError);
}
// InnerHash
if !block.verify_inner_hash() {
return Err(VerifyBlockHashsError);
}
// Nonce
// nothing to do
// Proof of work
if let Some(h) = block.hash {
let hash_string = h.0.to_hex();
let remainder = block.pow_min % 16;
let nb_zeros = (block.pow_min - remainder) / 16;
let repeated_zero_string = ZERO_STRING.repeat(nb_zeros);
if !hash_string.starts_with(&repeated_zero_string) {
return Err(ZerosHashBlockError);
}
}
// PreviousHash
// todo: solve ticket #150 (previous_hash must be an Option) before implementing this local verification
// PreviousIssuer
if block.number.0 > 0 && block.previous_issuer.is_none() {
return Err(PreviousIssuerRootBlockError);
}
if block.number.0 == 0 && block.previous_issuer.is_some() {
return Err(PreviousIssuerRootBlockError);
}
// Parameters
if block.number.0 == 0 && block.parameters.is_none() {
return Err(ParametersRootBlockError);
}
if block.number.0 > 0 && block.parameters.is_some() {
return Err(ParametersRootBlockError);
}
// Universal Dividend
if block.number.0 == 0 && block.dividend.is_none() {
return Err(UniversalDividendRootBlockError);
}
// UnitBase
if block.number.0 == 0 && block.unit_base > 0 {
return Err(UnitBaseRootBlockError);
}
// Signature
if block.signatures.len() != 1 {
// optimisable en ne calculant pas toute la longueur du tableau ?
return Err(SignaturesLenBlockError);
}
match block.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureBlockError),
}
// Dates
//let avg_gen_time = block.parameters.avg_gen_time as f64;
//let maxGenTime = avg_gen_time * 1.189;
let max_acceleration = 1; // todo: max_acceleration = CEIL(maxGenTime * median_time_blocks)
if block.time < block.median_time || block.median_time + max_acceleration < block.time {
return Err(BlockTimeError);
}
if block.number.0 == 0 && block.time != block.median_time {
return Err(BlockTimeError);
}
// Identities
for x in &block.identities {
match x.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureBlockError),
}
}
// Memberships (Joiners, Actives, Leavers)
for x in &block.joiners {
match x.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureMembershipDocumentError),
}
}
for x in &block.actives {
match x.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureMembershipDocumentError),
}
}
for x in &block.leavers {
match x.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureMembershipDocumentError),
}
}
// Revoked
for x in &block.revoked {
match x {
dubp_documents::text_document_traits::TextDocumentFormat::Complete(y) => {
match y.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureRevocationDocumentError),
}
}
dubp_documents::text_document_traits::TextDocumentFormat::Compact(y) => {
y
match y.verify_signatures() {
VerificationResult::Valid() => (),
_ => return Err(SignatureRevocationDocumentError),
}
}
_ => (), // question : ça peut arriver un compact ?
}
}
// Transactions
for x in &block.transactions {
match x {
TxDocOrTxHash::TxDoc(y) => {
// A transaction in compact format cannot measure more than 100 lines
if y.as_compact_text().lines().count() >= 100 {
// question : c'est strict ?
return Err(TransactionDocumentError(LengthError));
}
// A transaction must have at least 1 source
if y.get_inputs().is_empty() {
return Err(TransactionDocumentError(NumberSourcesError));
}
// A transaction cannot have `SIG(INDEX)` unlocks with `INDEX >= ` issuers count. ??
if y.get_unlocks().len() >= y.issuers().len() {
return Err(TransactionDocumentError(SignatureIssuerError));
}
// A transaction's version must be equal to 3
if y.version() != 3 {
// question : c'est certainement >= 3 vu les tests
return Err(TransactionDocumentError(VersionError));
}
// Signatures count must be the same as issuers count
if y.signatures().len() != y.issuers().len() {
return Err(TransactionDocumentError(SignatureCountError));
}
/*
* A transaction **must** have signatures matching its content **for each issuer**
* Signatures are ordered by issuer
* Signatures are made over the transaction's content, signatures excepted
*/
let result = y.verify_signatures();
match result {
VerificationResult::Valid() => (),
_ => return Err(TransactionDocumentError(VerificationResult(result))),
}
}
TxDocOrTxHash::TxHash(y) => {
fatal_error!("Block contains a Transaction Hash (and not a Transaction Document)")
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn generate_and_verify_block() {
let mut block = BlockDocument {
nonce: 0,
version: 10,
number: BlockNumber(107_984),
pow_min: 88,
time: 1_522_685_861,
median_time: 1522683184,
members_count: 896,
monetary_mass: 140_469_765,
unit_base: 0,
issuers_count: 42,
issuers_frame: 211,
issuers_frame_var: 0,
currency: CurrencyName(String::from("g1")),
issuers: vec![PubKey::Ed25519(ed25519::PublicKey::from_base58("DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ").unwrap())],
signatures: vec![Sig::Ed25519(ed25519::Signature::from_base64("92id58VmkhgVNee4LDqBGSm8u/ooHzAD67JM6fhAE/CV8LCz7XrMF1DvRl+eRpmlaVkp6I+Iy8gmZ1WUM5C8BA==").unwrap())],
hash: None,
parameters: None,
previous_hash: Hash::from_hex("000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB").expect("fail to parse previous_hash"),
previous_issuer: Some(PubKey::Ed25519(ed25519::PublicKey::from_base58("D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH").unwrap())),
inner_hash: None,
dividend: None,
identities: Vec::new(),
joiners: Vec::new(),
actives: Vec::new(),
leavers: Vec::new(),
revoked: Vec::new(),
excluded: Vec::new(),
certifications: vec![TextDocumentFormat::Complete(cert1)],
transactions: vec![TxDocOrTxHash::TxDoc(Box::new(tx1)), TxDocOrTxHash::TxDoc(Box::new(tx2))],
inner_hash_and_nonce_str: String::new(),
};
assert_eq!(local_verification(&block), Ok);
}
}
pub fn verify_block_validity<W: WebOfTrust>(
block: &BlockDocument,
blockchain_db: &BinDB<LocalBlockchainV10Datas>,
Loading