From 261bd518fdc25b02606032040012ab7cf8c7a204 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 2 Nov 2017 17:02:16 +0300 Subject: [PATCH 1/6] BitcoinCash: difficulty adjustment fork --- Cargo.lock | 2 +- message/src/message/message_header.rs | 6 +- network/Cargo.toml | 2 +- network/src/consensus.rs | 157 ++++++--- network/src/lib.rs | 6 +- network/src/network.rs | 46 +-- p2p/src/io/handshake.rs | 12 +- p2p/src/io/read_any_message.rs | 8 +- p2p/src/io/read_header.rs | 10 +- p2p/src/io/read_message.rs | 10 +- pbtc/config.rs | 22 +- sync/src/synchronization_verifier.rs | 4 +- verification/src/accept_transaction.rs | 14 +- verification/src/verify_header.rs | 2 +- verification/src/work.rs | 443 ++++++++++++++++++++++--- 15 files changed, 579 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0f3668b..fca26d15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -653,8 +653,8 @@ name = "network" version = "0.1.0" dependencies = [ "chain 0.1.0", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.1.0", - "serialization 0.1.0", ] [[package]] diff --git a/message/src/message/message_header.rs b/message/src/message/message_header.rs index 24dedd95..c0b31a0e 100644 --- a/message/src/message/message_header.rs +++ b/message/src/message/message_header.rs @@ -69,7 +69,7 @@ mod tests { fn test_message_header_serialization() { let expected = "f9beb4d96164647200000000000000001f000000ed52399b".into(); let header = MessageHeader { - magic: Network::Mainnet.magic(ConsensusFork::NoFork), + magic: Network::Mainnet.magic(&ConsensusFork::NoFork), command: "addr".into(), len: 0x1f, checksum: "ed52399b".into(), @@ -82,12 +82,12 @@ mod tests { fn test_message_header_deserialization() { let raw: Bytes = "f9beb4d96164647200000000000000001f000000ed52399b".into(); let expected = MessageHeader { - magic: Network::Mainnet.magic(ConsensusFork::NoFork), + magic: Network::Mainnet.magic(&ConsensusFork::NoFork), command: "addr".into(), len: 0x1f, checksum: "ed52399b".into(), }; - assert_eq!(expected, MessageHeader::deserialize(&raw, Network::Mainnet.magic(ConsensusFork::NoFork)).unwrap()); + assert_eq!(expected, MessageHeader::deserialize(&raw, Network::Mainnet.magic(&ConsensusFork::NoFork)).unwrap()); } } diff --git a/network/Cargo.toml b/network/Cargo.toml index 0d8e5383..21ee79bd 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" authors = ["debris "] [dependencies] -serialization = { path = "../serialization" } +lazy_static = "0.2" chain = { path = "../chain" } primitives = { path = "../primitives" } diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 081a5814..18363428 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -2,9 +2,9 @@ use hash::H256; use {Network, Magic, Deployment}; /// First block of SegWit2x fork. -pub const SEGWIT2X_FORK_BLOCK: u32 = 494784; // https://segwit2x.github.io/segwit2x-announce.html +const SEGWIT2X_FORK_BLOCK: u32 = 494784; // https://segwit2x.github.io/segwit2x-announce.html /// First block of BitcoinCash fork. -pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitcoin-cash/block/478559 +const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitcoin-cash/block/478559 #[derive(Debug, Clone)] /// Parameters that influence chain consensus. @@ -35,7 +35,25 @@ pub struct ConsensusParams { pub segwit_deployment: Option, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] +/// Bitcoin cash consensus parameters. +pub struct BitcoinCashConsensusParams { + /// Initial BCH hard fork height. + pub height: u32, + /// Time when difficulty adjustment hardfork becomes active (~Nov 13 2017). + /// https://reviews.bitcoinabc.org/D601 + // TODO: change to height after activation + pub difficulty_adjustion_time: u32, +} + +#[derive(Debug, Clone)] +/// SegWit2x consensus parameters. +pub struct SegWit2xConsensusParams { + /// Initial SegWit2x hard fork height. + pub height: u32, +} + +#[derive(Debug, Clone)] /// Concurrent consensus rule forks. pub enum ConsensusFork { /// No fork. @@ -47,14 +65,14 @@ pub enum ConsensusFork { /// Segregated Witness (Consensus layer) - https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki /// Block size increase to 2MB - https://github.com/bitcoin/bips/blob/master/bip-0102.mediawiki /// Readiness checklist - https://segwit2x.github.io/segwit2x-announce.html - SegWit2x(u32), + SegWit2x(SegWit2xConsensusParams), /// Bitcoin Cash (aka UAHF). /// `u32` is height of the first block, for which new consensus rules are applied. /// Briefly: no SegWit + blocks up to 8MB + replay protection. /// Technical specification: /// UAHF Technical Specification - https://github.com/Bitcoin-UAHF/spec/blob/master/uahf-technical-spec.md /// BUIP-HF Digest for replay protected signature verification across hard forks - https://github.com/Bitcoin-UAHF/spec/blob/master/replay-protected-sighash.md - BitcoinCash(u32), + BitcoinCash(BitcoinCashConsensusParams), } impl ConsensusParams { @@ -66,16 +84,6 @@ impl ConsensusParams { bip34_height: 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8 bip65_height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 bip66_height: 363725, // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 - fork: fork, - rule_change_activation_threshold: 1916, // 95% - miner_confirmation_window: 2016, - csv_deployment: Some(Deployment { - name: "csv", - bit: 0, - start_time: 1462060800, - timeout: 1493596800, - activation: Some(419328), - }), segwit_deployment: match fork { ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { name: "segwit", @@ -86,6 +94,16 @@ impl ConsensusParams { }), ConsensusFork::BitcoinCash(_) => None, }, + fork: fork, + rule_change_activation_threshold: 1916, // 95% + miner_confirmation_window: 2016, + csv_deployment: Some(Deployment { + name: "csv", + bit: 0, + start_time: 1462060800, + timeout: 1493596800, + activation: Some(419328), + }), }, Network::Testnet => ConsensusParams { network: network, @@ -93,16 +111,6 @@ impl ConsensusParams { bip34_height: 21111, // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 bip65_height: 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 bip66_height: 330776, // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 - fork: fork, - rule_change_activation_threshold: 1512, // 75% - miner_confirmation_window: 2016, - csv_deployment: Some(Deployment { - name: "csv", - bit: 0, - start_time: 1456790400, - timeout: 1493596800, - activation: Some(770112), - }), segwit_deployment: match fork { ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { name: "segwit", @@ -113,6 +121,16 @@ impl ConsensusParams { }), ConsensusFork::BitcoinCash(_) => None, }, + fork: fork, + rule_change_activation_threshold: 1512, // 75% + miner_confirmation_window: 2016, + csv_deployment: Some(Deployment { + name: "csv", + bit: 0, + start_time: 1456790400, + timeout: 1493596800, + activation: Some(770112), + }), }, Network::Regtest | Network::Unitest => ConsensusParams { network: network, @@ -120,16 +138,6 @@ impl ConsensusParams { bip34_height: 100000000, // not activated on regtest bip65_height: 1351, bip66_height: 1251, // used only in rpc tests - fork: fork, - rule_change_activation_threshold: 108, // 75% - miner_confirmation_window: 144, - csv_deployment: Some(Deployment { - name: "csv", - bit: 0, - start_time: 0, - timeout: 0, - activation: Some(0), - }), segwit_deployment: match fork { ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { name: "segwit", @@ -140,12 +148,22 @@ impl ConsensusParams { }), ConsensusFork::BitcoinCash(_) => None, }, + fork: fork, + rule_change_activation_threshold: 108, // 75% + miner_confirmation_window: 144, + csv_deployment: Some(Deployment { + name: "csv", + bit: 0, + start_time: 0, + timeout: 0, + activation: Some(0), + }), }, } } pub fn magic(&self) -> Magic { - self.network.magic(self.fork) + self.network.magic(&self.fork) } pub fn is_bip30_exception(&self, hash: &H256, height: u32) -> bool { @@ -170,6 +188,14 @@ impl ConsensusFork { 4 } + pub fn activation_height(&self) -> u32 { + match *self { + ConsensusFork::NoFork => 0, + ConsensusFork::SegWit2x(ref fork) => fork.height, + ConsensusFork::BitcoinCash(ref fork) => fork.height, + } + } + pub fn max_transaction_size(&self) -> usize { // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 // SegWit: size * 4 <= 4_000_000 ===> max size of tx is still 1_000_000 @@ -179,15 +205,15 @@ impl ConsensusFork { pub fn min_block_size(&self, height: u32) -> usize { match *self { // size of first fork block must be larger than 1MB - ConsensusFork::BitcoinCash(fork_height) if height == fork_height => 1_000_001, + ConsensusFork::BitcoinCash(ref fork) if height == fork.height => 1_000_001, ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 0, } } pub fn max_block_size(&self, height: u32) -> usize { match *self { - ConsensusFork::SegWit2x(fork_height) if height >= fork_height => 2_000_000, - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => 8_000_000, + ConsensusFork::SegWit2x(ref fork) if height >= fork.height => 2_000_000, + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => 8_000_000, ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 1_000_000, } } @@ -195,9 +221,9 @@ impl ConsensusFork { pub fn max_block_sigops(&self, height: u32, block_size: usize) -> usize { match *self { // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => 20_000 * (1 + (block_size - 1) / 1_000_000), - ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + ConsensusFork::SegWit2x(ref fork) if height >= fork.height => 40_000, ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => 20_000, } @@ -207,7 +233,7 @@ impl ConsensusFork { match *self { ConsensusFork::BitcoinCash(_) => self.max_block_sigops(height, block_size) * Self::witness_scale_factor(), - ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + ConsensusFork::SegWit2x(ref fork) if height >= fork.height => 160_000, ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => 80_000, @@ -216,7 +242,7 @@ impl ConsensusFork { pub fn max_block_weight(&self, height: u32) -> usize { match *self { - ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + ConsensusFork::SegWit2x(ref fork) if height >= fork.height => 8_000_000, ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => 4_000_000, @@ -226,6 +252,23 @@ impl ConsensusFork { } } +impl Default for BitcoinCashConsensusParams { + fn default() -> Self { + BitcoinCashConsensusParams { + height: BITCOIN_CASH_FORK_BLOCK, + difficulty_adjustion_time: 1510600000, + } + } +} + +impl Default for SegWit2xConsensusParams { + fn default() -> Self { + SegWit2xConsensusParams { + height: SEGWIT2X_FORK_BLOCK, + } + } +} + #[cfg(test)] mod tests { use super::super::Network; @@ -269,27 +312,31 @@ mod tests { #[test] fn test_consensus_fork_min_block_size() { assert_eq!(ConsensusFork::NoFork.min_block_size(0), 0); - assert_eq!(ConsensusFork::SegWit2x(100).min_block_size(0), 0); - assert_eq!(ConsensusFork::SegWit2x(100).min_block_size(100), 0); - assert_eq!(ConsensusFork::BitcoinCash(100).min_block_size(0), 0); - assert_eq!(ConsensusFork::BitcoinCash(100).min_block_size(100), 1_000_001); + let fork = ConsensusFork::SegWit2x(Default::default()); + assert_eq!(fork.min_block_size(0), 0); + assert_eq!(fork.min_block_size(fork.activation_height()), 0); + let fork = ConsensusFork::BitcoinCash(Default::default()); + assert_eq!(fork.min_block_size(0), 0); + assert_eq!(fork.min_block_size(fork.activation_height()), 1_000_001); } #[test] fn test_consensus_fork_max_transaction_size() { assert_eq!(ConsensusFork::NoFork.max_transaction_size(), 1_000_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_transaction_size(), 1_000_000); - assert_eq!(ConsensusFork::BitcoinCash(100).max_transaction_size(), 1_000_000); + assert_eq!(ConsensusFork::SegWit2x(Default::default()).max_transaction_size(), 1_000_000); + assert_eq!(ConsensusFork::BitcoinCash(Default::default()).max_transaction_size(), 1_000_000); } #[test] fn test_consensus_fork_max_block_sigops() { assert_eq!(ConsensusFork::NoFork.max_block_sigops(0, 1_000_000), 20_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(0, 1_000_000), 20_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(100, 2_000_000), 40_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(200, 3_000_000), 40_000); - assert_eq!(ConsensusFork::BitcoinCash(100).max_block_sigops(0, 1_000_000), 20_000); - assert_eq!(ConsensusFork::BitcoinCash(100).max_block_sigops(100, 2_000_000), 40_000); - assert_eq!(ConsensusFork::BitcoinCash(100).max_block_sigops(200, 3_000_000), 60_000); + let fork = ConsensusFork::SegWit2x(Default::default()); + assert_eq!(fork.max_block_sigops(0, 1_000_000), 20_000); + assert_eq!(fork.max_block_sigops(fork.activation_height(), 2_000_000), 40_000); + assert_eq!(fork.max_block_sigops(fork.activation_height() + 100, 3_000_000), 40_000); + let fork = ConsensusFork::BitcoinCash(Default::default()); + assert_eq!(fork.max_block_sigops(0, 1_000_000), 20_000); + assert_eq!(fork.max_block_sigops(fork.activation_height(), 2_000_000), 40_000); + assert_eq!(fork.max_block_sigops(fork.activation_height() + 100, 3_000_000), 60_000); } } diff --git a/network/src/lib.rs b/network/src/lib.rs index f602837d..55f09a3f 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -1,6 +1,8 @@ +#[macro_use] +extern crate lazy_static; + extern crate chain; extern crate primitives; -extern crate serialization as ser; mod consensus; mod deployments; @@ -8,6 +10,6 @@ mod network; pub use primitives::{hash, compact}; -pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK}; +pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams, SegWit2xConsensusParams}; pub use deployments::Deployment; pub use network::{Magic, Network}; diff --git a/network/src/network.rs b/network/src/network.rs index c2a8d5f3..6175a42a 100644 --- a/network/src/network.rs +++ b/network/src/network.rs @@ -4,6 +4,7 @@ use compact::Compact; use chain::Block; use primitives::hash::H256; +use primitives::bigint::U256; use {ConsensusFork}; const MAGIC_MAINNET: u32 = 0xD9B4BEF9; @@ -15,9 +16,14 @@ const BITCOIN_CASH_MAGIC_MAINNET: u32 = 0xE8F3E1E3; const BITCOIN_CASH_MAGIC_TESTNET: u32 = 0xF4F3E5F4; const BITCOIN_CASH_MAGIC_REGTEST: u32 = 0xFABFB5DA; -const MAX_BITS_MAINNET: u32 = 0x1d00ffff; -const MAX_BITS_TESTNET: u32 = 0x1d00ffff; -const MAX_BITS_REGTEST: u32 = 0x207fffff; +lazy_static! { + static ref MAX_BITS_MAINNET: U256 = "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse() + .expect("hardcoded value should parse without errors"); + static ref MAX_BITS_TESTNET: U256 = "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse() + .expect("hardcoded value should parse without errors"); + static ref MAX_BITS_REGTEST: U256 = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse() + .expect("hardcoded value should parse without errors"); +} /// Network magic type. pub type Magic = u32; @@ -38,11 +44,11 @@ pub enum Network { } impl Network { - pub fn magic(&self, fork: ConsensusFork) -> Magic { + pub fn magic(&self, fork: &ConsensusFork) -> Magic { match (fork, *self) { - (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::BitcoinCash(_), Network::Mainnet) => BITCOIN_CASH_MAGIC_MAINNET, + (&ConsensusFork::BitcoinCash(_), Network::Testnet) => BITCOIN_CASH_MAGIC_TESTNET, + (&ConsensusFork::BitcoinCash(_), Network::Regtest) => BITCOIN_CASH_MAGIC_REGTEST, (_, Network::Mainnet) => MAGIC_MAINNET, (_, Network::Testnet) => MAGIC_TESTNET, (_, Network::Regtest) => MAGIC_REGTEST, @@ -51,12 +57,12 @@ impl Network { } } - pub fn max_bits(&self) -> Compact { + pub fn max_bits(&self) -> U256 { match *self { - Network::Mainnet | Network::Other(_) => MAX_BITS_MAINNET.into(), - Network::Testnet => MAX_BITS_TESTNET.into(), - Network::Regtest => MAX_BITS_REGTEST.into(), - Network::Unitest => Compact::max_value(), + Network::Mainnet | Network::Other(_) => MAX_BITS_MAINNET.clone(), + Network::Testnet => MAX_BITS_TESTNET.clone(), + Network::Regtest => MAX_BITS_REGTEST.clone(), + Network::Unitest => Compact::max_value().into(), } } @@ -104,18 +110,18 @@ mod tests { #[test] fn test_network_magic_number() { - assert_eq!(MAGIC_MAINNET, Network::Mainnet.magic(ConsensusFork::NoFork)); - assert_eq!(MAGIC_TESTNET, Network::Testnet.magic(ConsensusFork::NoFork)); - assert_eq!(MAGIC_REGTEST, Network::Regtest.magic(ConsensusFork::NoFork)); - assert_eq!(MAGIC_UNITEST, Network::Unitest.magic(ConsensusFork::NoFork)); + assert_eq!(MAGIC_MAINNET, Network::Mainnet.magic(&ConsensusFork::NoFork)); + assert_eq!(MAGIC_TESTNET, Network::Testnet.magic(&ConsensusFork::NoFork)); + assert_eq!(MAGIC_REGTEST, Network::Regtest.magic(&ConsensusFork::NoFork)); + assert_eq!(MAGIC_UNITEST, Network::Unitest.magic(&ConsensusFork::NoFork)); } #[test] fn test_network_max_bits() { - assert_eq!(Network::Mainnet.max_bits(), MAX_BITS_MAINNET.into()); - assert_eq!(Network::Testnet.max_bits(), MAX_BITS_TESTNET.into()); - assert_eq!(Network::Regtest.max_bits(), MAX_BITS_REGTEST.into()); - assert_eq!(Network::Unitest.max_bits(), Compact::max_value()); + assert_eq!(Network::Mainnet.max_bits(), *MAX_BITS_MAINNET); + assert_eq!(Network::Testnet.max_bits(), *MAX_BITS_TESTNET); + assert_eq!(Network::Regtest.max_bits(), *MAX_BITS_REGTEST); + assert_eq!(Network::Unitest.max_bits(), Compact::max_value().into()); } #[test] diff --git a/p2p/src/io/handshake.rs b/p2p/src/io/handshake.rs index e488cf73..21a50ab4 100644 --- a/p2p/src/io/handshake.rs +++ b/p2p/src/io/handshake.rs @@ -287,7 +287,7 @@ mod tests { #[test] fn test_handshake() { - let magic = Network::Mainnet.magic(ConsensusFork::NoFork); + let magic = Network::Mainnet.magic(&ConsensusFork::NoFork); let version = 70012; let local_version = local_version(); let remote_version = remote_version(); @@ -317,7 +317,7 @@ mod tests { #[test] fn test_accept_handshake() { - let magic = Network::Mainnet.magic(ConsensusFork::NoFork); + let magic = Network::Mainnet.magic(&ConsensusFork::NoFork); let version = 70012; let local_version = local_version(); let remote_version = remote_version(); @@ -346,7 +346,7 @@ mod tests { #[test] fn test_self_handshake() { - let magic = Network::Mainnet.magic(ConsensusFork::NoFork); + let magic = Network::Mainnet.magic(&ConsensusFork::NoFork); let version = 70012; let remote_version = local_version(); let local_version = local_version(); @@ -367,7 +367,7 @@ mod tests { #[test] fn test_accept_self_handshake() { - let magic = Network::Mainnet.magic(ConsensusFork::NoFork); + let magic = Network::Mainnet.magic(&ConsensusFork::NoFork); let version = 70012; let remote_version = local_version(); let local_version = local_version(); @@ -388,8 +388,8 @@ mod tests { #[test] fn test_fails_to_accept_other_fork_node() { - let magic1 = Network::Mainnet.magic(ConsensusFork::NoFork); - let magic2 = Network::Mainnet.magic(ConsensusFork::BitcoinCash(0)); + let magic1 = Network::Mainnet.magic(&ConsensusFork::NoFork); + let magic2 = Network::Mainnet.magic(&ConsensusFork::BitcoinCash(Default::default())); let version = 70012; let local_version = local_version(); let remote_version = remote_version(); diff --git a/p2p/src/io/read_any_message.rs b/p2p/src/io/read_any_message.rs index 5ca63c45..4170c636 100644 --- a/p2p/src/io/read_any_message.rs +++ b/p2p/src/io/read_any_message.rs @@ -74,20 +74,20 @@ mod tests { let nonce = "5845303b6da97786".into(); let expected = (name, nonce); - assert_eq!(read_any_message(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork)).wait().unwrap(), Ok(expected)); - assert_eq!(read_any_message(raw.as_ref(), Network::Testnet.magic(ConsensusFork::NoFork)).wait().unwrap(), Err(Error::InvalidMagic)); + assert_eq!(read_any_message(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork)).wait().unwrap(), Ok(expected)); + assert_eq!(read_any_message(raw.as_ref(), Network::Testnet.magic(&ConsensusFork::NoFork)).wait().unwrap(), Err(Error::InvalidMagic)); } #[test] fn test_read_too_short_any_message() { let raw: Bytes = "f9beb4d970696e6700000000000000000800000083c00c765845303b6da977".into(); - assert!(read_any_message(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork)).wait().is_err()); + assert!(read_any_message(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork)).wait().is_err()); } #[test] fn test_read_any_message_with_invalid_checksum() { let raw: Bytes = "f9beb4d970696e6700000000000000000800000083c01c765845303b6da97786".into(); - assert_eq!(read_any_message(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork)).wait().unwrap(), Err(Error::InvalidChecksum)); + assert_eq!(read_any_message(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork)).wait().unwrap(), Err(Error::InvalidChecksum)); } } diff --git a/p2p/src/io/read_header.rs b/p2p/src/io/read_header.rs index 4b3d9850..605d05bf 100644 --- a/p2p/src/io/read_header.rs +++ b/p2p/src/io/read_header.rs @@ -40,25 +40,25 @@ mod tests { fn test_read_header() { let raw: Bytes = "f9beb4d96164647200000000000000001f000000ed52399b".into(); let expected = MessageHeader { - magic: Network::Mainnet.magic(ConsensusFork::NoFork), + magic: Network::Mainnet.magic(&ConsensusFork::NoFork), command: "addr".into(), len: 0x1f, checksum: "ed52399b".into(), }; - assert_eq!(read_header(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork)).wait().unwrap().1, Ok(expected)); - assert_eq!(read_header(raw.as_ref(), Network::Testnet.magic(ConsensusFork::NoFork)).wait().unwrap().1, Err(Error::InvalidMagic)); + assert_eq!(read_header(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork)).wait().unwrap().1, Ok(expected)); + assert_eq!(read_header(raw.as_ref(), Network::Testnet.magic(&ConsensusFork::NoFork)).wait().unwrap().1, Err(Error::InvalidMagic)); } #[test] fn test_read_header_with_invalid_magic() { let raw: Bytes = "f9beb4d86164647200000000000000001f000000ed52399b".into(); - assert_eq!(read_header(raw.as_ref(), Network::Testnet.magic(ConsensusFork::NoFork)).wait().unwrap().1, Err(Error::InvalidMagic)); + assert_eq!(read_header(raw.as_ref(), Network::Testnet.magic(&ConsensusFork::NoFork)).wait().unwrap().1, Err(Error::InvalidMagic)); } #[test] fn test_read_too_short_header() { let raw: Bytes = "f9beb4d96164647200000000000000001f000000ed5239".into(); - assert!(read_header(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork)).wait().is_err()); + assert!(read_header(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork)).wait().is_err()); } } diff --git a/p2p/src/io/read_message.rs b/p2p/src/io/read_message.rs index c0c70823..3e7a15d5 100644 --- a/p2p/src/io/read_message.rs +++ b/p2p/src/io/read_message.rs @@ -78,21 +78,21 @@ mod tests { fn test_read_message() { let raw: Bytes = "f9beb4d970696e6700000000000000000800000083c00c765845303b6da97786".into(); let ping = Ping::new(u64::from_str_radix("8677a96d3b304558", 16).unwrap()); - assert_eq!(read_message(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork), 0).wait().unwrap().1, Ok(ping)); - assert_eq!(read_message::(raw.as_ref(), Network::Testnet.magic(ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidMagic)); - assert_eq!(read_message::(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidCommand)); + assert_eq!(read_message(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork), 0).wait().unwrap().1, Ok(ping)); + assert_eq!(read_message::(raw.as_ref(), Network::Testnet.magic(&ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidMagic)); + assert_eq!(read_message::(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidCommand)); } #[test] fn test_read_too_short_message() { let raw: Bytes = "f9beb4d970696e6700000000000000000800000083c00c765845303b6da977".into(); - assert!(read_message::(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork), 0).wait().is_err()); + assert!(read_message::(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork), 0).wait().is_err()); } #[test] fn test_read_message_with_invalid_checksum() { let raw: Bytes = "f9beb4d970696e6700000000000000000800000083c01c765845303b6da97786".into(); - assert_eq!(read_message::(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidChecksum)); + assert_eq!(read_message::(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidChecksum)); } } diff --git a/pbtc/config.rs b/pbtc/config.rs index b8a17075..00e10e64 100644 --- a/pbtc/config.rs +++ b/pbtc/config.rs @@ -2,7 +2,7 @@ use std::net; use clap; use db; use message::Services; -use network::{Network, ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK}; +use network::{Network, ConsensusParams, ConsensusFork}; use p2p::InternetProtocol; use seednodes::{mainnet_seednodes, testnet_seednodes, segwit2x_seednodes, bitcoin_cash_seednodes, bitcoin_cash_testnet_seednodes}; use rpc_apis::ApiSet; @@ -71,7 +71,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { }; // to skip idiotic 30 seconds delay in test-scripts - let user_agent_suffix = match consensus_fork { + let user_agent_suffix = match consensus.fork { ConsensusFork::NoFork => "", ConsensusFork::SegWit2x(_) => "/SegWit2x", ConsensusFork::BitcoinCash(_) => "/UAHF", @@ -98,15 +98,15 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { let mut 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::BitcoinCash(_)) => bitcoin_cash_seednodes().into_iter().map(Into::into).collect(), - (Network::Testnet, ConsensusFork::BitcoinCash(_)) => bitcoin_cash_testnet_seednodes().into_iter().map(Into::into).collect(), + None => match (network, &consensus.fork) { + (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(), (Network::Testnet, _) => testnet_seednodes().into_iter().map(Into::into).collect(), (Network::Other(_), _) | (Network::Regtest, _) | (Network::Unitest, _) => Vec::new(), }, }; - match consensus_fork { + match consensus.fork { ConsensusFork::SegWit2x(_) => seednodes.extend(segwit2x_seednodes().into_iter().map(Into::into)), _ => (), } @@ -124,9 +124,9 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { }; let services = Services::default().with_network(true); - let services = match consensus.fork { - ConsensusFork::BitcoinCash(_) => services.with_bitcoin_cash(true), - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => services.with_witness(true), + let services = match &consensus.fork { + &ConsensusFork::BitcoinCash(_) => services.with_bitcoin_cash(true), + &ConsensusFork::NoFork | &ConsensusFork::SegWit2x(_) => services.with_witness(true), }; let verification_level = match matches.value_of("verification-level") { @@ -194,8 +194,8 @@ fn parse_consensus_fork(db: &db::SharedStore, matches: &clap::ArgMatches) -> Res Ok(match new_consensus_fork { "segwit" => ConsensusFork::NoFork, - "segwit2x" => ConsensusFork::SegWit2x(SEGWIT2X_FORK_BLOCK), - "bitcoin-cash" => ConsensusFork::BitcoinCash(BITCOIN_CASH_FORK_BLOCK), + "segwit2x" => ConsensusFork::SegWit2x(Default::default()), + "bitcoin-cash" => ConsensusFork::BitcoinCash(Default::default()), _ => unreachable!("hardcoded above"), }) } diff --git a/sync/src/synchronization_verifier.rs b/sync/src/synchronization_verifier.rs index 535bf924..fb5be51b 100644 --- a/sync/src/synchronization_verifier.rs +++ b/sync/src/synchronization_verifier.rs @@ -382,7 +382,7 @@ pub mod tests { .build() .merkled_header() .parent(rolling_hash.clone()) - .bits(Network::Unitest.max_bits()) + .bits(Network::Unitest.max_bits().into()) .build() .build(); rolling_hash = next_block.hash(); @@ -401,7 +401,7 @@ pub mod tests { .build() .merkled_header() .parent(last_block_hash) - .bits(Network::Unitest.max_bits()) + .bits(Network::Unitest.max_bits().into()) .build() .build().into(); diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index fbb79b2c..6422be60 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -306,13 +306,13 @@ impl<'a> TransactionEval<'a> { ) -> Self { let verify_p2sh = time >= params.bip16_time; let verify_strictenc = match params.fork { - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => true, + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => true, _ => false, }; let verify_locktime = height >= params.bip65_height; let verify_dersig = height >= params.bip66_height; let signature_version = match params.fork { - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => SignatureVersion::ForkId, + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => SignatureVersion::ForkId, ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => SignatureVersion::Base, }; @@ -429,9 +429,9 @@ impl<'a> TransactionReturnReplayProtection<'a> { } fn check(&self) -> Result<(), TransactionError> { - if let ConsensusFork::BitcoinCash(fork_block) = self.consensus.fork { + if let ConsensusFork::BitcoinCash(ref fork) = self.consensus.fork { // Transactions with such OP_RETURNs shall be considered valid again for block 530,001 and onwards - if self.height >= fork_block && self.height <= 530_000 { + if self.height >= fork.height && self.height <= 530_000 { if (*self.transaction).raw.outputs.iter() .any(|out| out.script_pubkey == *BITCOIN_CASH_RETURN_REPLAY_PROTECTION_SCRIPT) { return Err(TransactionError::ReturnReplayProtection) @@ -492,10 +492,10 @@ mod tests { assert_eq!(transaction.raw.outputs[0].script_pubkey.len(), 46 + 2); - let consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(100)); - let checker = TransactionReturnReplayProtection::new(CanonTransaction::new(&transaction), &consensus, 100); + let consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(Default::default())); + let checker = TransactionReturnReplayProtection::new(CanonTransaction::new(&transaction), &consensus, consensus.fork.activation_height()); assert_eq!(checker.check(), Err(TransactionError::ReturnReplayProtection)); - let checker = TransactionReturnReplayProtection::new(CanonTransaction::new(&transaction), &consensus, 50); + let checker = TransactionReturnReplayProtection::new(CanonTransaction::new(&transaction), &consensus, consensus.fork.activation_height() - 1); assert_eq!(checker.check(), Ok(())); let consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::NoFork); diff --git a/verification/src/verify_header.rs b/verification/src/verify_header.rs index cb997e0a..0b8e5be9 100644 --- a/verification/src/verify_header.rs +++ b/verification/src/verify_header.rs @@ -34,7 +34,7 @@ impl<'a> HeaderProofOfWork<'a> { fn new(header: &'a IndexedBlockHeader, network: Network) -> Self { HeaderProofOfWork { header: header, - max_work_bits: network.max_bits(), + max_work_bits: network.max_bits().into(), } } diff --git a/verification/src/work.rs b/verification/src/work.rs index aedccd40..4a46f886 100644 --- a/verification/src/work.rs +++ b/verification/src/work.rs @@ -1,13 +1,14 @@ use std::cmp; use primitives::compact::Compact; use primitives::hash::H256; -use primitives::bigint::U256; +use primitives::bigint::{Uint, U256}; +use chain::BlockHeader; use network::{Network, ConsensusParams, ConsensusFork}; use db::{BlockHeaderProvider, BlockRef}; use timestamp::median_timestamp_inclusive; use constants::{ - DOUBLE_SPACING_SECONDS, + TARGET_SPACING_SECONDS, DOUBLE_SPACING_SECONDS, TARGET_TIMESPAN_SECONDS, MIN_TIMESPAN, MAX_TIMESPAN, RETARGETING_INTERVAL }; @@ -57,13 +58,26 @@ pub fn retarget_timespan(retarget_timestamp: u32, last_timestamp: u32) -> u32 { /// Returns work required for given header pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHeaderProvider, consensus: &ConsensusParams) -> Compact { - let max_bits = consensus.network.max_bits(); + let max_bits = consensus.network.max_bits().into(); if height == 0 { return max_bits; } let parent_header = store.block_header(parent_hash.clone().into()).expect("self.height != 0; qed"); + // special processing of Bitcoin Cash difficulty adjustment hardfork (Nov 2017), where difficulty is adjusted after each block + let parent_timestamp = match consensus.fork { + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => { + let parent_timestamp = median_timestamp_inclusive(parent_header.hash(), store); + if parent_timestamp >= fork.difficulty_adjustion_time { + return work_required_bitcoin_cash_adjusted(parent_header, time, height, store, consensus); + } + + Some(parent_timestamp) + }, + _ => None, + }; + if is_retarget_height(height) { let retarget_ref = (height - RETARGETING_INTERVAL).into(); let retarget_header = store.block_header(retarget_ref).expect("self.height != 0 && self.height % RETARGETING_INTERVAL == 0; qed"); @@ -84,7 +98,7 @@ pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHea match consensus.fork { _ if parent_header.bits == max_bits => parent_header.bits, - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => { + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => { // REQ-7 Difficulty adjustement in case of hashrate drop // In case the MTP of the tip of the chain is 12h or more after the MTP 6 block before the tip, // the proof of work target is increased by a quarter, or 25%, which corresponds to a difficulty @@ -93,8 +107,9 @@ pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHea let ancient_header = store.block_header(ancient_block_ref) .expect("parent_header.bits != max_bits; difficulty is max_bits for first RETARGETING_INTERVAL height; RETARGETING_INTERVAL > 7; qed"); + let parent_timestamp = parent_timestamp + .expect("we are in BitcoinCash-HF branch; parent_timestamp is calculated for BitcoinCash-HF branch above; qed"); let ancient_timestamp = median_timestamp_inclusive(ancient_header.hash(), store); - let parent_timestamp = median_timestamp_inclusive(parent_header.hash(), store); let timestamp_diff = parent_timestamp.checked_sub(ancient_timestamp).unwrap_or_default(); if timestamp_diff < 43_200 { // less than 12h => no difficulty change needed @@ -122,8 +137,9 @@ pub fn work_required_testnet(parent_hash: H256, time: u32, height: u32, store: & let parent_header = store.block_header(block_ref.clone()).expect("height != 0; qed"); let max_time_gap = parent_header.time + DOUBLE_SPACING_SECONDS; + let max_bits = network.max_bits().into(); if time > max_time_gap { - return network.max_bits(); + return max_bits; } // TODO: optimize it, so it does not make 2016!!! redundant queries each time @@ -137,12 +153,12 @@ pub fn work_required_testnet(parent_hash: H256, time: u32, height: u32, store: & } for (index, bit) in bits.into_iter().enumerate() { - if bit != network.max_bits() || is_retarget_height(height - index as u32 - 1) { + if bit != max_bits || is_retarget_height(height - index as u32 - 1) { return bit; } } - network.max_bits() + max_bits } /// Algorithm used for retargeting work every 2 weeks @@ -160,6 +176,120 @@ pub fn work_required_retarget(max_work_bits: Compact, retarget_timestamp: u32, l } } +/// Algorithm to adjust difficulty after each block. Implementation is based on Bitcoin ABC commit: +/// https://github.com/Bitcoin-ABC/bitcoin-abc/commit/be51cf295c239ff6395a0aa67a3e13906aca9cb2 +pub fn work_required_bitcoin_cash_adjusted(parent_header: BlockHeader, time: u32, height: u32, store: &BlockHeaderProvider, consensus: &ConsensusParams) -> Compact { + /// To reduce the impact of timestamp manipulation, we select the block we are + /// basing our computation on via a median of 3. + fn suitable_block(mut header2: BlockHeader, store: &BlockHeaderProvider) -> BlockHeader { + let reason = "header.height >= RETARGETNG_INTERVAL; RETARGETING_INTERVAL > 2; qed"; + let mut header1 = store.block_header(header2.previous_header_hash.clone().into()).expect(reason); + let mut header0 = store.block_header(header1.previous_header_hash.clone().into()).expect(reason); + + if header0.time > header2.time { + ::std::mem::swap(&mut header0, &mut header2); + } + if header0.time > header1.time { + ::std::mem::swap(&mut header0, &mut header1); + } + if header1.time > header2.time { + ::std::mem::swap(&mut header1, &mut header2); + } + + header1 + } + + /// Get block proof. + fn block_proof(header: &BlockHeader) -> U256 { + let proof: U256 = header.bits.into(); + // We need to compute 2**256 / (bnTarget+1), but we can't represent 2**256 + // as it's too large for a arith_uint256. However, as 2**256 is at least as + // large as bnTarget+1, it is equal to ((2**256 - bnTarget - 1) / + // (bnTarget+1)) + 1, or ~bnTarget / (nTarget+1) + 1. + (!proof / (proof + U256::one())) + U256::one() + } + + /// Compute chain work between two blocks. Last block work is included. First block work is excluded. + fn compute_work_between_blocks(first: H256, last: &BlockHeader, store: &BlockHeaderProvider) -> U256 { + debug_assert!(last.hash() != first); + let mut chain_work: U256 = block_proof(last); + let mut prev_hash = last.previous_header_hash.clone(); + loop { + let header = store.block_header(prev_hash.into()) + .expect("last header is on main chain; first is at height last.height - 144; it is on main chain; qed"); + + chain_work = chain_work + block_proof(&header); + prev_hash = header.previous_header_hash; + if prev_hash == first { + return chain_work; + } + } + } + + /// Compute the a target based on the work done between 2 blocks and the time + /// required to produce that work. + fn compute_target(first_header: BlockHeader, last_header: BlockHeader, store: &BlockHeaderProvider) -> U256 { + // From the total work done and the time it took to produce that much work, + // we can deduce how much work we expect to be produced in the targeted time + // between blocks. + let mut work = compute_work_between_blocks(first_header.hash(), &last_header, store); + work = work * TARGET_SPACING_SECONDS.into(); + + // In order to avoid difficulty cliffs, we bound the amplitude of the + // adjustement we are going to do. + debug_assert!(last_header.time > first_header.time); + let mut actual_timespan = last_header.time - first_header.time; + if actual_timespan > 288 * TARGET_SPACING_SECONDS { + actual_timespan = 288 * TARGET_SPACING_SECONDS; + } else if actual_timespan < 72 * TARGET_SPACING_SECONDS { + actual_timespan = 72 * TARGET_SPACING_SECONDS; + } + + let work = work / actual_timespan.into(); + + // We need to compute T = (2^256 / W) - 1 but 2^256 doesn't fit in 256 bits. + // By expressing 1 as W / W, we get (2^256 - W) / W, and we can compute + // 2^256 - W as the complement of W. + (!work) / work + } + + // This cannot handle the genesis block and early blocks in general. + debug_assert!(height > 0); + + // Special difficulty rule for testnet: + // If the new block's timestamp is more than 2 * 10 minutes then allow + // mining of a min-difficulty block. + let max_bits = consensus.network.max_bits(); + if consensus.network == Network::Testnet { + let max_time_gap = parent_header.time + DOUBLE_SPACING_SECONDS; + if time > max_time_gap { + return max_bits.into(); + } + } + + // Compute the difficulty based on the full adjustement interval. + let last_height = height - 1; + debug_assert!(last_height >= RETARGETING_INTERVAL); + + // Get the last suitable block of the difficulty interval. + let last_header = suitable_block(parent_header, store); + + // Get the first suitable block of the difficulty interval. + let first_height = last_height - 144; + let first_header = store.block_header(first_height.into()) + .expect("last_height >= RETARGETING_INTERVAL; RETARGETING_INTERVAL - 144 > 0; qed"); + let first_header = suitable_block(first_header, store); + + // Compute the target based on time and work done during the interval. + let next_target = compute_target(first_header, last_header, store); + let max_bits = consensus.network.max_bits(); + if next_target > max_bits { + return max_bits.into(); + } + + next_target.into() +} + pub fn block_reward_satoshi(block_height: u32) -> u64 { let mut res = 50 * 100 * 1000 * 1000; for _ in 0..block_height / 210000 { res /= 2 } @@ -171,11 +301,39 @@ mod tests { use std::collections::HashMap; use primitives::bytes::Bytes; use primitives::hash::H256; + use primitives::bigint::U256; use primitives::compact::Compact; - use network::{Network, ConsensusParams, ConsensusFork}; + use network::{Network, ConsensusParams, BitcoinCashConsensusParams, ConsensusFork}; use db::{BlockHeaderProvider, BlockRef}; use chain::BlockHeader; - use super::{work_required, is_valid_proof_of_work_hash, is_valid_proof_of_work, block_reward_satoshi}; + use super::{work_required, is_valid_proof_of_work_hash, is_valid_proof_of_work, + block_reward_satoshi, work_required_bitcoin_cash_adjusted}; + + #[derive(Default)] + struct MemoryBlockHeaderProvider { + pub by_height: Vec, + pub by_hash: HashMap, + } + + impl MemoryBlockHeaderProvider { + pub fn insert(&mut self, header: BlockHeader) { + self.by_hash.insert(header.hash(), self.by_height.len()); + self.by_height.push(header); + } + } + + impl BlockHeaderProvider for MemoryBlockHeaderProvider { + fn block_header_bytes(&self, _block_ref: BlockRef) -> Option { + unimplemented!() + } + + fn block_header(&self, block_ref: BlockRef) -> Option { + match block_ref { + BlockRef::Hash(ref hash) => self.by_hash.get(hash).map(|h| &self.by_height[*h]).cloned(), + BlockRef::Number(height) => self.by_height.get(height as usize).cloned(), + } + } + } fn is_valid_pow(max: Compact, bits: u32, hash: &'static str) -> bool { is_valid_proof_of_work_hash(bits.into(), &H256::from_reversed_str(hash)) && @@ -185,14 +343,14 @@ mod tests { #[test] fn test_is_valid_proof_of_work() { // block 2 - assert!(is_valid_pow(Network::Mainnet.max_bits(), 486604799u32, "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd")); + assert!(is_valid_pow(Network::Mainnet.max_bits().into(), 486604799u32, "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd")); // block 400_000 - assert!(is_valid_pow(Network::Mainnet.max_bits(), 403093919u32, "000000000000000004ec466ce4732fe6f1ed1cddc2ed4b328fff5224276e3f6f")); + assert!(is_valid_pow(Network::Mainnet.max_bits().into(), 403093919u32, "000000000000000004ec466ce4732fe6f1ed1cddc2ed4b328fff5224276e3f6f")); // other random tests - assert!(is_valid_pow(Network::Regtest.max_bits(), 0x181bc330u32, "00000000000000001bc330000000000000000000000000000000000000000000")); - assert!(!is_valid_pow(Network::Regtest.max_bits(), 0x181bc330u32, "00000000000000001bc330000000000000000000000000000000000000000001")); - assert!(!is_valid_pow(Network::Regtest.max_bits(), 0x181bc330u32, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + assert!(is_valid_pow(Network::Regtest.max_bits().into(), 0x181bc330u32, "00000000000000001bc330000000000000000000000000000000000000000000")); + assert!(!is_valid_pow(Network::Regtest.max_bits().into(), 0x181bc330u32, "00000000000000001bc330000000000000000000000000000000000000000001")); + assert!(!is_valid_pow(Network::Regtest.max_bits().into(), 0x181bc330u32, "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); } #[test] @@ -211,34 +369,11 @@ mod tests { // https://github.com/bitcoinclassic/bitcoinclassic/blob/8bf1fb856df44d1b790b0b835e4c1969be736e25/src/test/pow_tests.cpp#L108 #[test] fn bitcoin_cash_req7() { - #[derive(Default)] - struct MemoryBlockHeaderProvider { - pub by_height: Vec, - pub by_hash: HashMap, - } - - impl MemoryBlockHeaderProvider { - pub fn insert(&mut self, header: BlockHeader) { - self.by_hash.insert(header.hash(), self.by_height.len()); - self.by_height.push(header); - } - } - - impl BlockHeaderProvider for MemoryBlockHeaderProvider { - fn block_header_bytes(&self, _block_ref: BlockRef) -> Option { - unimplemented!() - } - - fn block_header(&self, block_ref: BlockRef) -> Option { - match block_ref { - BlockRef::Hash(ref hash) => self.by_hash.get(hash).map(|h| &self.by_height[*h]).cloned(), - BlockRef::Number(height) => self.by_height.get(height as usize).cloned(), - } - } - } - let main_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::NoFork); - let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(1000)); + let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams { + height: 1000, + difficulty_adjustion_time: 0xffffffff, + })); let mut header_provider = MemoryBlockHeaderProvider::default(); header_provider.insert(BlockHeader { version: 0, @@ -282,4 +417,228 @@ mod tests { let uahf_bits: u32 = work_required(header.hash(), 0, 1010, &header_provider, &uahf_consensus).into(); assert_eq!(uahf_bits, 0x1d00ffff_u32); } + + // original test link: + // https://github.com/Bitcoin-ABC/bitcoin-abc/blob/d8eac91f8d16716eed0ad11ccac420122280bb13/src/test/pow_tests.cpp#L193 + #[test] + fn bitcoin_cash_adjusted_difficulty() { + let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams { + height: 1000, + difficulty_adjustion_time: 0xffffffff, + })); + + + let limit_bits = uahf_consensus.network.max_bits(); + let initial_bits = limit_bits >> 4; + let mut header_provider = MemoryBlockHeaderProvider::default(); + + // Genesis block. + header_provider.insert(BlockHeader { + version: 0, + previous_header_hash: 0.into(), + merkle_root_hash: 0.into(), + time: 1269211443, + bits: initial_bits.into(), + nonce: 0, + }); + + // Pile up some blocks every 10 mins to establish some history. + for height in 1..2050 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 600; + header_provider.insert(header); + } + + // Difficulty stays the same as long as we produce a block every 10 mins. + let current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2049.into()).unwrap(), + 0, 2050, &header_provider, &uahf_consensus); + for height in 2050..2060 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 600; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + } + + // Make sure we skip over blocks that are out of wack. To do so, we produce + // a block that is far in the future + let mut header = header_provider.block_header(2059.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2060.into()).unwrap(), + 0, 2061, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + + // .. and then produce a block with the expected timestamp. + let mut header = header_provider.block_header(2060.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 2 * 600 - 6000; + header.bits = current_bits; + header_provider.insert(header); + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2061.into()).unwrap(), + 0, 2062, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + + // The system should continue unaffected by the block with a bogous timestamps. + for height in 2062..2082 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 600; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + } + + // We start emitting blocks slightly faster. The first block has no impact. + let mut header = header_provider.block_header(2081.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 550; + header.bits = current_bits; + header_provider.insert(header); + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2082.into()).unwrap(), + 0, 2083, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + + // Now we should see difficulty increase slowly. + let mut current_bits = current_bits; + for height in 2083..2093 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 550; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work < current_work); + debug_assert!((current_work - calculated_work) < (current_work >> 10)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c0fe7b1.into()); + + // If we dramatically shorten block production, difficulty increases faster. + for height in 2093..2113 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 10; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work < current_work); + debug_assert!((current_work - calculated_work) < (current_work >> 4)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c0db19f.into()); + + // We start to emit blocks significantly slower. The first block has no + // impact. + let mut header = header_provider.block_header(2112.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + let mut current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2113.into()).unwrap(), + 0, 2114, &header_provider, &uahf_consensus); + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c0d9222.into()); + + // If we dramatically slow down block production, difficulty decreases. + for height in 2114..2207 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work < limit_bits); + debug_assert!(calculated_work > current_work); + debug_assert!((calculated_work - current_work) < (current_work >> 3)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c2f13b9.into()); + + // Due to the window of time being bounded, next block's difficulty actually + // gets harder. + let mut header = header_provider.block_header(2206.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + let mut current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2207.into()).unwrap(), + 0, 2208, &header_provider, &uahf_consensus); + debug_assert_eq!(current_bits, 0x1c2ee9bf.into()); + + // And goes down again. It takes a while due to the window being bounded and + // the skewed block causes 2 blocks to get out of the window. + for height in 2208..2400 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work <= limit_bits); + debug_assert!(calculated_work > current_work); + debug_assert!((calculated_work - current_work) < (current_work >> 3)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1d00ffff.into()); + + // Once the difficulty reached the minimum allowed level, it doesn't get any + // easier. + for height in 2400..2405 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), + 0, height + 1, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, limit_bits.into()); + + current_bits = calculated_bits; + } + } } From 15449f69bbf79ed40872770e86a01638669a0c58 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 6 Nov 2017 11:30:47 +0300 Subject: [PATCH 2/6] BitcoinCash: extracted work_reuired to separate file --- verification/src/lib.rs | 1 + verification/src/work.rs | 508 ++--------------------------------- verification/src/work_bch.rs | 487 +++++++++++++++++++++++++++++++++ 3 files changed, 515 insertions(+), 481 deletions(-) create mode 100644 verification/src/work_bch.rs diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 15c9b5ab..c6a074cd 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -75,6 +75,7 @@ mod error; mod sigops; mod timestamp; mod work; +mod work_bch; // pre-verification mod verify_block; diff --git a/verification/src/work.rs b/verification/src/work.rs index 4a46f886..681ef5a6 100644 --- a/verification/src/work.rs +++ b/verification/src/work.rs @@ -1,15 +1,15 @@ use std::cmp; use primitives::compact::Compact; use primitives::hash::H256; -use primitives::bigint::{Uint, U256}; -use chain::BlockHeader; +use primitives::bigint::U256; +use chain::{IndexedBlockHeader, BlockHeader}; use network::{Network, ConsensusParams, ConsensusFork}; use db::{BlockHeaderProvider, BlockRef}; -use timestamp::median_timestamp_inclusive; +use work_bch::work_required_bitcoin_cash; use constants::{ - TARGET_SPACING_SECONDS, DOUBLE_SPACING_SECONDS, - TARGET_TIMESPAN_SECONDS, MIN_TIMESPAN, MAX_TIMESPAN, RETARGETING_INTERVAL + DOUBLE_SPACING_SECONDS, TARGET_TIMESPAN_SECONDS, + MIN_TIMESPAN, MAX_TIMESPAN, RETARGETING_INTERVAL }; pub fn is_retarget_height(height: u32) -> bool { @@ -65,31 +65,17 @@ 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"); - // special processing of Bitcoin Cash difficulty adjustment hardfork (Nov 2017), where difficulty is adjusted after each block - let parent_timestamp = match consensus.fork { - ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => { - let parent_timestamp = median_timestamp_inclusive(parent_header.hash(), store); - if parent_timestamp >= fork.difficulty_adjustion_time { - return work_required_bitcoin_cash_adjusted(parent_header, time, height, store, consensus); - } - - Some(parent_timestamp) - }, - _ => None, - }; + match consensus.fork { + ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => + return work_required_bitcoin_cash(IndexedBlockHeader { + hash: parent_hash, + raw: parent_header + }, time, height, store, consensus, fork, max_bits), + _ => (), + } if is_retarget_height(height) { - let retarget_ref = (height - RETARGETING_INTERVAL).into(); - let retarget_header = store.block_header(retarget_ref).expect("self.height != 0 && self.height % RETARGETING_INTERVAL == 0; qed"); - - // timestamp of block(height - RETARGETING_INTERVAL) - let retarget_timestamp = retarget_header.time; - // timestamp of parent block - let last_timestamp = parent_header.time; - // bits of last block - let last_bits = parent_header.bits; - - return work_required_retarget(max_bits, retarget_timestamp, last_timestamp, last_bits); + return work_required_retarget(parent_header, height, store, max_bits); } if consensus.network == Network::Testnet { @@ -98,33 +84,6 @@ pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHea match consensus.fork { _ if parent_header.bits == max_bits => parent_header.bits, - ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => { - // REQ-7 Difficulty adjustement in case of hashrate drop - // In case the MTP of the tip of the chain is 12h or more after the MTP 6 block before the tip, - // the proof of work target is increased by a quarter, or 25%, which corresponds to a difficulty - // reduction of 20%. - let ancient_block_ref = (height - 6 - 1).into(); - let ancient_header = store.block_header(ancient_block_ref) - .expect("parent_header.bits != max_bits; difficulty is max_bits for first RETARGETING_INTERVAL height; RETARGETING_INTERVAL > 7; qed"); - - let parent_timestamp = parent_timestamp - .expect("we are in BitcoinCash-HF branch; parent_timestamp is calculated for BitcoinCash-HF branch above; qed"); - let ancient_timestamp = median_timestamp_inclusive(ancient_header.hash(), store); - let timestamp_diff = parent_timestamp.checked_sub(ancient_timestamp).unwrap_or_default(); - if timestamp_diff < 43_200 { - // less than 12h => no difficulty change needed - return parent_header.bits; - } - - let mut new_bits: U256 = parent_header.bits.into(); - let max_bits: U256 = max_bits.into(); - new_bits = new_bits + (new_bits >> 2); - if new_bits > max_bits { - new_bits = max_bits - } - - new_bits.into() - }, _ => parent_header.bits, } } @@ -162,7 +121,17 @@ pub fn work_required_testnet(parent_hash: H256, time: u32, height: u32, store: & } /// Algorithm used for retargeting work every 2 weeks -pub fn work_required_retarget(max_work_bits: Compact, retarget_timestamp: u32, last_timestamp: u32, last_bits: Compact) -> Compact { +pub fn work_required_retarget(parent_header: BlockHeader, height: u32, store: &BlockHeaderProvider, max_work_bits: Compact) -> Compact { + let retarget_ref = (height - RETARGETING_INTERVAL).into(); + let retarget_header = store.block_header(retarget_ref).expect("self.height != 0 && self.height % RETARGETING_INTERVAL == 0; qed"); + + // timestamp of block(height - RETARGETING_INTERVAL) + let retarget_timestamp = retarget_header.time; + // timestamp of parent block + let last_timestamp = parent_header.time; + // bits of last block + let last_bits = parent_header.bits; + let mut retarget: U256 = last_bits.into(); let maximum: U256 = max_work_bits.into(); @@ -176,120 +145,6 @@ pub fn work_required_retarget(max_work_bits: Compact, retarget_timestamp: u32, l } } -/// Algorithm to adjust difficulty after each block. Implementation is based on Bitcoin ABC commit: -/// https://github.com/Bitcoin-ABC/bitcoin-abc/commit/be51cf295c239ff6395a0aa67a3e13906aca9cb2 -pub fn work_required_bitcoin_cash_adjusted(parent_header: BlockHeader, time: u32, height: u32, store: &BlockHeaderProvider, consensus: &ConsensusParams) -> Compact { - /// To reduce the impact of timestamp manipulation, we select the block we are - /// basing our computation on via a median of 3. - fn suitable_block(mut header2: BlockHeader, store: &BlockHeaderProvider) -> BlockHeader { - let reason = "header.height >= RETARGETNG_INTERVAL; RETARGETING_INTERVAL > 2; qed"; - let mut header1 = store.block_header(header2.previous_header_hash.clone().into()).expect(reason); - let mut header0 = store.block_header(header1.previous_header_hash.clone().into()).expect(reason); - - if header0.time > header2.time { - ::std::mem::swap(&mut header0, &mut header2); - } - if header0.time > header1.time { - ::std::mem::swap(&mut header0, &mut header1); - } - if header1.time > header2.time { - ::std::mem::swap(&mut header1, &mut header2); - } - - header1 - } - - /// Get block proof. - fn block_proof(header: &BlockHeader) -> U256 { - let proof: U256 = header.bits.into(); - // We need to compute 2**256 / (bnTarget+1), but we can't represent 2**256 - // as it's too large for a arith_uint256. However, as 2**256 is at least as - // large as bnTarget+1, it is equal to ((2**256 - bnTarget - 1) / - // (bnTarget+1)) + 1, or ~bnTarget / (nTarget+1) + 1. - (!proof / (proof + U256::one())) + U256::one() - } - - /// Compute chain work between two blocks. Last block work is included. First block work is excluded. - fn compute_work_between_blocks(first: H256, last: &BlockHeader, store: &BlockHeaderProvider) -> U256 { - debug_assert!(last.hash() != first); - let mut chain_work: U256 = block_proof(last); - let mut prev_hash = last.previous_header_hash.clone(); - loop { - let header = store.block_header(prev_hash.into()) - .expect("last header is on main chain; first is at height last.height - 144; it is on main chain; qed"); - - chain_work = chain_work + block_proof(&header); - prev_hash = header.previous_header_hash; - if prev_hash == first { - return chain_work; - } - } - } - - /// Compute the a target based on the work done between 2 blocks and the time - /// required to produce that work. - fn compute_target(first_header: BlockHeader, last_header: BlockHeader, store: &BlockHeaderProvider) -> U256 { - // From the total work done and the time it took to produce that much work, - // we can deduce how much work we expect to be produced in the targeted time - // between blocks. - let mut work = compute_work_between_blocks(first_header.hash(), &last_header, store); - work = work * TARGET_SPACING_SECONDS.into(); - - // In order to avoid difficulty cliffs, we bound the amplitude of the - // adjustement we are going to do. - debug_assert!(last_header.time > first_header.time); - let mut actual_timespan = last_header.time - first_header.time; - if actual_timespan > 288 * TARGET_SPACING_SECONDS { - actual_timespan = 288 * TARGET_SPACING_SECONDS; - } else if actual_timespan < 72 * TARGET_SPACING_SECONDS { - actual_timespan = 72 * TARGET_SPACING_SECONDS; - } - - let work = work / actual_timespan.into(); - - // We need to compute T = (2^256 / W) - 1 but 2^256 doesn't fit in 256 bits. - // By expressing 1 as W / W, we get (2^256 - W) / W, and we can compute - // 2^256 - W as the complement of W. - (!work) / work - } - - // This cannot handle the genesis block and early blocks in general. - debug_assert!(height > 0); - - // Special difficulty rule for testnet: - // If the new block's timestamp is more than 2 * 10 minutes then allow - // mining of a min-difficulty block. - let max_bits = consensus.network.max_bits(); - if consensus.network == Network::Testnet { - let max_time_gap = parent_header.time + DOUBLE_SPACING_SECONDS; - if time > max_time_gap { - return max_bits.into(); - } - } - - // Compute the difficulty based on the full adjustement interval. - let last_height = height - 1; - debug_assert!(last_height >= RETARGETING_INTERVAL); - - // Get the last suitable block of the difficulty interval. - let last_header = suitable_block(parent_header, store); - - // Get the first suitable block of the difficulty interval. - let first_height = last_height - 144; - let first_header = store.block_header(first_height.into()) - .expect("last_height >= RETARGETING_INTERVAL; RETARGETING_INTERVAL - 144 > 0; qed"); - let first_header = suitable_block(first_header, store); - - // Compute the target based on time and work done during the interval. - let next_target = compute_target(first_header, last_header, store); - let max_bits = consensus.network.max_bits(); - if next_target > max_bits { - return max_bits.into(); - } - - next_target.into() -} - pub fn block_reward_satoshi(block_height: u32) -> u64 { let mut res = 50 * 100 * 1000 * 1000; for _ in 0..block_height / 210000 { res /= 2 } @@ -298,42 +153,10 @@ pub fn block_reward_satoshi(block_height: u32) -> u64 { #[cfg(test)] mod tests { - use std::collections::HashMap; - use primitives::bytes::Bytes; use primitives::hash::H256; - use primitives::bigint::U256; use primitives::compact::Compact; - use network::{Network, ConsensusParams, BitcoinCashConsensusParams, ConsensusFork}; - use db::{BlockHeaderProvider, BlockRef}; - use chain::BlockHeader; - use super::{work_required, is_valid_proof_of_work_hash, is_valid_proof_of_work, - block_reward_satoshi, work_required_bitcoin_cash_adjusted}; - - #[derive(Default)] - struct MemoryBlockHeaderProvider { - pub by_height: Vec, - pub by_hash: HashMap, - } - - impl MemoryBlockHeaderProvider { - pub fn insert(&mut self, header: BlockHeader) { - self.by_hash.insert(header.hash(), self.by_height.len()); - self.by_height.push(header); - } - } - - impl BlockHeaderProvider for MemoryBlockHeaderProvider { - fn block_header_bytes(&self, _block_ref: BlockRef) -> Option { - unimplemented!() - } - - fn block_header(&self, block_ref: BlockRef) -> Option { - match block_ref { - BlockRef::Hash(ref hash) => self.by_hash.get(hash).map(|h| &self.by_height[*h]).cloned(), - BlockRef::Number(height) => self.by_height.get(height as usize).cloned(), - } - } - } + use network::Network; + use super::{is_valid_proof_of_work_hash, is_valid_proof_of_work, block_reward_satoshi}; fn is_valid_pow(max: Compact, bits: u32, hash: &'static str) -> bool { is_valid_proof_of_work_hash(bits.into(), &H256::from_reversed_str(hash)) && @@ -364,281 +187,4 @@ mod tests { assert_eq!(block_reward_satoshi(630000), 625000000); assert_eq!(block_reward_satoshi(630001), 625000000); } - - // original test link: - // https://github.com/bitcoinclassic/bitcoinclassic/blob/8bf1fb856df44d1b790b0b835e4c1969be736e25/src/test/pow_tests.cpp#L108 - #[test] - fn bitcoin_cash_req7() { - let main_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::NoFork); - let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams { - height: 1000, - difficulty_adjustion_time: 0xffffffff, - })); - let mut header_provider = MemoryBlockHeaderProvider::default(); - header_provider.insert(BlockHeader { - version: 0, - previous_header_hash: 0.into(), - merkle_root_hash: 0.into(), - time: 1269211443, - bits: 0x207fffff.into(), - nonce: 0, - }); - - // create x100 pre-HF blocks - for height in 1..1000 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 10 * 60; - header_provider.insert(header); - } - - // create x10 post-HF blocks every 2 hours - // MTP still less than 12h - for height in 1000..1010 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 2 * 60 * 60; - header_provider.insert(header.clone()); - - let main_bits: u32 = work_required(header.hash(), 0, height as u32, &header_provider, &main_consensus).into(); - assert_eq!(main_bits, 0x207fffff_u32); - let uahf_bits: u32 = work_required(header.hash(), 0, height as u32, &header_provider, &uahf_consensus).into(); - assert_eq!(uahf_bits, 0x207fffff_u32); - } - - // MTP becames greater than 12h - let mut header = header_provider.block_header(1009.into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 2 * 60 * 60; - header_provider.insert(header.clone()); - - let main_bits: u32 = work_required(header.hash(), 0, 1010, &header_provider, &main_consensus).into(); - assert_eq!(main_bits, 0x207fffff_u32); - let uahf_bits: u32 = work_required(header.hash(), 0, 1010, &header_provider, &uahf_consensus).into(); - assert_eq!(uahf_bits, 0x1d00ffff_u32); - } - - // original test link: - // https://github.com/Bitcoin-ABC/bitcoin-abc/blob/d8eac91f8d16716eed0ad11ccac420122280bb13/src/test/pow_tests.cpp#L193 - #[test] - fn bitcoin_cash_adjusted_difficulty() { - let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams { - height: 1000, - difficulty_adjustion_time: 0xffffffff, - })); - - - let limit_bits = uahf_consensus.network.max_bits(); - let initial_bits = limit_bits >> 4; - let mut header_provider = MemoryBlockHeaderProvider::default(); - - // Genesis block. - header_provider.insert(BlockHeader { - version: 0, - previous_header_hash: 0.into(), - merkle_root_hash: 0.into(), - time: 1269211443, - bits: initial_bits.into(), - nonce: 0, - }); - - // Pile up some blocks every 10 mins to establish some history. - for height in 1..2050 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 600; - header_provider.insert(header); - } - - // Difficulty stays the same as long as we produce a block every 10 mins. - let current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2049.into()).unwrap(), - 0, 2050, &header_provider, &uahf_consensus); - for height in 2050..2060 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 600; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - debug_assert_eq!(calculated_bits, current_bits); - } - - // Make sure we skip over blocks that are out of wack. To do so, we produce - // a block that is far in the future - let mut header = header_provider.block_header(2059.into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 6000; - header.bits = current_bits; - header_provider.insert(header); - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2060.into()).unwrap(), - 0, 2061, &header_provider, &uahf_consensus); - debug_assert_eq!(calculated_bits, current_bits); - - // .. and then produce a block with the expected timestamp. - let mut header = header_provider.block_header(2060.into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 2 * 600 - 6000; - header.bits = current_bits; - header_provider.insert(header); - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2061.into()).unwrap(), - 0, 2062, &header_provider, &uahf_consensus); - debug_assert_eq!(calculated_bits, current_bits); - - // The system should continue unaffected by the block with a bogous timestamps. - for height in 2062..2082 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 600; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - debug_assert_eq!(calculated_bits, current_bits); - } - - // We start emitting blocks slightly faster. The first block has no impact. - let mut header = header_provider.block_header(2081.into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 550; - header.bits = current_bits; - header_provider.insert(header); - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2082.into()).unwrap(), - 0, 2083, &header_provider, &uahf_consensus); - debug_assert_eq!(calculated_bits, current_bits); - - // Now we should see difficulty increase slowly. - let mut current_bits = current_bits; - for height in 2083..2093 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 550; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - - let current_work: U256 = current_bits.into(); - let calculated_work: U256 = calculated_bits.into(); - debug_assert!(calculated_work < current_work); - debug_assert!((current_work - calculated_work) < (current_work >> 10)); - - current_bits = calculated_bits; - } - - // Check the actual value. - debug_assert_eq!(current_bits, 0x1c0fe7b1.into()); - - // If we dramatically shorten block production, difficulty increases faster. - for height in 2093..2113 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 10; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - - let current_work: U256 = current_bits.into(); - let calculated_work: U256 = calculated_bits.into(); - debug_assert!(calculated_work < current_work); - debug_assert!((current_work - calculated_work) < (current_work >> 4)); - - current_bits = calculated_bits; - } - - // Check the actual value. - debug_assert_eq!(current_bits, 0x1c0db19f.into()); - - // We start to emit blocks significantly slower. The first block has no - // impact. - let mut header = header_provider.block_header(2112.into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 6000; - header.bits = current_bits; - header_provider.insert(header); - let mut current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2113.into()).unwrap(), - 0, 2114, &header_provider, &uahf_consensus); - - // Check the actual value. - debug_assert_eq!(current_bits, 0x1c0d9222.into()); - - // If we dramatically slow down block production, difficulty decreases. - for height in 2114..2207 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 6000; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - - let current_work: U256 = current_bits.into(); - let calculated_work: U256 = calculated_bits.into(); - debug_assert!(calculated_work < limit_bits); - debug_assert!(calculated_work > current_work); - debug_assert!((calculated_work - current_work) < (current_work >> 3)); - - current_bits = calculated_bits; - } - - // Check the actual value. - debug_assert_eq!(current_bits, 0x1c2f13b9.into()); - - // Due to the window of time being bounded, next block's difficulty actually - // gets harder. - let mut header = header_provider.block_header(2206.into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 6000; - header.bits = current_bits; - header_provider.insert(header); - let mut current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2207.into()).unwrap(), - 0, 2208, &header_provider, &uahf_consensus); - debug_assert_eq!(current_bits, 0x1c2ee9bf.into()); - - // And goes down again. It takes a while due to the window being bounded and - // the skewed block causes 2 blocks to get out of the window. - for height in 2208..2400 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 6000; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - - let current_work: U256 = current_bits.into(); - let calculated_work: U256 = calculated_bits.into(); - debug_assert!(calculated_work <= limit_bits); - debug_assert!(calculated_work > current_work); - debug_assert!((calculated_work - current_work) < (current_work >> 3)); - - current_bits = calculated_bits; - } - - // Check the actual value. - debug_assert_eq!(current_bits, 0x1d00ffff.into()); - - // Once the difficulty reached the minimum allowed level, it doesn't get any - // easier. - for height in 2400..2405 { - let mut header = header_provider.block_header((height - 1).into()).unwrap(); - header.previous_header_hash = header.hash(); - header.time = header.time + 6000; - header.bits = current_bits; - header_provider.insert(header); - - let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap(), - 0, height + 1, &header_provider, &uahf_consensus); - debug_assert_eq!(calculated_bits, limit_bits.into()); - - current_bits = calculated_bits; - } - } } diff --git a/verification/src/work_bch.rs b/verification/src/work_bch.rs new file mode 100644 index 00000000..16d991cb --- /dev/null +++ b/verification/src/work_bch.rs @@ -0,0 +1,487 @@ +use primitives::compact::Compact; +use primitives::hash::H256; +use primitives::bigint::{Uint, U256}; +use chain::{IndexedBlockHeader, BlockHeader}; +use network::{Network, ConsensusParams, BitcoinCashConsensusParams}; +use db::BlockHeaderProvider; +use timestamp::median_timestamp_inclusive; +use work::{is_retarget_height, work_required_testnet, work_required_retarget}; + +use constants::{ + DOUBLE_SPACING_SECONDS, TARGET_SPACING_SECONDS, RETARGETING_INTERVAL +}; + +/// Returns work required for given header for the post-HF Bitcoin Cash block +pub fn work_required_bitcoin_cash(parent_header: IndexedBlockHeader, time: u32, height: u32, store: &BlockHeaderProvider, consensus: &ConsensusParams, fork: &BitcoinCashConsensusParams, max_bits: Compact) -> Compact { + // special processing of Bitcoin Cash difficulty adjustment hardfork (Nov 2017), where difficulty is adjusted after each block + let parent_timestamp = median_timestamp_inclusive(parent_header.hash.clone(), store); + if parent_timestamp >= fork.difficulty_adjustion_time { + return work_required_bitcoin_cash_adjusted(parent_header, time, height, store, consensus); + } + + if is_retarget_height(height) { + return work_required_retarget(parent_header.raw, height, store, max_bits); + } + + if consensus.network == Network::Testnet { + return work_required_testnet(parent_header.hash, time, height, store, Network::Testnet) + } + + if parent_header.raw.bits == max_bits { + return parent_header.raw.bits; + } + + // REQ-7 Difficulty adjustement in case of hashrate drop + // In case the MTP of the tip of the chain is 12h or more after the MTP 6 block before the tip, + // the proof of work target is increased by a quarter, or 25%, which corresponds to a difficulty + // reduction of 20%. + let ancient_block_ref = (height - 6 - 1).into(); + let ancient_header = store.block_header(ancient_block_ref) + .expect("parent_header.bits != max_bits; difficulty is max_bits for first RETARGETING_INTERVAL height; RETARGETING_INTERVAL > 7; qed"); + + let ancient_timestamp = median_timestamp_inclusive(ancient_header.hash(), store); + let timestamp_diff = parent_timestamp.checked_sub(ancient_timestamp).unwrap_or_default(); + if timestamp_diff < 43_200 { + // less than 12h => no difficulty change needed + return parent_header.raw.bits; + } + + let mut new_bits: U256 = parent_header.raw.bits.into(); + let max_bits: U256 = max_bits.into(); + new_bits = new_bits + (new_bits >> 2); + if new_bits > max_bits { + new_bits = max_bits + } + + new_bits.into() +} + +/// Algorithm to adjust difficulty after each block. Implementation is based on Bitcoin ABC commit: +/// https://github.com/Bitcoin-ABC/bitcoin-abc/commit/be51cf295c239ff6395a0aa67a3e13906aca9cb2 +fn work_required_bitcoin_cash_adjusted(parent_header: IndexedBlockHeader, time: u32, height: u32, store: &BlockHeaderProvider, consensus: &ConsensusParams) -> Compact { + /// To reduce the impact of timestamp manipulation, we select the block we are + /// basing our computation on via a median of 3. + fn suitable_block(mut header2: BlockHeader, store: &BlockHeaderProvider) -> BlockHeader { + let reason = "header.height >= RETARGETNG_INTERVAL; RETARGETING_INTERVAL > 2; qed"; + let mut header1 = store.block_header(header2.previous_header_hash.clone().into()).expect(reason); + let mut header0 = store.block_header(header1.previous_header_hash.clone().into()).expect(reason); + + if header0.time > header2.time { + ::std::mem::swap(&mut header0, &mut header2); + } + if header0.time > header1.time { + ::std::mem::swap(&mut header0, &mut header1); + } + if header1.time > header2.time { + ::std::mem::swap(&mut header1, &mut header2); + } + + header1 + } + + /// Get block proof. + fn block_proof(header: &BlockHeader) -> U256 { + let proof: U256 = header.bits.into(); + // We need to compute 2**256 / (bnTarget+1), but we can't represent 2**256 + // as it's too large for a arith_uint256. However, as 2**256 is at least as + // large as bnTarget+1, it is equal to ((2**256 - bnTarget - 1) / + // (bnTarget+1)) + 1, or ~bnTarget / (nTarget+1) + 1. + (!proof / (proof + U256::one())) + U256::one() + } + + /// Compute chain work between two blocks. Last block work is included. First block work is excluded. + fn compute_work_between_blocks(first: H256, last: &BlockHeader, store: &BlockHeaderProvider) -> U256 { + debug_assert!(last.hash() != first); + let mut chain_work: U256 = block_proof(last); + let mut prev_hash = last.previous_header_hash.clone(); + loop { + let header = store.block_header(prev_hash.into()) + .expect("last header is on main chain; first is at height last.height - 144; it is on main chain; qed"); + + chain_work = chain_work + block_proof(&header); + prev_hash = header.previous_header_hash; + if prev_hash == first { + return chain_work; + } + } + } + + /// Compute the a target based on the work done between 2 blocks and the time + /// required to produce that work. + fn compute_target(first_header: BlockHeader, last_header: BlockHeader, store: &BlockHeaderProvider) -> U256 { + // From the total work done and the time it took to produce that much work, + // we can deduce how much work we expect to be produced in the targeted time + // between blocks. + let mut work = compute_work_between_blocks(first_header.hash(), &last_header, store); + work = work * TARGET_SPACING_SECONDS.into(); + + // In order to avoid difficulty cliffs, we bound the amplitude of the + // adjustement we are going to do. + debug_assert!(last_header.time > first_header.time); + let mut actual_timespan = last_header.time - first_header.time; + if actual_timespan > 288 * TARGET_SPACING_SECONDS { + actual_timespan = 288 * TARGET_SPACING_SECONDS; + } else if actual_timespan < 72 * TARGET_SPACING_SECONDS { + actual_timespan = 72 * TARGET_SPACING_SECONDS; + } + + let work = work / actual_timespan.into(); + + // We need to compute T = (2^256 / W) - 1 but 2^256 doesn't fit in 256 bits. + // By expressing 1 as W / W, we get (2^256 - W) / W, and we can compute + // 2^256 - W as the complement of W. + (!work) / work + } + + // This cannot handle the genesis block and early blocks in general. + debug_assert!(height > 0); + + // Special difficulty rule for testnet: + // If the new block's timestamp is more than 2 * 10 minutes then allow + // mining of a min-difficulty block. + let max_bits = consensus.network.max_bits(); + if consensus.network == Network::Testnet { + let max_time_gap = parent_header.raw.time + DOUBLE_SPACING_SECONDS; + if time > max_time_gap { + return max_bits.into(); + } + } + + // Compute the difficulty based on the full adjustement interval. + let last_height = height - 1; + debug_assert!(last_height >= RETARGETING_INTERVAL); + + // Get the last suitable block of the difficulty interval. + let last_header = suitable_block(parent_header.raw, store); + + // Get the first suitable block of the difficulty interval. + let first_height = last_height - 144; + let first_header = store.block_header(first_height.into()) + .expect("last_height >= RETARGETING_INTERVAL; RETARGETING_INTERVAL - 144 > 0; qed"); + let first_header = suitable_block(first_header, store); + + // Compute the target based on time and work done during the interval. + let next_target = compute_target(first_header, last_header, store); + let max_bits = consensus.network.max_bits(); + if next_target > max_bits { + return max_bits.into(); + } + + next_target.into() +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use primitives::bytes::Bytes; + use primitives::hash::H256; + use primitives::bigint::U256; + use network::{Network, ConsensusParams, BitcoinCashConsensusParams, ConsensusFork}; + use db::{BlockHeaderProvider, BlockRef}; + use chain::BlockHeader; + use work::work_required; + use super::work_required_bitcoin_cash_adjusted; + + #[derive(Default)] + struct MemoryBlockHeaderProvider { + pub by_height: Vec, + pub by_hash: HashMap, + } + + impl MemoryBlockHeaderProvider { + pub fn insert(&mut self, header: BlockHeader) { + self.by_hash.insert(header.hash(), self.by_height.len()); + self.by_height.push(header); + } + } + + impl BlockHeaderProvider for MemoryBlockHeaderProvider { + fn block_header_bytes(&self, _block_ref: BlockRef) -> Option { + unimplemented!() + } + + fn block_header(&self, block_ref: BlockRef) -> Option { + match block_ref { + BlockRef::Hash(ref hash) => self.by_hash.get(hash).map(|h| &self.by_height[*h]).cloned(), + BlockRef::Number(height) => self.by_height.get(height as usize).cloned(), + } + } + } + + // original test link: + // https://github.com/bitcoinclassic/bitcoinclassic/blob/8bf1fb856df44d1b790b0b835e4c1969be736e25/src/test/pow_tests.cpp#L108 + #[test] + fn bitcoin_cash_req7() { + let main_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::NoFork); + let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams { + height: 1000, + difficulty_adjustion_time: 0xffffffff, + })); + let mut header_provider = MemoryBlockHeaderProvider::default(); + header_provider.insert(BlockHeader { + version: 0, + previous_header_hash: 0.into(), + merkle_root_hash: 0.into(), + time: 1269211443, + bits: 0x207fffff.into(), + nonce: 0, + }); + + // create x100 pre-HF blocks + for height in 1..1000 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 10 * 60; + header_provider.insert(header); + } + + // create x10 post-HF blocks every 2 hours + // MTP still less than 12h + for height in 1000..1010 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 2 * 60 * 60; + header_provider.insert(header.clone()); + + let main_bits: u32 = work_required(header.hash(), 0, height as u32, &header_provider, &main_consensus).into(); + assert_eq!(main_bits, 0x207fffff_u32); + let uahf_bits: u32 = work_required(header.hash(), 0, height as u32, &header_provider, &uahf_consensus).into(); + assert_eq!(uahf_bits, 0x207fffff_u32); + } + + // MTP becames greater than 12h + let mut header = header_provider.block_header(1009.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 2 * 60 * 60; + header_provider.insert(header.clone()); + + let main_bits: u32 = work_required(header.hash(), 0, 1010, &header_provider, &main_consensus).into(); + assert_eq!(main_bits, 0x207fffff_u32); + let uahf_bits: u32 = work_required(header.hash(), 0, 1010, &header_provider, &uahf_consensus).into(); + assert_eq!(uahf_bits, 0x1d00ffff_u32); + } + + // original test link: + // https://github.com/Bitcoin-ABC/bitcoin-abc/blob/d8eac91f8d16716eed0ad11ccac420122280bb13/src/test/pow_tests.cpp#L193 + #[test] + fn bitcoin_cash_adjusted_difficulty() { + let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams { + height: 1000, + difficulty_adjustion_time: 0xffffffff, + })); + + + let limit_bits = uahf_consensus.network.max_bits(); + let initial_bits = limit_bits >> 4; + let mut header_provider = MemoryBlockHeaderProvider::default(); + + // Genesis block. + header_provider.insert(BlockHeader { + version: 0, + previous_header_hash: 0.into(), + merkle_root_hash: 0.into(), + time: 1269211443, + bits: initial_bits.into(), + nonce: 0, + }); + + // Pile up some blocks every 10 mins to establish some history. + for height in 1..2050 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 600; + header_provider.insert(header); + } + + // Difficulty stays the same as long as we produce a block every 10 mins. + let current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2049.into()).unwrap().into(), + 0, 2050, &header_provider, &uahf_consensus); + for height in 2050..2060 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 600; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + } + + // Make sure we skip over blocks that are out of wack. To do so, we produce + // a block that is far in the future + let mut header = header_provider.block_header(2059.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2060.into()).unwrap().into(), + 0, 2061, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + + // .. and then produce a block with the expected timestamp. + let mut header = header_provider.block_header(2060.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 2 * 600 - 6000; + header.bits = current_bits; + header_provider.insert(header); + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2061.into()).unwrap().into(), + 0, 2062, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + + // The system should continue unaffected by the block with a bogous timestamps. + for height in 2062..2082 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 600; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + } + + // We start emitting blocks slightly faster. The first block has no impact. + let mut header = header_provider.block_header(2081.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 550; + header.bits = current_bits; + header_provider.insert(header); + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2082.into()).unwrap().into(), + 0, 2083, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, current_bits); + + // Now we should see difficulty increase slowly. + let mut current_bits = current_bits; + for height in 2083..2093 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 550; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work < current_work); + debug_assert!((current_work - calculated_work) < (current_work >> 10)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c0fe7b1.into()); + + // If we dramatically shorten block production, difficulty increases faster. + for height in 2093..2113 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 10; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work < current_work); + debug_assert!((current_work - calculated_work) < (current_work >> 4)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c0db19f.into()); + + // We start to emit blocks significantly slower. The first block has no + // impact. + let mut header = header_provider.block_header(2112.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + let mut current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2113.into()).unwrap().into(), + 0, 2114, &header_provider, &uahf_consensus); + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c0d9222.into()); + + // If we dramatically slow down block production, difficulty decreases. + for height in 2114..2207 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work < limit_bits); + debug_assert!(calculated_work > current_work); + debug_assert!((calculated_work - current_work) < (current_work >> 3)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1c2f13b9.into()); + + // Due to the window of time being bounded, next block's difficulty actually + // gets harder. + let mut header = header_provider.block_header(2206.into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + let mut current_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(2207.into()).unwrap().into(), + 0, 2208, &header_provider, &uahf_consensus); + debug_assert_eq!(current_bits, 0x1c2ee9bf.into()); + + // And goes down again. It takes a while due to the window being bounded and + // the skewed block causes 2 blocks to get out of the window. + for height in 2208..2400 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + + let current_work: U256 = current_bits.into(); + let calculated_work: U256 = calculated_bits.into(); + debug_assert!(calculated_work <= limit_bits); + debug_assert!(calculated_work > current_work); + debug_assert!((calculated_work - current_work) < (current_work >> 3)); + + current_bits = calculated_bits; + } + + // Check the actual value. + debug_assert_eq!(current_bits, 0x1d00ffff.into()); + + // Once the difficulty reached the minimum allowed level, it doesn't get any + // easier. + for height in 2400..2405 { + let mut header = header_provider.block_header((height - 1).into()).unwrap(); + header.previous_header_hash = header.hash(); + header.time = header.time + 6000; + header.bits = current_bits; + header_provider.insert(header); + + let calculated_bits = work_required_bitcoin_cash_adjusted(header_provider.block_header(height.into()).unwrap().into(), + 0, height + 1, &header_provider, &uahf_consensus); + debug_assert_eq!(calculated_bits, limit_bits.into()); + + current_bits = calculated_bits; + } + } +} \ No newline at end of file From fcde7f3a783d189c033b1ed4ed02c3a91b11df0f Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 6 Nov 2017 12:09:32 +0300 Subject: [PATCH 3/6] removed redundant match --- verification/src/work.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/verification/src/work.rs b/verification/src/work.rs index 681ef5a6..182c7d27 100644 --- a/verification/src/work.rs +++ b/verification/src/work.rs @@ -82,10 +82,7 @@ pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHea return work_required_testnet(parent_hash, time, height, store, Network::Testnet) } - match consensus.fork { - _ if parent_header.bits == max_bits => parent_header.bits, - _ => parent_header.bits, - } + parent_header.bits, } pub fn work_required_testnet(parent_hash: H256, time: u32, height: u32, store: &BlockHeaderProvider, network: Network) -> Compact { From b07258cedce98f8c6d29075cb24f650f4cf52046 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 6 Nov 2017 12:26:55 +0300 Subject: [PATCH 4/6] missed NL --- verification/src/work_bch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verification/src/work_bch.rs b/verification/src/work_bch.rs index 16d991cb..d3e43849 100644 --- a/verification/src/work_bch.rs +++ b/verification/src/work_bch.rs @@ -484,4 +484,4 @@ mod tests { current_bits = calculated_bits; } } -} \ No newline at end of file +} From 2b96829fb85991b4dd667869ad375f2ddaeb8d78 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 6 Nov 2017 12:46:17 +0300 Subject: [PATCH 5/6] fixed typo --- verification/src/work.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verification/src/work.rs b/verification/src/work.rs index 182c7d27..36fd29bb 100644 --- a/verification/src/work.rs +++ b/verification/src/work.rs @@ -82,7 +82,7 @@ pub fn work_required(parent_hash: H256, time: u32, height: u32, store: &BlockHea return work_required_testnet(parent_hash, time, height, store, Network::Testnet) } - parent_header.bits, + parent_header.bits } pub fn work_required_testnet(parent_hash: H256, time: u32, height: u32, store: &BlockHeaderProvider, network: Network) -> Compact { From 6757f546b8080a2aaa82f6e03887c9a4e704db4c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 6 Nov 2017 13:16:47 +0300 Subject: [PATCH 6/6] fixed seednodes ports --- pbtc/seednodes.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pbtc/seednodes.rs b/pbtc/seednodes.rs index d716ac7e..4730ff02 100644 --- a/pbtc/seednodes.rs +++ b/pbtc/seednodes.rs @@ -37,8 +37,8 @@ pub fn bitcoin_cash_seednodes() -> Vec<&'static str> { pub fn bitcoin_cash_testnet_seednodes() -> Vec<&'static str> { vec![ - "testnet-seed-abc.bitcoinforks.org:8333", - "testnet-seed.bitprim.org:8333", + "testnet-seed-abc.bitcoinforks.org:18333", + "testnet-seed.bitprim.org:18333", ] }