Skip to content

Commit

Permalink
Implement simple moving average over work difficulty adjustement algo…
Browse files Browse the repository at this point in the history
…rithm.

Summary: As per title. Hopefully this is much simpler than alternative, but more complex alternative do not perform suffisciently better to be worth it.

Test Plan:
  make check

Added unit tests for it.

Reviewers: kyuupichan, Mengerian, #bitcoin_abc, schancel

Reviewed By: Mengerian, #bitcoin_abc, schancel

Subscribers: zanza321

Differential Revision: https://reviews.bitcoinabc.org/D601
  • Loading branch information
deadalnix committed Oct 30, 2017
1 parent a438bb8 commit be51cf2
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 4 deletions.
7 changes: 5 additions & 2 deletions src/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,12 @@ class CBlockIndex {
};

arith_uint256 GetBlockProof(const CBlockIndex &block);
/** Return the time it would take to redo the work difference between from and

/**
* Return the time it would take to redo the work difference between from and
* to, assuming the current hashrate corresponds to the difficulty at tip, in
* seconds. */
* seconds.
*/
int64_t GetBlockProofEquivalentTime(const CBlockIndex &to,
const CBlockIndex &from,
const CBlockIndex &tip,
Expand Down
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define BITCOIN_CONSENSUS_PARAMS_H

#include "uint256.h"

#include <map>
#include <string>

Expand Down
122 changes: 122 additions & 0 deletions src/pow.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

Expand Down Expand Up @@ -149,3 +150,124 @@ bool CheckProofOfWork(uint256 hash, uint32_t nBits,

return true;
}

/**
* Compute the a target based on the work done between 2 blocks and the time
* required to produce that work.
*/
static arith_uint256 ComputeTarget(const CBlockIndex *pindexFirst,
const CBlockIndex *pindexLast,
const Consensus::Params &params) {
assert(pindexLast->nHeight > pindexFirst->nHeight);

/**
* From the total work done and the time it took to produce that much work,
* we can deduce how much work we expect to be produced in the targeted time
* between blocks.
*/
arith_uint256 work = pindexLast->nChainWork - pindexFirst->nChainWork;
work *= params.nPowTargetSpacing;

// In order to avoid difficulty cliffs, we bound the amplitude of the
// adjustement we are going to do.
assert(pindexLast->nTime > pindexFirst->nTime);
int64_t nActualTimespan = pindexLast->nTime - pindexFirst->nTime;
if (nActualTimespan > 288 * params.nPowTargetSpacing) {
nActualTimespan = 288 * params.nPowTargetSpacing;
} else if (nActualTimespan < 72 * params.nPowTargetSpacing) {
nActualTimespan = 72 * params.nPowTargetSpacing;
}

work /= nActualTimespan;

/**
* We need to compute T = (2^256 / W) - 1 but 2^256 doesn't fit in 256 bits.
* By expressing 1 as W / W, we get (2^256 - W) / W, and we can compute
* 2^256 - W as the complement of W.
*/
return (-work) / work;
}

/**
* To reduce the impact of timestamp manipulation, we select the block we are
* basing our computation on via a median of 3.
*/
static const CBlockIndex *GetSuitableBlock(const CBlockIndex *pindex) {
assert(pindex->nHeight >= 3);

/**
* In order to avoid a block is a very skewed timestamp to have too much
* influence, we select the median of the 3 top most blocks as a starting
* point.
*/
const CBlockIndex *blocks[3];
blocks[2] = pindex;
blocks[1] = pindex->pprev;
blocks[0] = blocks[1]->pprev;

// Sorting network.
if (blocks[0]->nTime > blocks[2]->nTime) {
std::swap(blocks[0], blocks[2]);
}

if (blocks[0]->nTime > blocks[1]->nTime) {
std::swap(blocks[0], blocks[1]);
}

if (blocks[1]->nTime > blocks[2]->nTime) {
std::swap(blocks[1], blocks[2]);
}

// We should have our candidate in the middle now.
return blocks[1];
}

/**
* Compute the next required proof of work using a weighted average of the
* estimated hashrate per block.
*
* Using a weighted average ensure that the timestamp parameter cancels out in
* most of the calculation - except for the timestamp of the first and last
* block. Because timestamps are the least trustworthy information we have as
* input, this ensures the algorithm is more resistant to malicious inputs.
*/
uint32_t GetNextCashWorkRequired(const CBlockIndex *pindexPrev,
const CBlockHeader *pblock,
const Consensus::Params &params) {
// This cannot handle the genesis block and early blocks in general.
assert(pindexPrev);

// Special difficulty rule for testnet:
// If the new block's timestamp is more than 2* 10 minutes then allow
// mining of a min-difficulty block.
if (params.fPowAllowMinDifficultyBlocks &&
(pblock->GetBlockTime() >
pindexPrev->GetBlockTime() + 2 * params.nPowTargetSpacing)) {
return UintToArith256(params.powLimit).GetCompact();
}

// Compute the difficulty based on the full adjustement interval.
const uint32_t nHeight = pindexPrev->nHeight;
assert(nHeight >= params.DifficultyAdjustmentInterval());

// Get the last suitable block of the difficulty interval.
const CBlockIndex *pindexLast = GetSuitableBlock(pindexPrev);
assert(pindexLast);

// Get the first suitable block of the difficulty interval.
uint32_t nHeightFirst = nHeight - 144;
const CBlockIndex *pindexFirst =
GetSuitableBlock(pindexPrev->GetAncestor(nHeightFirst));
assert(pindexFirst);

// Compute the target based on time and work done during the interval.
const arith_uint256 nextTarget =
ComputeTarget(pindexFirst, pindexLast, params);

const arith_uint256 powLimit = UintToArith256(params.powLimit);
if (nextTarget > powLimit) {
return powLimit.GetCompact();
}

return nextTarget.GetCompact();
}
7 changes: 7 additions & 0 deletions src/pow.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ uint32_t CalculateNextWorkRequired(const CBlockIndex *pindexPrev,
*/
bool CheckProofOfWork(uint256 hash, uint32_t nBits, const Consensus::Params &);

/**
* Bitcoin cash's difficulty adjustment mechanism.
*/
uint32_t GetNextCashWorkRequired(const CBlockIndex *pindexPrev,
const CBlockHeader *pblock,
const Consensus::Params &params);

#endif // BITCOIN_POW_H
184 changes: 182 additions & 2 deletions src/test/pow_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test) {
blocks[i].nTime = 1269211443 + i * params.nPowTargetSpacing;
blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */
blocks[i].nChainWork =
i ? blocks[i - 1].nChainWork + GetBlockProof(blocks[i - 1])
i ? blocks[i - 1].nChainWork + GetBlockProof(blocks[i])
: arith_uint256(0);
}

Expand All @@ -106,6 +106,7 @@ static CBlockIndex GetBlockIndex(CBlockIndex *pindexPrev, int64_t nTimeInterval,
block.nTime = pindexPrev->nTime + nTimeInterval;
block.nBits = nBits;

block.nChainWork = pindexPrev->nChainWork + GetBlockProof(block);
return block;
}

Expand All @@ -119,12 +120,14 @@ BOOST_AUTO_TEST_CASE(retargeting_test) {
arith_uint256 currentPow = powLimit >> 1;
uint32_t initialBits = currentPow.GetCompact();

// Genesis block?
// Genesis block.
blocks[0] = CBlockIndex();
blocks[0].nHeight = 0;
blocks[0].nTime = 1269211443;
blocks[0].nBits = initialBits;

blocks[0].nChainWork = GetBlockProof(blocks[0]);

// Pile up some blocks.
for (size_t i = 1; i < 100; i++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], params.nPowTargetSpacing,
Expand Down Expand Up @@ -187,4 +190,181 @@ BOOST_AUTO_TEST_CASE(retargeting_test) {
powLimit.GetCompact());
}

BOOST_AUTO_TEST_CASE(cash_difficulty_test) {
SelectParams(CBaseChainParams::MAIN);
const Consensus::Params &params = Params().GetConsensus();

std::vector<CBlockIndex> blocks(3000);

const arith_uint256 powLimit = UintToArith256(params.powLimit);
uint32_t powLimitBits = powLimit.GetCompact();
arith_uint256 currentPow = powLimit >> 4;
uint32_t initialBits = currentPow.GetCompact();

// Genesis block.
blocks[0] = CBlockIndex();
blocks[0].nHeight = 0;
blocks[0].nTime = 1269211443;
blocks[0].nBits = initialBits;

blocks[0].nChainWork = GetBlockProof(blocks[0]);

// Block counter.
size_t i;

// Pile up some blocks every 10 mins to establish some history.
for (i = 1; i < 2050; i++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 600, initialBits);
}

CBlockHeader blkHeaderDummy;
uint32_t nBits =
GetNextCashWorkRequired(&blocks[2049], &blkHeaderDummy, params);

// Difficulty stays the same as long as we produce a block every 10 mins.
for (size_t j = 0; j < 10; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 600, nBits);
BOOST_CHECK_EQUAL(
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params),
nBits);
}

