Skip to content

Commit 1d4cfa4

Browse files
committed
Add function to validate difficulty changes
The rule against difficulty adjustments changing by more than a factor of 4 can be helpful for anti-DoS measures in contexts where we lack a full headers chain, so expose this functionality separately and in the narrow case where we only know the height, new value, and old value. Includes fuzz test by Martin Zumsande.
1 parent 2bd9aa5 commit 1d4cfa4

File tree

4 files changed

+125
-4
lines changed

4 files changed

+125
-4
lines changed

src/pow.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,57 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
7171
return bnNew.GetCompact();
7272
}
7373

74+
// Check that on difficulty adjustments, the new difficulty does not increase
75+
// or decrease beyond the permitted limits.
76+
bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t height, uint32_t old_nbits, uint32_t new_nbits)
77+
{
78+
if (params.fPowAllowMinDifficultyBlocks) return true;
79+
80+
if (height % params.DifficultyAdjustmentInterval() == 0) {
81+
int64_t smallest_timespan = params.nPowTargetTimespan/4;
82+
int64_t largest_timespan = params.nPowTargetTimespan*4;
83+
84+
const arith_uint256 pow_limit = UintToArith256(params.powLimit);
85+
arith_uint256 observed_new_target;
86+
observed_new_target.SetCompact(new_nbits);
87+
88+
// Calculate the largest difficulty value possible:
89+
arith_uint256 largest_difficulty_target;
90+
largest_difficulty_target.SetCompact(old_nbits);
91+
largest_difficulty_target *= largest_timespan;
92+
largest_difficulty_target /= params.nPowTargetTimespan;
93+
94+
if (largest_difficulty_target > pow_limit) {
95+
largest_difficulty_target = pow_limit;
96+
}
97+
98+
// Round and then compare this new calculated value to what is
99+
// observed.
100+
arith_uint256 maximum_new_target;
101+
maximum_new_target.SetCompact(largest_difficulty_target.GetCompact());
102+
if (maximum_new_target < observed_new_target) return false;
103+
104+
// Calculate the smallest difficulty value possible:
105+
arith_uint256 smallest_difficulty_target;
106+
smallest_difficulty_target.SetCompact(old_nbits);
107+
smallest_difficulty_target *= smallest_timespan;
108+
smallest_difficulty_target /= params.nPowTargetTimespan;
109+
110+
if (smallest_difficulty_target > pow_limit) {
111+
smallest_difficulty_target = pow_limit;
112+
}
113+
114+
// Round and then compare this new calculated value to what is
115+
// observed.
116+
arith_uint256 minimum_new_target;
117+
minimum_new_target.SetCompact(smallest_difficulty_target.GetCompact());
118+
if (minimum_new_target > observed_new_target) return false;
119+
} else if (old_nbits != new_nbits) {
120+
return false;
121+
}
122+
return true;
123+
}
124+
74125
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
75126
{
76127
bool fNegative;

src/pow.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,18 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
2020
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
2121
bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
2222

23+
/**
24+
* Return false if the proof-of-work requirement specified by new_nbits at a
25+
* given height is not possible, given the proof-of-work on the prior block as
26+
* specified by old_nbits.
27+
*
28+
* This function only checks that the new value is within a factor of 4 of the
29+
* old value for blocks at the difficulty adjustment interval, and otherwise
30+
* requires the values to be the same.
31+
*
32+
* Always returns true on networks where min difficulty blocks are allowed,
33+
* such as regtest/testnet.
34+
*/
35+
bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t height, uint32_t old_nbits, uint32_t new_nbits);
36+
2337
#endif // BITCOIN_POW_H

