tidecoin 0.33.0-beta

General purpose library for using and interoperating with Tidecoin.
// SPDX-License-Identifier: CC0-1.0

//! Tidecoin consensus parameters.
//!
//! This module provides a predefined set of parameters for different Tidecoin
//! chains (mainnet, testnet, regtest).
//!
//! Some field names intentionally keep the node's inherited BIP identifiers
//! (`bip34_height`, `bip65_height`, `bip66_height`, `enforce_bip94`) because
//! those are the names used for Tidecoin consensus activation parameters in
//! the node. In this module they describe active Tidecoin node semantics, not
//! upstream Bitcoin compatibility.
//!
//! # Custom Parameters Example
//!
//! In various places in this crate we take `AsRef<Params>` as a parameter, in order to create a
//! custom type that can be used in such places you might want to do the following:
//!
//! ```
//! use tidecoin::network::Params;
//! use tidecoin::{Network, Target};
//!
//! const POW_TARGET_SPACING: u32 = 120; // Two minutes.
//!
//! pub struct CustomParams {
//!     params: Params,
//! }
//!
//! impl CustomParams {
//!     /// Constructs a new custom params.
//!     pub fn new() -> Self {
//!         let mut params = Params::new(Network::Testnet);
//!         params.pow_target_spacing = POW_TARGET_SPACING;
//!
//!         Self {
//!             params,
//!         }
//!     }
//! }
//!
//! impl AsRef<Params> for CustomParams {
//!     fn as_ref(&self) -> &Params { &self.params }
//! }
//!
//! impl Default for CustomParams {
//!     fn default() -> Self { Self::new() }
//! }
//!
//! # { // Just check the code above is usable.
//! #    let target = Target::MAX_ATTAINABLE_MAINNET;
//! #
//! #    let mainnet = Params::MAINNET;
//! #    let _ = target.difficulty(mainnet);
//! #
//! #    let custom = CustomParams::new();
//! #    let _ = target.difficulty(custom);
//! # }
//! ```

use super::{Network, NetworkExt as _};
#[cfg(doc)]
use crate::pow::CompactTarget;
use crate::pow::Target;
use crate::BlockHeight;

/// Parameters that influence chain consensus.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct Params {
    /// Network for which parameters are valid.
    pub network: Network,
    /// Block height at which coinbase height validation becomes active.
    ///
    /// The `bip34_height` name is retained to match the Tidecoin node consensus
    /// parameter name.
    pub bip34_height: BlockHeight,
    /// Block height at which CHECKLOCKTIMEVERIFY validation becomes active.
    ///
    /// The `bip65_height` name is retained to match the Tidecoin node consensus
    /// parameter name.
    pub bip65_height: BlockHeight,
    /// Block height at which strict signature encoding validation becomes active.
    ///
    /// The `bip66_height` name is retained to match the Tidecoin node consensus
    /// parameter name.
    pub bip66_height: BlockHeight,
    /// Block height at which CSV/relative-lock-time validation becomes active.
    pub csv_height: BlockHeight,
    /// Block height at which Segwit witness commitment validation becomes active.
    pub segwit_height: BlockHeight,
    /// Block height at which auxpow activates, if any.
    pub auxpow_start_height: Option<BlockHeight>,
    /// Auxpow chain ID encoded into header version bits.
    pub auxpow_chain_id: u32,
    /// Whether auxpow chain ID checks are consensus-enforced.
    pub strict_auxpow_chain_id: bool,
    /// Initial block interval for the Tidecoin doubling-interval subsidy schedule.
    pub subsidy_initial_interval: u32,
    /// Enforce Tidecoin block storm mitigation.
    ///
    /// The `enforce_bip94` name is retained to match the Tidecoin node consensus
    /// parameter name.
    pub enforce_bip94: bool,
    /// 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`]).
    ///
    /// Tidecoin node consensus compares compact-encoded targets. This field stores the largest
    /// target value that is exactly representable by the compact target format for the network.
    pub max_attainable_target: Target,
    /// Expected amount of time to mine one block.
    pub pow_target_spacing: u32,
    /// Difficulty recalculation interval.
    pub pow_target_timespan: u32,
    /// Determines whether minimal difficulty may be used for blocks or not.
    pub allow_min_difficulty_blocks: bool,
    /// Height from which the node's post-auxpow min-difficulty exception may be used.
    pub pow_allow_min_difficulty_blocks_after_height: Option<BlockHeight>,
    /// Number of previous blocks averaged by the post-auxpow retarget path.
    pub pow_averaging_window: u32,
    /// Maximum downward target adjustment percentage in the post-auxpow retarget path.
    pub pow_max_adjust_down: u32,
    /// Maximum upward difficulty adjustment percentage in the post-auxpow retarget path.
    pub pow_max_adjust_up: u32,
    /// Determines whether retargeting is disabled for this network or not.
    pub no_pow_retargeting: bool,
}

/// The mainnet parameters.
///
/// Use this for a static reference e.g., `&params::MAINNET`.
///
/// For more on static vs const see The Rust Reference [using-statics-or-consts] section.
///
/// [using-statics-or-consts]: <https://doc.rust-lang.org/reference/items/static-items.html#using-statics-or-consts>
pub static MAINNET: Params = Params::MAINNET;
/// The testnet parameters.
pub static TESTNET: Params = Params::TESTNET;
/// The regtest parameters.
pub static REGTEST: Params = Params::REGTEST;

