use crate::*;
use dubp_documents::identity::IdentityDocumentV10;
use dubp_documents::membership::v10::MembershipDocumentV10;
use dubp_documents::revocation::{CompactRevocationDocumentV10, RevocationDocumentV10};
use dubp_documents::transaction::v10::{TransactionDocumentV10, TransactionDocumentV10Stringified};
use dubp_documents::{
certification::{v10::CertificationDocumentV10, CompactCertificationDocumentV10},
dubp_wallet::prelude::SourceAmount,
};
use std::borrow::Cow;
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct DubpBlockV10Content {
pub version: usize,
pub number: BlockNumber,
pub pow_min: usize,
pub time: u64,
pub median_time: u64,
pub members_count: usize,
pub monetary_mass: u64,
pub unit_base: usize,
pub issuers_count: usize,
pub issuers_frame: usize,
pub issuers_frame_var: isize,
pub currency: CurrencyName,
pub issuer: ed25519::PublicKey,
pub parameters: Option<BlockV10Parameters>,
pub previous_hash: Hash,
pub previous_issuer: ed25519::PublicKey,
pub dividend: Option<usize>,
pub identities: Vec<IdentityDocumentV10>,
pub joiners: Vec<MembershipDocumentV10>,
pub actives: Vec<MembershipDocumentV10>,
pub leavers: Vec<MembershipDocumentV10>,
pub revoked: Vec<TextDocumentFormat<RevocationDocumentV10>>,
pub excluded: Vec<ed25519::PublicKey>,
pub certifications: Vec<TextDocumentFormat<CertificationDocumentV10>>,
pub transactions: Vec<TransactionDocumentV10>,
}
impl DubpBlockV10Content {
pub(crate) fn gen_hashable_text(&self) -> String {
let mut identities_str = String::from("");
for identity in &self.identities {
identities_str.push('\n');
identities_str.push_str(&identity.generate_compact_text());
}
let mut joiners_str = String::new();
for joiner in &self.joiners {
joiners_str.push('\n');
joiners_str.push_str(&joiner.generate_compact_text());
}
let mut actives_str = String::new();
for active in &self.actives {
actives_str.push('\n');
actives_str.push_str(&active.generate_compact_text());
}
let mut leavers_str = String::new();
for leaver in &self.leavers {
leavers_str.push('\n');
leavers_str.push_str(&leaver.generate_compact_text());
}
let mut identities_str = String::new();
for identity in &self.identities {
identities_str.push('\n');
identities_str.push_str(&identity.generate_compact_text());
}
let mut revokeds_str = String::new();
for revocation in &self.revoked {
revokeds_str.push('\n');
revokeds_str.push_str(&revocation.as_compact_text());
}
let mut excludeds_str = String::new();
for exclusion in &self.excluded {
excludeds_str.push('\n');
excludeds_str.push_str(&exclusion.to_string());
}
let mut certifications_str = String::new();
for certification in &self.certifications {
certifications_str.push('\n');
certifications_str.push_str(&certification.as_compact_text());
}
let mut transactions_str = String::new();
for transaction in &self.transactions {
transactions_str.push('\n');
transactions_str.push_str(&transaction.generate_compact_text());
}
let mut dividend_str = String::new();
if let Some(dividend) = self.dividend {
if dividend > 0 {
dividend_str.push_str("UniversalDividend: ");
dividend_str.push_str(÷nd.to_string());
dividend_str.push('\n');
}
}
let mut parameters_str = String::new();
if let Some(params) = self.parameters {
parameters_str.push_str("Parameters: ");
parameters_str.push_str(¶ms.to_string());
parameters_str.push('\n');
}
let mut previous_hash_str = String::new();
if self.number.0 > 0 {
previous_hash_str.push_str("PreviousHash: ");
previous_hash_str.push_str(&self.previous_hash.to_string());
previous_hash_str.push('\n');
}
let mut previous_issuer_str = String::new();
if self.number.0 > 0 {
previous_issuer_str.push_str("PreviousIssuer: ");
previous_issuer_str.push_str(&self.previous_issuer.to_string());
previous_issuer_str.push('\n');
}
format!(
"Version: {version}
Type: Block
Currency: {currency}
Number: {block_number}
PoWMin: {pow_min}
Time: {time}
MedianTime: {median_time}
{dividend}UnitBase: {unit_base}
Issuer: {issuer}
IssuersFrame: {issuers_frame}
IssuersFrameVar: {issuers_frame_var}
DifferentIssuersCount: {issuers_count}
{parameters}{previous_hash}{previous_issuer}MembersCount: {members_count}
Identities:{identities}
Joiners:{joiners}
Actives:{actives}
Leavers:{leavers}
Revoked:{revoked}
Excluded:{excluded}
Certifications:{certifications}
Transactions:{transactions}
",
version = self.version,
currency = self.currency,
block_number = self.number,
pow_min = self.pow_min,
time = self.time,
median_time = self.median_time,
dividend = dividend_str,
unit_base = self.unit_base,
issuer = self.issuer,
issuers_frame = self.issuers_frame,
issuers_frame_var = self.issuers_frame_var,
issuers_count = self.issuers_count,
parameters = parameters_str,
previous_hash = previous_hash_str,
previous_issuer = previous_issuer_str,
members_count = self.members_count,
identities = identities_str,
joiners = joiners_str,
actives = actives_str,
leavers = leavers_str,
revoked = revokeds_str,
excluded = excludeds_str,
certifications = certifications_str,
transactions = transactions_str,
)
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct DubpBlockV10AfterPowData {
pub nonce: u64,
pub signature: ed25519::Signature,
pub hash: BlockHash,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct DubpBlockV10Builder {
content: DubpBlockV10Content,
inner_hash: Hash,
}
impl DubpBlockV10Builder {
pub fn new(content: DubpBlockV10Content) -> Self {
DubpBlockV10Builder {
inner_hash: Hash::compute_str(&content.gen_hashable_text()),
content,
}
}
pub fn inner_hash(&self) -> Hash {
self.inner_hash
}
pub fn build_unchecked(self, after_pow_data: DubpBlockV10AfterPowData) -> DubpBlockV10 {
DubpBlockV10 {
content: self.content,
inner_hash: Some(self.inner_hash),
nonce: after_pow_data.nonce,
signature: after_pow_data.signature,
hash: after_pow_data.hash,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub struct DubpBlockV10 {
content: DubpBlockV10Content,
inner_hash: Option<Hash>,
nonce: u64,
signature: ed25519::Signature,
hash: BlockHash,
}
impl DubpBlockTrait for DubpBlockV10 {
type Signator = ed25519::Signator;
fn common_time(&self) -> u64 {
self.content.median_time
}
fn compute_hashed_string(&self) -> String {
format!("{}{}\n", self.compute_signed_string(), self.signature)
}
fn compute_signed_string(&self) -> String {
let inner_hash = if let Some(inner_hash) = self.inner_hash {
inner_hash
} else {
self.compute_inner_hash()
};
format!(
"InnerHash: {}\nNonce: {}\n",
inner_hash.to_hex(),
self.nonce
)
}
fn current_frame_size(&self) -> usize {
self.content.issuers_frame
}
fn dividend(&self) -> Option<SourceAmount> {
if let Some(dividend) = self.content.dividend {
Some(SourceAmount::new(
dividend as i64,
self.content.unit_base as i64,
))
} else {
None
}
}
fn generate_compact_inner_text(&self) -> String {
self.content.gen_hashable_text()
}
fn hash(&self) -> BlockHash {
self.hash
}
fn inner_hash(&self) -> Hash {
if let Some(inner_hash) = self.inner_hash {
inner_hash
} else {
self.compute_inner_hash()
}
}
fn issuers_count(&self) -> usize {
self.content.issuers_count
}
fn issuers_frame(&self) -> usize {
self.content.issuers_frame
}
fn issuer(&self) -> ed25519::PublicKey {
self.content.issuer
}
fn local_time(&self) -> u64 {
self.content.time
}
fn members_count(&self) -> usize {
self.content.members_count
}
fn monetary_mass(&self) -> u64 {
self.content.monetary_mass
}
fn nonce(&self) -> u64 {
self.nonce
}
fn number(&self) -> BlockNumber {
self.content.number
}
fn pow_min(&self) -> usize {
self.content.pow_min
}
fn previous_blockstamp(&self) -> Blockstamp {
if self.content.number.0 > 0 {
Blockstamp {
number: BlockNumber(self.content.number.0 - 1),
hash: BlockHash(self.content.previous_hash),
}
} else {
Blockstamp::default()
}
}
fn previous_hash(&self) -> Hash {
self.content.previous_hash
}
fn reduce(&mut self) {
self.inner_hash = None;
for i in &mut self.content.identities {
i.reduce();
}
for i in &mut self.content.joiners {
i.reduce();
}
for i in &mut self.content.actives {
i.reduce();
}
for i in &mut self.content.leavers {
i.reduce();
}
for i in &mut self.content.transactions {
i.reduce();
}
}
fn sign(&mut self, signator: &Self::Signator) -> Result<(), SignError> {
self.signature = signator.sign(self.compute_signed_string().as_bytes());
Ok(())
}
fn signature(&self) -> ed25519::Signature {
self.signature
}
fn verify_inner_hash(&self) -> Result<(), VerifyBlockHashError> {
match self.inner_hash {
Some(inner_hash) => {
let computed_hash = self.compute_inner_hash();
if inner_hash == computed_hash {
Ok(())
} else {
Err(VerifyBlockHashError::InvalidHash {
block_number: self.content.number,
expected_hash: computed_hash,
actual_hash: inner_hash,
})
}
}
None => Err(VerifyBlockHashError::MissingHash {
block_number: self.content.number,
}),
}
}
fn verify_signature(&self) -> Result<(), SigError> {
self.content
.issuer
.verify(self.compute_signed_string().as_bytes(), &self.signature)
}
fn verify_hash(&self) -> Result<(), VerifyBlockHashError> {
let expected_hash = self.compute_hash();
if self.hash == expected_hash {
Ok(())
} else {
warn!(
"Block #{} have invalid hash (expected='{}', actual='{}', datas='{}').",
self.content.number.0,
expected_hash,
self.hash,
self.compute_hashed_string()
);
Err(VerifyBlockHashError::InvalidHash {
block_number: self.content.number,
expected_hash: expected_hash.0,
actual_hash: self.hash.0,
})
}
}
fn unit_base(&self) -> usize {
self.content.unit_base
}
}
impl DubpBlockV10 {
pub fn identities(&self) -> &[IdentityDocumentV10] {
&self.content.identities
}
pub fn joiners(&self) -> &[MembershipDocumentV10] {
&self.content.joiners
}
pub fn actives(&self) -> &[MembershipDocumentV10] {
&self.content.actives
}
pub fn leavers(&self) -> &[MembershipDocumentV10] {
&self.content.leavers
}
pub fn revoked(&self) -> Vec<Cow<CompactRevocationDocumentV10>> {
self.content
.revoked
.iter()
.map(|revo| revo.to_compact_document())
.collect()
}
pub fn excluded(&self) -> &[ed25519::PublicKey] {
&self.content.excluded
}
pub fn certifications(&self) -> Vec<Cow<CompactCertificationDocumentV10>> {
self.content
.certifications
.iter()
.map(|cert| cert.to_compact_document())
.collect()
}
pub fn transactions(&self) -> &[TransactionDocumentV10] {
&self.content.transactions
}
#[cfg(not(tarpaulin_include))]
pub fn as_compact_text(&self) -> String {
let compact_inner_text = self.generate_compact_inner_text();
let inner_hash = if let Some(inner_hash) = self.inner_hash {
inner_hash
} else {
Hash::compute(compact_inner_text.as_bytes())
};
format!(
"{}InnerHash: {}\nNonce: ",
compact_inner_text,
inner_hash.to_hex()
)
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DubpBlockV10Stringified {
pub version: u64,
pub nonce: u64,
pub number: u64,
pub pow_min: u64,
pub time: u64,
pub median_time: u64,
pub members_count: u64,
pub monetary_mass: u64,
#[serde(rename = "unitbase")]
pub unit_base: u64,
pub issuers_count: u64,
pub issuers_frame: u64,
pub issuers_frame_var: i64,
pub currency: String,
pub issuer: String,
pub signature: String,
pub hash: Option<String>,
pub parameters: Option<String>,
pub previous_hash: Option<String>,
pub previous_issuer: Option<String>,
#[serde(rename = "inner_hash")]
pub inner_hash: Option<String>,
pub dividend: Option<u64>,
pub identities: Vec<String>,
pub joiners: Vec<String>,
pub actives: Vec<String>,
pub leavers: Vec<String>,
pub revoked: Vec<String>,
pub excluded: Vec<String>,
pub certifications: Vec<String>,
pub transactions: Vec<TransactionDocumentV10Stringified>,
}
impl ToStringObject for DubpBlockV10 {
type StringObject = DubpBlockV10Stringified;
fn to_string_object(&self) -> DubpBlockV10Stringified {
DubpBlockV10Stringified {
version: self.content.version as u64,
nonce: self.nonce,
number: u64::from(self.content.number.0),
pow_min: self.content.pow_min as u64,
time: self.content.time,
median_time: self.content.median_time,
members_count: self.content.members_count as u64,
monetary_mass: self.content.monetary_mass,
unit_base: self.content.unit_base as u64,
issuers_count: self.content.issuers_count as u64,
issuers_frame: self.content.issuers_frame as u64,
issuers_frame_var: self.content.issuers_frame_var as i64,
currency: self.content.currency.to_string(),
issuer: self.content.issuer.to_string(),
signature: self.signature.to_string(),
hash: Some(self.hash.to_string()),
parameters: self
.content
.parameters
.map(|parameters| parameters.to_string()),
previous_hash: if self.content.number.0 == 0 {
None
} else {
Some(self.content.previous_hash.to_string())
},
previous_issuer: if self.content.number.0 == 0 {
None
} else {
Some(self.content.previous_issuer.to_string())
},
inner_hash: self.inner_hash.map(|hash| hash.to_string()),
dividend: self.content.dividend.map(|d| d as u64),
identities: self
.content
.identities
.iter()
.map(|doc| doc.to_compact_document().as_compact_text())
.collect(),
joiners: self
.content
.joiners
.iter()
.map(|doc| doc.to_compact_document().as_compact_text())
.collect(),
actives: self
.content
.actives
.iter()
.map(|doc| doc.to_compact_document().as_compact_text())
.collect(),
leavers: self
.content
.leavers
.iter()
.map(|doc| doc.to_compact_document().as_compact_text())
.collect(),
revoked: self
.content
.revoked
.iter()
.map(|doc| doc.to_compact_document().as_compact_text())
.collect(),
excluded: self
.content
.excluded
.iter()
.map(ToString::to_string)
.collect(),
certifications: self
.content
.certifications
.iter()
.map(|doc| doc.to_compact_document().as_compact_text())
.collect(),
transactions: self
.content
.transactions
.iter()
.map(|tx_doc| tx_doc.to_string_object())
.collect(),
}
}
}
impl FromStringObject for DubpBlockV10 {
fn from_string_object(stringified: &DubpBlockV10Stringified) -> Result<Self, TextParseError> {
let str_identities: Vec<_> = stringified.identities.iter().map(|x| &**x).collect();
let str_joiners: Vec<_> = stringified.joiners.iter().map(|x| &**x).collect();
let str_actives: Vec<_> = stringified.actives.iter().map(|x| &**x).collect();
let str_leavers: Vec<_> = stringified.leavers.iter().map(|x| &**x).collect();
let str_revoked: Vec<_> = stringified.revoked.iter().map(|x| &**x).collect();
let str_certs: Vec<_> = stringified.certifications.iter().map(|x| &**x).collect();
Ok(DubpBlockV10 {
content: DubpBlockV10Content {
version: stringified.version as usize,
number: BlockNumber(stringified.number as u32),
pow_min: stringified.pow_min as usize,
time: stringified.time,
median_time: stringified.median_time,
members_count: stringified.members_count as usize,
monetary_mass: stringified.monetary_mass,
unit_base: stringified.unit_base as usize,
issuers_count: stringified.issuers_count as usize,
issuers_frame: stringified.issuers_frame as usize,
issuers_frame_var: stringified.issuers_frame_var as isize,
currency: CurrencyName(stringified.currency.clone()),
issuer: ed25519::PublicKey::from_base58(&stringified.issuer).map_err(|error| {
TextParseError::BaseConversionError {
field: "block.issuer",
error,
}
})?,
parameters: None,
previous_hash: if let Some(ref previous_hash) = stringified.previous_hash {
if !previous_hash.is_empty() {
Hash::from_hex(previous_hash).map_err(|error| {
TextParseError::BaseConversionError {
field: "block.previous_hash",
error,
}
})?
} else {
Hash::default()
}
} else {
Hash::default()
},
previous_issuer: if let Some(ref previous_issuer) = stringified.previous_issuer {
if !previous_issuer.is_empty() {
ed25519::PublicKey::from_base58(previous_issuer).map_err(|error| {
TextParseError::BaseConversionError {
field: "block.previous_issuer",
error,
}
})?
} else {
ed25519::PublicKey::default()
}
} else {
ed25519::PublicKey::default()
},
dividend: stringified.dividend.map(|dividend| dividend as usize),
identities: parse_compact_identities(&stringified.currency, &str_identities)
.map_err(|error| TextParseError::CompactDoc {
field: "block.identities",
error,
})?,
joiners: parse_compact_memberships(
&stringified.currency,
MembershipType::In(),
&str_joiners,
)
.map_err(|error| TextParseError::CompactDoc {
field: "block.joiners",
error,
})?,
actives: parse_compact_memberships(
&stringified.currency,
MembershipType::In(),
&str_actives,
)
.map_err(|error| TextParseError::CompactDoc {
field: "block.actives",
error,
})?,
leavers: parse_compact_memberships(
&stringified.currency,
MembershipType::Out(),
&str_leavers,
)
.map_err(|error| TextParseError::CompactDoc {
field: "block.leavers",
error,
})?,
revoked: parse_compact_revocations(&str_revoked).map_err(|error| {
TextParseError::CompactDoc {
field: "block.revoked",
error,
}
})?,
excluded: stringified
.excluded
.iter()
.map(|pubkey| ed25519::PublicKey::from_base58(pubkey))
.collect::<Result<Vec<_>, _>>()
.map_err(|error| TextParseError::BaseConversionError {
field: "block.excluded",
error,
})?,
certifications: parse_compact_certifications(&str_certs).map_err(|error| {
TextParseError::CompactDoc {
field: "block.certifications",
error,
}
})?,
transactions: stringified
.transactions
.iter()
.map(|tx| TransactionDocumentV10::from_string_object(tx))
.collect::<Result<Vec<_>, _>>()?,
},
inner_hash: Some(
Hash::from_hex(stringified.inner_hash.as_ref().ok_or_else(|| {
TextParseError::InvalidInnerFormat("Block without inner_hash".to_owned())
})?)
.map_err(|error| TextParseError::BaseConversionError {
field: "block.inner_hash",
error,
})?,
),
nonce: stringified.nonce,
signature: ed25519::Signature::from_base64(&stringified.signature).map_err(
|error| TextParseError::BaseConversionError {
field: "block.signature",
error,
},
)?,
hash: BlockHash(
Hash::from_hex(stringified.hash.as_ref().ok_or_else(|| {
TextParseError::InvalidInnerFormat("Block without inner_hash".to_owned())
})?)
.map_err(|error| TextParseError::BaseConversionError {
field: "block.hash",
error,
})?,
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
use dubp_documents::certification::CertificationDocument;
use dubp_documents::membership::MembershipDocument;
use dubp_documents::transaction::TransactionDocument;
use pretty_assertions::assert_eq;
use unwrap::unwrap;
#[test]
fn test_default_block_v10() {
let mut default_block = DubpBlockV10::default();
let mut default_stringified_block = default_block.to_string_object();
default_stringified_block.currency = String::with_capacity(0);
default_stringified_block.issuer = String::with_capacity(0);
default_stringified_block.signature = String::with_capacity(0);
default_stringified_block.hash = None;
assert_eq!(
default_stringified_block,
DubpBlockV10Stringified::default()
);
assert_eq!(default_block.common_time(), 0);
assert_eq!(default_block.current_frame_size(), 0);
assert_eq!(default_block.issuers_count(), 0);
assert_eq!(default_block.members_count(), 0);
assert_eq!(default_block.number(), BlockNumber(0));
assert_eq!(default_block.pow_min(), 0);
assert_eq!(default_block.previous_blockstamp(), Blockstamp::default());
assert_eq!(default_block.previous_hash(), Hash::default());
assert_eq!(
Err(VerifyBlockHashError::MissingHash {
block_number: BlockNumber(0)
}),
default_block.verify_inner_hash()
);
default_block.inner_hash = Some(Hash::default());
assert_eq!(
Err(VerifyBlockHashError::InvalidHash {
block_number: BlockNumber(0),
actual_hash: Hash::default(),
expected_hash: default_block.compute_inner_hash(),
}),
default_block.verify_inner_hash()
);
assert_eq!(default_block.signature, ed25519::Signature::default());
assert_eq!(Err(SigError::InvalidSig), default_block.verify_signature());
let signator = unwrap!(ed25519::Ed25519KeyPair::generate_random()).generate_signator();
default_block.content.issuer = signator.public_key();
unwrap!(default_block.sign(&signator));
assert_eq!(Ok(()), default_block.verify_signature());
assert_eq!(BlockHash(Hash::default()), default_block.hash());
assert_eq!(
Err(VerifyBlockHashError::InvalidHash {
block_number: BlockNumber(0),
actual_hash: Hash::default(),
expected_hash: default_block.compute_hash().0,
}),
default_block.verify_hash()
);
}
#[test]
fn generate_and_verify_empty_block() {
let block_content = DubpBlockV10Content {
version: 10,
number: BlockNumber(174_260),
pow_min: 68,
time: 1_525_296_873,
median_time: 1_525_292_577,
members_count: 33,
monetary_mass: 15_633_687,
unit_base: 0,
issuers_count: 8,
issuers_frame: 41,
issuers_frame_var: 0,
currency: CurrencyName(String::from("g1-test")),
issuer: pk("39Fnossy1GrndwCnAXGDw3K5UYXhNXAFQe7yhYZp8ELP"),
parameters: None,
previous_hash: unwrap!(Hash::from_hex(
"0000A7D4361B9EBF4CE974A521149A73E8A5DE9B73907AB3BC918726AED7D40A"
)),
previous_issuer: pk("EPKuZA1Ek5y8S1AjAmAPtGrVCMFqUGzUEAa7Ei62CY2L"),
dividend: None,
identities: Vec::new(),
joiners: Vec::new(),
actives: Vec::new(),
leavers: Vec::new(),
revoked: Vec::new(),
excluded: Vec::new(),
certifications: Vec::new(),
transactions: Vec::new(),
};
let mut block = DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
nonce: 100_010_200_000_006_940,
signature: unwrap!(ed25519::Signature::from_base64("lqXrNOopjM39oM7hgB7Vq13uIohdCuLlhh/q8RVVEZ5UVASphow/GXikCdhbWID19Bn0XrXzTbt/R7akbE9xAg==")),
hash: BlockHash::default(),
});
println!("{}", block.generate_compact_inner_text());
assert!(block.verify_inner_hash().is_ok());
assert_eq!(
unwrap!(block.inner_hash).to_hex(),
"58E4865A47A46E0DF1449AABC449B5406A12047C413D61B5E17F86BE6641E7B0"
);
assert!(block.verify_signature().is_ok());
let computed_hash = block.compute_hash();
block.hash = computed_hash;
assert!(block.verify_hash().is_ok());
assert_eq!(
block.hash.0.to_hex(),
"00002EE584F36C15D3EB21AAC78E0896C75EF9070E73B4EC33BFA2C3D561EEB2"
);
}
#[test]
fn generate_and_verify_block() {
let cert1 = unwrap!(CertificationDocument::parse_from_raw_text("Version: 10
Type: Certification
Currency: g1
Issuer: 6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3
IdtyIssuer: CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV
IdtyUniqueID: PascaleM
IdtyTimestamp: 97401-0000003821911909F98519CC773D2D3E5CFE3D5DBB39F4F4FF33B96B4D41800E
IdtySignature: QncUVXxZ2NfARjdJOn6luILvDuG1NuK9qSoaU4CST2Ij8z7oeVtEgryHl+EXOjSe6XniALsCT0gU8wtadcA/Cw==
CertTimestamp: 106669-000003682E6FE38C44433DCE92E8B2A26C69B6D7867A2BAED231E788DDEF4251
UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA=="));
let CertificationDocument::V10(cert1) = cert1;
let TransactionDocument::V10(tx1) = unwrap!(TransactionDocument::parse_from_raw_text("Version: 10
Type: Transaction
Currency: g1
Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
Locktime: 0
Issuers:
8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
Inputs:
1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345
Unlocks:
0:SIG(0)
Outputs:
1002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)
Comment: DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci
T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ=="));
let TransactionDocument::V10(tx2) = unwrap!(TransactionDocument::parse_from_raw_text("Version: 10
Type: Transaction
Currency: g1
Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
Locktime: 0
Issuers:
8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
Inputs:
1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614
Unlocks:
0:SIG(0)
Outputs:
1002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)
Comment: DU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci
a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg=="));
let block_content = DubpBlockV10Content {
version: 10,
number: BlockNumber(107_984),
pow_min: 88,
time: 1_522_685_861,
median_time: 1_522_683_184,
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")),
issuer: pk("DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ"),
parameters: None,
previous_hash: unwrap!(Hash::from_hex(
"000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB"
)),
previous_issuer: pk("D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH"),
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![tx1, tx2],
};
let mut block = DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
nonce: 10_300_000_018_323,
signature: unwrap!(ed25519::Signature::from_base64("92id58VmkhgVNee4LDqBGSm8u/ooHzAD67JM6fhAE/CV8LCz7XrMF1DvRl+eRpmlaVkp6I+Iy8gmZ1WUM5C8BA==")),
hash: BlockHash::default(),
});
println!("{}", block.generate_compact_inner_text());
assert!(block.verify_inner_hash().is_ok());
assert_eq!(
unwrap!(block.inner_hash).to_hex(),
"C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1"
);
assert_eq!(
block.generate_compact_inner_text(),
"Version: 10\nType: Block\nCurrency: g1\nNumber: 107984\nPoWMin: 88\nTime: 1522685861\nMedianTime: 1522683184\nUnitBase: 0\nIssuer: DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ\nIssuersFrame: 211\nIssuersFrameVar: 0\nDifferentIssuersCount: 42\nPreviousHash: 000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB\nPreviousIssuer: D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH\nMembersCount: 896\nIdentities:\nJoiners:\nActives:\nLeavers:\nRevoked:\nExcluded:\nCertifications:\n6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3:CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV:106669:UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==\nTransactions:\nTX:10:1:1:1:1:1:0\n107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B\n8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3\n1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345\n0:SIG(0)\n1002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)\nDU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci\nT0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==\nTX:10:1:1:1:1:1:0\n107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B\n8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3\n1002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614\n0:SIG(0)\n1002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)\nDU symbolique pour demander le codage de nouvelles fonctionnalites cf. https://forum.monnaie-libre.fr/t/creer-de-nouvelles-fonctionnalites-dans-cesium-les-autres-applications/2025 Merci\na9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg==\n"
);
assert!(block.verify_signature().is_ok());
block.hash = block.compute_hash();
assert!(block.verify_hash().is_ok());
assert_eq!(
block.hash.0.to_hex(),
"000004F8B84A3590243BA562E5F2BA379F55A0B387C5D6FAC1022DFF7FFE6014"
);
let block_size = unwrap!(bincode::serialize(&block)).len();
block.reduce();
let block_reduced_size = unwrap!(bincode::serialize(&block)).len();
assert!(block_reduced_size < block_size);
println!(
"block reduction: {} octets -> {} octets",
block_size, block_reduced_size
);
}
#[test]
fn generate_and_verify_block_2() {
let ms1 = unwrap!(MembershipDocument::parse_from_raw_text(
"Version: 10
Type: Membership
Currency: g1
Issuer: 4VZkro3N7VonygybESHngKUABA6gSrbW77Ktb94zE969
Block: 165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2
Membership: IN
UserID: piaaf31
CertTS: 74077-0000022816648B2F7801E059F67CCD0C023FF0ED84459D52C70494D74DDCC6F6
gvaZ1QnJf8FjjRDJ0cYusgpBgQ8r0NqEz39BooH6DtIrgX+WTeXuLSnjZDl35VCBjokvyjry+v0OkTT8FKpABA==",
));
let MembershipDocument::V10(ms1) = ms1;
let TransactionDocument::V10(tx1) = unwrap!(TransactionDocument::parse_from_raw_text(
"Version: 10
Type: Transaction
Currency: g1
Blockstamp: 165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2
Locktime: 0
Issuers:
51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2
Inputs:
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:163766
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164040
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164320
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164584
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164849
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:165118
1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:165389
Unlocks:
0:SIG(0)
1:SIG(0)
2:SIG(0)
3:SIG(0)
4:SIG(0)
5:SIG(0)
6:SIG(0)
Outputs:
7000:0:SIG(98wxzS683Tc1WWm1YxpL5WpxS7wBa1mZBccKSsYpaant)
28:0:SIG(51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2)
Comment: Panier mixte plus 40 pommes merci
7o/yIh0BNSAv5pNmHz04uUBl8TuP2s4HRFMtKeGFQfXNYJPUyJTP/dj6hdrgKtJkm5dCfbxT4KRy6wJf+dj1Cw==",
));
let TransactionDocument::V10(tx2) = unwrap!(TransactionDocument::parse_from_raw_text(
"Version: 10
Type: Transaction
Currency: g1
Blockstamp: 165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2
Locktime: 0
Issuers:
3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX
Inputs:
1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:148827
1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149100
1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149370
1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149664
1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149943
1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:150222
Unlocks:
0:SIG(0)
1:SIG(0)
2:SIG(0)
3:SIG(0)
4:SIG(0)
5:SIG(0)
Outputs:
6000:0:SIG(AopwTfXhj8VqZReFJYGGWnoWnXNj3RgaqFcGGywXpZrD)
12:0:SIG(3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX)
Comment: En reglement de tes bons bocaux de fruits et legumes
nxr4exGrt16jteN9ZX3XZPP9l+X0OUbZ1o/QjE1hbWQNtVU3HhH9SJoEvNj2iVl3gCRr9u2OA9uj9vCyUDyjAg==
",
));
let block_content = DubpBlockV10Content {
version: 10,
number: BlockNumber(165_647),
pow_min: 90,
time: 1_540_633_175,
median_time: 1_540_627_811,
members_count: 1402,
monetary_mass: 386_008_811,
unit_base: 0,
issuers_count: 37,
issuers_frame: 186,
issuers_frame_var: 0,
currency: CurrencyName(String::from("g1")),
issuer: pk("A4pc9Uuk4NXkWG8CibicjjPpEPdiup1mhjMoRWUZsonq"),
parameters: None,
previous_hash: unwrap!(Hash::from_hex(
"000003E78FA4133F2C13B416F330C8DFB5A41EB87E37190615DB334F2C914A51"
)),
previous_issuer: pk("8NmGZmGjL1LUgJQRg282yQF7KTdQuRNAg8QfSa2qvd65"),
dividend: None,
identities: vec![],
joiners: vec![],
actives: vec![ms1],
leavers: vec![],
revoked: vec![],
excluded: vec![],
certifications: vec![],
transactions: vec![tx1, tx2],
};
let mut block = DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
nonce: 10_300_000_090_296,
signature: unwrap!(ed25519::Signature::from_base64("2Z/+9ADdZvHXs19YR8+qDzgfl8WJlBG5PcbFvBG9TOuUJbjAdxhcgxrFrSRIABGWcCrIgLkB805fZVLP8jOjBA==")),
hash: BlockHash::default(),
});
println!("{}", block.generate_compact_inner_text());
assert!(block.verify_inner_hash().is_ok());
assert_eq!(
unwrap!(block.inner_hash).to_hex(),
"3B49ECC1475549CFD94CA7B399311548A0FD0EC93C8EDD5670DAA5A958A41846"
);
let block_compact_text = block.generate_compact_inner_text();
assert_eq!(
block_compact_text,
"Version: 10\nType: Block\nCurrency: g1\nNumber: 165647\nPoWMin: 90\nTime: 1540633175\nMedianTime: 1540627811\nUnitBase: 0\nIssuer: A4pc9Uuk4NXkWG8CibicjjPpEPdiup1mhjMoRWUZsonq\nIssuersFrame: 186\nIssuersFrameVar: 0\nDifferentIssuersCount: 37\nPreviousHash: 000003E78FA4133F2C13B416F330C8DFB5A41EB87E37190615DB334F2C914A51\nPreviousIssuer: 8NmGZmGjL1LUgJQRg282yQF7KTdQuRNAg8QfSa2qvd65\nMembersCount: 1402\nIdentities:\nJoiners:\nActives:\n4VZkro3N7VonygybESHngKUABA6gSrbW77Ktb94zE969:gvaZ1QnJf8FjjRDJ0cYusgpBgQ8r0NqEz39BooH6DtIrgX+WTeXuLSnjZDl35VCBjokvyjry+v0OkTT8FKpABA==:165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2:74077-0000022816648B2F7801E059F67CCD0C023FF0ED84459D52C70494D74DDCC6F6:piaaf31\nLeavers:\nRevoked:\nExcluded:\nCertifications:\nTransactions:\nTX:10:1:7:7:2:1:0\n165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2\n51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:163766\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164040\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164320\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164584\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164849\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:165118\n1004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:165389\n0:SIG(0)\n1:SIG(0)\n2:SIG(0)\n3:SIG(0)\n4:SIG(0)\n5:SIG(0)\n6:SIG(0)\n7000:0:SIG(98wxzS683Tc1WWm1YxpL5WpxS7wBa1mZBccKSsYpaant)\n28:0:SIG(51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2)\nPanier mixte plus 40 pommes merci\n7o/yIh0BNSAv5pNmHz04uUBl8TuP2s4HRFMtKeGFQfXNYJPUyJTP/dj6hdrgKtJkm5dCfbxT4KRy6wJf+dj1Cw==\nTX:10:1:6:6:2:1:0\n165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2\n3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX\n1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:148827\n1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149100\n1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149370\n1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149664\n1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149943\n1002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:150222\n0:SIG(0)\n1:SIG(0)\n2:SIG(0)\n3:SIG(0)\n4:SIG(0)\n5:SIG(0)\n6000:0:SIG(AopwTfXhj8VqZReFJYGGWnoWnXNj3RgaqFcGGywXpZrD)\n12:0:SIG(3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX)\nEn reglement de tes bons bocaux de fruits et legumes\nnxr4exGrt16jteN9ZX3XZPP9l+X0OUbZ1o/QjE1hbWQNtVU3HhH9SJoEvNj2iVl3gCRr9u2OA9uj9vCyUDyjAg==\n"
);
assert!(block.verify_signature().is_ok());
block.hash = block.compute_hash();
assert!(block.verify_hash().is_ok());
assert_eq!(
block.hash.0.to_hex(),
"000002026E32A3D649B34968AAF9D03C4F19A5954229C54A801BBB1CD216B230"
);
}
}