duniter_documents/blockchain/v10/documents/
block.rs

1//  Copyright (C) 2018  The Duniter Project Developers.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16//! Wrappers around Block document.
17
18use crypto::digest::Digest;
19use crypto::sha2::Sha256;
20use duniter_crypto::keys::{PrivateKey, ed25519};
21
22use Hash;
23use blockchain::{BlockchainProtocol, Document, IntoSpecializedDocument};
24use blockchain::v10::documents::{TextDocument, V10Document};
25use blockchain::v10::documents::identity::IdentityDocument;
26use blockchain::v10::documents::membership::MembershipDocument;
27use blockchain::v10::documents::certification::CertificationDocument;
28use blockchain::v10::documents::revocation::RevocationDocument;
29use blockchain::v10::documents::transaction::TransactionDocument;
30
31/// Currency parameters
32#[derive(Debug, Copy, Clone, PartialEq)]
33pub struct BlockParameters {
34    /// UD target growth rate (see Relative Theorie of Money)
35    pub c: f64,
36    /// Duration between the creation of two DU (in seconds)
37    pub dt: i64,
38    /// Amount of the initial UD
39    pub ud0: i64,
40    /// Minimum duration between the writing of 2 certifications from the same issuer (in seconds)
41    pub sig_period: u64,
42    /// Maximum number of active certifications at the same time (for the same issuer)
43    pub sig_stock: i64,
44    /// Maximum retention period of a pending certification
45    pub sig_window: i64,
46    /// Time to expiry of written certification
47    pub sig_validity: i64,
48    /// Minimum number of certifications required to become a member
49    pub sig_qty: i64,
50    /// Maximum retention period of a pending identity
51    pub idty_window: i64,
52    /// Maximum retention period of a pending membership
53    pub ms_window: i64,
54    /// Percentage of referring members who must be within step_max steps of each member
55    pub x_percent: f64,
56    /// Time to expiry of written membership
57    pub ms_validity: u64,
58    /// For a member to respect the distance rule,
59    /// there must exist for more than x_percent % of the referring members
60    /// a path of less than step_max steps from the referring member to the evaluated member.
61    pub step_max: u32,
62    /// Number of blocks used for calculating median time.
63    pub median_time_blocks: i64,
64    /// The average time for writing 1 block (wished time)
65    pub avg_gen_time: i64,
66    /// The number of blocks required to evaluate again PoWMin value
67    pub dt_diff_eval: i64,
68    /// The percent of previous issuers to reach for personalized difficulty
69    pub percent_rot: f64,
70    /// Time of first UD.
71    pub ud_time0: i64,
72    /// Time of first reevaluation of the UD.
73    pub ud_reeval_time0: i64,
74    /// Time period between two re-evaluation of the UD.
75    pub dt_reeval: i64,
76}
77
78/// Wrap a Block document.
79///
80/// Must be created by parsing a text document or using a builder.
81#[derive(Debug, Clone)]
82pub struct BlockDocument {
83    /// Nonce
84    nonce: u64,
85    /// number
86    number: u64,
87    /// Minimal proof of work difficulty
88    pow_min: usize,
89    /// Local time of the block issuer
90    time: u64,
91    /// Average time
92    median_time: u64,
93    /// Members count
94    members_count: usize,
95    /// Monetary mass
96    monetary_mass: usize,
97    /// Unit base (power of ten)
98    unit_base: usize,
99    /// Number of compute members in the current frame
100    issuers_count: usize,
101    /// Current frame size (in blocks)
102    issuers_frame: isize,
103    /// Current frame variation buffer
104    issuers_frame_var: isize,
105    /// Currency.
106    currency: String,
107    /// Document issuer (there should be only one).
108    issuers: Vec<ed25519::PublicKey>,
109    /// Document signature (there should be only one).
110    /// This vector is empty, when the block is generated but the proof of work has not yet started
111    signatures: Vec<ed25519::Signature>,
112    /// The hash is None, when the block is generated but the proof of work has not yet started
113    hash: Option<Hash>,
114    /// Currency parameters (only in genesis block)
115    parameters: Option<BlockParameters>,
116    /// Hash of the previous block
117    previous_hash: Option<Hash>,
118    /// Issuer of the previous block
119    previous_issuer: Option<ed25519::PublicKey>,
120    /// Hash of the deterministic content of the block
121    inner_hash: Option<Hash>,
122    /// Amount of new dividend created at this block, None if no dividend is created at this block
123    dividend: Option<usize>,
124    /// Identities
125    identities: Vec<IdentityDocument>,
126    /// joiners
127    joiners: Vec<MembershipDocument>,
128    /// Actives (=renewals)
129    actives: Vec<MembershipDocument>,
130    /// Leavers
131    leavers: Vec<MembershipDocument>,
132    /// Revokeds
133    revoked: Vec<RevocationDocument>,
134    /// Excludeds
135    excluded: Vec<ed25519::PublicKey>,
136    /// Certifications
137    certifications: Vec<CertificationDocument>,
138    /// Transactions
139    transactions: Vec<TransactionDocument>,
140    /// Part to sign
141    inner_hash_and_nonce_str: String,
142}
143
144impl BlockDocument {
145    fn _compute_inner_hash(&mut self) {
146        let mut sha256 = Sha256::new();
147        sha256.input_str(&self.generate_compact_inner_text());
148        self.inner_hash = Some(Hash::from_hex(&sha256.result_str()).unwrap());
149    }
150    fn _change_nonce(&mut self, new_nonce: u64) {
151        self.nonce = new_nonce;
152        self.inner_hash_and_nonce_str = format!(
153            "InnerHash: {}\nNonce: {}\n",
154            self.inner_hash.unwrap().to_hex(),
155            self.nonce
156        );
157    }
158    fn _sign(&mut self, privkey: ed25519::PrivateKey) {
159        self.signatures = vec![privkey.sign(self.inner_hash_and_nonce_str.as_bytes())];
160    }
161    fn _compute_hash(&mut self) {
162        let mut sha256 = Sha256::new();
163        sha256.input_str(&format!(
164            "InnerHash: {}\nNonce: {}\n{}\n",
165            self.inner_hash.unwrap().to_hex(),
166            self.nonce,
167            self.signatures[0]
168        ));
169        self.hash = Some(Hash::from_hex(&sha256.result_str()).unwrap());
170    }
171    fn generate_compact_inner_text(&self) -> String {
172        let mut identities_str = String::from("");
173        for identity in self.identities.clone() {
174            identities_str.push_str("\n");
175            identities_str.push_str(&identity.generate_compact_text());
176        }
177        let mut joiners_str = String::from("");
178        for joiner in self.joiners.clone() {
179            joiners_str.push_str("\n");
180            joiners_str.push_str(&joiner.generate_compact_text());
181        }
182        let mut actives_str = String::from("");
183        for active in self.actives.clone() {
184            actives_str.push_str("\n");
185            actives_str.push_str(&active.generate_compact_text());
186        }
187        let mut leavers_str = String::from("");
188        for leaver in self.leavers.clone() {
189            leavers_str.push_str("\n");
190            leavers_str.push_str(&leaver.generate_compact_text());
191        }
192        let mut identities_str = String::from("");
193        for identity in self.identities.clone() {
194            identities_str.push_str("\n");
195            identities_str.push_str(&identity.generate_compact_text());
196        }
197        let mut revokeds_str = String::from("");
198        for revocation in self.revoked.clone() {
199            revokeds_str.push_str("\n");
200            revokeds_str.push_str(&revocation.generate_compact_text());
201        }
202        let mut excludeds_str = String::from("");
203        for exclusion in self.excluded.clone() {
204            excludeds_str.push_str("\n");
205            excludeds_str.push_str(&exclusion.to_string());
206        }
207        let mut certifications_str = String::from("");
208        for certification in self.certifications.clone() {
209            certifications_str.push_str("\n");
210            certifications_str.push_str(&certification.generate_compact_text());
211        }
212        let mut transactions_str = String::from("");
213        for transaction in self.transactions.clone() {
214            transactions_str.push_str("\n");
215            transactions_str.push_str(&transaction.generate_compact_text());
216        }
217        format!(
218            "Version: 10
219Type: Block
220Currency: {currency}
221Number: {block_number}
222PoWMin: {pow_min}
223Time: {time}
224MedianTime: {median_time}
225UnitBase: {unit_base}
226Issuer: {issuer}
227IssuersFrame: {issuers_frame}
228IssuersFrameVar: {issuers_frame_var}
229DifferentIssuersCount: {issuers_count}
230PreviousHash: {previous_hash}
231PreviousIssuer: {previous_issuer}
232MembersCount: {members_count}
233Identities:{identities}
234Joiners:{joiners}
235Actives:{actives}
236Leavers:{leavers}
237Revoked:{revoked}
238Excluded:{excluded}
239Certifications:{certifications}
240Transactions:{transactions}
241",
242            currency = self.currency,
243            block_number = self.number,
244            pow_min = self.pow_min,
245            time = self.time,
246            median_time = self.median_time,
247            unit_base = self.unit_base,
248            issuer = self.issuers[0],
249            issuers_frame = self.issuers_frame,
250            issuers_frame_var = self.issuers_frame_var,
251            issuers_count = self.issuers_count,
252            previous_hash = self.previous_hash.unwrap(),
253            previous_issuer = self.previous_issuer.unwrap(),
254            members_count = self.members_count,
255            identities = identities_str,
256            joiners = joiners_str,
257            actives = actives_str,
258            leavers = leavers_str,
259            revoked = revokeds_str,
260            excluded = excludeds_str,
261            certifications = certifications_str,
262            transactions = transactions_str,
263        )
264    }
265}
266
267impl Document for BlockDocument {
268    type PublicKey = ed25519::PublicKey;
269    type CurrencyType = str;
270
271    fn version(&self) -> u16 {
272        10
273    }
274
275    fn currency(&self) -> &str {
276        &self.currency
277    }
278
279    fn issuers(&self) -> &Vec<ed25519::PublicKey> {
280        &self.issuers
281    }
282
283    fn signatures(&self) -> &Vec<ed25519::Signature> {
284        &self.signatures
285    }
286
287    fn as_bytes(&self) -> &[u8] {
288        self.inner_hash_and_nonce_str.as_bytes()
289    }
290}
291
292impl TextDocument for BlockDocument {
293    fn as_text(&self) -> &str {
294        panic!();
295    }
296
297    fn generate_compact_text(&self) -> String {
298        let compact_inner_text = self.generate_compact_inner_text();
299        format!(
300            "{}InnerHash: {}\nNonce: ",
301            compact_inner_text,
302            self.inner_hash.unwrap().to_hex()
303        )
304    }
305}
306
307impl IntoSpecializedDocument<BlockchainProtocol> for BlockDocument {
308    fn into_specialized(self) -> BlockchainProtocol {
309        BlockchainProtocol::V10(Box::new(V10Document::Block(Box::new(self))))
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    use std::ops::Deref;
317    use duniter_crypto::keys::{PublicKey, Signature};
318    use blockchain::{Document, DocumentParser, VerificationResult};
319    use blockchain::v10::documents::V10DocumentParser;
320
321    #[test]
322    fn generate_and_verify_empty_block() {
323        let mut block = BlockDocument {
324            nonce: 10_500_000_089_933,
325            number: 107_777,
326            pow_min: 89,
327            time: 1_522_624_657,
328            median_time: 1_522_616_790,
329            members_count: 894,
330            monetary_mass: 139_571_973,
331            unit_base: 0,
332            issuers_count: 41,
333            issuers_frame: 201,
334            issuers_frame_var: 5,
335            currency: String::from("g1"),
336            issuers: vec![ed25519::PublicKey::from_base58("2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT").unwrap()],
337            signatures: vec![ed25519::Signature::from_base64("FsRxB+NOiL+8zTr2d3B2j2KBItDuCa0KjFMF6hXmdQzfqXAs9g3m7DlGgYLcqzqe6JXjx/Lyzqze1HBR4cS0Aw==").unwrap()],
338            hash: None,
339            parameters: None,
340            previous_hash: Some(Hash::from_hex("0000001F8AACF6764135F3E5D0D4E8358A3CBE537A4BF71152A00CC442EFD136").expect("fail to parse previous_hash")),
341            previous_issuer: Some(ed25519::PublicKey::from_base58("38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE").unwrap()),
342            inner_hash: None,
343            dividend: None,
344            identities: Vec::new(),
345            joiners: Vec::new(),
346            actives: Vec::new(),
347            leavers: Vec::new(),
348            revoked: Vec::new(),
349            excluded: Vec::new(),
350            certifications: Vec::new(),
351            transactions: Vec::new(),
352            inner_hash_and_nonce_str: String::new(),
353        };
354        // test inner_hash computation
355        block._compute_inner_hash();
356        println!("{}", block.generate_compact_text());
357        assert_eq!(
358            block.inner_hash.unwrap().to_hex(),
359            "95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C"
360        );
361        // test generate_compact_text()
362        assert_eq!(
363            block.generate_compact_text(),
364            "Version: 10
365Type: Block
366Currency: g1
367Number: 107777
368PoWMin: 89
369Time: 1522624657
370MedianTime: 1522616790
371UnitBase: 0
372Issuer: 2sZF6j2PkxBDNAqUde7Dgo5x3crkerZpQ4rBqqJGn8QT
373IssuersFrame: 201
374IssuersFrameVar: 5
375DifferentIssuersCount: 41
376PreviousHash: 0000001F8AACF6764135F3E5D0D4E8358A3CBE537A4BF71152A00CC442EFD136
377PreviousIssuer: 38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE
378MembersCount: 894
379Identities:
380Joiners:
381Actives:
382Leavers:
383Revoked:
384Excluded:
385Certifications:
386Transactions:
387InnerHash: 95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C
388Nonce: "
389        );
390        // Test signature validity
391        block._change_nonce(10_500_000_089_933);
392        assert_eq!(block.verify_signatures(), VerificationResult::Valid());
393        // Test hash computation
394        block._compute_hash();
395        assert_eq!(
396            block.hash.unwrap().to_hex(),
397            "000002D3296A2D257D01F6FEE8AEC5C3E5779D04EA43F08901F41998FA97D9A1"
398        );
399    }
400
401    #[test]
402    fn generate_and_verify_block() {
403        let cert1_ = V10DocumentParser::parse("Version: 10
404Type: Certification
405Currency: g1
406Issuer: 6TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3
407IdtyIssuer: CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV
408IdtyUniqueID: PascaleM
409IdtyTimestamp: 97401-0000003821911909F98519CC773D2D3E5CFE3D5DBB39F4F4FF33B96B4D41800E
410IdtySignature: QncUVXxZ2NfARjdJOn6luILvDuG1NuK9qSoaU4CST2Ij8z7oeVtEgryHl+EXOjSe6XniALsCT0gU8wtadcA/Cw==
411CertTimestamp: 106669-000003682E6FE38C44433DCE92E8B2A26C69B6D7867A2BAED231E788DDEF4251
412UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==").unwrap();
413        let cert1 = match cert1_ {
414            V10Document::Certification(doc) => (*doc.deref()).clone(),
415            _ => panic!("Wrong document type"),
416        };
417
418        let tx1_ = V10DocumentParser::parse("Version: 10
419Type: Transaction
420Currency: g1
421Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
422Locktime: 0
423Issuers:
4248dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
425Inputs:
4261002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345
427Unlocks:
4280:SIG(0)
429Outputs:
4301002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)
431Comment: 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
432T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==").unwrap();
433        let tx1 = match tx1_ {
434            V10Document::Transaction(doc) => (*doc.deref()).clone(),
435            _ => panic!("Wrong document type"),
436        };
437
438        let tx2_ = V10DocumentParser::parse("Version: 10
439Type: Transaction
440Currency: g1
441Blockstamp: 107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
442Locktime: 0
443Issuers:
4448dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
445Inputs:
4461002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614
447Unlocks:
4480:SIG(0)
449Outputs:
4501002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)
451Comment: 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
452a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg==").unwrap();
453        let tx2 = match tx2_ {
454            V10Document::Transaction(doc) => (*doc.deref()).clone(),
455            _ => panic!("Wrong document type"),
456        };
457
458        let mut block = BlockDocument {
459            nonce: 0,
460            number: 107_984,
461            pow_min: 88,
462            time: 1_522_685_861,
463            median_time: 1522683184,
464            members_count: 896,
465            monetary_mass: 140_469_765,
466            unit_base: 0,
467            issuers_count: 42,
468            issuers_frame: 211,
469            issuers_frame_var: 0,
470            currency: String::from("g1"),
471            issuers: vec![ed25519::PublicKey::from_base58("DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ").unwrap()],
472            signatures: vec![ed25519::Signature::from_base64("92id58VmkhgVNee4LDqBGSm8u/ooHzAD67JM6fhAE/CV8LCz7XrMF1DvRl+eRpmlaVkp6I+Iy8gmZ1WUM5C8BA==").unwrap()],
473            hash: None,
474            parameters: None,
475            previous_hash: Some(Hash::from_hex("000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB").expect("fail to parse previous_hash")),
476            previous_issuer: Some(ed25519::PublicKey::from_base58("D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH").unwrap()),
477            inner_hash: None,
478            dividend: None,
479            identities: Vec::new(),
480            joiners: Vec::new(),
481            actives: Vec::new(),
482            leavers: Vec::new(),
483            revoked: Vec::new(),
484            excluded: Vec::new(),
485            certifications: vec![cert1],
486            transactions: vec![tx1, tx2],
487            inner_hash_and_nonce_str: String::new(),
488        };
489        // test inner_hash computation
490        block._compute_inner_hash();
491        println!("{}", block.generate_compact_text());
492        assert_eq!(
493            block.inner_hash.unwrap().to_hex(),
494            "C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1"
495        );
496        // test generate_compact_text()
497        assert_eq!(
498            block.generate_compact_text(),
499            "Version: 10
500Type: Block
501Currency: g1
502Number: 107984
503PoWMin: 88
504Time: 1522685861
505MedianTime: 1522683184
506UnitBase: 0
507Issuer: DA4PYtXdvQqk1nCaprXH52iMsK5Ahxs1nRWbWKLhpVkQ
508IssuersFrame: 211
509IssuersFrameVar: 0
510DifferentIssuersCount: 42
511PreviousHash: 000001144968D0C3516BE6225E4662F182E28956AF46DD7FB228E3D0F9413FEB
512PreviousIssuer: D3krfq6J9AmfpKnS3gQVYoy7NzGCc61vokteTS8LJ4YH
513MembersCount: 896
514Identities:
515Joiners:
516Actives:
517Leavers:
518Revoked:
519Excluded:
520Certifications:
5216TAzLWuNcSqgNDNpAutrKpPXcGJwy1ZEMeVvZSZNs2e3:CYPsYTdt87Tx6cCiZs9KD4jqPgYxbcVEqVZpRgJ9jjoV:106669:UmseG2XKNwKcY8RFi6gUCT91udGnnNmSh7se10J1jeRVlwf+O2Tyb2Cccot9Dt7BO4+Kx2P6vFJB3oVGGHMxBA==
522Transactions:
523TX:10:1:1:1:1:1:0
524107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
5258dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
5261002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106345
5270:SIG(0)
5281002:0:SIG(CitdnuQgZ45tNFCagay7Wh12gwwHM8VLej1sWmfHWnQX)
529DU 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
530T0LlCcbIn7xDFws48H8LboN6NxxwNXXTovG4PROLf7tkUAueHFWjfwZFKQXeZEHxfaL1eYs3QspGtLWUHPRVCQ==
531TX:10:1:1:1:1:1:0
532107982-000001242F6DA51C06A915A96C58BAA37AB3D1EB51F6E1C630C707845ACF764B
5338dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3
5341002:0:D:8dkCwvAqSczUjKsoVMDPVbQ3i6bBQeBQYawL87kqTSQ3:106614
5350:SIG(0)
5361002:0:SIG(78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8)
537DU 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
538a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8xrNcCg==
539InnerHash: C8AB69E33ECE2612EADC7AB30D069B1F1A3D8C95EBBFD50DE583AC8E3666CCA1
540Nonce: "
541        );
542        // Test signature validity
543        block._change_nonce(10_300_000_018_323);
544        assert_eq!(block.verify_signatures(), VerificationResult::Valid());
545        // Test hash computation
546        block._compute_hash();
547        assert_eq!(
548            block.hash.unwrap().to_hex(),
549            "000004F8B84A3590243BA562E5F2BA379F55A0B387C5D6FAC1022DFF7FFE6014"
550        );
551    }
552}