Skip to content

Commit

Permalink
Merge #2337: Add check to max difficulty transition threshold
Browse files Browse the repository at this point in the history
fd6fedc Improve API for max target threshold calculation (Tobin C. Harding)
6e47d57 Rename difficulty transition threshold functions (Tobin C. Harding)
4121c9a Rename Params::pow_limit to max_attainable_target (Tobin C. Harding)
f0f6d3f Take Params instead of Network in difficulty function (Tobin C. Harding)
104dee9 Debug assert that target != zero in difficulty calc (Tobin C. Harding)
c1ba496 Document current behaviour of difficulty_float (Tobin C. Harding)
3d01146 Allow needless-borrows-for-generic-args (Tobin C. Harding)
2a6821b Use link to CompactTarget in rustdoc (Tobin C. Harding)

Pull request description:

  When computing the maximum difficulty transition threshold we forgot to check that the returned `Target` is not bigger than the maximum. This value is network specific so keep the original logic but with `_unchecked` on the function name.

  This was noted in the discussion on #2161

ACKs for top commit:
  apoelstra:
    ACK fd6fedc
  sanket1729:
    ACK fd6fedc

Tree-SHA512: 520ee2a07edb251c84b5ce8b48ed6e5a5c1945126dc7bcdb5570e97101ec4a3dc63fa7992725194869e22b21ee4f5955579d5e2499fcb48167637fd1fb3ae74d
  • Loading branch information
apoelstra committed Apr 2, 2024
2 parents 6a2fd96 + fd6fedc commit 499f36f
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 20 deletions.
17 changes: 10 additions & 7 deletions bitcoin/src/blockdata/block.rs
Expand Up @@ -16,11 +16,11 @@ use io::{BufRead, Write};
use super::Weight;
use crate::blockdata::script;
use crate::blockdata::transaction::{Transaction, Txid, Wtxid};
use crate::consensus::{encode, Decodable, Encodable};
use crate::consensus::{encode, Decodable, Encodable, Params};
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
use crate::pow::{CompactTarget, Target, Work};
use crate::prelude::*;
use crate::{merkle_tree, Network, VarInt};
use crate::{merkle_tree, VarInt};

