mirror of
https://github.com/logos-co/nomos-node.git
synced 2026-09-02 12:31:08 +00:00
Push half baked solution waiting for full RFC to be specced out
This commit is contained in:
@@ -29,9 +29,6 @@ cryptarchia:
|
||||
learning_rate: 1.0
|
||||
pow_config:
|
||||
blend:
|
||||
# TODO: calibrate. `base_difficulty` is the threshold at the reference
|
||||
# load of `target_transactions_per_block`; damping alpha = 1/2 and a
|
||||
# per-epoch clamp of 4x follow the spec's own examples.
|
||||
base_difficulty: '40420f0000000000000000000000000000000000000000000000000000000000'
|
||||
target_transactions_per_block: 10
|
||||
max_step: 4
|
||||
|
||||
@@ -29,9 +29,6 @@ cryptarchia:
|
||||
learning_rate: 0.5
|
||||
pow_config:
|
||||
blend:
|
||||
# TODO: calibrate. `base_difficulty` is the threshold at the reference
|
||||
# load of `target_transactions_per_block`; damping alpha = 1/2 and a
|
||||
# per-epoch clamp of 4x follow the spec's own examples.
|
||||
base_difficulty: '40420f0000000000000000000000000000000000000000000000000000000000'
|
||||
target_transactions_per_block: 10
|
||||
max_step: 4
|
||||
|
||||
@@ -29,9 +29,6 @@ cryptarchia:
|
||||
learning_rate: 1.0
|
||||
pow_config:
|
||||
blend:
|
||||
# TODO: calibrate. `base_difficulty` is the threshold at the reference
|
||||
# load of `target_transactions_per_block`; damping alpha = 1/2 and a
|
||||
# per-epoch clamp of 4x follow the spec's own examples.
|
||||
base_difficulty: '40420f0000000000000000000000000000000000000000000000000000000000'
|
||||
target_transactions_per_block: 10
|
||||
max_step: 4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod block_density;
|
||||
mod stake;
|
||||
mod tx_density;
|
||||
pub(crate) mod tx_density;
|
||||
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
@@ -105,13 +105,10 @@ impl EpochState {
|
||||
fn update_from_ledger(self, ledger: &LedgerState, sdp: &SdpLedger, config: &Config) -> Self {
|
||||
let nonce_snapshot_slot = config.nonce_snapshot(self.epoch);
|
||||
let (nonce, blend_pow_difficulty) = if ledger.slot < nonce_snapshot_slot {
|
||||
let (txs_in_previous_epoch, blocks_in_previous_epoch) =
|
||||
ledger.tx_density.last_closed_epoch_totals();
|
||||
(
|
||||
ledger.nonce,
|
||||
compute_epoch_blend_difficulty(
|
||||
txs_in_previous_epoch,
|
||||
blocks_in_previous_epoch,
|
||||
ledger.tx_density.last_closed_epoch_load(),
|
||||
ledger.epoch_state.blend_pow_difficulty,
|
||||
&config.pow_config.blend,
|
||||
),
|
||||
@@ -831,6 +828,7 @@ pub mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
Ledger,
|
||||
cryptarchia::tx_density::ClosedEpochLoad,
|
||||
leader_proof::LeaderProof,
|
||||
mantle::sdp::{
|
||||
ServiceRewardsParameters,
|
||||
@@ -1422,7 +1420,11 @@ pub mod tests {
|
||||
let epoch_1_difficulty = state.epoch_state.blend_pow_difficulty;
|
||||
assert_eq!(
|
||||
epoch_1_difficulty,
|
||||
compute_epoch_blend_difficulty(0, 0, genesis_difficulty, blend_config)
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(0, 0),
|
||||
genesis_difficulty,
|
||||
blend_config
|
||||
)
|
||||
);
|
||||
|
||||
// Epoch 1, with blocks on both sides of epoch 2's snapshot slot.
|
||||
@@ -1445,7 +1447,11 @@ pub mod tests {
|
||||
assert_eq!(state.epoch_state.epoch, 2);
|
||||
assert_eq!(
|
||||
state.epoch_state.blend_pow_difficulty,
|
||||
compute_epoch_blend_difficulty(12, 3, epoch_1_difficulty, blend_config)
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(12, 3),
|
||||
epoch_1_difficulty,
|
||||
blend_config
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
state.epoch_state.blend_pow_difficulty,
|
||||
@@ -1453,6 +1459,88 @@ pub mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_difficulty_counts_only_the_branch_it_is_read_from() {
|
||||
// The block and transaction counters live in the per-block
|
||||
// `LedgerState`, which `Ledger::prepare_update` derives from a clone of
|
||||
// the parent's. Competing branches therefore accumulate independently,
|
||||
// and reading the difficulty at a tip counts exactly the blocks on that
|
||||
// tip's ancestry — no unwinding is needed when a re-org picks a
|
||||
// different tip.
|
||||
let config = config();
|
||||
let blend_config = &config.pow_config.blend;
|
||||
let sdp = SdpLedger::new(0.into());
|
||||
|
||||
// A common ancestor carrying 4 transactions, in epoch 0.
|
||||
let mut ancestor = genesis_state(&[utxo()]);
|
||||
ancestor = ancestor
|
||||
.update_epoch_state::<HeaderId>(10.into(), &sdp, &config)
|
||||
.unwrap();
|
||||
ancestor.record_block_txs(4);
|
||||
|
||||
// Two competing blocks for the same slot, each built on its own clone
|
||||
// of the ancestor's state.
|
||||
let mut busy_branch = ancestor.clone();
|
||||
busy_branch = busy_branch
|
||||
.update_epoch_state::<HeaderId>(20.into(), &sdp, &config)
|
||||
.unwrap();
|
||||
busy_branch.record_block_txs(96);
|
||||
|
||||
let mut quiet_branch = ancestor;
|
||||
quiet_branch = quiet_branch
|
||||
.update_epoch_state::<HeaderId>(20.into(), &sdp, &config)
|
||||
.unwrap();
|
||||
quiet_branch.record_block_txs(6);
|
||||
|
||||
// Carry both branches past the epoch-0 close and the epoch-2 snapshot.
|
||||
let advance = |mut state: LedgerState| {
|
||||
for slot in [100u64, 110, 200] {
|
||||
state = state
|
||||
.update_epoch_state::<HeaderId>(slot.into(), &sdp, &config)
|
||||
.unwrap();
|
||||
state.record_block_txs(0);
|
||||
}
|
||||
state
|
||||
};
|
||||
let busy_branch = advance(busy_branch);
|
||||
let quiet_branch = advance(quiet_branch);
|
||||
|
||||
// Both branches enter epoch 1 with the same difficulty: no epoch had
|
||||
// closed when epoch 1's value was snapshotted, so both eased by the
|
||||
// full clamp step from the baseline.
|
||||
let epoch_1_difficulty = compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(0, 0),
|
||||
blend_config.base_difficulty,
|
||||
blend_config,
|
||||
);
|
||||
|
||||
// Each tip's epoch 2 then reads its own epoch 0: 100 transactions over
|
||||
// two blocks on one branch, 10 over two on the other. The shared
|
||||
// ancestor's 4 transactions are counted once on each — never twice, and
|
||||
// never leaked from the branch that lost.
|
||||
assert_eq!(
|
||||
busy_branch.epoch_state.blend_pow_difficulty,
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(100, 2),
|
||||
epoch_1_difficulty,
|
||||
blend_config
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
quiet_branch.epoch_state.blend_pow_difficulty,
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(10, 2),
|
||||
epoch_1_difficulty,
|
||||
blend_config
|
||||
)
|
||||
);
|
||||
// The busier branch admits fewer `PoW`-backed messages.
|
||||
assert!(
|
||||
busy_branch.epoch_state.blend_pow_difficulty
|
||||
< quiet_branch.epoch_state.blend_pow_difficulty
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_difficulty_reads_a_skipped_epoch_as_no_load() {
|
||||
let config = config();
|
||||
@@ -1490,7 +1578,11 @@ pub mod tests {
|
||||
assert_eq!(state.epoch_state.epoch, 3);
|
||||
assert_eq!(
|
||||
state.epoch_state.blend_pow_difficulty,
|
||||
compute_epoch_blend_difficulty(0, 0, epoch_2_difficulty, blend_config)
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(0, 0),
|
||||
epoch_2_difficulty,
|
||||
blend_config
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,23 +7,11 @@
|
||||
//! boundary and consumed later, at the nonce snapshot slot of the epoch after
|
||||
//! that.
|
||||
|
||||
/// Totals accumulated over a single epoch.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
struct EpochTotals {
|
||||
blocks: u64,
|
||||
txs: u64,
|
||||
}
|
||||
|
||||
impl EpochTotals {
|
||||
const fn record_block(&mut self, txs_in_block: u64) {
|
||||
self.blocks = self.blocks.saturating_add(1);
|
||||
self.txs = self.txs.saturating_add(txs_in_block);
|
||||
}
|
||||
}
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The rolling block and transaction counts of the epoch being extended and of
|
||||
/// the last epoch that closed.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct TxDensity {
|
||||
/// Totals of the epoch the ledger is currently extending, still growing
|
||||
/// with every applied block.
|
||||
@@ -42,17 +30,73 @@ impl TxDensity {
|
||||
/// Close the current epoch's totals and start counting a new epoch.
|
||||
///
|
||||
/// Called once per epoch crossed, so that epochs skipped entirely (no
|
||||
/// block was produced in them) close as empty and are read as zero
|
||||
/// demand.
|
||||
/// block was produced in them) close as empty and are read as no load.
|
||||
pub(super) const fn close_epoch(&mut self) {
|
||||
self.last_closed_epoch = self.current_epoch;
|
||||
self.current_epoch = EpochTotals { blocks: 0, txs: 0 };
|
||||
}
|
||||
|
||||
/// The totals — `(transactions, blocks)` — of the last closed epoch, the
|
||||
/// observation the Blend difficulty retarget is computed from.
|
||||
pub(super) const fn last_closed_epoch_totals(&self) -> (u64, u64) {
|
||||
(self.last_closed_epoch.txs, self.last_closed_epoch.blocks)
|
||||
/// The load of the last closed epoch — the observation the Blend
|
||||
/// difficulty retarget reads.
|
||||
pub(crate) const fn last_closed_epoch_load(&self) -> ClosedEpochLoad {
|
||||
ClosedEpochLoad {
|
||||
transactions: self.last_closed_epoch.txs,
|
||||
blocks: self.last_closed_epoch.blocks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The transaction load of a single epoch that has *closed*.
|
||||
///
|
||||
/// The retarget must never read an epoch that is still being extended — its
|
||||
/// totals would still be growing, and a difficulty derived from them would
|
||||
/// depend on when it was read. Only [`TxDensity::close_epoch`] can put an
|
||||
/// epoch's counts into this type, so passing an open epoch to the controller
|
||||
/// does not compile rather than silently mis-targeting the difficulty. Naming
|
||||
/// the two counts also keeps them from being transposed at the call site,
|
||||
/// where as bare integers they are indistinguishable.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ClosedEpochLoad {
|
||||
transactions: u64,
|
||||
blocks: u64,
|
||||
}
|
||||
|
||||
impl ClosedEpochLoad {
|
||||
/// Transactions carried by the whole epoch.
|
||||
pub const fn transactions(self) -> u64 {
|
||||
self.transactions
|
||||
}
|
||||
|
||||
/// Blocks the epoch produced. Zero for an epoch that was skipped entirely.
|
||||
pub const fn blocks(self) -> u64 {
|
||||
self.blocks
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl ClosedEpochLoad {
|
||||
/// Test-only: stand in for an epoch closed by the ledger, so the
|
||||
/// controller can be exercised over loads a test does not have to
|
||||
/// accumulate block by block.
|
||||
pub const fn new(transactions: u64, blocks: u64) -> Self {
|
||||
Self {
|
||||
transactions,
|
||||
blocks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Totals accumulated over a single epoch.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct EpochTotals {
|
||||
blocks: u64,
|
||||
txs: u64,
|
||||
}
|
||||
|
||||
impl EpochTotals {
|
||||
const fn record_block(&mut self, txs_in_block: u64) {
|
||||
self.blocks = self.blocks.saturating_add(1);
|
||||
self.txs = self.txs.saturating_add(txs_in_block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,16 +107,19 @@ mod tests {
|
||||
#[test]
|
||||
fn totals_accumulate_until_the_epoch_closes() {
|
||||
let mut density = TxDensity::default();
|
||||
assert_eq!(density.last_closed_epoch_totals(), (0, 0));
|
||||
assert_eq!(density.last_closed_epoch_load(), ClosedEpochLoad::new(0, 0));
|
||||
|
||||
density.record_block(3);
|
||||
density.record_block(0);
|
||||
density.record_block(7);
|
||||
// Still open: nothing is observable yet.
|
||||
assert_eq!(density.last_closed_epoch_totals(), (0, 0));
|
||||
assert_eq!(density.last_closed_epoch_load(), ClosedEpochLoad::new(0, 0));
|
||||
|
||||
density.close_epoch();
|
||||
assert_eq!(density.last_closed_epoch_totals(), (10, 3));
|
||||
assert_eq!(
|
||||
density.last_closed_epoch_load(),
|
||||
ClosedEpochLoad::new(10, 3)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -81,7 +128,7 @@ mod tests {
|
||||
density.record_block(5);
|
||||
density.close_epoch();
|
||||
density.record_block(100);
|
||||
assert_eq!(density.last_closed_epoch_totals(), (5, 1));
|
||||
assert_eq!(density.last_closed_epoch_load(), ClosedEpochLoad::new(5, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -92,6 +139,6 @@ mod tests {
|
||||
density.record_block(5);
|
||||
density.close_epoch();
|
||||
density.close_epoch();
|
||||
assert_eq!(density.last_closed_epoch_totals(), (0, 0));
|
||||
assert_eq!(density.last_closed_epoch_load(), ClosedEpochLoad::new(0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -250,8 +250,10 @@ impl LedgerState {
|
||||
// block that is actually applied, contents included, belongs in the
|
||||
// epoch's average.
|
||||
let mut txs_in_block = 0u64;
|
||||
let (mut state, tx_events) = state
|
||||
.try_apply_contents::<_, _, Profile>(config, txs.inspect(|_| txs_in_block += 1))?;
|
||||
let (mut state, tx_events) = state.try_apply_contents::<_, _, Profile>(
|
||||
config,
|
||||
txs.inspect(|_| txs_in_block = txs_in_block.saturating_add(1)),
|
||||
)?;
|
||||
state.cryptarchia_ledger.record_block_txs(txs_in_block);
|
||||
state.update_pow_reward_difficulty(
|
||||
// count all claimed rewards
|
||||
|
||||
@@ -7,59 +7,42 @@
|
||||
//! means real traffic already supplies an anonymity set, so `PoW` entry can be
|
||||
//! rate-limited harder; thin load eases the threshold so `PoW`-backed messages
|
||||
//! come in and build the anonymity set.
|
||||
//!
|
||||
//! The controller is parallel to the reward-difficulty retarget in
|
||||
//! [`super::difficulty`], but is driven by a transaction count rather than a
|
||||
//! claim rate, and moves once per epoch rather than once per block.
|
||||
|
||||
use lb_core::mantle::ops::pow::PowTarget;
|
||||
use lb_groth16::{Field as _, fr_to_bytes};
|
||||
use num_bigint::BigUint;
|
||||
|
||||
use crate::config::BlendPoWConfig;
|
||||
use crate::{config::BlendPoWConfig, cryptarchia::tx_density::ClosedEpochLoad};
|
||||
|
||||
/// Retarget `d_blend` from a whole epoch's transaction load.
|
||||
///
|
||||
/// With `load = avg_txs_per_block / T_tx` and a damping exponent
|
||||
/// `alpha = a / b <= 1`, the new threshold is the baseline divided down by the
|
||||
/// load — smaller target, harder puzzle, as load rises:
|
||||
///
|
||||
/// ```text
|
||||
/// d_blend = BASE / load^alpha, clamped to [previous / k, previous * k]
|
||||
/// ```
|
||||
///
|
||||
/// The response is deliberately gentle: sub-linear in the load (at
|
||||
/// `alpha = 1/2`, quadrupling the transaction count only halves the
|
||||
/// threshold), averaged over a whole epoch, and bounded per epoch by the
|
||||
/// `max_step` factor `k`. An adversary stuffing blocks to shrink the anonymity
|
||||
/// set therefore has to pay for it every epoch, for a small and capped effect.
|
||||
///
|
||||
/// An epoch that carried no transactions at all — including an epoch with no
|
||||
/// blocks — is read as no observed load and eases the threshold as far as this
|
||||
/// epoch's clamp allows.
|
||||
pub fn compute_epoch_blend_difficulty(
|
||||
txs_in_epoch: u64,
|
||||
blocks_in_epoch: u64,
|
||||
load: ClosedEpochLoad,
|
||||
previous_difficulty: PowTarget,
|
||||
config: &BlendPoWConfig,
|
||||
) -> PowTarget {
|
||||
// The arithmetic happens on plain integers: `PowTarget` is a field
|
||||
// element, whose division (multiplication by the modular inverse) does not
|
||||
// compute a ratio.
|
||||
let previous = BigUint::from_bytes_le(&fr_to_bytes(&previous_difficulty));
|
||||
let previous_difficulty = BigUint::from(previous_difficulty);
|
||||
let numerator = BigUint::from(load.transactions());
|
||||
let max_step = BigUint::from(config.max_step.get());
|
||||
|
||||
let clamp_upper_bound = previous_difficulty * max_step;
|
||||
|
||||
if numerator == BigUint::ZERO {
|
||||
return clamp_upper_bound; // no load observed: as easy as this epoch's clamp allows
|
||||
}
|
||||
|
||||
let clamp_lower_bound = previous_difficulty / max_step;
|
||||
|
||||
let lo = previous_difficulty / config.max_step.get();
|
||||
|
||||
let max_step = BigUint::from(config.max_step.get());
|
||||
// Bound the change to at most a factor of `k` in either direction.
|
||||
let low = &previous / &max_step;
|
||||
let high = previous * max_step;
|
||||
|
||||
// `load` is kept exactly as the ratio `numerator / denominator`
|
||||
// (they are equal at the reference load); no division is performed here.
|
||||
let numerator = BigUint::from(txs_in_epoch);
|
||||
if numerator == BigUint::ZERO {
|
||||
// No load observed: as easy as this epoch's clamp allows.
|
||||
return into_target(high);
|
||||
}
|
||||
let denominator = BigUint::from(config.target_transactions_per_block.get()) * blocks_in_epoch;
|
||||
let denominator = BigUint::from(config.target_transactions_per_block.get()) * load.blocks();
|
||||
|
||||
// target = BASE / load^alpha
|
||||
// = (BASE^b * denominator^a // numerator^a)^(1/b)
|
||||
@@ -105,7 +88,11 @@ mod tests {
|
||||
// so the threshold is the baseline itself.
|
||||
let config = config();
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(70, 7, config.base_difficulty, &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(70, 7),
|
||||
config.base_difficulty,
|
||||
&config
|
||||
),
|
||||
config.base_difficulty
|
||||
);
|
||||
}
|
||||
@@ -115,7 +102,11 @@ mod tests {
|
||||
// At alpha = 1/2, quadrupling the load only halves the threshold.
|
||||
let config = config();
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(280, 7, config.base_difficulty, &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(280, 7),
|
||||
config.base_difficulty,
|
||||
&config
|
||||
),
|
||||
PowTarget::from(500_000u64)
|
||||
);
|
||||
}
|
||||
@@ -127,7 +118,11 @@ mod tests {
|
||||
// the clamp does not bind.
|
||||
let config = config();
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(20, 8, config.base_difficulty, &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(20, 8),
|
||||
config.base_difficulty,
|
||||
&config
|
||||
),
|
||||
PowTarget::from(2_000_000u64)
|
||||
);
|
||||
}
|
||||
@@ -137,7 +132,11 @@ mod tests {
|
||||
// Nothing observed: the threshold moves to the top of the clamp.
|
||||
let config = config();
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(0, 7, PowTarget::from(1_000u64), &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(0, 7),
|
||||
PowTarget::from(1_000u64),
|
||||
&config
|
||||
),
|
||||
PowTarget::from(4_000u64)
|
||||
);
|
||||
}
|
||||
@@ -148,7 +147,11 @@ mod tests {
|
||||
// divide by the empty block count.
|
||||
let config = config();
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(0, 0, PowTarget::from(1_000u64), &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(0, 0),
|
||||
PowTarget::from(1_000u64),
|
||||
&config
|
||||
),
|
||||
PowTarget::from(4_000u64)
|
||||
);
|
||||
}
|
||||
@@ -159,12 +162,20 @@ mod tests {
|
||||
// A flood far past the clamp: the threshold falls by the factor k and
|
||||
// no further, so the anonymity set can shrink only gradually.
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(1_000_000, 7, config.base_difficulty, &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(1_000_000, 7),
|
||||
config.base_difficulty,
|
||||
&config
|
||||
),
|
||||
PowTarget::from(250_000u64)
|
||||
);
|
||||
// And symmetrically upwards on a near-empty epoch.
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(1, 7, config.base_difficulty, &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(1, 7),
|
||||
config.base_difficulty,
|
||||
&config
|
||||
),
|
||||
PowTarget::from(4_000_000u64)
|
||||
);
|
||||
}
|
||||
@@ -176,7 +187,11 @@ mod tests {
|
||||
// while the clamp bounds how far one epoch may travel towards it.
|
||||
let config = config();
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(1_000_000, 7, PowTarget::from(1_000u64), &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(1_000_000, 7),
|
||||
PowTarget::from(1_000u64),
|
||||
&config
|
||||
),
|
||||
PowTarget::from(4_000u64)
|
||||
);
|
||||
}
|
||||
@@ -191,11 +206,11 @@ mod tests {
|
||||
};
|
||||
let previous = PowTarget::from(1_234u64);
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(9_999, 7, previous, &config),
|
||||
compute_epoch_blend_difficulty(ClosedEpochLoad::new(9_999, 7), previous, &config),
|
||||
previous
|
||||
);
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(0, 7, previous, &config),
|
||||
compute_epoch_blend_difficulty(ClosedEpochLoad::new(0, 7), previous, &config),
|
||||
previous
|
||||
);
|
||||
}
|
||||
@@ -209,7 +224,11 @@ mod tests {
|
||||
..config()
|
||||
};
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(280, 7, config.base_difficulty, &config),
|
||||
compute_epoch_blend_difficulty(
|
||||
ClosedEpochLoad::new(280, 7),
|
||||
config.base_difficulty,
|
||||
&config
|
||||
),
|
||||
PowTarget::from(250_000u64)
|
||||
);
|
||||
}
|
||||
@@ -222,7 +241,7 @@ mod tests {
|
||||
let config = config();
|
||||
let max_target = -PowTarget::ONE;
|
||||
assert_eq!(
|
||||
compute_epoch_blend_difficulty(0, 7, max_target, &config),
|
||||
compute_epoch_blend_difficulty(ClosedEpochLoad::new(0, 7), max_target, &config),
|
||||
max_target
|
||||
);
|
||||
}
|
||||
@@ -236,7 +255,8 @@ mod tests {
|
||||
base_difficulty: base,
|
||||
..config()
|
||||
};
|
||||
let retargeted = compute_epoch_blend_difficulty(280, 7, base, &config);
|
||||
let retargeted =
|
||||
compute_epoch_blend_difficulty(ClosedEpochLoad::new(280, 7), base, &config);
|
||||
// Quadruple load at alpha = 1/2: half the baseline, up to the flooring
|
||||
// of the square root.
|
||||
let expected = PowTarget::from(BigUint::from(1u8) << 249);
|
||||
|
||||
Reference in New Issue
Block a user