impl Params {
    /// The mainnet parameters.
    pub const MAINNET: Self = Self {
        network: Network::Tidecoin,
        bip34_height: BlockHeight::from_u32(1),
        bip65_height: BlockHeight::from_u32(1),
        bip66_height: BlockHeight::from_u32(1),
        csv_height: BlockHeight::from_u32(1),
        segwit_height: BlockHeight::from_u32(1),
        auxpow_start_height: None,
        auxpow_chain_id: 8,
        strict_auxpow_chain_id: true,
        subsidy_initial_interval: crate::blockdata::constants::SUBSIDY_INITIAL_INTERVAL,
        enforce_bip94: false,
        max_attainable_target: Target::MAX_ATTAINABLE_MAINNET,
        pow_target_spacing: 60,
        pow_target_timespan: 432_000,
        allow_min_difficulty_blocks: false,
        pow_allow_min_difficulty_blocks_after_height: None,
        pow_averaging_window: 17,
        pow_max_adjust_down: 32,
        pow_max_adjust_up: 16,
        no_pow_retargeting: false,
    };

    /// The testnet parameters.
    pub const TESTNET: Self = Self {
        network: Network::Testnet,
        bip34_height: BlockHeight::from_u32(1),
        bip65_height: BlockHeight::from_u32(1),
        bip66_height: BlockHeight::from_u32(1),
        csv_height: BlockHeight::from_u32(1),
        segwit_height: BlockHeight::from_u32(1),
        auxpow_start_height: Some(BlockHeight::from_u32(1000)),
        auxpow_chain_id: 8,
        strict_auxpow_chain_id: true,
        subsidy_initial_interval: crate::blockdata::constants::SUBSIDY_INITIAL_INTERVAL,
        enforce_bip94: false,
        max_attainable_target: Target::MAX_ATTAINABLE_MAINNET,
        pow_target_spacing: 60,
        pow_target_timespan: 432_000,
        allow_min_difficulty_blocks: true,
        pow_allow_min_difficulty_blocks_after_height: None,
        pow_averaging_window: 17,
        pow_max_adjust_down: 32,
        pow_max_adjust_up: 16,
        no_pow_retargeting: false,
    };

    /// The regtest parameters.
    pub const REGTEST: Self = Self {
        network: Network::Regtest,
        bip34_height: BlockHeight::from_u32(1),
        bip65_height: BlockHeight::from_u32(1),
        bip66_height: BlockHeight::from_u32(1),
        csv_height: BlockHeight::from_u32(1),
        segwit_height: BlockHeight::from_u32(0),
        auxpow_start_height: Some(BlockHeight::from_u32(0)),
        auxpow_chain_id: 8,
        strict_auxpow_chain_id: true,
        subsidy_initial_interval: 20,
        enforce_bip94: true,
        max_attainable_target: Target::MAX_ATTAINABLE_REGTEST,
        pow_target_spacing: 60,
        pow_target_timespan: 14_400,
        allow_min_difficulty_blocks: true,
        pow_allow_min_difficulty_blocks_after_height: None,
        pow_averaging_window: 17,
        pow_max_adjust_down: 0,
        pow_max_adjust_up: 0,
        no_pow_retargeting: true,
    };

    /// Constructs parameters set for the given network.
    pub const fn new(network: Network) -> Self {
        match network {
            Network::Tidecoin => Self::MAINNET,
            Network::Testnet => Self::TESTNET,
            Network::Regtest => Self::REGTEST,
        }
    }

    /// Calculates the number of blocks between difficulty adjustments.
    pub fn difficulty_adjustment_interval(&self) -> u32 {
        self.pow_target_timespan / self.pow_target_spacing
    }
}

impl From<Network> for Params {
    fn from(value: Network) -> Self {
        Self::new(value)
    }
}

impl From<&Network> for Params {
    fn from(value: &Network) -> Self {
        Self::new(*value)
    }
}

impl From<Network> for &'static Params {
    fn from(value: Network) -> Self {
        value.params()
    }
}

impl From<&Network> for &'static Params {
    fn from(value: &Network) -> Self {
        value.params()
    }
}

impl AsRef<Self> for Params {
    fn as_ref(&self) -> &Self {
        self
    }
}

impl AsRef<Params> for Network {
    fn as_ref(&self) -> &Params {
        Self::params(*self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::blockdata::constants::genesis_block;
    use crate::pow::Target;

    fn assert_chainparam_sanity(params: &'static Params) {
        assert_eq!(params.pow_target_timespan % params.pow_target_spacing, 0);
        assert_ne!(params.subsidy_initial_interval, 0);

        let genesis_target = Target::from(genesis_block(params).header().bits);
        assert!(genesis_target <= params.max_attainable_target);
    }

    #[test]
    fn mainnet_chainparams_sanity() {
        assert_chainparam_sanity(&Params::MAINNET);
    }

    #[test]
    fn testnet_chainparams_sanity() {
        assert_chainparam_sanity(&Params::TESTNET);
    }

    #[test]
    fn regtest_chainparams_sanity() {
        assert_chainparam_sanity(&Params::REGTEST);
    }
}