1use crate::*;
19use dubp_documents::identity::IdentityDocumentV10;
20use dubp_documents::membership::v10::MembershipDocumentV10;
21use dubp_documents::revocation::{CompactRevocationDocumentV10, RevocationDocumentV10};
22use dubp_documents::transaction::v10::{TransactionDocumentV10, TransactionDocumentV10Stringified};
23use dubp_documents::{
24 certification::{v10::CertificationDocumentV10, CompactCertificationDocumentV10},
25 dubp_wallet::prelude::SourceAmount,
26};
27use std::borrow::Cow;
28
29#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
30pub struct DubpBlockV10Content {
31 pub version: usize,
33 pub number: BlockNumber,
35 pub pow_min: usize,
37 pub time: u64,
39 pub median_time: u64,
41 pub members_count: usize,
43 pub monetary_mass: u64,
45 pub unit_base: usize,
47 pub issuers_count: usize,
49 pub issuers_frame: usize,
51 pub issuers_frame_var: isize,
53 pub currency: CurrencyName,
55 pub issuer: ed25519::PublicKey,
57 pub parameters: Option<BlockV10Parameters>,
59 pub previous_hash: Hash,
61 pub previous_issuer: ed25519::PublicKey,
63 pub dividend: Option<usize>,
65 pub identities: Vec<IdentityDocumentV10>,
67 pub joiners: Vec<MembershipDocumentV10>,
69 pub actives: Vec<MembershipDocumentV10>,
71 pub leavers: Vec<MembershipDocumentV10>,
73 pub revoked: Vec<TextDocumentFormat<RevocationDocumentV10>>,
75 pub excluded: Vec<ed25519::PublicKey>,
77 pub certifications: Vec<TextDocumentFormat<CertificationDocumentV10>>,
79 pub transactions: Vec<TransactionDocumentV10>,
81}
82
83impl DubpBlockV10Content {
84 pub(crate) fn gen_hashable_text(&self) -> String {
85 let mut identities_str = String::from("");
86 for identity in &self.identities {
87 identities_str.push('\n');
88 identities_str.push_str(&identity.generate_compact_text());
89 }
90 let mut joiners_str = String::new();
91 for joiner in &self.joiners {
92 joiners_str.push('\n');
93 joiners_str.push_str(&joiner.generate_compact_text());
94 }
95 let mut actives_str = String::new();
96 for active in &self.actives {
97 actives_str.push('\n');
98 actives_str.push_str(&active.generate_compact_text());
99 }
100 let mut leavers_str = String::new();
101 for leaver in &self.leavers {
102 leavers_str.push('\n');
103 leavers_str.push_str(&leaver.generate_compact_text());
104 }
105 let mut identities_str = String::new();
106 for identity in &self.identities {
107 identities_str.push('\n');
108 identities_str.push_str(&identity.generate_compact_text());
109 }
110 let mut revokeds_str = String::new();
111 for revocation in &self.revoked {
112 revokeds_str.push('\n');
113 revokeds_str.push_str(&revocation.as_compact_text());
114 }
115 let mut excludeds_str = String::new();
116 for exclusion in &self.excluded {
117 excludeds_str.push('\n');
118 excludeds_str.push_str(&exclusion.to_string());
119 }
120 let mut certifications_str = String::new();
121 for certification in &self.certifications {
122 certifications_str.push('\n');
123 certifications_str.push_str(&certification.as_compact_text());
124 }
125 let mut transactions_str = String::new();
126 for transaction in &self.transactions {
127 transactions_str.push('\n');
128 transactions_str.push_str(&transaction.generate_compact_text());
129 }
130 let mut dividend_str = String::new();
131 if let Some(dividend) = self.dividend {
132 if dividend > 0 {
133 dividend_str.push_str("UniversalDividend: ");
134 dividend_str.push_str(÷nd.to_string());
135 dividend_str.push('\n');
136 }
137 }
138 let mut parameters_str = String::new();
139 if let Some(params) = self.parameters {
140 parameters_str.push_str("Parameters: ");
141 parameters_str.push_str(¶ms.to_string());
142 parameters_str.push('\n');
143 }
144 let mut previous_hash_str = String::new();
145 if self.number.0 > 0 {
146 previous_hash_str.push_str("PreviousHash: ");
147 previous_hash_str.push_str(&self.previous_hash.to_string());
148 previous_hash_str.push('\n');
149 }
150 let mut previous_issuer_str = String::new();
151 if self.number.0 > 0 {
152 previous_issuer_str.push_str("PreviousIssuer: ");
153 previous_issuer_str.push_str(&self.previous_issuer.to_string());
154 previous_issuer_str.push('\n');
155 }
156 format!(
157 "Version: {version}
158Type: Block
159Currency: {currency}
160Number: {block_number}
161PoWMin: {pow_min}
162Time: {time}
163MedianTime: {median_time}
164{dividend}UnitBase: {unit_base}
165Issuer: {issuer}
166IssuersFrame: {issuers_frame}
167IssuersFrameVar: {issuers_frame_var}
168DifferentIssuersCount: {issuers_count}
169{parameters}{previous_hash}{previous_issuer}MembersCount: {members_count}
170Identities:{identities}
171Joiners:{joiners}
172Actives:{actives}
173Leavers:{leavers}
174Revoked:{revoked}
175Excluded:{excluded}
176Certifications:{certifications}
177Transactions:{transactions}
178",
179 version = self.version,
180 currency = self.currency,
181 block_number = self.number,
182 pow_min = self.pow_min,
183 time = self.time,
184 median_time = self.median_time,
185 dividend = dividend_str,
186 unit_base = self.unit_base,
187 issuer = self.issuer,
188 issuers_frame = self.issuers_frame,
189 issuers_frame_var = self.issuers_frame_var,
190 issuers_count = self.issuers_count,
191 parameters = parameters_str,
192 previous_hash = previous_hash_str,
193 previous_issuer = previous_issuer_str,
194 members_count = self.members_count,
195 identities = identities_str,
196 joiners = joiners_str,
197 actives = actives_str,
198 leavers = leavers_str,
199 revoked = revokeds_str,
200 excluded = excludeds_str,
201 certifications = certifications_str,
202 transactions = transactions_str,
203 )
204 }
205}
206
207#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq)]
208pub struct DubpBlockV10AfterPowData {
209 pub nonce: u64,
210 pub signature: ed25519::Signature,
211 pub hash: BlockHash,
212}
213#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
214pub struct DubpBlockV10Builder {
215 content: DubpBlockV10Content,
217 inner_hash: Hash,
219}
220
221impl DubpBlockV10Builder {
222 pub fn new(content: DubpBlockV10Content) -> Self {
223 DubpBlockV10Builder {
224 inner_hash: Hash::compute(content.gen_hashable_text().as_bytes()),
225 content,
226 }
227 }
228 pub fn inner_hash(&self) -> Hash {
229 self.inner_hash
230 }
231 pub fn build_unchecked(self, after_pow_data: DubpBlockV10AfterPowData) -> DubpBlockV10 {
232 DubpBlockV10 {
233 content: self.content,
234 inner_hash: Some(self.inner_hash),
235 nonce: after_pow_data.nonce,
236 signature: after_pow_data.signature,
237 hash: after_pow_data.hash,
238 }
239 }
240}
241
242#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
246pub struct DubpBlockV10 {
247 content: DubpBlockV10Content,
249 inner_hash: Option<Hash>,
252 nonce: u64,
254 signature: ed25519::Signature,
256 hash: BlockHash,
258}
259
260impl DubpBlockTrait for DubpBlockV10 {
261 type Signator = ed25519::Signator;
262
263 fn common_time(&self) -> u64 {
264 self.content.median_time
265 }
266 fn compute_hashed_string(&self) -> String {
267 format!("{}{}\n", self.compute_signed_string(), self.signature)
268 }
269 fn compute_signed_string(&self) -> String {
270 let inner_hash = if let Some(inner_hash) = self.inner_hash {
271 inner_hash
272 } else {
273 self.compute_inner_hash()
274 };
275 format!(
276 "InnerHash: {}\nNonce: {}\n",
277 inner_hash.to_hex(),
278 self.nonce
279 )
280 }
281 fn current_frame_size(&self) -> usize {
282 self.content.issuers_frame
283 }
284 fn currency_name(&self) -> CurrencyName {
285 self.content.currency.clone()
286 }
287 fn currency_parameters(&self) -> Option<CurrencyParameters> {
288 if let Some(genesis_parameters) = self.content.parameters {
289 Some(CurrencyParameters::from((
290 &self.content.currency,
291 genesis_parameters,
292 )))
293 } else {
294 None
295 }
296 }
297 fn dividend(&self) -> Option<SourceAmount> {
298 if let Some(dividend) = self.content.dividend {
299 Some(SourceAmount::new(
300 dividend as i64,
301 self.content.unit_base as i64,
302 ))
303 } else {
304 None
305 }
306 }
307 fn generate_compact_inner_text(&self) -> String {
308 self.content.gen_hashable_text()
309 }
310 fn hash(&self) -> BlockHash {
311 self.hash
312 }
313 fn inner_hash(&self) -> Hash {
314 if let Some(inner_hash) = self.inner_hash {
315 inner_hash
316 } else {
317 self.compute_inner_hash()
318 }
319 }
320 fn issuers_count(&self) -> usize {
321 self.content.issuers_count
322 }
323 fn issuers_frame(&self) -> usize {
324 self.content.issuers_frame
325 }
326 fn issuer(&self) -> ed25519::PublicKey {
327 self.content.issuer
328 }
329 fn local_time(&self) -> u64 {
330 self.content.time
331 }
332 fn members_count(&self) -> usize {
333 self.content.members_count
334 }
335 fn monetary_mass(&self) -> u64 {
336 self.content.monetary_mass
337 }
338 fn nonce(&self) -> u64 {
339 self.nonce
340 }
341 fn number(&self) -> BlockNumber {
342 self.content.number
343 }
344 fn pow_min(&self) -> usize {
345 self.content.pow_min
346 }
347 fn previous_blockstamp(&self) -> Blockstamp {
348 if self.content.number.0 > 0 {
349 Blockstamp {
350 number: BlockNumber(self.content.number.0 - 1),
351 hash: BlockHash(self.content.previous_hash),
352 }
353 } else {
354 Blockstamp::default()
355 }
356 }
357 fn previous_hash(&self) -> Hash {
358 self.content.previous_hash
359 }
360 fn reduce(&mut self) {
361 self.inner_hash = None;
363 for i in &mut self.content.identities {
364 i.reduce();
365 }
366 for i in &mut self.content.joiners {
367 i.reduce();
368 }
369 for i in &mut self.content.actives {
370 i.reduce();
371 }
372 for i in &mut self.content.leavers {
373 i.reduce();
374 }
375 for i in &mut self.content.transactions {
376 i.reduce();
377 }
378 }
379 fn sign(&mut self, signator: &Self::Signator) -> Result<(), SignError> {
380 self.signature = signator.sign(self.compute_signed_string().as_bytes());
381 Ok(())
382 }
383 fn signature(&self) -> ed25519::Signature {
384 self.signature
385 }
386 fn verify_inner_hash(&self) -> Result<(), VerifyBlockHashError> {
387 match self.inner_hash {
388 Some(inner_hash) => {
389 let computed_hash = self.compute_inner_hash();
390 if inner_hash == computed_hash {
391 Ok(())
392 } else {
393 Err(VerifyBlockHashError::InvalidHash {
394 block_number: self.content.number,
395 expected_hash: computed_hash,
396 actual_hash: inner_hash,
397 })
398 }
399 }
400 None => Err(VerifyBlockHashError::MissingHash {
401 block_number: self.content.number,
402 }),
403 }
404 }
405 fn verify_signature(&self) -> Result<(), SigError> {
406 self.content
407 .issuer
408 .verify(self.compute_signed_string().as_bytes(), &self.signature)
409 }
410 fn verify_hash(&self) -> Result<(), VerifyBlockHashError> {
411 let expected_hash = self.compute_hash();
412 if self.hash == expected_hash {
413 Ok(())
414 } else {
415 warn!(
416 "Block #{} have invalid hash (expected='{}', actual='{}', datas='{}').",
417 self.content.number.0,
418 expected_hash,
419 self.hash,
420 self.compute_hashed_string()
421 );
422 Err(VerifyBlockHashError::InvalidHash {
423 block_number: self.content.number,
424 expected_hash: expected_hash.0,
425 actual_hash: self.hash.0,
426 })
427 }
428 }
429 fn unit_base(&self) -> usize {
430 self.content.unit_base
431 }
432}
433
434impl DubpBlockV10 {
435 pub fn identities(&self) -> &[IdentityDocumentV10] {
436 &self.content.identities
437 }
438 pub fn joiners(&self) -> &[MembershipDocumentV10] {
439 &self.content.joiners
440 }
441 pub fn actives(&self) -> &[MembershipDocumentV10] {
442 &self.content.actives
443 }
444 pub fn leavers(&self) -> &[MembershipDocumentV10] {
445 &self.content.leavers
446 }
447 pub fn revoked(&self) -> Vec<Cow<CompactRevocationDocumentV10>> {
448 self.content
449 .revoked
450 .iter()
451 .map(|revo| revo.to_compact_document())
452 .collect()
453 }
454 pub fn excluded(&self) -> &[ed25519::PublicKey] {
455 &self.content.excluded
456 }
457 pub fn certifications(&self) -> Vec<Cow<CompactCertificationDocumentV10>> {
458 self.content
459 .certifications
460 .iter()
461 .map(|cert| cert.to_compact_document())
462 .collect()
463 }
464 pub fn transactions(&self) -> &[TransactionDocumentV10] {
465 &self.content.transactions
466 }
467 #[cfg(not(tarpaulin_include))]
469 pub fn as_compact_text(&self) -> String {
470 let compact_inner_text = self.generate_compact_inner_text();
471 let inner_hash = if let Some(inner_hash) = self.inner_hash {
472 inner_hash
473 } else {
474 Hash::compute(compact_inner_text.as_bytes())
475 };
476 format!(
477 "{}InnerHash: {}\nNonce: ",
478 compact_inner_text,
479 inner_hash.to_hex()
480 )
481 }
482}
483
484#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
485#[serde(rename_all = "camelCase")]
486pub struct DubpBlockV10Stringified {
487 pub version: u64,
489 pub nonce: u64,
491 pub number: u64,
493 pub pow_min: u64,
495 pub time: u64,
497 pub median_time: u64,
499 pub members_count: u64,
501 pub monetary_mass: u64,
503 #[serde(rename = "unitbase")]
505 pub unit_base: u64,
506 pub issuers_count: u64,
508 pub issuers_frame: u64,
510 pub issuers_frame_var: i64,
512 pub currency: String,
514 pub issuer: String,
516 pub signature: String,
518 pub hash: Option<String>,
520 pub parameters: Option<String>,
522 pub previous_hash: Option<String>,
524 pub previous_issuer: Option<String>,
526 #[serde(rename = "inner_hash")]
528 pub inner_hash: Option<String>,
529 pub dividend: Option<u64>,
531 pub identities: Vec<String>,
533 pub joiners: Vec<String>,
535 pub actives: Vec<String>,
537 pub leavers: Vec<String>,
539 pub revoked: Vec<String>,
541 pub excluded: Vec<String>,
543 pub certifications: Vec<String>,
545 pub transactions: Vec<TransactionDocumentV10Stringified>,
547}
548
549impl ToStringObject for DubpBlockV10 {
550 type StringObject = DubpBlockV10Stringified;
551 fn to_string_object(&self) -> DubpBlockV10Stringified {
553 DubpBlockV10Stringified {
554 version: self.content.version as u64,
555 nonce: self.nonce,
556 number: u64::from(self.content.number.0),
557 pow_min: self.content.pow_min as u64,
558 time: self.content.time,
559 median_time: self.content.median_time,
560 members_count: self.content.members_count as u64,
561 monetary_mass: self.content.monetary_mass,
562 unit_base: self.content.unit_base as u64,
563 issuers_count: self.content.issuers_count as u64,
564 issuers_frame: self.content.issuers_frame as u64,
565 issuers_frame_var: self.content.issuers_frame_var as i64,
566 currency: self.content.currency.to_string(),
567 issuer: self.content.issuer.to_string(),
568 signature: self.signature.to_string(),
569 hash: Some(self.hash.to_string()),
570 parameters: self
571 .content
572 .parameters
573 .map(|parameters| parameters.to_string()),
574 previous_hash: if self.content.number.0 == 0 {
575 None
576 } else {
577 Some(self.content.previous_hash.to_string())
578 },
579 previous_issuer: if self.content.number.0 == 0 {
580 None
581 } else {
582 Some(self.content.previous_issuer.to_string())
583 },
584 inner_hash: self.inner_hash.map(|hash| hash.to_string()),
585 dividend: self.content.dividend.map(|d| d as u64),
586 identities: self
587 .content
588 .identities
589 .iter()
590 .map(|doc| doc.to_compact_document().as_compact_text())
591 .collect(),
592 joiners: self
593 .content
594 .joiners
595 .iter()
596 .map(|doc| doc.to_compact_document().as_compact_text())
597 .collect(),
598 actives: self
599 .content
600 .actives
601 .iter()
602 .map(|doc| doc.to_compact_document().as_compact_text())
603 .collect(),
604 leavers: self
605 .content
606 .leavers
607 .iter()
608 .map(|doc| doc.to_compact_document().as_compact_text())
609 .collect(),
610 revoked: self
611 .content
612 .revoked
613 .iter()
614 .map(|doc| doc.to_compact_document().as_compact_text())
615 .collect(),
616 excluded: self
617 .content
618 .excluded
619 .iter()
620 .map(ToString::to_string)
621 .collect(),
622 certifications: self
623 .content
624 .certifications
625 .iter()
626 .map(|doc| doc.to_compact_document().as_compact_text())
627 .collect(),
628 transactions: self
629 .content
630 .transactions
631 .iter()
632 .map(|tx_doc| tx_doc.to_string_object())
633 .collect(),
634 }
635 }
636}
637
638impl FromStringObject for DubpBlockV10 {
639 fn from_string_object(stringified: &DubpBlockV10Stringified) -> Result<Self, TextParseError> {
640 let str_identities: Vec<_> = stringified.identities.iter().map(|x| &**x).collect();
641 let str_joiners: Vec<_> = stringified.joiners.iter().map(|x| &**x).collect();
642 let str_actives: Vec<_> = stringified.actives.iter().map(|x| &**x).collect();
643 let str_leavers: Vec<_> = stringified.leavers.iter().map(|x| &**x).collect();
644 let str_revoked: Vec<_> = stringified.revoked.iter().map(|x| &**x).collect();
645 let str_certs: Vec<_> = stringified.certifications.iter().map(|x| &**x).collect();
646 Ok(DubpBlockV10 {
647 content: DubpBlockV10Content {
648 version: stringified.version as usize,
649 number: BlockNumber(stringified.number as u32),
650 pow_min: stringified.pow_min as usize,
651 time: stringified.time,
652 median_time: stringified.median_time,
653 members_count: stringified.members_count as usize,
654 monetary_mass: stringified.monetary_mass,
655 unit_base: stringified.unit_base as usize,
656 issuers_count: stringified.issuers_count as usize,
657 issuers_frame: stringified.issuers_frame as usize,
658 issuers_frame_var: stringified.issuers_frame_var as isize,
659 currency: CurrencyName(stringified.currency.clone()),
660 issuer: ed25519::PublicKey::from_base58(&stringified.issuer).map_err(|error| {
661 TextParseError::BaseConversionError {
662 field: "block.issuer",
663 error,
664 }
665 })?,
666 parameters: None,
667 previous_hash: if let Some(ref previous_hash) = stringified.previous_hash {
668 if !previous_hash.is_empty() {
669 Hash::from_hex(previous_hash).map_err(|error| {
670 TextParseError::BaseConversionError {
671 field: "block.previous_hash",
672 error,
673 }
674 })?
675 } else {
676 Hash::default()
677 }
678 } else {
679 Hash::default()
680 },
681 previous_issuer: if let Some(ref previous_issuer) = stringified.previous_issuer {
682 if !previous_issuer.is_empty() {
683 ed25519::PublicKey::from_base58(previous_issuer).map_err(|error| {
684 TextParseError::BaseConversionError {
685 field: "block.previous_issuer",
686 error,
687 }
688 })?
689 } else {
690 ed25519::PublicKey::default()
691 }
692 } else {
693 ed25519::PublicKey::default()
694 },
695 dividend: stringified.dividend.map(|dividend| dividend as usize),
696 identities: parse_compact_identities(&stringified.currency, &str_identities)
697 .map_err(|error| TextParseError::CompactDoc {
698 field: "block.identities",
699 error,
700 })?,
701 joiners: parse_compact_memberships(
702 &stringified.currency,
703 MembershipType::In(),
704 &str_joiners,
705 )
706 .map_err(|error| TextParseError::CompactDoc {
707 field: "block.joiners",
708 error,
709 })?,
710 actives: parse_compact_memberships(
711 &stringified.currency,
712 MembershipType::In(),
713 &str_actives,
714 )
715 .map_err(|error| TextParseError::CompactDoc {
716 field: "block.actives",
717 error,
718 })?,
719 leavers: parse_compact_memberships(
720 &stringified.currency,
721 MembershipType::Out(),
722 &str_leavers,
723 )
724 .map_err(|error| TextParseError::CompactDoc {
725 field: "block.leavers",
726 error,
727 })?,
728 revoked: parse_compact_revocations(&str_revoked).map_err(|error| {
729 TextParseError::CompactDoc {
730 field: "block.revoked",
731 error,
732 }
733 })?,
734 excluded: stringified
735 .excluded
736 .iter()
737 .map(|pubkey| ed25519::PublicKey::from_base58(pubkey))
738 .collect::<Result<Vec<_>, _>>()
739 .map_err(|error| TextParseError::BaseConversionError {
740 field: "block.excluded",
741 error,
742 })?,
743 certifications: parse_compact_certifications(&str_certs).map_err(|error| {
744 TextParseError::CompactDoc {
745 field: "block.certifications",
746 error,
747 }
748 })?,
749 transactions: stringified
750 .transactions
751 .iter()
752 .map(|tx| TransactionDocumentV10::from_string_object(tx))
753 .collect::<Result<Vec<_>, _>>()?,
754 },
755 inner_hash: Some(
756 Hash::from_hex(stringified.inner_hash.as_ref().ok_or_else(|| {
757 TextParseError::InvalidInnerFormat("Block without inner_hash".to_owned())
758 })?)
759 .map_err(|error| TextParseError::BaseConversionError {
760 field: "block.inner_hash",
761 error,
762 })?,
763 ),
764 nonce: stringified.nonce,
765 signature: ed25519::Signature::from_base64(&stringified.signature).map_err(
766 |error| TextParseError::BaseConversionError {
767 field: "block.signature",
768 error,
769 },
770 )?,
771 hash: BlockHash(
772 Hash::from_hex(stringified.hash.as_ref().ok_or_else(|| {
773 TextParseError::InvalidInnerFormat("Block without hash".to_owned())
774 })?)
775 .map_err(|error| TextParseError::BaseConversionError {
776 field: "block.hash",
777 error,
778 })?,
779 ),
780 })
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787 use crate::tests::*;
788 use dubp_documents::certification::CertificationDocument;
789 use dubp_documents::membership::MembershipDocument;
790 use dubp_documents::transaction::TransactionDocument;
791 use pretty_assertions::assert_eq;
792 use unwrap::unwrap;
793
794 #[test]
795 fn test_default_block_v10() {
796 let mut default_block = DubpBlockV10::default();
797
798 let mut default_stringified_block = default_block.to_string_object();
799 default_stringified_block.currency = String::with_capacity(0);
800 default_stringified_block.issuer = String::with_capacity(0);
801 default_stringified_block.signature = String::with_capacity(0);
802 default_stringified_block.hash = None;
803 assert_eq!(
804 default_stringified_block,
805 DubpBlockV10Stringified::default()
806 );
807
808 assert_eq!(default_block.common_time(), 0);
809 assert_eq!(default_block.current_frame_size(), 0);
810 assert_eq!(default_block.issuers_count(), 0);
811 assert_eq!(default_block.members_count(), 0);
812 assert_eq!(default_block.number(), BlockNumber(0));
813 assert_eq!(default_block.pow_min(), 0);
814 assert_eq!(default_block.previous_blockstamp(), Blockstamp::default());
815 assert_eq!(default_block.previous_hash(), Hash::default());
816
817 assert_eq!(
819 Err(VerifyBlockHashError::MissingHash {
820 block_number: BlockNumber(0)
821 }),
822 default_block.verify_inner_hash()
823 );
824 default_block.inner_hash = Some(Hash::default());
825 assert_eq!(
826 Err(VerifyBlockHashError::InvalidHash {
827 block_number: BlockNumber(0),
828 actual_hash: Hash::default(),
829 expected_hash: default_block.compute_inner_hash(),
830 }),
831 default_block.verify_inner_hash()
832 );
833
834 assert_eq!(default_block.signature, ed25519::Signature::default());
836 assert_eq!(Err(SigError::InvalidSig), default_block.verify_signature());
837 let signator = unwrap!(ed25519::Ed25519KeyPair::generate_random()).generate_signator();
838 default_block.content.issuer = signator.public_key();
839 unwrap!(default_block.sign(&signator));
840 assert_eq!(Ok(()), default_block.verify_signature());
841
842 assert_eq!(BlockHash(Hash::default()), default_block.hash());
844 assert_eq!(
845 Err(VerifyBlockHashError::InvalidHash {
846 block_number: BlockNumber(0),
847 actual_hash: Hash::default(),
848 expected_hash: default_block.compute_hash().0,
849 }),
850 default_block.verify_hash()
851 );
852 }
853
854 #[test]
855 fn generate_and_verify_empty_block() {
856 let block_content = DubpBlockV10Content {
857 version: 10,
858 number: BlockNumber(174_260),
859 pow_min: 68,
860 time: 1_525_296_873,
861 median_time: 1_525_292_577,
862 members_count: 33,
863 monetary_mass: 15_633_687,
864 unit_base: 0,
865 issuers_count: 8,
866 issuers_frame: 41,
867 issuers_frame_var: 0,
868 currency: CurrencyName(String::from("g1-test")),
869 issuer: pk("39Fnossy1GrndwCnAXGDw3K5UYXhNXAFQe7yhYZp8ELP"),
870 parameters: None,
871 previous_hash: unwrap!(Hash::from_hex(
872 "0000A7D4361B9EBF4CE974A521149A73E8A5DE9B73907AB3BC918726AED7D40A"
873 )),
874 previous_issuer: pk("EPKuZA1Ek5y8S1AjAmAPtGrVCMFqUGzUEAa7Ei62CY2L"),
875 dividend: None,
876 identities: Vec::new(),
877 joiners: Vec::new(),
878 actives: Vec::new(),
879 leavers: Vec::new(),
880 revoked: Vec::new(),
881 excluded: Vec::new(),
882 certifications: Vec::new(),
883 transactions: Vec::new(),
884 };
885 let mut block = DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
886 nonce: 100_010_200_000_006_940,
887 signature: unwrap!(ed25519::Signature::from_base64("lqXrNOopjM39oM7hgB7Vq13uIohdCuLlhh/q8RVVEZ5UVASphow/GXikCdhbWID19Bn0XrXzTbt/R7akbE9xAg==")),
888 hash: BlockHash::default(),
889 });
890 println!("{}", block.generate_compact_inner_text());
892 assert!(block.verify_inner_hash().is_ok());
893 assert_eq!(
894 unwrap!(block.inner_hash).to_hex(),
895 "58E4865A47A46E0DF1449AABC449B5406A12047C413D61B5E17F86BE6641E7B0"
896 );
897 assert!(block.verify_signature().is_ok());
899 let computed_hash = block.compute_hash();
901 block.hash = computed_hash;
902 assert!(block.verify_hash().is_ok());
903 assert_eq!(
904 block.hash.0.to_hex(),
905 "00002EE584F36C15D3EB21AAC78E0896C75EF9070E73B4EC33BFA2C3D561EEB2"
906 );
907 }
908
909 #[test]
910 fn generate_and_verify_block() {
911 let cert1 = unwrap!(CertificationDocument::parse_from_raw_text("Version: 10
912Type: Certification
913Currency: g1
914Issuer: 6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3
915IdtyIssuer: CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV
916IdtyUniqueID: PascaleM
917IdtyTimestamp: 97401-0000003821911909F98519CC773D2D3E5CFE3D5DBB39F4F4FF33B96B4D41800E
918IdtySignature: QncUVXxZ2NfARjdJOn6luILvDuG1NuK9qSoaU4CST2Ij8z7oeVtEgryHl+EXOjSe6XniALsCT0gU8wtadcA/Cw==
919CertTimestamp: 106669-000003682E6FE38C44433DCE92E8B2A26C69B6D7867A2BAED231E788DDEF4251
920UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA=="));
921 let CertificationDocument::V10(cert1) = cert1;
922
923 let TransactionDocument::V10(tx1) = unwrap!(TransactionDocument::parse_from_raw_text("Version: 10
924Type: Transaction
925Currency: g1
926Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
927Locktime: 0
928Issuers:
9298dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
930Inputs:
9311002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345
932Unlocks:
9330:SIG(0)
934Outputs:
9351002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)
936Comment: 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
937T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ=="));
938
939 let TransactionDocument::V10(tx2) = unwrap!(TransactionDocument::parse_from_raw_text("Version: 10
940Type: Transaction
941Currency: g1
942Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
943Locktime: 0
944Issuers:
9458dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
946Inputs:
9471002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614
948Unlocks:
9490:SIG(0)
950Outputs:
9511002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)
952Comment: 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
953a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg=="));
954
955 let block_content = DubpBlockV10Content {
956 version: 10,
957 number: BlockNumber(107_984),
958 pow_min: 88,
959 time: 1_522_685_861,
960 median_time: 1_522_683_184,
961 members_count: 896,
962 monetary_mass: 140_469_765,
963 unit_base: 0,
964 issuers_count: 42,
965 issuers_frame: 211,
966 issuers_frame_var: 0,
967 currency: CurrencyName(String::from("g1")),
968 issuer: pk("DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ"),
969 parameters: None,
970 previous_hash: unwrap!(Hash::from_hex(
971 "000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB"
972 )),
973 previous_issuer: pk("D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH"),
974 dividend: None,
975 identities: Vec::new(),
976 joiners: Vec::new(),
977 actives: Vec::new(),
978 leavers: Vec::new(),
979 revoked: Vec::new(),
980 excluded: Vec::new(),
981 certifications: vec![TextDocumentFormat::Complete(cert1)],
982 transactions: vec![tx1, tx2],
983 };
984 let mut block = DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
985 nonce: 10_300_000_018_323,
986 signature: unwrap!(ed25519::Signature::from_base64("92id58VmkhgVNee4LDqBGSm8u/ooHzAD67JM6fhAE/CV8LCz7XrMF1DvRl+eRpmlaVkp6I+Iy8gmZ1WUM5C8BA==")),
987 hash: BlockHash::default(),
988 });
989 println!("{}", block.generate_compact_inner_text());
991 assert!(block.verify_inner_hash().is_ok());
992
993 assert_eq!(
994 unwrap!(block.inner_hash).to_hex(),
995 "C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1"
996 );
997 assert_eq!(
999 block.generate_compact_inner_text(),
1000 "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"
1001 );
1002 assert!(block.verify_signature().is_ok());
1004 block.hash = block.compute_hash();
1006 assert!(block.verify_hash().is_ok());
1007 assert_eq!(
1008 block.hash.0.to_hex(),
1009 "000004F8B84A3590243BA562E5F2BA379F55A0B387C5D6FAC1022DFF7FFE6014"
1010 );
1011
1012 let block_size = unwrap!(bincode::serialize(&block)).len();
1014 block.reduce();
1015 let block_reduced_size = unwrap!(bincode::serialize(&block)).len();
1016 assert!(block_reduced_size < block_size);
1017 println!(
1018 "block reduction: {} octets -> {} octets",
1019 block_size, block_reduced_size
1020 );
1021 }
1022
1023 #[test]
1024 fn generate_and_verify_block_2() {
1025 let ms1 = unwrap!(MembershipDocument::parse_from_raw_text(
1026 "Version: 10
1027Type: Membership
1028Currency: g1
1029Issuer: 4VZkro3N7VonygybESHngKUABA6gSrbW77Ktb94zE969
1030Block: 165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2
1031Membership: IN
1032UserID: piaaf31
1033CertTS: 74077-0000022816648B2F7801E059F67CCD0C023FF0ED84459D52C70494D74DDCC6F6
1034gvaZ1QnJf8FjjRDJ0cYusgpBgQ8r0NqEz39BooH6DtIrgX+WTeXuLSnjZDl35VCBjokvyjry+v0OkTT8FKpABA==",
1035 ));
1036 let MembershipDocument::V10(ms1) = ms1;
1037
1038 let TransactionDocument::V10(tx1) = unwrap!(TransactionDocument::parse_from_raw_text(
1039 "Version: 10
1040Type: Transaction
1041Currency: g1
1042Blockstamp: 165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2
1043Locktime: 0
1044Issuers:
104551EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2
1046Inputs:
10471004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:163766
10481004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164040
10491004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164320
10501004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164584
10511004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:164849
10521004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:165118
10531004:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:165389
1054Unlocks:
10550:SIG(0)
10561:SIG(0)
10572:SIG(0)
10583:SIG(0)
10594:SIG(0)
10605:SIG(0)
10616:SIG(0)
1062Outputs:
10637000:0:SIG(98wxzS683Tc1WWm1YxpL5WpxS7wBa1mZBccKSsYpaant)
106428:0:SIG(51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2)
1065Comment: Panier mixte plus 40 pommes merci
10667o/yIh0BNSAv5pNmHz04uUBl8TuP2s4HRFMtKeGFQfXNYJPUyJTP/dj6hdrgKtJkm5dCfbxT4KRy6wJf+dj1Cw==",
1067 ));
1068
1069 let TransactionDocument::V10(tx2) = unwrap!(TransactionDocument::parse_from_raw_text(
1070 "Version: 10
1071Type: Transaction
1072Currency: g1
1073Blockstamp: 165645-000002D30130881939961A38D51CA233B3C696AA604439036DB1AAA4ED5046D2
1074Locktime: 0
1075Issuers:
10763Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX
1077Inputs:
10781002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:148827
10791002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149100
10801002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149370
10811002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149664
10821002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:149943
10831002:0:D:3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX:150222
1084Unlocks:
10850:SIG(0)
10861:SIG(0)
10872:SIG(0)
10883:SIG(0)
10894:SIG(0)
10905:SIG(0)
1091Outputs:
10926000:0:SIG(AopwTfXhj8VqZReFJYGGWnoWnXNj3RgaqFcGGywXpZrD)
109312:0:SIG(3Uwq4qNp2A97P1XQueEBCxmnvgtAKMdfrEq6VB7Ph2qX)
1094Comment: En reglement de tes bons bocaux de fruits et legumes
1095nxr4exGrt16jteN9ZX3XZPP9l+X0OUbZ1o/QjE1hbWQNtVU3HhH9SJoEvNj2iVl3gCRr9u2OA9uj9vCyUDyjAg==
1096",
1097 ));
1098
1099 let block_content = DubpBlockV10Content {
1100 version: 10,
1101 number: BlockNumber(165_647),
1102 pow_min: 90,
1103 time: 1_540_633_175,
1104 median_time: 1_540_627_811,
1105 members_count: 1402,
1106 monetary_mass: 386_008_811,
1107 unit_base: 0,
1108 issuers_count: 37,
1109 issuers_frame: 186,
1110 issuers_frame_var: 0,
1111 currency: CurrencyName(String::from("g1")),
1112 issuer: pk("A4pc9Uuk4NXkWG8CibicjjPpEPdiup1mhjMoRWUZsonq"),
1113 parameters: None,
1114 previous_hash: unwrap!(Hash::from_hex(
1115 "000003E78FA4133F2C13B416F330C8DFB5A41EB87E37190615DB334F2C914A51"
1116 )),
1117 previous_issuer: pk("8NmGZmGjL1LUgJQRg282yQF7KTdQuRNAg8QfSa2qvd65"),
1118 dividend: None,
1119 identities: vec![],
1120 joiners: vec![],
1121 actives: vec![ms1],
1122 leavers: vec![],
1123 revoked: vec![],
1124 excluded: vec![],
1125 certifications: vec![],
1126 transactions: vec![tx1, tx2],
1127 };
1128 let mut block = DubpBlockV10Builder::new(block_content).build_unchecked(DubpBlockV10AfterPowData {
1129 nonce: 10_300_000_090_296,
1130 signature: unwrap!(ed25519::Signature::from_base64("2Z/+9ADdZvHXs19YR8+qDzgfl8WJlBG5PcbFvBG9TOuUJbjAdxhcgxrFrSRIABGWcCrIgLkB805fZVLP8jOjBA==")),
1131 hash: BlockHash::default(),
1132 });
1133 println!("{}", block.generate_compact_inner_text());
1135 assert!(block.verify_inner_hash().is_ok());
1136 assert_eq!(
1137 unwrap!(block.inner_hash).to_hex(),
1138 "3B49ECC1475549CFD94CA7B399311548A0FD0EC93C8EDD5670DAA5A958A41846"
1139 );
1140 let block_compact_text = block.generate_compact_inner_text();
1142 assert_eq!(
1143 block_compact_text,
1144 "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"
1145 );
1146 assert!(block.verify_signature().is_ok());
1148 block.hash = block.compute_hash();
1150 assert!(block.verify_hash().is_ok());
1151 assert_eq!(
1152 block.hash.0.to_hex(),
1153 "000002026E32A3D649B34968AAF9D03C4F19A5954229C54A801BBB1CD216B230"
1154 );
1155 }
1156}