text: // Copyright (c) 2009-2010 Satoshi Nakamoto
  // Copyright (c) 2009-2015 The Bitcoin Core developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "pow.h"
  
  #include "arith_uint256.h"
  #include "chain.h"
  #include "chainparams.h"
  #include "primitives/block.h"
  #include "uint256.h"
  
  #include <math.h>
  
  const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake)
  {
  	while (pindex && pindex->pprev && (pindex->IsProofOfStake() != fProofOfStake))
  		pindex = pindex->pprev;
  	return pindex;
  }
  
  unsigned int Lwma3CalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params)
  {
  	const int64_t T = params.nPosTargetSpacing;
  	const int64_t N = 150;
  	const int64_t k = N * (N + 1) * T / 2;
  	const int64_t height = pindexLast->nHeight;
  	const arith_uint256 powLimit = UintToArith256(params.powLimit);
  
  	if (height < N) { return powLimit.GetCompact(); }
  
  	arith_uint256 sumTarget, nextTarget;
  	int64_t thisTimestamp, previousTimestamp;
  	int64_t t = 0, j = 0;
  
  	const CBlockIndex* blockPreviousTimestamp = pindexLast->GetAncestor(height - N);
  	previousTimestamp = blockPreviousTimestamp->GetBlockTime();
  
  	// Loop through N most recent blocks.
  	for (int64_t i = height - N + 1; i <= height; i++) {
  		const CBlockIndex* block = pindexLast->GetAncestor(i);
  		thisTimestamp = (block->GetBlockTime() > previousTimestamp) ?
  			block->GetBlockTime() : previousTimestamp + 1;
  		int64_t solvetime = std::min(6 * T, thisTimestamp - previousTimestamp);
  		previousTimestamp = thisTimestamp;
  		j++;
  		t += solvetime * j; // Weighted solvetime sum.
  		arith_uint256 target;
  		target.SetCompact(block->nBits);
  		sumTarget += target / (k * N);
  	}
  	nextTarget = t * sumTarget;
  	if (nextTarget > powLimit) { nextTarget = powLimit; }
  
  	return nextTarget.GetCompact();
  }
  
  unsigned int DualKGW3(const CBlockIndex* pindexLast, bool fProofOfStake, const Consensus::Params& params)
  {
  	const CBlockIndex *BlockLastSolved = pindexLast;
  	const CBlockIndex *BlockReading = pindexLast;
  	int64_t PastBlocksMass = 0;
  	int64_t PastRateActualSeconds = 0;
  	int64_t PastRateTargetSeconds = 0;
  	double PastRateAdjustmentRatio = double(1);
  	arith_uint256 PastDifficultyAverage;
  	arith_uint256 PastDifficultyAveragePrev;
  	double EventHorizonDeviation;
  	double EventHorizonDeviationFast;
  	double EventHorizonDeviationSlow;
  	static const int64_t Blocktime = fProofOfStake ? params.nPosTargetSpacing :
  		params.nPowTargetSpacing;
  	static const unsigned int timeDaySeconds = 86400;
  	uint64_t pastSecondsMin = timeDaySeconds * 0.025;
  	uint64_t pastSecondsMax = timeDaySeconds * 7;
  	uint64_t PastBlocksMin = pastSecondsMin / Blocktime;
  	uint64_t PastBlocksMax = pastSecondsMax / Blocktime;
  	const arith_uint256 bnPowLimit = fProofOfStake ? UintToArith256(params.posLimit) :
  		UintToArith256(params.powLimit);
  
  	if (BlockLastSolved == NULL || BlockLastSolved->nHeight == 0 ||
  		(uint64_t)BlockLastSolved->nHeight < PastBlocksMin)
  		return bnPowLimit.GetCompact();
  
  	for (unsigned int i = 1; BlockReading && BlockReading->nHeight > 0; i++) {
  		if (PastBlocksMax > 0 && i > PastBlocksMax) break;
  		PastBlocksMass++;
  		PastDifficultyAverage.SetCompact(BlockReading->nBits);
  		if (i > 1) {
  			if (PastDifficultyAverage >= PastDifficultyAveragePrev)
  				PastDifficultyAverage =
  				((PastDifficultyAverage - PastDifficultyAveragePrev) / i) +
  				PastDifficultyAveragePrev;
  			else
  				PastDifficultyAverage =
  				PastDifficultyAveragePrev -
  				((PastDifficultyAveragePrev - PastDifficultyAverage) / i);
  		}
  		PastDifficultyAveragePrev = PastDifficultyAverage;
  		PastRateActualSeconds =
  			BlockLastSolved->GetBlockTime() - BlockReading->GetBlockTime();
  		PastRateTargetSeconds = Blocktime * PastBlocksMass;
  		PastRateAdjustmentRatio = double(1);
  		if (PastRateActualSeconds < 0) PastRateActualSeconds = 0;
  		if (PastRateActualSeconds != 0 && PastRateTargetSeconds != 0)
  			PastRateAdjustmentRatio =
  			double(PastRateTargetSeconds) / double(PastRateActualSeconds);
  		EventHorizonDeviation =
  			1 + (0.7084 * pow((double(PastBlocksMass) / double(72)), -1.228));
  		EventHorizonDeviationFast = EventHorizonDeviation;
  		EventHorizonDeviationSlow = 1 / EventHorizonDeviation;
  
  		if (PastBlocksMass >= PastBlocksMin) {
  			if ((PastRateAdjustmentRatio <= EventHorizonDeviationSlow) ||
  				(PastRateAdjustmentRatio >= EventHorizonDeviationFast)) {
  				assert(BlockReading);
  				break;
  			}
  		}
  		if (BlockReading->pprev == NULL) {
  			assert(BlockReading);
  			break;
  		}
  		BlockReading = BlockReading->pprev;
  	}
  
  	arith_uint256 kgw_dual1(PastDifficultyAverage);
  	arith_uint256 kgw_dual2;
  	kgw_dual2.SetCompact(pindexLast->nBits);
  	if (PastRateActualSeconds != 0 && PastRateTargetSeconds != 0) {
  		kgw_dual1 *= PastRateActualSeconds;
  		kgw_dual1 /= PastRateTargetSeconds;
  	}
  	int64_t nActualTime1 =
  		pindexLast->GetBlockTime() - pindexLast->pprev->GetBlockTime();
  	int64_t nActualTimespanshort = nActualTime1;
  
  	if (nActualTime1 < 0) nActualTime1 = Blocktime;
  	if (nActualTime1 < Blocktime / 3) nActualTime1 = Blocktime / 3;
  	if (nActualTime1 > Blocktime * 3) nActualTime1 = Blocktime * 3;
  	kgw_dual2 *= nActualTime1;
  	kgw_dual2 /= Blocktime;
  	arith_uint256 bnNew;
  	bnNew = ((kgw_dual2 + kgw_dual1) / 2);
  
  	if (nActualTimespanshort < Blocktime / 6) {
  		const int nLongShortNew1 = 85;
  		const int nLongShortNew2 = 100;
  		bnNew = bnNew * nLongShortNew1;
  		bnNew = bnNew / nLongShortNew2;
  	}
  
  	if (bnNew > bnPowLimit) bnNew = bnPowLimit;
  
  	return bnNew.GetCompact();
  }
  
  unsigned int static KimotoGravityWell(const CBlockIndex* pindexLast, const Consensus::Params& params) {
  	const CBlockIndex *BlockLastSolved = pindexLast;
  	const CBlockIndex *BlockReading = pindexLast;
  	uint64_t PastBlocksMass = 0;
  	int64_t PastRateActualSeconds = 0;
  	int64_t PastRateTargetSeconds = 0;
  	double PastRateAdjustmentRatio = double(1);
  	arith_uint256 PastDifficultyAverage;
  	arith_uint256 PastDifficultyAveragePrev;
  	double EventHorizonDeviation;
  	double EventHorizonDeviationFast;
  	double EventHorizonDeviationSlow;
  
  	uint64_t pastSecondsMin = params.nPowTargetTimespan * 0.025;
  	uint64_t pastSecondsMax = params.nPowTargetTimespan * 7;
  	uint64_t PastBlocksMin = pastSecondsMin / params.nPowTargetSpacing;
  	uint64_t PastBlocksMax = pastSecondsMax / params.nPowTargetSpacing;
  
  	if (BlockLastSolved == nullptr || BlockLastSolved->nHeight == 0 || (uint64_t)BlockLastSolved->nHeight < PastBlocksMin) { return UintToArith256(params.powLimit).GetCompact(); }
  
  	for (unsigned int i = 1; BlockReading && BlockReading->nHeight > 0; i++) {
  		if (PastBlocksMax > 0 && i > PastBlocksMax) { break; }
  		PastBlocksMass++;
  
  		PastDifficultyAverage.SetCompact(BlockReading->nBits);
  		if (i > 1) {
  			// handle negative arith_uint256
  			if (PastDifficultyAverage >= PastDifficultyAveragePrev)
  				PastDifficultyAverage = ((PastDifficultyAverage - PastDifficultyAveragePrev) / i) + PastDifficultyAveragePrev;
  			else
  				PastDifficultyAverage = PastDifficultyAveragePrev - ((PastDifficultyAveragePrev - PastDifficultyAverage) / i);
  		}
  		PastDifficultyAveragePrev = PastDifficultyAverage;
  
  		PastRateActualSeconds = BlockLastSolved->GetBlockTime() - BlockReading->GetBlockTime();
  		PastRateTargetSeconds = params.nPowTargetSpacing * PastBlocksMass;
  		PastRateAdjustmentRatio = double(1);
  		if (PastRateActualSeconds < 0) { PastRateActualSeconds = 0; }
  		if (PastRateActualSeconds != 0 && PastRateTargetSeconds != 0) {
  			PastRateAdjustmentRatio = double(PastRateTargetSeconds) / double(PastRateActualSeconds);
  		}
  		EventHorizonDeviation = 1 + (0.7084 * pow((double(PastBlocksMass) / double(28.2)), -1.228));
  		EventHorizonDeviationFast = EventHorizonDeviation;
  		EventHorizonDeviationSlow = 1 / EventHorizonDeviation;
  
  		if (PastBlocksMass >= PastBlocksMin) {
  			if ((PastRateAdjustmentRatio <= EventHorizonDeviationSlow) || (PastRateAdjustmentRatio >= EventHorizonDeviationFast))
  			{
  				assert(BlockReading); break;
  			}
  		}
  		if (BlockReading->pprev == nullptr) { assert(BlockReading); break; }
  		BlockReading = BlockReading->pprev;
  	}
  
  	arith_uint256 bnNew(PastDifficultyAverage);
  	if (PastRateActualSeconds != 0 && PastRateTargetSeconds != 0) {
  		bnNew *= PastRateActualSeconds;
  		bnNew /= PastRateTargetSeconds;
  	}
  
  	if (bnNew > UintToArith256(params.powLimit)) {
  		bnNew = UintToArith256(params.powLimit);
  	}
  
  	return bnNew.GetCompact();
  }
  
  unsigned int static DarkGravityWave(const CBlockIndex* pindexLast, const Consensus::Params& params) {
  	/* current difficulty formula, lokal - DarkGravity v3, written by Evan Duffield - evan@lokal_coin.io */
  	const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
  	int64_t nPastBlocks = 24;
  
  	// make sure we have at least (nPastBlocks + 1) blocks, otherwise just return powLimit
  	if (!pindexLast || pindexLast->nHeight < nPastBlocks) {
  		return bnPowLimit.GetCompact();
  	}
  
  	const CBlockIndex *pindex = pindexLast;
  	arith_uint256 bnPastTargetAvg;
  
  	for (unsigned int nCountBlocks = 1; nCountBlocks <= nPastBlocks; nCountBlocks++) {
  		arith_uint256 bnTarget = arith_uint256().SetCompact(pindex->nBits);
  		if (nCountBlocks == 1) {
  			bnPastTargetAvg = bnTarget;
  		}
  		else {
  			// NOTE: that's not an average really...
  			bnPastTargetAvg = (bnPastTargetAvg * nCountBlocks + bnTarget) / (nCountBlocks + 1);
  		}
  
  		if (nCountBlocks != nPastBlocks) {
  			assert(pindex->pprev); // should never fail
  			pindex = pindex->pprev;
  		}
  	}
  
  	arith_uint256 bnNew(bnPastTargetAvg);
  
  	int64_t nActualTimespan = pindexLast->GetBlockTime() - pindex->GetBlockTime();
  	// NOTE: is this accurate? nActualTimespan counts it for (nPastBlocks - 1) blocks only...
  	int64_t nTargetTimespan = nPastBlocks * params.nPowTargetSpacing;
  
  	if (nActualTimespan < nTargetTimespan / 3)
  		nActualTimespan = nTargetTimespan / 3;
  	if (nActualTimespan > nTargetTimespan * 3)
  		nActualTimespan = nTargetTimespan * 3;
  
  	// Retarget
  	bnNew *= nActualTimespan;
  	bnNew /= nTargetTimespan;
  
  	if (bnNew > bnPowLimit) {
  		bnNew = bnPowLimit;
  	}
  
  	return bnNew.GetCompact();
  }
  
  unsigned int GetNextWorkRequiredBTC(const CBlockIndex* pindexLast, const Consensus::Params& params)
  {
  	assert(pindexLast != nullptr);
  	unsigned int nProofOfWorkLimit = UintToArith256(params.powLimit).GetCompact();
  
  	// Only change once per interval
  	if ((pindexLast->nHeight + 1) % params.DifficultyAdjustmentInterval() != 0) {
  		return pindexLast->nBits;
  	}
  
  	// Go back by what we want to be 1 day worth of blocks
  	int nHeightFirst = pindexLast->nHeight - (params.DifficultyAdjustmentInterval() - 1);
  	assert(nHeightFirst >= 0);
  	const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);
  	assert(pindexFirst);
  
  	return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);
  }
  
  unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params)
  {
  	const int nDiffSmoothingPeriod = 15;
  
  	if (Params().NetworkIDString() == CBaseChainParams::TESTNET)
  	{
  		if (pindexLast->nHeight + 1 > params.nLastPoWBlock)
  			return Lwma3CalculateNextWorkRequired(pindexLast, params);
  		return UintToArith256(params.powLimit).GetCompact();
  	}
  	else
  	{
  		// proof of stake
  		if (pindexLast->nHeight + 1 > params.DIP0003EnforcementHeight)
  			return Lwma3CalculateNextWorkRequired(pindexLast, params);
  		if (pindexLast->nHeight + 1 > params.nLastPoWBlock + nDiffSmoothingPeriod)
  			return DualKGW3(pindexLast, true, params);
  		if (pindexLast->nHeight + 1 > params.nLastPoWBlock)
  			return UintToArith256(params.powLimit).GetCompact();
  		// proof of work
  		if (pindexLast->nHeight + 1 >= params.nPowDGWHeight) {
  			return DarkGravityWave(pindexLast, params);
  		}
  		else if (pindexLast->nHeight + 1 >= params.nPowKGWHeight) {
  			return KimotoGravityWell(pindexLast, params);
  		}
  		else {
  			return GetNextWorkRequiredBTC(pindexLast, params);
  		}
  	}
  }
  
  // for DIFF_BTC only!
  unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)
  {
  	if (params.fPowNoRetargeting)
  		return pindexLast->nBits;
  
  	// Limit adjustment step
  	int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;
  	if (nActualTimespan < params.nPowTargetTimespan / 4)
  		nActualTimespan = params.nPowTargetTimespan / 4;
  	if (nActualTimespan > params.nPowTargetTimespan * 4)
  		nActualTimespan = params.nPowTargetTimespan * 4;
  
  	// Retarget
  	const arith_uint256 bnPowLimit = UintToArith256(params.powLimit);
  	arith_uint256 bnNew;
  	bnNew.SetCompact(pindexLast->nBits);
  	bnNew *= nActualTimespan;
  	bnNew /= params.nPowTargetTimespan;
  
  	if (bnNew > bnPowLimit)
  		bnNew = bnPowLimit;
  
  	return bnNew.GetCompact();
  }
  
  bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
  {
  	bool fNegative;
  	bool fOverflow;
  	arith_uint256 bnTarget;
  
  	bnTarget.SetCompact(nBits, &fNegative, &fOverflow);
  
  	// Check range
  	if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.powLimit))
  		return false;
  
  	// Check proof of work matches claimed amount
  	if (UintToArith256(hash) > bnTarget)
  		return false;
  
  	return true;
  }
  