go-blockchain/difficulty/difficulty.go
Claude 4c0b7f290e
feat: Phase 0 scaffold -- config, types, wire, difficulty
Chain parameters from Lethean C++ source. Base58 address encoding with
Keccak-256 checksum. CryptoNote varint. LWMA difficulty skeleton.

Co-Authored-By: Charon <charon@lethean.io>
2026-02-20 15:10:33 +00:00

100 lines
3.3 KiB
Go

// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// SPDX-License-Identifier: EUPL-1.2
// Package difficulty implements the LWMA (Linear Weighted Moving Average)
// difficulty adjustment algorithm used by the Lethean blockchain for both
// PoW and PoS blocks.
//
// The algorithm examines a window of recent block timestamps and cumulative
// difficulties to calculate the next target difficulty, ensuring blocks
// arrive at the desired interval on average.
package difficulty
import (
"math/big"
)
// Algorithm constants matching the C++ source.
const (
// Window is the number of blocks in the difficulty calculation window.
Window uint64 = 720
// Lag is the additional lookback beyond the window.
Lag uint64 = 15
// Cut is the number of extreme timestamps trimmed from each end after
// sorting. This dampens the effect of outlier timestamps.
Cut uint64 = 60
// BlocksCount is the total number of blocks considered (Window + Lag).
BlocksCount uint64 = Window + Lag
)
// StarterDifficulty is the minimum difficulty returned when there is
// insufficient data to calculate a proper value.
var StarterDifficulty = big.NewInt(1)
// NextDifficulty calculates the next block difficulty using the LWMA algorithm.
//
// Parameters:
// - timestamps: block timestamps for the last BlocksCount blocks, ordered
// from oldest to newest.
// - cumulativeDiffs: cumulative difficulties corresponding to each block.
// - target: the desired block interval in seconds (e.g. 120 for PoW/PoS).
//
// Returns the calculated difficulty for the next block.
//
// If the input slices are too short to perform a meaningful calculation, the
// function returns StarterDifficulty.
func NextDifficulty(timestamps []uint64, cumulativeDiffs []*big.Int, target uint64) *big.Int {
// Need at least 2 entries to compute a time span and difficulty delta.
if len(timestamps) < 2 || len(cumulativeDiffs) < 2 {
return new(big.Int).Set(StarterDifficulty)
}
length := uint64(len(timestamps))
if length > BlocksCount {
length = BlocksCount
}
// Use the available window, but ensure we have at least 2 points.
windowSize := length
if windowSize < 2 {
return new(big.Int).Set(StarterDifficulty)
}
// Calculate the time span across the window.
// Use only the last windowSize entries.
startIdx := uint64(len(timestamps)) - windowSize
endIdx := uint64(len(timestamps)) - 1
timeSpan := timestamps[endIdx] - timestamps[startIdx]
if timeSpan == 0 {
timeSpan = 1 // prevent division by zero
}
// Calculate the difficulty delta across the same window.
diffDelta := new(big.Int).Sub(cumulativeDiffs[endIdx], cumulativeDiffs[startIdx])
if diffDelta.Sign() <= 0 {
return new(big.Int).Set(StarterDifficulty)
}
// LWMA core: nextDiff = diffDelta * target / timeSpan
// This keeps the difficulty proportional to the hash rate needed to
// maintain the target block interval.
nextDiff := new(big.Int).Mul(diffDelta, new(big.Int).SetUint64(target))
nextDiff.Div(nextDiff, new(big.Int).SetUint64(timeSpan))
// Ensure we never return zero difficulty.
if nextDiff.Sign() <= 0 {
return new(big.Int).Set(StarterDifficulty)
}
return nextDiff
}