Merge rust-bitcoin/rust-bitcoin#2337: Add check to max difficulty transition threshold

fd6fedc3ad Improve API for max target threshold calculation (Tobin C. Harding)
6e47d57744 Rename difficulty transition threshold functions (Tobin C. Harding)
4121c9a09f Rename Params::pow_limit to max_attainable_target (Tobin C. Harding)
f0f6d3f162 Take Params instead of Network in difficulty function (Tobin C. Harding)
104dee9376 Debug assert that target != zero in difficulty calc (Tobin C. Harding)
c1ba496a07 Document current behaviour of difficulty_float (Tobin C. Harding)
3d01146374 Allow needless-borrows-for-generic-args (Tobin C. Harding)
2a6821b426 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 fd6fedc3ad
  sanket1729:
    ACK fd6fedc3ad

Tree-SHA512: 520ee2a07edb251c84b5ce8b48ed6e5a5c1945126dc7bcdb5570e97101ec4a3dc63fa7992725194869e22b21ee4f5955579d5e2499fcb48167637fd1fb3ae74d
This commit is contained in:
Andrew Poelstra 2024-04-02 12:54:16 +00:00
commit 499f36f972
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
8 changed files with 85 additions and 20 deletions

View File

@ -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.
@ -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() }
@ -488,6 +490,7 @@ mod tests {
use super::*;
use crate::consensus::encode::{deserialize, serialize};
use crate::Network;
#[test]
fn test_coinbase_and_bip34() {
@ -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");
@ -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());
@ -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);
@ -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());

View File

@ -7,6 +7,8 @@
//!
use crate::network::Network;
#[cfg(doc)]
use crate::pow::CompactTarget;
use crate::pow::Target;
/// Parameters that influence chain consensus.
@ -30,6 +32,12 @@ 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
@ -37,7 +45,7 @@ pub struct Params {
/// 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.
@ -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,
@ -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,
@ -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,
@ -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,
@ -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 }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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