src/test/fuzz/pow.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,40 @@ FUZZ_TARGET_INIT(pow, initialize_pow)
8383
}
8484
}
8585
}
86+
87+
88+
FUZZ_TARGET_INIT(pow_transition, initialize_pow)
89+
{
90+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
91+
const Consensus::Params& consensus_params{Params().GetConsensus()};
92+
std::vector<std::unique_ptr<CBlockIndex>> blocks;
93+
94+
const uint32_t old_time{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
95+
const uint32_t new_time{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
96+
const int32_t version{fuzzed_data_provider.ConsumeIntegral<int32_t>()};
97+
uint32_t nbits{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
98+
99+
const arith_uint256 pow_limit = UintToArith256(consensus_params.powLimit);
100+
arith_uint256 old_target;
101+
old_target.SetCompact(nbits);
102+
if (old_target > pow_limit) {
103+
nbits = pow_limit.GetCompact();
104+
}
105+
// Create one difficulty adjustment period worth of headers
106+
for (int height = 0; height < consensus_params.DifficultyAdjustmentInterval(); ++height) {
107+
CBlockHeader header;
108+
header.nVersion = version;
109+
header.nTime = old_time;
110+
header.nBits = nbits;
111+
if (height == consensus_params.DifficultyAdjustmentInterval() - 1) {
112+
header.nTime = new_time;
113+
}
114+
auto current_block{std::make_unique<CBlockIndex>(header)};
115+
current_block->pprev = blocks.empty() ? nullptr : blocks.back().get();
116+
current_block->nHeight = height;
117+
blocks.emplace_back(std::move(current_block)).get();
118+
}
119+
auto last_block{blocks.back().get()};
120+
unsigned int new_nbits{GetNextWorkRequired(last_block, nullptr, consensus_params)};
121+
Assert(PermittedDifficultyTransition(consensus_params, last_block->nHeight + 1, last_block->nBits, new_nbits));
122+
}

src/test/pow_tests.cpp

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ BOOST_AUTO_TEST_CASE(get_next_work)
2020
pindexLast.nHeight = 32255;
2121
pindexLast.nTime = 1262152739; // Block #32255
2222
pindexLast.nBits = 0x1d00ffff;
23-
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00d86aU);
23+
24+
// Here (and below): expected_nbits is calculated in
25+
// CalculateNextWorkRequired(); redoing the calculation here would be just
26+
// reimplementing the same code that is written in pow.cpp. Rather than
27+
// copy that code, we just hardcode the expected result.
28+
unsigned int expected_nbits = 0x1d00d86aU;
29+
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
30+
BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
2431
}
2532

2633
/* Test the constraint on the upper bound for next work */
@@ -32,7 +39,9 @@ BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
3239
pindexLast.nHeight = 2015;
3340
pindexLast.nTime = 1233061996; // Block #2015
3441
pindexLast.nBits = 0x1d00ffff;
35-
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00ffffU);
42+
unsigned int expected_nbits = 0x1d00ffffU;
43+
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
44+
BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
3645
}
3746

3847
/* Test the constraint on the lower bound for actual time taken */
@@ -44,7 +53,12 @@ BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
4453
pindexLast.nHeight = 68543;
4554
pindexLast.nTime = 1279297671; // Block #68543
4655
pindexLast.nBits = 0x1c05a3f4;
47-
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1c0168fdU);
56+
unsigned int expected_nbits = 0x1c0168fdU;
57+
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
58+
BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
59+
// Test that reducing nbits further would not be a PermittedDifficultyTransition.
60+
unsigned int invalid_nbits = expected_nbits-1;
61+
BOOST_CHECK(!PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, invalid_nbits));
4862
}
4963

5064
/* Test the constraint on the upper bound for actual time taken */
@@ -56,7 +70,12 @@ BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
5670
pindexLast.nHeight = 46367;
5771
pindexLast.nTime = 1269211443; // Block #46367
5872
pindexLast.nBits = 0x1c387f6f;
59-
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), 0x1d00e1fdU);
73+
unsigned int expected_nbits = 0x1d00e1fdU;
74+
BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, chainParams->GetConsensus()), expected_nbits);
75+
BOOST_CHECK(PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, expected_nbits));
76+
// Test that increasing nbits further would not be a PermittedDifficultyTransition.
77+
unsigned int invalid_nbits = expected_nbits+1;
78+
BOOST_CHECK(!PermittedDifficultyTransition(chainParams->GetConsensus(), pindexLast.nHeight+1, pindexLast.nBits, invalid_nbits));
6079
}
6180

6281
BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_negative_target)

0 commit comments

Comments
 (0)