hashes::hash_newtype! {
/// A bitcoin block hash.
Expand Down Expand Up @@ -93,7 +93,9 @@ impl Header {
///
/// Difficulty represents how difficult the current target makes it to find a block, relative to
/// how difficult it would be at the highest possible target (highest target == lowest difficulty).
pub fn difficulty(&self, network: Network) -> u128 { self.target().difficulty(network) }
pub fn difficulty(&self, params: impl AsRef<Params>) -> u128 {
self.target().difficulty(params)
}

/// Computes the popular "difficulty" measure for mining and returns a float value of f64.
pub fn difficulty_float(&self) -> f64 { self.target().difficulty_float() }
Expand Down Expand Up @@ -488,6 +490,7 @@ mod tests {

use super::*;
use crate::consensus::encode::{deserialize, serialize};
use crate::Network;

#[test]
fn test_coinbase_and_bip34() {
Expand All @@ -510,7 +513,7 @@ mod tests {

#[test]
fn block_test() {
let network = Network::Bitcoin;
let params = Params::new(Network::Bitcoin);
// Mainnet block 00000000b0c5a240b2a61d2e75692224efd4cbecdf6eaf4cc2cf477ca7c270e7
let some_block = hex!("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d026e04ffffffff0100f2052a0100000043410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac00000000010000000321f75f3139a013f50f315b23b0c9a2b6eac31e2bec98e5891c924664889942260000000049483045022100cb2c6b346a978ab8c61b18b5e9397755cbd17d6eb2fe0083ef32e067fa6c785a02206ce44e613f31d9a6b0517e46f3db1576e9812cc98d159bfdaf759a5014081b5c01ffffffff79cda0945903627c3da1f85fc95d0b8ee3e76ae0cfdc9a65d09744b1f8fc85430000000049483045022047957cdd957cfd0becd642f6b84d82f49b6cb4c51a91f49246908af7c3cfdf4a022100e96b46621f1bffcf5ea5982f88cef651e9354f5791602369bf5a82a6cd61a62501fffffffffe09f5fe3ffbf5ee97a54eb5e5069e9da6b4856ee86fc52938c2f979b0f38e82000000004847304402204165be9a4cbab8049e1af9723b96199bfd3e85f44c6b4c0177e3962686b26073022028f638da23fc003760861ad481ead4099312c60030d4cb57820ce4d33812a5ce01ffffffff01009d966b01000000434104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac00000000");
let cutoff_block = hex!("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0804ffff001d026e04ffffffff0100f2052a0100000043410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac00000000010000000321f75f3139a013f50f315b23b0c9a2b6eac31e2bec98e5891c924664889942260000000049483045022100cb2c6b346a978ab8c61b18b5e9397755cbd17d6eb2fe0083ef32e067fa6c785a02206ce44e613f31d9a6b0517e46f3db1576e9812cc98d159bfdaf759a5014081b5c01ffffffff79cda0945903627c3da1f85fc95d0b8ee3e76ae0cfdc9a65d09744b1f8fc85430000000049483045022047957cdd957cfd0becd642f6b84d82f49b6cb4c51a91f49246908af7c3cfdf4a022100e96b46621f1bffcf5ea5982f88cef651e9354f5791602369bf5a82a6cd61a62501fffffffffe09f5fe3ffbf5ee97a54eb5e5069e9da6b4856ee86fc52938c2f979b0f38e82000000004847304402204165be9a4cbab8049e1af9723b96199bfd3e85f44c6b4c0177e3962686b26073022028f638da23fc003760861ad481ead4099312c60030d4cb57820ce4d33812a5ce01ffffffff01009d966b01000000434104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac");
Expand All @@ -537,7 +540,7 @@ mod tests {
real_decode.header.validate_pow(real_decode.header.target()).unwrap(),
real_decode.block_hash()
);
assert_eq!(real_decode.header.difficulty(network), 1);
assert_eq!(real_decode.header.difficulty(&params), 1);
assert_eq!(real_decode.header.difficulty_float(), 1.0);

assert_eq!(real_decode.total_size(), some_block.len());
Expand All @@ -556,7 +559,7 @@ mod tests {
// Check testnet block 000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b
#[test]
fn segwit_block_test() {
let network = Network::Testnet;
let params = Params::new(Network::Testnet);
let segwit_block = include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw").to_vec();

let decode: Result<Block, _> = deserialize(&segwit_block);
Expand All @@ -579,7 +582,7 @@ mod tests {
real_decode.header.validate_pow(real_decode.header.target()).unwrap(),
real_decode.block_hash()
);
assert_eq!(real_decode.header.difficulty(network), 2456598);
assert_eq!(real_decode.header.difficulty(&params), 2456598);
assert_eq!(real_decode.header.difficulty_float(), 2456598.4399242126);

assert_eq!(real_decode.total_size(), segwit_block.len());
Expand Down
18 changes: 17 additions & 1 deletion bitcoin/src/consensus/params.rs
Expand Up @@ -7,6 +7,8 @@
//!

use crate::network::Network;
#[cfg(doc)]
use crate::pow::CompactTarget;
use crate::pow::Target;

/// Parameters that influence chain consensus.
Expand All @@ -30,14 +32,20 @@ pub struct Params {
/// Number of blocks with the same set of rules.
pub miner_confirmation_window: u32,
/// Proof of work limit value. It contains the lowest possible difficulty.
#[deprecated(since = "TBD", note = "field renamed to max_attainable_target")]
pub pow_limit: Target,
/// The maximum **attainable** target value for these params.
///
/// Not all target values are attainable because consensus code uses the compact format to
/// represent targets (see [`CompactTarget`]).
///
/// Note that this value differs from Bitcoin Core's powLimit field in that this value is
/// attainable, but Bitcoin Core's is not. Specifically, because targets in Bitcoin are always
/// rounded to the nearest float expressible in "compact form", not all targets are attainable.
/// Still, this should not affect consensus as the only place where the non-compact form of
/// this is used in Bitcoin Core's consensus algorithm is in comparison and there are no
/// compact-expressible values between Bitcoin Core's and the limit expressed here.
pub pow_limit: Target,
pub max_attainable_target: Target,
/// Expected amount of time to mine one block.
pub pow_target_spacing: u64,
/// Difficulty recalculation interval.
Expand All @@ -62,6 +70,7 @@ impl Params {
rule_change_activation_threshold: 1916, // 95%
miner_confirmation_window: 2016,
pow_limit: Target::MAX_ATTAINABLE_MAINNET,
max_attainable_target: Target::MAX_ATTAINABLE_MAINNET,
pow_target_spacing: 10 * 60, // 10 minutes.
pow_target_timespan: 14 * 24 * 60 * 60, // 2 weeks.
allow_min_difficulty_blocks: false,
Expand All @@ -78,6 +87,7 @@ impl Params {
rule_change_activation_threshold: 1512, // 75%
miner_confirmation_window: 2016,
pow_limit: Target::MAX_ATTAINABLE_TESTNET,
max_attainable_target: Target::MAX_ATTAINABLE_TESTNET,
pow_target_spacing: 10 * 60, // 10 minutes.
pow_target_timespan: 14 * 24 * 60 * 60, // 2 weeks.
allow_min_difficulty_blocks: true,
Expand All @@ -94,6 +104,7 @@ impl Params {
rule_change_activation_threshold: 1916, // 95%
miner_confirmation_window: 2016,
pow_limit: Target::MAX_ATTAINABLE_SIGNET,
max_attainable_target: Target::MAX_ATTAINABLE_SIGNET,
pow_target_spacing: 10 * 60, // 10 minutes.
pow_target_timespan: 14 * 24 * 60 * 60, // 2 weeks.
allow_min_difficulty_blocks: false,
Expand All @@ -110,6 +121,7 @@ impl Params {
rule_change_activation_threshold: 108, // 75%
miner_confirmation_window: 144,
pow_limit: Target::MAX_ATTAINABLE_REGTEST,
max_attainable_target: Target::MAX_ATTAINABLE_REGTEST,
pow_target_spacing: 10 * 60, // 10 minutes.
pow_target_timespan: 14 * 24 * 60 * 60, // 2 weeks.
allow_min_difficulty_blocks: true,
Expand Down Expand Up @@ -147,3 +159,7 @@ impl From<Network> for &'static Params {
impl From<&Network> for &'static Params {
fn from(value: &Network) -> Self { value.params() }
}

impl AsRef<Params> for Params {
fn as_ref(&self) -> &Params { self }
}
1 change: 1 addition & 0 deletions bitcoin/src/lib.rs
Expand Up @@ -40,6 +40,7 @@
// Exclude lints we don't think are valuable.
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::needless_borrows_for_generic_args)] // https://github.com/rust-lang/rust-clippy/issues/12454

// Disable 16-bit support at least for now as we can't guarantee it yet.
#[cfg(target_pointer_width = "16")]
Expand Down
65 changes: 53 additions & 12 deletions bitcoin/src/pow.rs
Expand Up @@ -6,6 +6,7 @@
//! functions here are designed to be fast, by that we mean it is safe to use them to check headers.
//!

use core::cmp;
use core::fmt::{self, LowerHex, UpperHex};
use core::ops::{Add, Div, Mul, Not, Rem, Shl, Shr, Sub};

Expand All @@ -16,10 +17,8 @@ use units::parse;

use crate::blockdata::block::BlockHash;
use crate::consensus::encode::{self, Decodable, Encodable};
#[cfg(doc)]
use crate::consensus::Params;
use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError};
use crate::Network;

/// Implement traits and methods shared by `Target` and `Work`.
macro_rules! do_impl {
Expand Down Expand Up @@ -126,7 +125,7 @@ impl Target {
/// The maximum **attainable** target value on mainnet.
///
/// Not all target values are attainable because consensus code uses the compact format to
/// represent targets (see `CompactTarget`).
/// represent targets (see [`CompactTarget`]).
pub const MAX_ATTAINABLE_MAINNET: Self = Target(U256(0xFFFF_u128 << (208 - 128), 0));

/// The proof of work limit on testnet.
Expand Down Expand Up @@ -227,16 +226,18 @@ impl Target {
/// integer but `difficulty()` returns only 128 bits this means for targets below approximately
/// `0xffff_ffff_ffff_ffff_ffff_ffff` `difficulty()` will saturate at `u128::MAX`.
///
/// # Panics
///
/// Panics if `self` is zero (divide by zero).
///
/// [max]: Target::max
/// [target]: crate::blockdata::block::Header::target
#[cfg_attr(all(test, mutate), mutate)]
pub fn difficulty(&self, network: Network) -> u128 {
let max = match network {
Network::Bitcoin => Target::MAX_ATTAINABLE_MAINNET,
Network::Testnet => Target::MAX_ATTAINABLE_TESTNET,
Network::Signet => Target::MAX_ATTAINABLE_SIGNET,
Network::Regtest => Target::MAX_ATTAINABLE_REGTEST,
};
pub fn difficulty(&self, params: impl AsRef<Params>) -> u128 {
// Panic here may be eaiser to debug than during the actual division.
assert_ne!(self.0, U256::ZERO, "divide by zero");

let max = params.as_ref().max_attainable_target;
let d = max.0 / self.0;
d.saturating_to_u128()
}
Expand All @@ -245,23 +246,63 @@ impl Target {
///
/// See [`difficulty`] for details.
///
/// # Returns
///
/// Returns [`f64::INFINITY`] if `self` is zero (caused by divide by zero).
///
/// [`difficulty`]: Target::difficulty
#[cfg_attr(all(test, mutate), mutate)]
pub fn difficulty_float(&self) -> f64 { TARGET_MAX_F64 / self.0.to_f64() }

/// Computes the minimum valid [`Target`] threshold allowed for a block in which a difficulty
/// adjustment occurs.
#[deprecated(since = "TBD", note = "use min_transition_threshold instead")]
pub fn min_difficulty_transition_threshold(&self) -> Self { self.min_transition_threshold() }

/// Computes the maximum valid [`Target`] threshold allowed for a block in which a difficulty
/// adjustment occurs.
#[deprecated(since = "TBD", note = "use max_transition_threshold instead")]
pub fn max_difficulty_transition_threshold(&self) -> Self {
self.max_transition_threshold_unchecked()
}

/// Computes the minimum valid [`Target`] threshold allowed for a block in which a difficulty
/// adjustment occurs.
///
/// The difficulty can only decrease or increase by a factor of 4 max on each difficulty
/// adjustment period.
pub fn min_difficulty_transition_threshold(&self) -> Self { Self(self.0 >> 2) }
///
/// # Returns
///
/// In line with Bitcoin Core this function may return a target value of zero.
pub fn min_transition_threshold(&self) -> Self { Self(self.0 >> 2) }

/// Computes the maximum valid [`Target`] threshold allowed for a block in which a difficulty
/// adjustment occurs.
///
/// The difficulty can only decrease or increase by a factor of 4 max on each difficulty
/// adjustment period.
pub fn max_difficulty_transition_threshold(&self) -> Self { Self(self.0 << 2) }
///
/// We also check that the calculated target is not greater than the maximum allowed target,
/// this value is network specific - hence the `params` parameter.
pub fn max_transition_threshold(&self, params: impl AsRef<Params>) -> Self {
let max_attainable = params.as_ref().max_attainable_target;
cmp::min(self.max_transition_threshold_unchecked(), max_attainable)
}

/// Computes the maximum valid [`Target`] threshold allowed for a block in which a difficulty
/// adjustment occurs.
///
/// The difficulty can only decrease or increase by a factor of 4 max on each difficulty
/// adjustment period.
///
/// # Returns
///
/// This function may return a value greater than the maximum allowed target for this network.
///
/// The return value should be checked against [`Params::max_attainable_target`] or use one of
/// the `Target::MAX_ATTAINABLE_FOO` constants.
pub fn max_transition_threshold_unchecked(&self) -> Self { Self(self.0 << 2) }
}
do_impl!(Target);

Expand Down
1 change: 1 addition & 0 deletions hashes/src/lib.rs
Expand Up @@ -76,6 +76,7 @@
// Exclude lints we don't think are valuable.
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::needless_borrows_for_generic_args)] // https://github.com/rust-lang/rust-clippy/issues/12454

#[cfg(all(feature = "alloc", not(feature = "std")))]
extern crate alloc;
Expand Down
1 change: 1 addition & 0 deletions internals/src/lib.rs
Expand Up @@ -14,6 +14,7 @@
// Exclude lints we don't think are valuable.
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::needless_borrows_for_generic_args)] // https://github.com/rust-lang/rust-clippy/issues/12454

#[cfg(feature = "alloc")]
extern crate alloc;
Expand Down
1 change: 1 addition & 0 deletions io/src/lib.rs
Expand Up @@ -16,6 +16,7 @@
// Exclude lints we don't think are valuable.
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::needless_borrows_for_generic_args)] // https://github.com/rust-lang/rust-clippy/issues/12454

#[cfg(feature = "alloc")]
extern crate alloc;
Expand Down
1 change: 1 addition & 0 deletions units/src/lib.rs
Expand Up @@ -11,6 +11,7 @@
// Exclude lints we don't think are valuable.
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::needless_borrows_for_generic_args)] // https://github.com/rust-lang/rust-clippy/issues/12454
#![no_std]

// Disable 16-bit support at least for now as we can't guarantee it yet.
Expand Down

0 comments on commit 499f36f

Please sign in to comment.