// Make sure we skip over blocks that are out of wack. To do so, we produce
// a block that is far in the future, and then produce a block with the
// expected timestamp.
blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
BOOST_CHECK_EQUAL(
GetNextCashWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);
blocks[i] = GetBlockIndex(&blocks[i - 1], 2 * 600 - 6000, nBits);
BOOST_CHECK_EQUAL(
GetNextCashWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);

// The system should continue unaffected by the block with a bogous
// timestamps.
for (size_t j = 0; j < 20; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 600, nBits);
BOOST_CHECK_EQUAL(
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params),
nBits);
}

// We start emitting blocks slightly faster. The first block has no impact.
blocks[i] = GetBlockIndex(&blocks[i - 1], 550, nBits);
BOOST_CHECK_EQUAL(
GetNextCashWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);

// Now we should see difficulty increase slowly.
for (size_t j = 0; j < 10; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 550, nBits);
const uint32_t nextBits =
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params);

arith_uint256 currentTarget;
currentTarget.SetCompact(nBits);
arith_uint256 nextTarget;
nextTarget.SetCompact(nextBits);

// Make sure that difficulty increases very slowly.
BOOST_CHECK(nextTarget < currentTarget);
BOOST_CHECK((currentTarget - nextTarget) < (currentTarget >> 10));

