bitcoin/dogecoin/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Dogecoin module.
4//!
5//! This module provides support for de/serialization, parsing and execution on data structures and
6//! network messages related to Dogecoin.
7
8pub mod address;
9pub mod constants;
10pub mod params;
11pub mod auxpow;
12
13pub use address::*;
14
15use crate::block::{Header as PureHeader, TxMerkleNode, Version};
16use crate::consensus::{encode, Decodable, Encodable};
17use crate::dogecoin::params::Params;
18use crate::internal_macros::impl_consensus_encoding;
19use crate::io::{Read, Write};
20use crate::p2p::Magic;
21use crate::prelude::*;
22use crate::{io, BlockHash, Transaction};
23use core::fmt;
24use core::ops::{Deref, DerefMut};
25use crate::dogecoin::auxpow::{AuxPow, VERSION_AUXPOW};
26
27/// Dogecoin block header.
28///
29/// ### Dogecoin Core References
30///
31/// * [CBlockHeader definition](https://github.com/dogecoin/dogecoin/blob/7237da74b8c356568644cbe4fba19d994704355b/src/primitives/block.h#L23)
32#[derive(PartialEq, Eq, Clone, Debug)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
35pub struct Header {
36    /// Block header without AuxPow information.
37    pub pure_header: PureHeader,
38    /// AuxPoW structure, present if merged mining was used to mine this block.
39    pub aux_pow: Option<AuxPow>,
40}
41
42impl Deref for Header {
43    type Target = PureHeader;
44    fn deref(&self) -> &Self::Target {
45        &self.pure_header
46    }
47}
48
49impl DerefMut for Header {
50    fn deref_mut(&mut self) -> &mut Self::Target {
51        &mut self.pure_header
52    }
53}
54
55impl From<PureHeader> for Header {
56    fn from(pure_header: PureHeader) -> Self {
57        Self { pure_header, aux_pow: None }
58    }
59}
60
61impl Decodable for Header {
62    #[inline]
63    fn consensus_decode_from_finite_reader<R: Read + ?Sized>(
64        r: &mut R,
65    ) -> Result<Self, encode::Error> {
66        let pure_header: PureHeader = Decodable::consensus_decode_from_finite_reader(r)?;
67        let aux_pow = if pure_header.has_auxpow_bit() {
68            Some(Decodable::consensus_decode_from_finite_reader(r)?)
69        } else {
70            None
71        };
72
73        Ok(Self { pure_header, aux_pow })
74    }
75}
76
77impl Encodable for Header {
78    #[inline]
79    fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
80        let mut len = 0;
81        len += self.pure_header.consensus_encode(w)?;
82        if let Some(ref aux_pow) = self.aux_pow {
83            len += aux_pow.consensus_encode(w)?;
84        }
85        Ok(len)
86    }
87}
88
89impl PureHeader {
90    /// Checks if a block header indicates it was merged mined and contains AuxPow information.
91    pub fn has_auxpow_bit(&self) -> bool {
92        (self.version.to_consensus() & VERSION_AUXPOW) != 0
93    }
94
95    /// Extracts the chain ID from the block header's version field.
96    pub fn extract_chain_id(&self) -> i32 {
97        self.version.to_consensus() >> 16
98    }
99
100    /// Determines if a block header represents a legacy (pre-AuxPoW) block.
101    pub fn is_legacy(&self) -> bool {
102        self.version == Version::ONE
103            // Random v2 block with no AuxPoW, treat as legacy
104            || (self.version == Version::TWO && self.extract_chain_id() == 0)
105    }
106
107    /// Extracts the base version number from a block header, removing AuxPoW and chain ID bits.
108    pub fn extract_base_version(&self) -> i32 {
109        self.version.to_consensus() % VERSION_AUXPOW
110    }
111}
112
113/// Dogecoin block.
114///
115/// A collection of transactions with an attached proof of work.
116/// The AuxPoW data is present in `header` if the block was mined using merged-mining.
117///
118/// See [Bitcoin Wiki: Block][wiki-block] and [Bitcoin Wiki: Merged_mining_specification][merged-mining]
119/// for more information.
120///
121/// [wiki-block]: https://en.bitcoin.it/wiki/Block
122/// [merged-mining]: https://en.bitcoin.it/wiki/Merged_mining_specification
123///
124/// ### Dogecoin Core References
125///
126/// * [CBlock definition](https://github.com/dogecoin/dogecoin/blob/d7cc7f8bbb5f790942d0ed0617f62447e7675233/src/primitives/block.h#L65)
127#[derive(PartialEq, Eq, Clone, Debug)]
128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
129#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
130pub struct Block {
131    /// The Dogecoin block header.
132    pub header: Header,
133    /// List of transactions contained in the block.
134    pub txdata: Vec<Transaction>,
135}
136
137impl Block {
138    /// Returns the block hash computed as SHA256d(header).
139    pub fn block_hash(&self) -> BlockHash { self.header.block_hash() }
140
141    /// Returns the block hash using the scrypt hash function.
142    pub fn block_hash_with_scrypt(&self) -> BlockHash { self.header.block_hash_with_scrypt() }
143
144    /// Checks if merkle root of header matches merkle root of the transaction list.
145    pub fn check_merkle_root(&self) -> bool {
146        match self.compute_merkle_root() {
147            Some(merkle_root) => self.header.merkle_root == merkle_root,
148            None => false,
149        }
150    }
151
152    /// Compute merkle root of the transaction list in this block.
153    pub fn compute_merkle_root(&self) -> Option<TxMerkleNode> {
154        let hashes = self
155            .txdata
156            .iter()
157            .map(|obj| obj.compute_txid().to_raw_hash());
158        crate::merkle_tree::calculate_root(hashes).map(|h| h.into())
159    }
160}
161
162impl_consensus_encoding!(Block, header, txdata);
163
164/// The cryptocurrency network to act on.
165#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
166#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
167#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
168#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
169#[non_exhaustive]
170pub enum Network {
171    /// Mainnet Dogecoin.
172    Dogecoin,
173    /// Dogecoin's testnet network.
174    Testnet,
175    /// Dogecoin's regtest network.
176    Regtest,
177}
178
179impl Network {
180    /// Returns the associated network parameters.
181    pub const fn params(self) -> &'static Params {
182        match self {
183            Network::Dogecoin => &Params::DOGECOIN,
184            Network::Testnet => &Params::TESTNET,
185            Network::Regtest => &Params::REGTEST,
186        }
187    }
188
189    /// Return the magic bytes for the given network.
190    pub fn magic(self) -> Magic {
191        match self {
192            Network::Dogecoin => Magic::from_bytes([0xC0, 0xC0, 0xC0, 0xC0]),
193            Network::Testnet => Magic::from_bytes([0xFC, 0xC1, 0xB7, 0xDC]),
194            Network::Regtest => Magic::from_bytes([0xFA, 0xBF, 0xB5, 0xDA]),
195        }
196    }
197}
198
199impl AsRef<Params> for Network {
200    fn as_ref(&self) -> &Params {
201        self.params()
202    }
203}
204
205impl fmt::Display for Network {
206    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207        match self {
208            Network::Dogecoin => write!(f, "dogecoin"),
209            Network::Testnet => write!(f, "testnet"),
210            Network::Regtest => write!(f, "regtest"),
211        }
212    }
213}
214
215impl core::str::FromStr for Network {
216    type Err = crate::network::ParseNetworkError;
217
218    fn from_str(s: &str) -> Result<Self, Self::Err> {
219        match s {
220            "dogecoin" => Ok(Network::Dogecoin),
221            "testnet" => Ok(Network::Testnet),
222            "regtest" => Ok(Network::Regtest),
223            _ => Err(crate::network::ParseNetworkError(s.to_owned())),
224        }
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use hex::{test_hex_unwrap as hex};
231    use hashes::Hash;
232    use super::*;
233    use crate::block::{ValidationError, Version};
234    use crate::consensus::encode::{deserialize, serialize};
235    use crate::{CompactTarget, Target, Work};
236    use crate::{Network as BitcoinNetwork};
237
238    #[test]
239    fn dogecoin_block_test() {
240        // Mainnet Dogecoin block 5794c80b80d9c33e0737a5353cd52b1f097f61d8d2b9f471e1702345080e0002
241        let some_block = hex!("01000000c76fe7f8ec09989d32b7907966fbd347134f80a7b71efce55fec502aa126ba3894b3065289ff8ba1ab4e8391771174d47cf2c974ebd24a1bdafd6c107d5a7a207d78bb52de8f001c00da8c3c0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2602bc6d062f503253482f047178bb5208f8042975030000000d2f7374726174756d506f6f6c2f000000000100629b29c45500001976a91450e9fe87c705dcd4b7523b47e3314c2115f5d5df88ac0000000001000000015f48fabf4425324df2b5e58f4e9c771297f76f5fa37db7556f6fc1d22742da1f010000006a473044022062d29d2d26f7d826e7b72257486e294d284832743c7803a2901eb07e326b25a002207efc391b0f4e724c9d518075c0e056cc425540f845b0fd419ba8a9d49d69288301210297a2568525760a98454d84f5e5adba9fd0a41726a6fb774ddc407279e41e2061ffffffff0240bab598200000001976a91401348a2b83aeb6b1ba2a174a1a40b7c75fbeb12088ac0040be40250000001976a914025407d928ef333979d064ae233353d80e29d58c88ac00000000");
242        let cutoff_block = hex!("01000000c76fe7f8ec09989d32b7907966fbd347134f80a7b71efce55fec502aa126ba3894b3065289ff8ba1ab4e8391771174d47cf2c974ebd24a1bdafd6c107d5a7a207d78bb52de8f001c00da8c3c0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2602bc6d062f503253482f047178bb5208f8042975030000000d2f7374726174756d506f6f6c2f000000000100629b29c45500001976a91450e9fe87c705dcd4b7523b47e3314c2115f5d5df88ac0000000001000000015f48fabf4425324df2b5e58f4e9c771297f76f5fa37db7556f6fc1d22742da1f010000006a473044022062d29d2d26f7d826e7b72257486e294d284832743c7803a2901eb07e326b25a002207efc391b0f4e724c9d518075c0e056cc425540f845b0fd419ba8a9d49d69288301210297a2568525760a98454d84f5e5adba9fd0a41726a6fb774ddc407279e41e2061ffffffff0240bab598200000001976a91401348a2b83aeb6b1ba2a174a1a40b7c75fbeb12088ac0040be40250000001976a914025407d928ef333979d064ae233353d80e29d58c88ac");
243        let header = &some_block[0..80];
244
245        let currhash = hex!("02000e08452370e171f4b9d2d8617f091f2bd53c35a537073ec3d9800bc89457");
246        let prevhash = hex!("c76fe7f8ec09989d32b7907966fbd347134f80a7b71efce55fec502aa126ba38");
247        let merkle = hex!("94b3065289ff8ba1ab4e8391771174d47cf2c974ebd24a1bdafd6c107d5a7a20");
248        let work = Work::from(0x1c788001c78_u128);
249
250        let decode: Result<Block, _> = deserialize(&some_block);
251        let bad_decode: Result<Block, _> = deserialize(&cutoff_block);
252
253        assert!(decode.is_ok());
254        assert!(bad_decode.is_err());
255        let real_decode = decode.unwrap();
256        assert_eq!(serialize(&real_decode.header.block_hash()), currhash);
257        assert_eq!(real_decode.header.version, Version::ONE);
258        assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash);
259        assert_eq!(real_decode.header.merkle_root, real_decode.compute_merkle_root().unwrap());
260        assert_eq!(serialize(&real_decode.header.merkle_root), merkle);
261        assert_eq!(real_decode.header.time, 1388017789);
262        assert_eq!(real_decode.header.bits, CompactTarget::from_consensus(469798878));
263        assert_eq!(real_decode.header.nonce, 1015863808);
264        assert_eq!(real_decode.header.work(), work);
265        assert_eq!(
266            real_decode.header.validate_pow_with_scrypt(real_decode.header.target()).unwrap(),
267            real_decode.block_hash_with_scrypt()
268        );
269        // Bitcoin network is used because Dogecoin's difficulty calculation is based on Bitcoin's,
270        // which uses Bitcoin's `max_attainable_target` value
271        assert_eq!(real_decode.header.difficulty(BitcoinNetwork::Bitcoin), 455);
272        assert_eq!(real_decode.header.difficulty_float(), 455.52430084170516);
273
274        assert!(!real_decode.header.has_auxpow_bit());
275        assert_eq!(real_decode.header.extract_chain_id(), 0);
276        assert_eq!(real_decode.header.extract_base_version(), 1);
277        assert!(real_decode.header.is_legacy());
278
279        assert!(real_decode.header.aux_pow.is_none());
280
281        assert_eq!(serialize(&real_decode.header.pure_header), header);
282        assert_eq!(serialize(&real_decode.header), header);
283        assert_eq!(serialize(&real_decode), some_block);
284    }
285
286    #[test]
287    fn dogecoin_block_test_with_auxpow() {
288        // Mainnet Dogecoin block d3ea48350b102b90acf9eac6629072d5f697c02faf360b26d365e7b2bfb98070
289        let block = hex!("020162001e21ad14bc1ef20cf2d58e2b755ae4a7bfb75c906c74ef3dbb97cc57dcd77581b14423c43517df2b4f3277731daba29d0d865b515a6f19f5fb61d5799b28f2c9b48713540fa8071b0000000001000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4f032ac209fabe6d6dd3ea48350b102b90acf9eac6629072d5f697c02faf360b26d365e7b2bfb980700100000000000000062f503253482f04ce871354080811312915000000092f7374726174756d2f000000000100f2052a010000001976a914f332ec6f1729495e7edcd8ce9d887742567fe60988ac0000000006d2bbd93141ea6d2c8434caeb01828a2a522275b66d2b21fe4ed8230cfe65a101ad2f07c348abdc05f57e2e7d8763488aad71df9f557aec46ae0207ae2bb74a1500000000000000000002000000f288b555ed9b44c814afbbbac135d95e0984a5cc7cb554fccbd2ca27c5e423cebf3021ed058ac83eaa0f64b0d405fc99216209b7e56deeeefceb3629210d1cabcb8713545a50021bc40227b50301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d0300b1050101062f503253482fffffffff010085fd36af050000232102c9cbaeb767cc8c884204601f322c6977890cdd3d274f8b1a704ae00382102191ac0000000001000000011fcccc77028fc5fb96d60ee5f258da271ba9b4fdec12594a5dd2efa1fb5bd14b010000006b483045022100f4a17664176706f74877433dbfb8e68a5c1e45730da9248a8da3bab833cea1ca022018fee77621c20f196b82c3c8fd663959b0c8ea985b0407bdbac1414a5a5a21bf0121033fcc1cb9c1b7b11758eb2cd3a25b4ff917a5e248f5d6fcc74160dd6a450acf8bffffffff020759dd6e000000001976a914a553c686ac1aa534ffcb3b694c463944175a6f3c88ac8548f276890700001976a914a1ea13863020f36897b671ad328d98e9364f12b488ac000000000100000001215c45fc31d3beae4a5c76efbb0925d0b4bace72f62248aa3777927eecb42152000000006b483045022100bd3f18da6acd8180ed99c7c7fc6feab18653008cc58be5c32a470193aea330860220719c64121805606240c48dab2e8e3f3d82501b78b78597043ca134abf4c85443012103e6435d9ad2a3f3ff2d2c58aef41075df4eb5417c313d3ea4d5bb87ab46241940ffffffff0140ab8aac140000001976a91481db1aa49ebc6a71cad96949eb28e22af85eb0bd88ac00000000");
290        let header = &block[0..398];
291        let pure_header = &header[0..80];
292        let auxpow = &header[80..];
293        let auxpow_coinbase_tx = &auxpow[..164];
294        let auxpow_parent_hash = &auxpow[164..196];
295        let auxpow_coinbase_branch = &auxpow[196..229];
296        let auxpow_coinbase_index = &auxpow[229..233];
297        let auxpow_blockchain_branch = &auxpow[233..234];
298        let auxpow_blockchain_index = &auxpow[234..238];
299        let auxpow_parent_block_header = &auxpow[238..];
300
301        let currhash = hex!("7080b9bfb2e765d3260b36af2fc097f6d5729062c6eaf9ac902b100b3548ead3");
302        let prevhash = hex!("1e21ad14bc1ef20cf2d58e2b755ae4a7bfb75c906c74ef3dbb97cc57dcd77581");
303        let merkle = hex!("b14423c43517df2b4f3277731daba29d0d865b515a6f19f5fb61d5799b28f2c9");
304
305        let decode: Result<Block, _> = deserialize(&block);
306        assert!(decode.is_ok());
307        let block_decode = decode.unwrap();
308
309        assert_eq!(serialize(&block_decode.header.block_hash()), currhash);
310        assert_eq!(block_decode.header.version, Version::from_consensus(6422786));
311        assert_eq!(serialize(&block_decode.header.prev_blockhash), prevhash);
312        assert_eq!(block_decode.header.merkle_root, block_decode.compute_merkle_root().unwrap());
313        assert_eq!(serialize(&block_decode.header.merkle_root), merkle);
314        assert_eq!(block_decode.header.time, 1410566068);
315        assert_eq!(block_decode.header.bits, CompactTarget::from_consensus(453486607));
316        assert_eq!(block_decode.header.nonce, 0);
317
318        // Should fail because AuxPow is used
319        assert_eq!(
320            block_decode.header.validate_pow_with_scrypt(block_decode.header.target()),
321            Err(ValidationError::BadProofOfWork)
322        );
323        // Bitcoin network is used because Dogecoin's difficulty calculation is based on Bitcoin's,
324        // which uses Bitcoin's `max_attainable_target` value
325        assert_eq!(block_decode.header.difficulty(BitcoinNetwork::Bitcoin), 8559);
326        assert_eq!(block_decode.header.difficulty_float(), 8559.417587564147);
327
328        assert!(block_decode.header.has_auxpow_bit());
329        assert_eq!(block_decode.header.extract_chain_id(), 98);
330        assert_eq!(block_decode.header.extract_base_version(), 2);
331        assert!(!block_decode.header.is_legacy());
332
333        assert!(block_decode.header.aux_pow.is_some());
334        let auxpow_decode = block_decode.header.aux_pow.as_ref().unwrap();
335        assert_eq!(serialize(&auxpow_decode.coinbase_tx), auxpow_coinbase_tx);
336        assert_eq!(auxpow_decode.parent_hash.to_byte_array(), auxpow_parent_hash);
337        assert_eq!(serialize(&auxpow_decode.coinbase_branch), auxpow_coinbase_branch);
338        assert_eq!(auxpow_decode.coinbase_index.to_le_bytes(), auxpow_coinbase_index);
339        assert_eq!(serialize(&auxpow_decode.blockchain_branch), auxpow_blockchain_branch);
340        assert_eq!(auxpow_decode.blockchain_index.to_le_bytes(), auxpow_blockchain_index);
341        assert_eq!(serialize(&auxpow_decode.parent_block_header), auxpow_parent_block_header);
342
343        assert_eq!(serialize(&auxpow_decode), auxpow);
344        assert_eq!(serialize(&block_decode.header.pure_header), pure_header);
345        assert_eq!(serialize(&block_decode.header), header);
346        assert_eq!(serialize(&block_decode), block);
347    }
348
349    #[test]
350    fn validate_pow_with_scrypt_test() {
351        let some_header = hex!("01000000c76fe7f8ec09989d32b7907966fbd347134f80a7b71efce55fec502aa126ba3894b3065289ff8ba1ab4e8391771174d47cf2c974ebd24a1bdafd6c107d5a7a207d78bb52de8f001c00da8c3c");
352        let some_header: Header =
353            deserialize(&some_header).expect("Can't deserialize correct block header");
354        assert_eq!(
355            some_header.validate_pow_with_scrypt(some_header.target()).unwrap(),
356            some_header.block_hash_with_scrypt()
357        );
358
359        // test with zero target
360        match some_header.validate_pow_with_scrypt(Target::ZERO) {
361            Err(ValidationError::BadTarget) => (),
362            _ => panic!("unexpected result from validate_pow_with_scrypt"),
363        }
364
365        // test with modified header
366        let mut invalid_header: Header = some_header;
367        invalid_header.nonce += 1;
368        match invalid_header.validate_pow_with_scrypt(invalid_header.target()) {
369            Err(ValidationError::BadProofOfWork) => (),
370            _ => panic!("unexpected result from validate_pow_with_scrypt"),
371        }
372    }
373
374    #[test]
375    fn block_hash_with_scrypt_test() {
376        struct Test {
377            input: Vec<u8>,
378            output: Vec<u8>,
379            output_str: &'static str,
380        }
381
382        let tests = vec![
383            // Example from <https://litecoin.info/docs/key-concepts/proof-of-work>
384            Test {
385                input: hex!("01000000f615f7ce3b4fc6b8f61e8f89aedb1d0852507650533a9e3b10b9bbcc30639f279fcaa86746e1ef52d3edb3c4ad8259920d509bd073605c9bf1d59983752a6b06b817bb4ea78e011d012d59d4"),
386                output: vec![217, 235, 134, 99, 255, 236, 36, 28, 47, 177, 24, 173, 183, 222, 151, 168, 44, 128, 59, 111, 244, 109, 87, 102, 121, 53, 200, 16, 1, 0, 0, 0],
387                output_str: "0000000110c8357966576df46f3b802ca897deb7ad18b12f1c24ecff6386ebd9"
388            },
389            // Examples from <https://github.com/dogecoin/ltc-scrypt/blob/main/test.py>
390            Test {
391                input: hex!("020000004c1271c211717198227392b029a64a7971931d351b387bb80db027f270411e398a07046f7d4a08dd815412a8712f874a7ebf0507e3878bd24e20a3b73fd750a667d2f451eac7471b00de6659"),
392                output: vec![6, 88, 152, 215, 171, 45, 170, 130, 53, 205, 218, 149, 17, 210, 72, 243, 1, 11, 94, 17, 246, 130, 248, 7, 65, 239, 43, 0, 0, 0, 0, 0],
393                output_str: "00000000002bef4107f882f6115e0b01f348d21195dacd3582aa2dabd7985806",
394            },
395            Test {
396                input: hex!("0200000011503ee6a855e900c00cfdd98f5f55fffeaee9b6bf55bea9b852d9de2ce35828e204eef76acfd36949ae56d1fbe81c1ac9c0209e6331ad56414f9072506a77f8c6faf551eac7471b00389d01"),
397                output: vec![148, 252, 136, 28, 159, 241, 218, 80, 210, 53, 237, 40, 242, 187, 207, 221, 254, 183, 8, 78, 99, 235, 213, 189, 17, 13, 58, 0, 0, 0, 0, 0],
398                output_str: "00000000003a0d11bdd5eb634e08b7feddcfbbf228ed35d250daf19f1c88fc94",
399            },
400            Test {
401                input: hex!("02000000a72c8a177f523946f42f22c3e86b8023221b4105e8007e59e81f6beb013e29aaf635295cb9ac966213fb56e046dc71df5b3f7f67ceaeab24038e743f883aff1aaafaf551eac7471b0166249b"),
402                output: vec![129, 202, 168, 20, 81, 221, 248, 101, 156, 242, 175, 216, 89, 157, 45, 108, 138, 114, 68, 50, 225, 136, 242, 149, 248, 64, 11, 0, 0, 0, 0, 0],
403                output_str: "00000000000b40f895f288e13244728a6c2d9d59d8aff29c65f8dd5114a8ca81",
404            },
405            Test {
406                input: hex!("010000007824bc3a8a1b4628485eee3024abd8626721f7f870f8ad4d2f33a27155167f6a4009d1285049603888fe85a84b6c803a53305a8d497965a5e896e1a00568359589faf551eac7471b0065434e"),
407                output: vec![254, 5, 225, 151, 24, 24, 134, 106, 220, 126, 142, 110, 47, 215, 232, 216, 153, 30, 3, 35, 73, 205, 145, 88, 0, 7, 48, 0, 0, 0, 0, 0],
408                output_str: "00000000003007005891cd4923031e99d8e8d72f6e8e7edc6a86181897e105fe",
409            },
410            Test {
411                input: hex!("0200000050bfd4e4a307a8cb6ef4aef69abc5c0f2d579648bd80d7733e1ccc3fbc90ed664a7f74006cb11bde87785f229ecd366c2d4e44432832580e0608c579e4cb76f383f7f551eac7471b00c36982"),
412                output: vec![140, 236, 0, 56, 77, 114, 199, 231, 79, 91, 52, 13, 115, 175, 2, 250, 71, 203, 12, 19, 199, 175, 164, 38, 180, 240, 24, 0, 0, 0, 0, 0],
413                output_str: "000000000018f0b426a4afc7130ccb47fa02af730d345b4fe7c7724d3800ec8c",
414            },
415        ];
416
417        for test in tests {
418            let header: Header =
419                deserialize(&test.input).expect("Can't deserialize correct block header");
420            assert_eq!(header.block_hash_with_scrypt().to_string(), test.output_str);
421            assert_eq!(serialize(&header.block_hash_with_scrypt()), test.output);
422        }
423    }
424
425    #[test]
426    fn max_target_from_compact() {
427        // The highest possible target in Dogecoin is defined as 0x1e0fffff
428        let bits = 0x1e0fffff_u32;
429        let want = Target::MAX_ATTAINABLE_MAINNET_DOGE;
430        let got = Target::from_compact(CompactTarget::from_consensus(bits));
431        assert_eq!(got, want)
432    }
433
434    #[test]
435    fn compact_target_from_adjustment_is_max_target() {
436        let height = 480;
437        let params = Params::new(Network::Dogecoin);
438        let starting_bits = CompactTarget::from_consensus(0x1e0fffff); // Max target
439        let timespan =  4 * params.pow_target_timespan(height); // 4x Slower than expected
440        let got = CompactTarget::from_next_work_required_dogecoin(starting_bits, timespan, &params, height);
441        let want = params.max_attainable_target.to_compact_lossy();
442        assert_eq!(got, want);
443    }
444
445    #[test]
446    fn compact_target_from_adjustment_is_max_target_digishield() {
447        let height = 145_000;
448        let params = Params::new(Network::Dogecoin);
449        let starting_bits = CompactTarget::from_consensus(0x1e0fffff); // Max target
450        let timespan =  5 * params.pow_target_timespan(height); // 5x Slower than expected
451        let got = CompactTarget::from_next_work_required_dogecoin(starting_bits, timespan, &params, height);
452        let want = params.max_attainable_target.to_compact_lossy();
453        assert_eq!(got, want);
454    }
455
456    #[test]
457    fn compact_target_from_minimum_downward_difficulty_adjustment() {
458        let pre_digishield_heights = vec![5_000, 10_000, 15_000];
459        let digishield_heights = vec![145_000, 1_000_000];
460        let starting_bits = CompactTarget::from_consensus(0x1b02f5b6); // Arbitrary difficulty
461        let params = Params::new(Network::Dogecoin);
462        for height in pre_digishield_heights {
463            let timespan =  4 * params.pow_target_timespan(height); // 4x Slower than expected
464            let got = CompactTarget::from_next_work_required_dogecoin(starting_bits, timespan, &params, height);
465            let want = Target::from_compact(starting_bits)
466                .max_transition_threshold_dogecoin(&params, height)
467                .to_compact_lossy();
468            assert_eq!(got, want);
469        }
470        for height in digishield_heights {
471            let timespan = 5 * params.pow_target_timespan(height); // 5x Slower than expected
472            let got = CompactTarget::from_next_work_required_dogecoin(starting_bits, timespan, &params, height);
473            let want = Target::from_compact(starting_bits)
474                .max_transition_threshold_dogecoin(&params, height)
475                .to_compact_lossy();
476            assert_eq!(got, want);
477        }
478    }
479
480    #[test]
481    fn should_compute_target() {
482        #[derive(Debug)]
483        struct TestCase<'a> {
484            name: &'a str,
485            height: u32,
486            starting_bits: CompactTarget,
487            start_time: i64,
488            end_time: i64,
489            expected_adjustment_bits: CompactTarget,
490        }
491
492        let test_cases = vec![
493            TestCase {
494                name: "Downwards difficulty adjustment (timespan: 150,098 s, slower than expected)",
495                height: 240,
496                starting_bits: CompactTarget::from_consensus(0x1e0ffff0), // Genesis compact target on Mainnet
497                start_time: 1386325540, // Genesis block unix time
498                end_time: 1386475638, // Block 239 unix time
499                expected_adjustment_bits: CompactTarget::from_consensus(0x1e0fffff) // Block 240 compact target
500            },
501            // Adapted from: <https://github.com/dogecoin/dogecoin/blob/7237da74b8c356568644cbe4fba19d994704355b/src/test/dogecoin_tests.cpp#L151>
502            TestCase {
503                name: "Downwards difficulty adjustment Digishield (timespan: 252 s, slower than expected)",
504                height: 145_000,
505                starting_bits: CompactTarget::from_consensus(0x1b499dfd), // Block 145_000 compact target,
506                start_time: 1395094427, // Block 144_999 unix time
507                end_time: 1395094679, // Block 145_000 unix time
508                expected_adjustment_bits: CompactTarget::from_consensus(0x1b671062), // Block 145_001 compact target
509            },
510            // Adapted from: <https://github.com/dogecoin/dogecoin/blob/7237da74b8c356568644cbe4fba19d994704355b/src/test/dogecoin_tests.cpp#L166>
511            TestCase {
512                name: "Downwards difficulty adjustment Digishield (timespan: 525 s, slower than expected)",
513                height: 145_000,
514                starting_bits: CompactTarget::from_consensus(0x1b3439cd), // Block 145_107 compact target
515                start_time: 1395100835, // Block 145_106 unix time
516                end_time: 1395101360, // Block 145_107 unix time
517                expected_adjustment_bits: CompactTarget::from_consensus(0x1b4e56b3), // Block 145_108 compact target
518            },
519            TestCase {
520                name: "Downwards difficulty adjustment Digishield (timespan: 225 s, slower than expected)",
521                height: 1_131_290,
522                starting_bits: CompactTarget::from_consensus(0x1b01cf5d), // Block 1_131_289 compact target
523                start_time: 1458248044, // Block 1_131_288 unix time
524                end_time: 1458248269, // Block 1_131_289 unix time
525                expected_adjustment_bits: CompactTarget::from_consensus(0x1b0269d1) // Block 1_131_290 compact target
526            },
527            TestCase {
528                name: "Downwards difficulty adjustment Digishield (timespan: 77 s, slower than expected)",
529                height: 1_531_886,
530                starting_bits: CompactTarget::from_consensus(0x1b01c45a), // Block 1_531_885 compact target
531                start_time: 1483302792, // Block 1_531_884 unix time
532                end_time: 1483302869, // Block 1_531_885 unix time
533                expected_adjustment_bits: CompactTarget::from_consensus(0x1b01d36e) // Block 1_531_886 compact target
534            },
535            TestCase {
536                name: "Upwards difficulty adjustment (timespan: 202 s, faster than expected)",
537                height: 480,
538                starting_bits: CompactTarget::from_consensus(0x1e0fffff), // Block 240 compact target
539                start_time: 1386475638, // Block 239 unix time
540                end_time: 1386475840, // Block 479 unix time
541                expected_adjustment_bits: CompactTarget::from_consensus(0x1e00ffff) // Block 480 compact target
542            },
543            // Adapted from: <https://github.com/dogecoin/dogecoin/blob/7237da74b8c356568644cbe4fba19d994704355b/src/test/dogecoin_tests.cpp#L137>
544            TestCase {
545                name: "Upwards difficulty adjustment (timespan: 12,105 s, faster than expected)",
546                height: 0,
547                starting_bits: CompactTarget::from_consensus(0x1c1a1206), // Block 9_359 compact target,
548                start_time: 1386942008, // Block 9_359 unix time,
549                end_time: 1386954113, // Block 9_599 unix time,
550                expected_adjustment_bits: CompactTarget::from_consensus(0x1c15ea59), // Block 9_600 compact target,
551            },
552            // Adapted from: <https://github.com/dogecoin/dogecoin/blob/7237da74b8c356568644cbe4fba19d994704355b/src/test/dogecoin_tests.cpp#L181>
553            TestCase {
554                name: "Upwards difficulty adjustment Digishield (timespan: -70 s, faster than expected)",
555                height: 145_000,
556                starting_bits: CompactTarget::from_consensus(0x1b446f21), // Block 149_423 compact target
557                start_time: 1395380517, // Block 149_422 unix time
558                end_time: 1395380447, // Block 149_423 unix time
559                expected_adjustment_bits: CompactTarget::from_consensus(0x1b335358), // Block 149_424 compact target
560            },
561            TestCase {
562                name: "Upwards difficulty adjustment Digishield (timespan: 8 s, faster than expected)",
563                height: 1_131_286,
564                starting_bits: CompactTarget::from_consensus(0x1b029d4f), // Block 1_131_285 compact target
565                start_time: 1458247987, // Block 1_131_284 unix time
566                end_time: 1458247995, // Block 1_131_285 unix time
567                expected_adjustment_bits: CompactTarget::from_consensus(0x1b025a60) // Block 1_131_286 compact target
568            },
569            TestCase {
570                name: "Upwards difficulty adjustment Digishield (timespan: 36 s, faster than expected)",
571                height: 1_531_882,
572                starting_bits: CompactTarget::from_consensus(0x1b01dc29), // Block 1_531_881 compact target
573                start_time: 1483302572, // Block 1_531_880 unix time
574                end_time: 1483302608, // Block 1_531_881 unix time
575                expected_adjustment_bits: CompactTarget::from_consensus(0x1b01c45a) // Block 1_531_882 compact target
576            },
577            // Adapted from: <https://github.com/dogecoin/dogecoin/blob/7237da74b8c356568644cbe4fba19d994704355b/src/test/dogecoin_tests.cpp#L196>
578            TestCase {
579                name: "Difficulty adjustment rounding Digishield (timespan: 48 s, faster than expected)",
580                height: 145_000,
581                starting_bits: CompactTarget::from_consensus(0x1b671062), // Block 145_001 compact target
582                start_time: 1395094679, // Block 145_000 unix time
583                end_time: 1395094727, // Block 145_001 unix time
584                expected_adjustment_bits: CompactTarget::from_consensus(0x1b6558a4), // Block 145_002 compact target
585            },
586        ];
587
588        let params = Params::new(Network::Dogecoin);
589
590        // Test difficulty adjustment
591        for test_case in test_cases.iter() {
592            let timespan = test_case.end_time - test_case.start_time;
593            let adjustment = CompactTarget::from_next_work_required_dogecoin(
594                test_case.starting_bits,
595                timespan,
596                &params,
597                test_case.height,
598            );
599            assert_eq!(
600                adjustment, test_case.expected_adjustment_bits,
601                "Unexpected adjustment bits for test case: {}",
602                test_case.name
603            );
604        }
605
606        // Test difficulty adjustment using headers
607        for test_case in test_cases.iter() {
608            let start_header = PureHeader {
609                version: Version::ONE,
610                prev_blockhash: BlockHash::all_zeros(),
611                merkle_root: TxMerkleNode::all_zeros(),
612                time: test_case.start_time as u32,
613                bits: CompactTarget::from_consensus(0x1e0fffff), // Note: this value does not matter
614                nonce: 0
615            }.into();
616            let end_header = PureHeader {
617                version: Version::ONE,
618                prev_blockhash: BlockHash::all_zeros(),
619                merkle_root: TxMerkleNode::all_zeros(),
620                time: test_case.end_time as u32,
621                bits: test_case.starting_bits,
622                nonce: 0
623            }.into();
624            let adjustment = CompactTarget::from_header_difficulty_adjustment_dogecoin(
625                start_header,
626                end_header,
627                &params,
628                test_case.height
629            );
630            assert_eq!(
631                adjustment, test_case.expected_adjustment_bits,
632                "Unexpected adjustment bits for test case using headers: {}",
633                test_case.name
634            );
635        }
636    }
637
638    #[test]
639    fn compact_target_from_maximum_upward_difficulty_adjustment() {
640        let pre_digishield_heights = vec![5_000, 10_000, 15_000];
641        let digishield_heights = vec![145_000, 1_000_000];
642        let starting_bits = CompactTarget::from_consensus(0x1b025a60); // Arbitrary difficulty
643        let params = Params::new(Network::Dogecoin);
644        for height in pre_digishield_heights {
645            let timespan = (0.06 * params.pow_target_timespan(height) as f64) as i64; // > 16x Faster than expected
646            let got = CompactTarget::from_next_work_required_dogecoin(starting_bits, timespan, &params, height);
647            let want = Target::from_compact(starting_bits)
648                .min_transition_threshold_dogecoin(&params, height)
649                .to_compact_lossy();
650            assert_eq!(got, want);
651        }
652        for height in digishield_heights {
653            let timespan = -params.pow_target_timespan(height); // Negative timespan
654            let got = CompactTarget::from_next_work_required_dogecoin(starting_bits, timespan, &params, height);
655            let want = Target::from_compact(starting_bits)
656                .min_transition_threshold_dogecoin(&params, height)
657                .to_compact_lossy();
658            assert_eq!(got, want);
659        }
660    }
661
662    #[test]
663    fn roundtrip_compact_target() {
664        let consensus = 0x1e0f_ffff;
665        let compact = CompactTarget::from_consensus(consensus);
666        let t = Target::from_compact(CompactTarget::from_consensus(consensus));
667        assert_eq!(t, Target::from(compact)); // From/Into sanity check.
668
669        let back = t.to_compact_lossy();
670        assert_eq!(back, compact); // From/Into sanity check.
671
672        assert_eq!(back.to_consensus(), consensus);
673    }
674}