diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 426f27cb..e533ec1d 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -43,6 +43,15 @@ pub struct BitcoinCashConsensusParams { pub monolith_time: u32, } +#[derive(Debug, Clone)] +/// ZCash consensus parameters. +pub struct ZCashConsensusParams { + pub pow_averaging_window: u32, + pub pow_max_adjust_down: u32, + pub pow_max_adjust_up: u32, + pub pow_target_spacing: u32, +} + #[derive(Debug, Clone)] /// Concurrent consensus rule forks. pub enum ConsensusFork { @@ -56,7 +65,7 @@ pub enum ConsensusFork { /// BUIP-HF Digest for replay protected signature verification across hard forks - https://github.com/Bitcoin-UAHF/spec/blob/master/replay-protected-sighash.md BitcoinCash(BitcoinCashConsensusParams), /// ZCash. - ZCash, + ZCash(ZCashConsensusParams), } impl ConsensusParams { @@ -65,19 +74,19 @@ impl ConsensusParams { Network::Mainnet | Network::Other(_) => ConsensusParams { network: network, bip16_time: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 1333238400, // Apr 1 2012 }, bip34_height: match fork { - ConsensusFork::ZCash => 1, + ConsensusFork::ZCash(_) => 1, _ => 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8 }, bip65_height: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 }, bip66_height: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 363725, // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 }, segwit_deployment: match fork { @@ -88,7 +97,7 @@ impl ConsensusParams { timeout: 1510704000, activation: Some(481824), }), - ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash => None, + ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash(_) => None, }, fork: fork, rule_change_activation_threshold: 1916, // 95% @@ -104,19 +113,19 @@ impl ConsensusParams { Network::Testnet => ConsensusParams { network: network, bip16_time: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 1333238400, // Apr 1 2012 }, bip34_height: match fork { - ConsensusFork::ZCash => 1, + ConsensusFork::ZCash(_) => 1, _ => 21111, // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 }, bip65_height: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 }, bip66_height: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 330776, // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 }, segwit_deployment: match fork { @@ -127,7 +136,7 @@ impl ConsensusParams { timeout: 1493596800, activation: Some(834624), }), - ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash => None, + ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash(_) => None, }, fork: fork, rule_change_activation_threshold: 1512, // 75% @@ -143,19 +152,19 @@ impl ConsensusParams { Network::Regtest | Network::Unitest => ConsensusParams { network: network, bip16_time: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 1333238400, // Apr 1 2012 }, bip34_height: match fork { - ConsensusFork::ZCash => 1, + ConsensusFork::ZCash(_) => 1, _ => 100000000, // not activated on regtest }, bip65_height: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 1351, }, bip66_height: match fork { - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, _ => 1251, // used only in rpc tests }, segwit_deployment: match fork { @@ -166,7 +175,7 @@ impl ConsensusParams { timeout: ::std::u32::MAX, activation: None, }), - ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash => None, + ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash(_) => None, }, fork: fork, rule_change_activation_threshold: 108, // 75% @@ -212,7 +221,7 @@ impl ConsensusFork { match *self { ConsensusFork::BitcoinCore => 0, ConsensusFork::BitcoinCash(ref fork) => fork.height, - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, } } @@ -227,7 +236,7 @@ impl ConsensusFork { // size of first fork block must be larger than 1MB ConsensusFork::BitcoinCash(ref fork) if height == fork.height => 1_000_001, ConsensusFork::BitcoinCore | ConsensusFork::BitcoinCash(_) => 0, - ConsensusFork::ZCash => 0, + ConsensusFork::ZCash(_) => 0, } } @@ -236,7 +245,7 @@ impl ConsensusFork { ConsensusFork::BitcoinCash(ref fork) if median_time_past >= fork.monolith_time => 32_000_000, ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => 8_000_000, ConsensusFork::BitcoinCore | ConsensusFork::BitcoinCash(_) => 1_000_000, - ConsensusFork::ZCash => 2_000_000, + ConsensusFork::ZCash(_) => 2_000_000, } } @@ -245,13 +254,13 @@ impl ConsensusFork { // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => 20_000 * (1 + (block_size - 1) / 1_000_000), - ConsensusFork::BitcoinCore | ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash => 20_000, + ConsensusFork::BitcoinCore | ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash(_) => 20_000, } } pub fn max_block_sigops_cost(&self, height: u32, block_size: usize) -> usize { match *self { - ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash => + ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash(_) => self.max_block_sigops(height, block_size) * Self::witness_scale_factor(), ConsensusFork::BitcoinCore => 80_000, @@ -260,7 +269,7 @@ impl ConsensusFork { pub fn max_block_weight(&self, _height: u32) -> usize { match *self { - ConsensusFork::BitcoinCore | ConsensusFork::ZCash => + ConsensusFork::BitcoinCore | ConsensusFork::ZCash(_) => 4_000_000, ConsensusFork::BitcoinCash(_) => unreachable!("BitcoinCash has no SegWit; weight is only checked with SegWit activated; qed"), @@ -290,6 +299,43 @@ impl BitcoinCashConsensusParams { } } +impl ZCashConsensusParams { + pub fn new(network: Network) -> Self { + match network { + Network::Mainnet | Network::Other(_) => ZCashConsensusParams { + pow_averaging_window: 17, + pow_max_adjust_down: 32, + pow_max_adjust_up: 16, + pow_target_spacing: (2.5 * 60.0) as u32, + }, + Network::Testnet => ZCashConsensusParams { + pow_averaging_window: 17, + pow_max_adjust_down: 32, + pow_max_adjust_up: 16, + pow_target_spacing: (2.5 * 60.0) as u32, + }, + Network::Regtest | Network::Unitest => ZCashConsensusParams { + pow_averaging_window: 17, + pow_max_adjust_down: 0, + pow_max_adjust_up: 0, + pow_target_spacing: (2.5 * 60.0) as u32, + }, + } + } + + pub fn averaging_window_timespan(&self) -> u32 { + self.pow_averaging_window * self.pow_target_spacing + } + + pub fn min_actual_timespan(&self) -> u32 { + (self.averaging_window_timespan() * (100 - self.pow_max_adjust_up)) / 100 + } + + pub fn max_actual_timespan(&self) -> u32 { + (self.averaging_window_timespan() * (100 + self.pow_max_adjust_down)) / 100 + } +} + #[cfg(test)] mod tests { use super::super::Network; diff --git a/network/src/lib.rs b/network/src/lib.rs index 37531ed6..d28483e2 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -11,6 +11,6 @@ mod network; pub use primitives::{hash, compact}; -pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams}; +pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams, ZCashConsensusParams}; pub use deployments::Deployment; pub use network::{Magic, Network}; diff --git a/network/src/network.rs b/network/src/network.rs index 1a43f6df..ce8001d0 100644 --- a/network/src/network.rs +++ b/network/src/network.rs @@ -60,9 +60,9 @@ impl Network { (&ConsensusFork::BitcoinCash(_), Network::Mainnet) => BITCOIN_CASH_MAGIC_MAINNET, (&ConsensusFork::BitcoinCash(_), Network::Testnet) => BITCOIN_CASH_MAGIC_TESTNET, (&ConsensusFork::BitcoinCash(_), Network::Regtest) => BITCOIN_CASH_MAGIC_REGTEST, - (&ConsensusFork::ZCash, Network::Mainnet) => ZCASH_MAGIC_MAINNET, - (&ConsensusFork::ZCash, Network::Testnet) => ZCASH_MAGIC_TESTNET, - (&ConsensusFork::ZCash, Network::Regtest) => ZCASH_MAGIC_REGTEST, + (&ConsensusFork::ZCash(_), Network::Mainnet) => ZCASH_MAGIC_MAINNET, + (&ConsensusFork::ZCash(_), Network::Testnet) => ZCASH_MAGIC_TESTNET, + (&ConsensusFork::ZCash(_), Network::Regtest) => ZCASH_MAGIC_REGTEST, (_, Network::Mainnet) => MAGIC_MAINNET, (_, Network::Testnet) => MAGIC_TESTNET, (_, Network::Regtest) => MAGIC_REGTEST, @@ -73,9 +73,9 @@ impl Network { pub fn max_bits(&self, fork: &ConsensusFork) -> U256 { match (fork, *self) { - (&ConsensusFork::ZCash, Network::Mainnet) => ZCASH_MAX_BITS_MAINNET.clone(), - (&ConsensusFork::ZCash, Network::Testnet) => ZCASH_MAX_BITS_TESTNET.clone(), - (&ConsensusFork::ZCash, Network::Testnet) => ZCASH_MAX_BITS_REGTEST.clone(), + (&ConsensusFork::ZCash(_), Network::Mainnet) => ZCASH_MAX_BITS_MAINNET.clone(), + (&ConsensusFork::ZCash(_), Network::Testnet) => ZCASH_MAX_BITS_TESTNET.clone(), + (&ConsensusFork::ZCash(_), Network::Testnet) => ZCASH_MAX_BITS_REGTEST.clone(), (_, Network::Mainnet) | (_, Network::Other(_)) => MAX_BITS_MAINNET.clone(), (_, Network::Testnet) => MAX_BITS_TESTNET.clone(), (_, Network::Regtest) => MAX_BITS_REGTEST.clone(), @@ -85,9 +85,9 @@ impl Network { pub fn port(&self, fork: &ConsensusFork) -> u16 { match (fork, *self) { - (&ConsensusFork::ZCash, Network::Mainnet) | (&ConsensusFork::ZCash, Network::Other(_)) => 8233, - (&ConsensusFork::ZCash, Network::Testnet) => 18233, - (&ConsensusFork::ZCash, Network::Regtest) | (&ConsensusFork::ZCash, Network::Unitest) => 18344, + (&ConsensusFork::ZCash(_), Network::Mainnet) | (&ConsensusFork::ZCash(_), Network::Other(_)) => 8233, + (&ConsensusFork::ZCash(_), Network::Testnet) => 18233, + (&ConsensusFork::ZCash(_), Network::Regtest) | (&ConsensusFork::ZCash(_), Network::Unitest) => 18344, (_, Network::Mainnet) | (_, Network::Other(_)) => 8333, (_, Network::Testnet) => 18333, (_, Network::Regtest) | (_, Network::Unitest) => 18444, @@ -105,7 +105,7 @@ impl Network { pub fn genesis_block(&self, fork: &ConsensusFork) -> Block { match (fork, *self) { // TODO - (&ConsensusFork::ZCash, Network::Mainnet) | (&ConsensusFork::ZCash, Network::Other(_)) => { + (&ConsensusFork::ZCash(_), Network::Mainnet) | (&ConsensusFork::ZCash(_), Network::Other(_)) => { use serialization; use chain; use chain::hex::FromHex; @@ -114,9 +114,9 @@ impl Network { let genesis: chain::Block = serialization::deserialize_with_flags(&origin as &[u8], serialization::DESERIALIZE_ZCASH).unwrap(); genesis }, - (&ConsensusFork::ZCash, Network::Testnet) => + (&ConsensusFork::ZCash(_), Network::Testnet) => "".into(), - (&ConsensusFork::ZCash, Network::Regtest) | (&ConsensusFork::ZCash, Network::Unitest) => + (&ConsensusFork::ZCash(_), Network::Regtest) | (&ConsensusFork::ZCash(_), Network::Unitest) => "".into(), (_, Network::Mainnet) | (_, Network::Other(_)) => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000".into(), diff --git a/pbtc/commands/start.rs b/pbtc/commands/start.rs index 0f526c01..b407a2cd 100644 --- a/pbtc/commands/start.rs +++ b/pbtc/commands/start.rs @@ -91,7 +91,7 @@ pub fn start(cfg: config::Config) -> Result<(), String> { let SERIALIZE_ZCASH = 0x80000000; // TODO let serialization_flags = match cfg.consensus.fork { - ConsensusFork::ZCash => SERIALIZE_ZCASH, + ConsensusFork::ZCash(_) => SERIALIZE_ZCASH, _ => 0, }; @@ -101,11 +101,11 @@ pub fn start(cfg: config::Config) -> Result<(), String> { outbound_connections: cfg.outbound_connections, connection: p2p::NetConfig { protocol_version: match &cfg.consensus.fork { - &ConsensusFork::ZCash => ZCASH_PROTOCOL_VERSION, + &ConsensusFork::ZCash(_) => ZCASH_PROTOCOL_VERSION, _ => PROTOCOL_VERSION, }, protocol_minimum: match &cfg.consensus.fork { - &ConsensusFork::ZCash => ZCASH_PROTOCOL_MINIMUM, + &ConsensusFork::ZCash(_) => ZCASH_PROTOCOL_MINIMUM, _ => PROTOCOL_MINIMUM, }, magic: cfg.consensus.magic(), diff --git a/pbtc/config.rs b/pbtc/config.rs index 1c2dce3c..4f229e99 100644 --- a/pbtc/config.rs +++ b/pbtc/config.rs @@ -2,7 +2,7 @@ use std::net; use clap; use storage; use message::Services; -use network::{Network, ConsensusParams, ConsensusFork, BitcoinCashConsensusParams}; +use network::{Network, ConsensusParams, ConsensusFork, BitcoinCashConsensusParams, ZCashConsensusParams}; use p2p::InternetProtocol; use seednodes::{mainnet_seednodes, testnet_seednodes, bitcoin_cash_seednodes, bitcoin_cash_testnet_seednodes, zcash_seednodes}; @@ -63,7 +63,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { let consensus = ConsensusParams::new(network, consensus_fork); match consensus.fork { - ConsensusFork::ZCash => ser::set_default_flags(ser::SERIALIZE_ZCASH), + ConsensusFork::ZCash(_) => ser::set_default_flags(ser::SERIALIZE_ZCASH), _ => (), }; @@ -81,7 +81,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { let user_agent_suffix = match consensus.fork { ConsensusFork::BitcoinCore => "", ConsensusFork::BitcoinCash(_) => "/UAHF", - ConsensusFork::ZCash => "", + ConsensusFork::ZCash(_) => "", }; let user_agent = match network { Network::Testnet | Network::Mainnet | Network::Unitest | Network::Other(_) => format!("{}{}", USER_AGENT, user_agent_suffix), @@ -106,7 +106,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { let seednodes: Vec = match matches.value_of("seednode") { Some(s) => vec![s.parse().map_err(|_| "Invalid seednode".to_owned())?], None => match (network, &consensus.fork) { - (Network::Mainnet, &ConsensusFork::ZCash) => zcash_seednodes().into_iter().map(Into::into).collect(), + (Network::Mainnet, &ConsensusFork::ZCash(_)) => zcash_seednodes().into_iter().map(Into::into).collect(), (Network::Mainnet, &ConsensusFork::BitcoinCash(_)) => bitcoin_cash_seednodes().into_iter().map(Into::into).collect(), (Network::Testnet, &ConsensusFork::BitcoinCash(_)) => bitcoin_cash_testnet_seednodes().into_iter().map(Into::into).collect(), (Network::Mainnet, _) => mainnet_seednodes().into_iter().map(Into::into).collect(), @@ -131,7 +131,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { let services = match &consensus.fork { &ConsensusFork::BitcoinCash(_) => services.with_bitcoin_cash(true), &ConsensusFork::BitcoinCore => services.with_witness(true), - &ConsensusFork::ZCash => services, + &ConsensusFork::ZCash(_) => services, }; let verification_level = match matches.value_of("verification-level") { @@ -200,7 +200,7 @@ fn parse_consensus_fork(network: Network, db: &storage::SharedStore, matches: &c Ok(match new_consensus_fork { "btc" => ConsensusFork::BitcoinCore, "bch" => ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(network)), - "zcash" => ConsensusFork::ZCash, + "zcash" => ConsensusFork::ZCash(ZCashConsensusParams::new(network)), _ => unreachable!("hardcoded above"), }) } diff --git a/test-data/src/block.rs b/test-data/src/block.rs index 45ba599e..f297a789 100644 --- a/test-data/src/block.rs +++ b/test-data/src/block.rs @@ -248,6 +248,7 @@ impl BlockHeaderBuilder where F: Invoke { nonce: self.nonce.into(), merkle_root_hash: self.merkle_root, version: self.version, + hash_final_sapling_root: None, equihash_solution: None, } ) diff --git a/verification/src/accept_header.rs b/verification/src/accept_header.rs index 973a356f..c84345cd 100644 --- a/verification/src/accept_header.rs +++ b/verification/src/accept_header.rs @@ -82,7 +82,7 @@ impl<'a> HeaderEquihashSolution<'a> { fn check(&self) -> Result<(), Error> { match self.consensus.fork { - ConsensusFork::ZCash => (), + ConsensusFork::ZCash(_) => (), _ => return Ok(()), } diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 475386c6..eb679674 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -321,7 +321,7 @@ impl<'a> TransactionEval<'a> { }; let signature_version = match params.fork { ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => SignatureVersion::ForkId, - ConsensusFork::BitcoinCore | ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash => SignatureVersion::Base, + ConsensusFork::BitcoinCore | ConsensusFork::BitcoinCash(_) | ConsensusFork::ZCash(_) => SignatureVersion::Base, }; let verify_checksequence = deployments.csv(); diff --git a/verification/src/deployments.rs b/verification/src/deployments.rs index 6b568213..08b877dd 100644 --- a/verification/src/deployments.rs +++ b/verification/src/deployments.rs @@ -294,6 +294,7 @@ mod tests { time: time, bits: 0.into(), nonce: height.into(), + hash_final_sapling_root: None, equihash_solution: None, }; previous_header_hash = header.hash(); diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 3ff62677..4c9b4a0c 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -81,6 +81,7 @@ mod sigops; mod timestamp; mod work; mod work_bch; +mod work_zcash; // pre-verification mod verify_block; diff --git a/verification/src/timestamp.rs b/verification/src/timestamp.rs index ad487f74..0f40c90f 100644 --- a/verification/src/timestamp.rs +++ b/verification/src/timestamp.rs @@ -23,5 +23,6 @@ pub fn median_timestamp_inclusive(previous_header_hash: H256, store: &BlockHeade } timestamps.sort(); +println!("=== timestamps: {}..{}", timestamps[0], timestamps[10]); timestamps[timestamps.len() / 2] } diff --git a/verification/src/work.rs b/verification/src/work.rs index 006275d2..5adc6a4c 100644 --- a/verification/src/work.rs +++ b/verification/src/work.rs @@ -6,6 +6,7 @@ use chain::{IndexedBlockHeader, BlockHeader}; use network::{Network, ConsensusParams, ConsensusFork}; use storage::{BlockHeaderProvider, BlockRef}; use work_bch::work_required_bitcoin_cash; +use work_zcash::work_required_zcash; use constants::{ DOUBLE_SPACING_SECONDS, TARGET_TIMESPAN_SECONDS, @@ -66,6 +67,11 @@ pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHea let parent_header = store.block_header(parent_hash.clone().into()).expect("self.height != 0; qed"); match consensus.fork { + ConsensusFork::ZCash(ref fork) => + return work_required_zcash(IndexedBlockHeader { + hash: parent_hash, + raw: parent_header + }, time, height, store, fork, max_bits), ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => return work_required_bitcoin_cash(IndexedBlockHeader { hash: parent_hash, diff --git a/verification/src/work_bch.rs b/verification/src/work_bch.rs index b8bfe59c..e60b7899 100644 --- a/verification/src/work_bch.rs +++ b/verification/src/work_bch.rs @@ -172,7 +172,7 @@ fn work_required_bitcoin_cash_adjusted(parent_header: IndexedBlockHeader, time: } #[cfg(test)] -mod tests { +pub mod tests { use std::collections::HashMap; use primitives::bytes::Bytes; use primitives::hash::H256; @@ -184,12 +184,16 @@ mod tests { use super::work_required_bitcoin_cash_adjusted; #[derive(Default)] - struct MemoryBlockHeaderProvider { + pub struct MemoryBlockHeaderProvider { pub by_height: Vec, pub by_hash: HashMap, } impl MemoryBlockHeaderProvider { + pub fn last(&self) -> &BlockHeader { + self.by_height.last().unwrap() + } + pub fn insert(&mut self, header: BlockHeader) { self.by_hash.insert(header.hash(), self.by_height.len()); self.by_height.push(header); @@ -227,6 +231,7 @@ mod tests { time: 1269211443, bits: 0x207fffff.into(), nonce: 0.into(), + hash_final_sapling_root: None, equihash_solution: None, }); @@ -287,6 +292,7 @@ mod tests { time: 1269211443, bits: initial_bits.into(), nonce: 0.into(), + hash_final_sapling_root: None, equihash_solution: None, }); diff --git a/verification/src/work_zcash.rs b/verification/src/work_zcash.rs new file mode 100644 index 00000000..9b7b9c6e --- /dev/null +++ b/verification/src/work_zcash.rs @@ -0,0 +1,166 @@ +use primitives::compact::Compact; +use primitives::hash::H256; +use primitives::bigint::{Uint, U256}; +use chain::{IndexedBlockHeader, BlockHeader}; +use network::{Network, ConsensusParams, ZCashConsensusParams}; +use storage::BlockHeaderProvider; +use timestamp::median_timestamp_inclusive; +use work::{is_retarget_height, work_required_testnet, work_required_retarget}; + +/// Returns work required for given header for the ZCash block +pub fn work_required_zcash(parent_header: IndexedBlockHeader, time: u32, height: u32, store: &BlockHeaderProvider, fork: &ZCashConsensusParams, max_bits: Compact) -> Compact { + // Find the first block in the averaging interval + let mut oldest_hash = parent_header.hash.clone(); + let mut bits_total: U256 = parent_header.raw.bits.into(); + for i in 1..fork.pow_averaging_window { + let block_number = match height.checked_sub(i + 1) { + Some(block_number) => block_number, + None => { +println!("=== XXX"); + return max_bits + }, + }; + + let previous_header = store.block_header(block_number.into()).expect("block_number > 0 && block_number < height; qed"); + bits_total = bits_total + previous_header.bits.into(); + oldest_hash = previous_header.hash(); + } +println!("=== bits_total = {:?}", Compact::from_u256(bits_total)); + let bits_avg = bits_total / fork.pow_averaging_window.into(); + let parent_mtp = median_timestamp_inclusive(parent_header.hash.clone(), store); + let oldest_mtp = median_timestamp_inclusive(oldest_hash, store); + calculate_work_required(bits_avg, parent_mtp, oldest_mtp, fork, max_bits) +} + +fn calculate_work_required(bits_avg: U256, parent_mtp: u32, oldest_mtp: u32, fork: &ZCashConsensusParams, max_bits: Compact) -> Compact { + // Limit adjustment step + // Use medians to prevent time-warp attacks + let actual_timespan = parent_mtp - oldest_mtp; +println!("=== parent_mtp: {}", parent_mtp); +println!("=== oldest_mtp: {}", oldest_mtp); +println!("=== actual_timespan_0: {}", actual_timespan); + let mut actual_timespan = fork.averaging_window_timespan() as i64 + + (actual_timespan as i64 - fork.averaging_window_timespan() as i64) / 4; +println!("=== actual_timespan_1: {}", actual_timespan); + if actual_timespan < fork.min_actual_timespan() as i64 { + actual_timespan = fork.min_actual_timespan() as i64; + } + if actual_timespan > fork.max_actual_timespan() as i64 { + actual_timespan = fork.max_actual_timespan() as i64; + } +println!("=== actual_timespan_2: {}", actual_timespan); + // Retarget + let actual_timespan = actual_timespan as u32; + let mut bits_new = bits_avg / fork.averaging_window_timespan().into(); + bits_new = bits_new * actual_timespan.into(); + + if bits_new > max_bits.into() { + return max_bits; + } + + bits_new.into() +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use primitives::bytes::Bytes; + use primitives::compact::Compact; + use primitives::hash::H256; + use primitives::bigint::U256; + use network::{Network, ZCashConsensusParams, ConsensusFork}; + use storage::{BlockHeaderProvider, BlockRef}; + use chain::BlockHeader; + use timestamp::median_timestamp_inclusive; + use work::work_required; + use work_bch::tests::MemoryBlockHeaderProvider; + use super::{work_required_zcash, calculate_work_required}; + + // original test link: + // https://github.com/Bitcoin-ABC/bitcoin-abc/blob/d8eac91f8d16716eed0ad11ccac420122280bb13/src/test/pow_tests.cpp#L193 + #[test] + fn zcash_work_required_works() { + let fork = ZCashConsensusParams::new(Network::Mainnet); + let max_bits = Network::Mainnet.max_bits(&ConsensusFork::ZCash(fork.clone())); + + let last_block = 2 * fork.pow_averaging_window; + let first_block = last_block - fork.pow_averaging_window; + + // insert genesis block + let mut header_provider = MemoryBlockHeaderProvider::default(); + header_provider.insert(BlockHeader { + time: 1269211443, + bits: Compact::new(0x1e7fffff), + version: 0, + previous_header_hash: 0.into(), + merkle_root_hash: 0.into(), + nonce: 0.into(), + hash_final_sapling_root: None, + equihash_solution: None, + }); + + // Start with blocks evenly-spaced and equal difficulty + for i in 1..last_block+1 { + let header = BlockHeader { + time: header_provider.last().time + fork.pow_target_spacing, + bits: Compact::new(0x1e7fffff), + version: 0, + previous_header_hash: 0.into(), + merkle_root_hash: 0.into(), + nonce: 0.into(), + hash_final_sapling_root: None, + equihash_solution: None, + }; + header_provider.insert(header); + } + + // Result should be unchanged, modulo integer division precision loss + let mut expected: U256 = Compact::new(0x1e7fffff).into(); + expected = expected / fork.averaging_window_timespan().into(); + expected = expected * fork.averaging_window_timespan().into(); + let actual = work_required_zcash(header_provider.last().clone().into(), + 0, header_provider.by_height.len() as u32, &header_provider, &fork, max_bits.into()); + assert_eq!(actual, expected.into()); + + // Result should be the same as if last difficulty was used + let bits_avg: U256 = header_provider.by_height[last_block as usize].bits.into(); + let expected = calculate_work_required(bits_avg, + median_timestamp_inclusive(header_provider.by_height[last_block as usize].hash(), &header_provider), + median_timestamp_inclusive(header_provider.by_height[first_block as usize].hash(), &header_provider), + &fork, max_bits.into()); + let actual = work_required_zcash(header_provider.last().clone().into(), + 0, header_provider.by_height.len() as u32, &header_provider, &fork, max_bits.into()); + assert_eq!(actual, expected); + +/* + // Result should be unchanged, modulo integer division precision loss + let mut bits_expected: U256 = Compact::new(0x1e7fffff).into(); + bits_expected = bits_expected / fork.averaging_window_timespan().into(); + bits_expected = bits_expected * fork.averaging_window_timespan().into(); + assert_eq!(calculate_work_required(bits_expected, + median_timestamp_inclusive(header_provider.by_height[last_block as usize].hash(), &header_provider), + median_timestamp_inclusive(header_provider.by_height[first_block as usize].hash(), &header_provider), + &fork, max_bits.into()), bits_expected.into()); + + // Randomise the final block time (plus 1 to ensure it is always different) + use std::rand::{task_rng, Rng}; + header_provider.by_height[last_block].time += task_rng().gen_range(1, fork.pow_target_spacing / 2); + + // Result should be the same as if last difficulty was used + bits_expected = header_provider.by_height[last_block].bits; + assert_eq!(calculate_work_required(bits_expected, + median_timestamp_inclusive(header_provider.by_height[last_block as usize].hash(), &header_provider), + median_timestamp_inclusive(header_provider.by_height[first_block as usize].hash(), &header_provider), + &fork, max_bits.into()), bits_expected.into()); + + // Result should be the same as if last difficulty was used + bnAvg.SetCompact(blocks[lastBlk].nBits); + EXPECT_EQ(CalculateNextWorkRequired(bnAvg, + blocks[lastBlk].GetMedianTimePast(), + blocks[firstBlk].GetMedianTimePast(), + params), + GetNextWorkRequired(&blocks[lastBlk], nullptr, params)); + // Result should not be unchanged + EXPECT_NE(0x1e7fffff, GetNextWorkRequired(&blocks[lastBlk], nullptr, params));*/ + } +} \ No newline at end of file