nBits = nextBits;
}

// Check the actual value.
BOOST_CHECK_EQUAL(nBits, 0x1c0fe7b1);

// If we dramatically shorten block production, difficulty increases faster.
for (size_t j = 0; j < 20; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 10, nBits);
const uint32_t nextBits =
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params);

arith_uint256 currentTarget;
currentTarget.SetCompact(nBits);
arith_uint256 nextTarget;
nextTarget.SetCompact(nextBits);

// Make sure that difficulty increases faster.
BOOST_CHECK(nextTarget < currentTarget);
BOOST_CHECK((currentTarget - nextTarget) < (currentTarget >> 4));

nBits = nextBits;
}

// Check the actual value.
BOOST_CHECK_EQUAL(nBits, 0x1c0db19f);

// We start to emit blocks significantly slower. The first block has no
// impact.
blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
nBits = GetNextCashWorkRequired(&blocks[i++], &blkHeaderDummy, params);

// Check the actual value.
BOOST_CHECK_EQUAL(nBits, 0x1c0d9222);

// If we dramatically slow down block production, difficulty decreases.
for (size_t j = 0; j < 93; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
const uint32_t nextBits =
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params);

arith_uint256 currentTarget;
currentTarget.SetCompact(nBits);
arith_uint256 nextTarget;
nextTarget.SetCompact(nextBits);

// Check the difficulty decreases.
BOOST_CHECK(nextTarget <= powLimit);
BOOST_CHECK(nextTarget > currentTarget);
BOOST_CHECK((nextTarget - currentTarget) < (currentTarget >> 3));

nBits = nextBits;
}

// Check the actual value.
BOOST_CHECK_EQUAL(nBits, 0x1c2f13b9);

// Due to the window of time being bounded, next block's difficulty actually
// gets harder.
blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
nBits = GetNextCashWorkRequired(&blocks[i++], &blkHeaderDummy, params);
BOOST_CHECK_EQUAL(nBits, 0x1c2ee9bf);

// And goes down again. It takes a while due to the window being bounded and
// the skewed block causes 2 blocks to get out of the window.
for (size_t j = 0; j < 192; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
const uint32_t nextBits =
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params);

arith_uint256 currentTarget;
currentTarget.SetCompact(nBits);
arith_uint256 nextTarget;
nextTarget.SetCompact(nextBits);

// Check the difficulty decreases.
BOOST_CHECK(nextTarget <= powLimit);
BOOST_CHECK(nextTarget > currentTarget);
BOOST_CHECK((nextTarget - currentTarget) < (currentTarget >> 3));

nBits = nextBits;
}

// Check the actual value.
BOOST_CHECK_EQUAL(nBits, 0x1d00ffff);

// Once the difficulty reached the minimum allowed level, it doesn't get any
// easier.
for (size_t j = 0; j < 5; i++, j++) {
blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
const uint32_t nextBits =
GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params);

// Check the difficulty stays constant.
BOOST_CHECK_EQUAL(nextBits, powLimitBits);
nBits = nextBits;
}
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit be51cf2

Please sign in to comment.