go-blockchain/chain/difficulty.go
Snider 34128d8e98
Some checks failed
Security Scan / security (pull_request) Successful in 11s
Test / Test (pull_request) Failing after 19s
refactor: migrate module path to dappco.re/go/core/blockchain
Update go.mod module line, all require/replace directives, and every
.go import path from forge.lthn.ai/core/go-blockchain to
dappco.re/go/core/blockchain. Add replace directives to bridge
dappco.re paths to existing forge.lthn.ai registry during migration.
Update CLAUDE.md, README, and docs to reflect the new module path.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-22 01:49:26 +00:00

87 lines
3.1 KiB
Go

// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2
package chain
import (
"math/big"
"dappco.re/go/core/blockchain/config"
"dappco.re/go/core/blockchain/difficulty"
)
// nextDifficultyWith computes the expected difficulty for the block at the
// given height using the LWMA algorithm, parameterised by pre/post-HF6 targets.
//
// The genesis block (height 0) is excluded from the difficulty window,
// matching the C++ daemon's load_targetdata_cache which skips index 0.
//
// The target block time depends on the hardfork schedule:
// - Pre-HF6: baseTarget (120s for both PoW and PoS on Lethean)
// - Post-HF6: hf6Target (240s -- halves block rate, halves emission)
//
// NOTE: This was originally gated on HF2, matching the Zano upstream where
// HF2 coincides with the difficulty target change. Lethean mainnet keeps 120s
// blocks between HF2 (height 10,080) and HF6 (height 999,999,999), so the
// gate was corrected to HF6 in March 2026.
func (c *Chain) nextDifficultyWith(height uint64, forks []config.HardFork, baseTarget, hf6Target uint64) (uint64, error) {
if height == 0 {
return 1, nil
}
// LWMA needs N+1 entries (N solve-time intervals).
// Start from height 1 -- genesis is excluded from the difficulty window.
maxLookback := difficulty.LWMAWindow + 1
lookback := min(height, maxLookback) // height excludes genesis since we start from 1
// Start from max(1, height - lookback) to exclude genesis.
startHeight := height - lookback
if startHeight == 0 {
startHeight = 1
lookback = height - 1
}
if lookback == 0 {
return 1, nil
}
count := int(lookback)
timestamps := make([]uint64, count)
cumulDiffs := make([]*big.Int, count)
for i := range count {
meta, err := c.getBlockMeta(startHeight + uint64(i))
if err != nil {
// Fewer blocks than expected -- use what we have.
timestamps = timestamps[:i]
cumulDiffs = cumulDiffs[:i]
break
}
timestamps[i] = meta.Timestamp
cumulDiffs[i] = new(big.Int).SetUint64(meta.CumulativeDiff)
}
// Determine the target block time based on hardfork status.
// HF6 doubles the target from 120s to 240s (corrected from HF2 gate).
target := baseTarget
if config.IsHardForkActive(forks, config.HF6, height) {
target = hf6Target
}
result := difficulty.NextDifficulty(timestamps, cumulDiffs, target)
return result.Uint64(), nil
}
// NextDifficulty computes the expected PoW difficulty for the block at the
// given height. Pre-HF6 the target is 120s; post-HF6 it doubles to 240s.
func (c *Chain) NextDifficulty(height uint64, forks []config.HardFork) (uint64, error) {
return c.nextDifficultyWith(height, forks, config.DifficultyPowTarget, config.DifficultyPowTargetHF6)
}
// NextPoSDifficulty computes the expected PoS difficulty for the block at the
// given height. Pre-HF6 the target is 120s; post-HF6 it doubles to 240s.
func (c *Chain) NextPoSDifficulty(height uint64, forks []config.HardFork) (uint64, error) {
return c.nextDifficultyWith(height, forks, config.DifficultyPosTarget, config.DifficultyPosTargetHF6)
}