Merge pull request #467 from paritytech/bitcoincash_november

Suport Bitcoin Cash difficulty adjustment hardfork
This commit is contained in:
Svyatoslav Nikolsky 2017-11-12 10:05:56 +03:00 committed by GitHub
commit aa5a14369d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 707 additions and 261 deletions

2
Cargo.lock generated
View File

@ -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]]

View File

@ -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());
}
}

View File

@ -4,6 +4,6 @@ version = "0.1.0"
authors = ["debris <marek.kotewicz@gmail.com>"]
[dependencies]
serialization = { path = "../serialization" }
lazy_static = "0.2"
chain = { path = "../chain" }
primitives = { path = "../primitives" }

View File

@ -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<Deployment>,
}
#[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,16 @@ 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) | ConsensusFork::SegWit2x(fork_height) if height == fork_height => 1_000_001,
ConsensusFork::BitcoinCash(ref fork) if height == fork.height => 1_000_001,
ConsensusFork::SegWit2x(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 +222,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 +234,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 +243,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 +253,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 +313,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), 1_000_001);
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()), 1_000_001);
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);
}
}

View File

@ -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};

View File

@ -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]

View File

@ -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();

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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::<Ping, _>(raw.as_ref(), Network::Testnet.magic(ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidMagic));
assert_eq!(read_message::<Pong, _>(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::<Ping, _>(raw.as_ref(), Network::Testnet.magic(&ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidMagic));
assert_eq!(read_message::<Pong, _>(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::<Ping, _>(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork), 0).wait().is_err());
assert!(read_message::<Ping, _>(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::<Ping, _>(raw.as_ref(), Network::Mainnet.magic(ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidChecksum));
assert_eq!(read_message::<Ping, _>(raw.as_ref(), Network::Mainnet.magic(&ConsensusFork::NoFork), 0).wait().unwrap().1, Err(Error::InvalidChecksum));
}
}

View File

@ -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<Config, String> {
};
// 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<Config, String> {
let mut seednodes: Vec<String> = 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<Config, String> {
};
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"),
})
}

View File

@ -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",
]
}

View File

@ -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();

View File

@ -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);

View File

@ -75,6 +75,7 @@ mod error;
mod sigops;
mod timestamp;
mod work;
mod work_bch;
// pre-verification
mod verify_block;

View File

@ -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(),
}
}

View File

@ -2,13 +2,14 @@ use std::cmp;
use primitives::compact::Compact;
use primitives::hash::H256;
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::{
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 {
@ -57,61 +58,31 @@ 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");
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 {
return work_required_testnet(parent_hash, time, height, store, Network::Testnet)
}
match consensus.fork {
_ if parent_header.bits == max_bits => parent_header.bits,
ConsensusFork::BitcoinCash(fork_height) 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 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
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,
}
parent_header.bits
}
pub fn work_required_testnet(parent_hash: H256, time: u32, height: u32, store: &BlockHeaderProvider, network: Network) -> Compact {
@ -122,8 +93,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,16 +109,26 @@ 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
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();
@ -168,14 +150,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::compact::Compact;
use network::{Network, ConsensusParams, 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 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)) &&
@ -185,14 +163,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]
@ -206,80 +184,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() {
#[derive(Default)]
struct MemoryBlockHeaderProvider {
pub by_height: Vec<BlockHeader>,
pub by_hash: HashMap<H256, usize>,
}
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<Bytes> {
unimplemented!()
}
fn block_header(&self, block_ref: BlockRef) -> Option<BlockHeader> {
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 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);
}
}

View File

@ -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<BlockHeader>,
pub by_hash: HashMap<H256, usize>,
}
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<Bytes> {
unimplemented!()
}
fn block_header(&self, block_ref: BlockRef) -> Option<BlockHeader> {
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;
}
}
}