use neptune_primitives::block_height::BlockHeight;
use neptune_primitives::block_height::NUM_BLOCKS_SKIPPED_BECAUSE_REBOOT;
use neptune_primitives::network::Network;
use neptune_primitives::timestamp::Timestamp;
use num_traits::Zero;
use tasm_lib::twenty_first::math::b_field_element::BFieldElement;
use crate::block::pow::LustrationStatus;
use crate::block::INITIAL_BLOCK_SUBSIDY;
use crate::block::MAX_NUM_INPUTS_OUTPUTS_ANNOUNCEMENTS;
use crate::block::PREMINE_MAX_SIZE;
use crate::type_scripts::native_currency_amount::NativeCurrencyAmount;
pub const BLOCK_HEIGHT_HARDFORK_ALPHA_MAIN_NET: BlockHeight =
BlockHeight::new(BFieldElement::new(15_000u64));
pub const BLOCK_HEIGHT_HARDFORK_ALPHA_TESTNET: BlockHeight =
BlockHeight::new(BFieldElement::new(120u64));
pub const BLOCK_HEIGHT_HARDFORK_TVMV_PROOF_V1_TESTNET: BlockHeight =
BlockHeight::new(BFieldElement::new(3571u64));
pub const BLOCK_HEIGHT_HARDFORK_TVMV_PROOF_V1_MAIN_NET: BlockHeight =
BlockHeight::new(BFieldElement::new(23_401u64));
pub const BLOCK_HEIGHT_HARDFORK_BETA_MAIN_NET: BlockHeight =
BlockHeight::new(BFieldElement::new(38_000u64));
pub const BLOCK_HEIGHT_HARDFORK_BETA_TESTNET: BlockHeight =
BlockHeight::new(BFieldElement::new(3_669));
pub const BLOCK_HEIGHT_HARDFORK_GAMMA_MAIN_NET: BlockHeight =
BlockHeight::new(BFieldElement::new(40_300u64));
pub const BLOCK_HEIGHT_HARDFORK_GAMMA_TESTNET: BlockHeight =
BlockHeight::new(BFieldElement::new(4_650));
pub const TX_BACKDATING_LIMIT: Timestamp = Timestamp::days(3);
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter, Default, strum::Display)]
pub enum ConsensusRuleSet {
Reboot,
HardforkAlpha,
TvmProofVersion1,
HardforkBeta,
#[default]
HardforkGamma,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display)]
pub enum TritonProofVersion {
V0,
V1,
V5,
}
impl TritonProofVersion {
pub(crate) fn version(&self) -> u32 {
match self {
TritonProofVersion::V0 => 0,
TritonProofVersion::V1 => 1,
TritonProofVersion::V5 => 5,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LustrationRule {
Initial(LustrationStatus),
Updated {
initial_counter: NativeCurrencyAmount,
},
}
impl ConsensusRuleSet {
pub fn infer_from(network: Network, block_height: BlockHeight) -> Self {
let first_lustration_block = Self::first_lustration_block(network);
match network {
Network::Main => {
if block_height < BLOCK_HEIGHT_HARDFORK_ALPHA_MAIN_NET {
ConsensusRuleSet::Reboot
} else if block_height < BLOCK_HEIGHT_HARDFORK_TVMV_PROOF_V1_MAIN_NET {
ConsensusRuleSet::HardforkAlpha
} else if block_height < first_lustration_block {
ConsensusRuleSet::TvmProofVersion1
} else if block_height < BLOCK_HEIGHT_HARDFORK_GAMMA_MAIN_NET {
ConsensusRuleSet::HardforkBeta
} else {
ConsensusRuleSet::HardforkGamma
}
}
Network::Testnet(0) => {
if block_height < BLOCK_HEIGHT_HARDFORK_ALPHA_TESTNET {
ConsensusRuleSet::Reboot
} else if block_height < BLOCK_HEIGHT_HARDFORK_TVMV_PROOF_V1_TESTNET {
ConsensusRuleSet::HardforkAlpha
} else if block_height < first_lustration_block {
ConsensusRuleSet::TvmProofVersion1
} else if block_height < BLOCK_HEIGHT_HARDFORK_GAMMA_TESTNET {
ConsensusRuleSet::HardforkBeta
} else {
ConsensusRuleSet::HardforkGamma
}
}
_ => ConsensusRuleSet::HardforkGamma,
}
}
pub fn memory_hard_pow(&self) -> bool {
match self {
ConsensusRuleSet::Reboot => true,
ConsensusRuleSet::HardforkAlpha => true,
ConsensusRuleSet::TvmProofVersion1 => true,
ConsensusRuleSet::HardforkBeta => false,
ConsensusRuleSet::HardforkGamma => false,
}
}
pub fn use_parent_difficulty(&self) -> bool {
match self {
ConsensusRuleSet::Reboot => true,
ConsensusRuleSet::HardforkAlpha => true,
ConsensusRuleSet::TvmProofVersion1 => true,
ConsensusRuleSet::HardforkBeta => false,
ConsensusRuleSet::HardforkGamma => false,
}
}
pub fn requires_lustration_status_in_block_header(&self) -> bool {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1 => false,
ConsensusRuleSet::HardforkBeta => true,
ConsensusRuleSet::HardforkGamma => true,
}
}
pub(crate) fn requires_version_in_pow(&self) -> bool {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1 => false,
ConsensusRuleSet::HardforkBeta => true,
ConsensusRuleSet::HardforkGamma => true,
}
}
pub fn transaction_backdating_threshold(&self) -> Option<Timestamp> {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1
| ConsensusRuleSet::HardforkBeta => None,
ConsensusRuleSet::HardforkGamma => Some(TX_BACKDATING_LIMIT),
}
}
pub fn fix_lustration_double_counting(&self) -> bool {
match self {
ConsensusRuleSet::Reboot => unreachable!("Lustration not active"),
ConsensusRuleSet::HardforkAlpha => unreachable!("Lustration not active"),
ConsensusRuleSet::TvmProofVersion1 => unreachable!("Lustration not active"),
ConsensusRuleSet::HardforkBeta => false,
ConsensusRuleSet::HardforkGamma => true,
}
}
pub fn triton_proof_version(&self) -> TritonProofVersion {
match self {
ConsensusRuleSet::Reboot => TritonProofVersion::V0,
ConsensusRuleSet::HardforkAlpha => TritonProofVersion::V0,
ConsensusRuleSet::TvmProofVersion1 => TritonProofVersion::V1,
ConsensusRuleSet::HardforkBeta => TritonProofVersion::V1,
ConsensusRuleSet::HardforkGamma => TritonProofVersion::V5,
}
}
pub const fn max_block_size(&self) -> usize {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1
| ConsensusRuleSet::HardforkBeta
| ConsensusRuleSet::HardforkGamma => {
1_000_000
}
}
}
pub fn max_num_inputs(&self) -> usize {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1
| ConsensusRuleSet::HardforkBeta
| ConsensusRuleSet::HardforkGamma => MAX_NUM_INPUTS_OUTPUTS_ANNOUNCEMENTS,
}
}
pub fn max_num_outputs(&self) -> usize {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1
| ConsensusRuleSet::HardforkBeta
| ConsensusRuleSet::HardforkGamma => MAX_NUM_INPUTS_OUTPUTS_ANNOUNCEMENTS,
}
}
pub fn max_num_announcements(&self) -> usize {
match self {
ConsensusRuleSet::Reboot
| ConsensusRuleSet::HardforkAlpha
| ConsensusRuleSet::TvmProofVersion1
| ConsensusRuleSet::HardforkBeta
| ConsensusRuleSet::HardforkGamma => MAX_NUM_INPUTS_OUTPUTS_ANNOUNCEMENTS,
}
}
pub fn first_lustration_block(network: Network) -> BlockHeight {
match network {
Network::Main => BLOCK_HEIGHT_HARDFORK_BETA_MAIN_NET,
Network::Testnet(0) => BLOCK_HEIGHT_HARDFORK_BETA_TESTNET,
_ => BlockHeight::genesis().next(),
}
}
pub fn latest_checkpoint(network: Network) -> BlockHeight {
match network {
Network::Main => BLOCK_HEIGHT_HARDFORK_GAMMA_MAIN_NET.previous().unwrap(),
Network::Testnet(0) => BLOCK_HEIGHT_HARDFORK_GAMMA_TESTNET.previous().unwrap(),
_ => BlockHeight::genesis(),
}
}
pub(crate) fn lustration_rule(
network: Network,
block_height: BlockHeight,
last_aocl_leaf_index: u64,
) -> Option<LustrationRule> {
let premine = PREMINE_MAX_SIZE;
let claims_pool = INITIAL_BLOCK_SUBSIDY
.scalar_mul(u32::try_from(NUM_BLOCKS_SKIPPED_BECAUSE_REBOOT).unwrap());
let first_hf_beta_block = Self::first_lustration_block(network);
let first_hf_gamma_block = Self::hardfork_gamma_activation_block_height(network);
assert!(
first_hf_beta_block.get_generation().is_zero()
&& first_hf_gamma_block.get_generation().is_zero(),
"This calculation assumes all transparency gateways start at generation zero."
);
let mined_at_hf_beta_activation =
INITIAL_BLOCK_SUBSIDY.scalar_mul(u32::try_from(first_hf_beta_block.value()).unwrap());
let mined_at_hf_gamma_activation =
INITIAL_BLOCK_SUBSIDY.scalar_mul(u32::try_from(first_hf_gamma_block.value()).unwrap());
let initial_counter_beta = premine + claims_pool + mined_at_hf_beta_activation;
let initial_counter_gamma = premine + claims_pool + mined_at_hf_gamma_activation;
if block_height < first_hf_beta_block {
None
} else if block_height == first_hf_beta_block {
Some(LustrationRule::Initial(LustrationStatus {
counter: initial_counter_beta,
max_lustrating_aocl_leaf_index: last_aocl_leaf_index,
}))
} else if block_height < first_hf_gamma_block {
Some(LustrationRule::Updated {
initial_counter: initial_counter_beta,
})
} else if block_height == first_hf_gamma_block {
Some(LustrationRule::Initial(LustrationStatus {
counter: initial_counter_gamma,
max_lustrating_aocl_leaf_index: last_aocl_leaf_index,
}))
} else {
Some(LustrationRule::Updated {
initial_counter: initial_counter_gamma,
})
}
}
fn hardfork_gamma_activation_block_height(network: Network) -> BlockHeight {
let one = BlockHeight::genesis().next();
match network {
Network::Main => BLOCK_HEIGHT_HARDFORK_GAMMA_MAIN_NET,
Network::Testnet(0) => BLOCK_HEIGHT_HARDFORK_GAMMA_TESTNET,
Network::Testnet(_) => one,
Network::TestnetMock => one,
Network::RegTest => one,
_ => one,
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use neptune_primitives::difficulty_control::Difficulty;
use tasm_lib::twenty_first::bfe;
use tracing_test::traced_test;
use super::*;
use crate::block::validity::block_primitive_witness::BlockPrimitiveWitness;
use crate::block::Block;
use crate::proof_abstractions::test_runtime::tokio_runtime;
use crate::type_scripts::native_currency_amount::NativeCurrencyAmount;
#[test]
fn lustration_counter_has_expected_initial_value() {
let network = Network::Main;
let first_lustration = ConsensusRuleSet::lustration_rule(
network,
BLOCK_HEIGHT_HARDFORK_BETA_MAIN_NET,
100_000,
)
.unwrap();
let LustrationRule::Initial(first_lustration) = first_lustration else {
panic!("First lustration rule must be of type 'initial'");
};
assert_eq!(
NativeCurrencyAmount::coins(8_423_168),
first_lustration.counter
);
assert_eq!(100_000, first_lustration.max_lustrating_aocl_leaf_index);
let second_lustration = ConsensusRuleSet::lustration_rule(
network,
BLOCK_HEIGHT_HARDFORK_GAMMA_MAIN_NET,
200_000,
)
.unwrap();
let LustrationRule::Initial(second_lustration) = second_lustration else {
panic!("Restarted lustration rule must be of type 'initial'");
};
assert_eq!(
NativeCurrencyAmount::coins(8_717_568),
second_lustration.counter
);
assert_eq!(200_000, second_lustration.max_lustrating_aocl_leaf_index);
assert!(
matches!(
ConsensusRuleSet::lustration_rule(
network,
BLOCK_HEIGHT_HARDFORK_GAMMA_MAIN_NET.next(),
200_000,
)
.unwrap(),
LustrationRule::Updated { .. },
),
"Updated lustration rule must follow initial"
);
}
#[test]
fn future_and_past_memory_hardness() {
assert!(ConsensusRuleSet::infer_from(Network::Main, 1_000u64.into()).memory_hard_pow());
assert!(!ConsensusRuleSet::infer_from(Network::Main, 100_000u64.into()).memory_hard_pow());
}
#[test]
fn future_and_past_lustration_rule() {
let dummy_count = 55647;
let network = Network::Main;
assert!(
ConsensusRuleSet::lustration_rule(network, 10_000u64.into(), dummy_count).is_none()
);
assert!(matches!(
ConsensusRuleSet::lustration_rule(network, 100_000u64.into(), dummy_count).unwrap(),
LustrationRule::Updated { .. }
));
}
#[test]
fn expected_use_parent_difficulty() {
assert!(ConsensusRuleSet::Reboot.use_parent_difficulty());
assert!(ConsensusRuleSet::HardforkAlpha.use_parent_difficulty());
assert!(ConsensusRuleSet::TvmProofVersion1.use_parent_difficulty());
assert!(!ConsensusRuleSet::HardforkBeta.use_parent_difficulty());
assert!(!ConsensusRuleSet::HardforkGamma.use_parent_difficulty());
}
#[test]
fn expected_tvm_proof_versions() {
assert_eq!(0, ConsensusRuleSet::Reboot.triton_proof_version().version());
assert_eq!(
0,
ConsensusRuleSet::HardforkAlpha
.triton_proof_version()
.version()
);
assert_eq!(
1,
ConsensusRuleSet::TvmProofVersion1
.triton_proof_version()
.version()
);
assert_eq!(
1,
ConsensusRuleSet::HardforkBeta
.triton_proof_version()
.version()
);
assert_eq!(
5,
ConsensusRuleSet::HardforkGamma
.triton_proof_version()
.version(),
);
}
#[test]
fn tvm_v1_preceeds_hf_beta() {
let network = Network::Main;
let first_lustration_block = ConsensusRuleSet::first_lustration_block(network);
assert_eq!(
ConsensusRuleSet::TvmProofVersion1,
ConsensusRuleSet::infer_from(network, first_lustration_block.previous().unwrap())
);
assert_eq!(
ConsensusRuleSet::HardforkBeta,
ConsensusRuleSet::infer_from(network, first_lustration_block)
);
let dummy_count = 55647;
assert!(ConsensusRuleSet::lustration_rule(
network,
first_lustration_block.previous().unwrap(),
dummy_count
)
.is_none(),);
assert!(matches!(
ConsensusRuleSet::lustration_rule(network, first_lustration_block, dummy_count),
Some(LustrationRule::Initial(_)),
));
assert!(matches!(
ConsensusRuleSet::lustration_rule(network, first_lustration_block.next(), dummy_count),
Some(LustrationRule::Updated { .. }),
));
}
#[traced_test]
#[test]
fn allow_non_zero_version() {
let init_block_heigth = BlockHeight::from(59998u64);
let network = Network::Main;
let bpw = BlockPrimitiveWitness::deterministic_with_block_height_and_difficulty(
init_block_heigth,
Difficulty::MINIMUM,
network,
);
tokio_runtime().block_on(new_block_allow_non_zero_version(bpw, network));
async fn new_block_allow_non_zero_version(
block_primitive_witness: BlockPrimitiveWitness,
network: Network,
) {
let (invalid_block, mut valid_successor) =
Block::fake_block_pair_genesis_and_child_from_witness(block_primitive_witness)
.await;
assert!(
valid_successor
.is_valid(&invalid_block, valid_successor.header().timestamp, network)
.await
);
valid_successor.set_version_consistently(bfe!(5550001));
assert!(
valid_successor
.is_valid(&invalid_block, valid_successor.header().timestamp, network)
.await
);
let consensus_rule_set = ConsensusRuleSet::HardforkGamma;
assert_eq!(
consensus_rule_set,
ConsensusRuleSet::infer_from(network, valid_successor.header().height)
);
valid_successor.satisfy_pow(invalid_block.header().difficulty, consensus_rule_set);
assert!(
valid_successor
.is_valid(&invalid_block, valid_successor.header().timestamp, network)
.await
);
assert!(valid_successor.has_proof_of_work(network, invalid_block.header()));
}
}
}