Skip to content

Commit

Permalink
Merge #11748: [Tests] Adding unit tests for GetDifficulty in blockcha…
Browse files Browse the repository at this point in the history
…in.cpp.

3e1ee31 [Tests] Adding unit tests for GetDifficulty in blockchain.cpp. (sean)

Pull request description:

  blockchain.cpp has low unit test coverage. This commit is intended
  to start improving its code coverage to reasonable levels. One or more
  follow up commits will complete the task that this commit is starting
  (though the usefulness of this commit is not dependent upon later
  commits).

  Note that these tests were not written based upon a specification of how
  GetDifficulty *should* work, but rather how it actually *does* work. As
  a result, if there are any bugs in the current GetDifficulty
  implementation, these unit tests serve to lock them in rather than
  expose them.

  -- Why has blockchain.cpp been modified if this is a unit testing change?

  Since the existing GetDifficulty function relies on a global variable,
  chainActive, it was not suitable for unit testing purposes. Both the
  existing GetDifficulty function and the unit tests now call through to
  a new, more modular version of GetDifficulty that can work on any chain,
  not just chainActive.

  -- Why does blockchain_tests.cpp directly include blockchain.cpp instead
  of blockchain.h?

  While the new GetDifficulty function's signature is arguably better than
  the old one's, it still isn't great, and doesn't seem to warrant inclusion
  as part of the blockchain.h API, especially since only test code is
  directly using it. If a better way of exposing the new GetDifficulty
  function to unit tests exists, please mention it and the commit will be
  updated accordingly.

  -- Why is the test fixture named blockchain_difficulty_tests rather than
  blockchain_tests?

  The Bitcoin Core policy for naming unit test files is to match the the
  file under test ("blockchain" becomes "blockchain_tests"). While this
  commit complies with that, blockchain.cpp is a massive file, such that
  having all of the unit tests in one file will tend towards disorder.
  Since there will be a lot more tests added to this file, the intention
  is to divide up different types of tests into different test fixtures
  within the same file.

Tree-SHA512: a7dda9c2a9414d4819b4d2911f5637891dc19cecbecfc1463846161d2a78793151927a5ab911c69a5d3013f7668e75a1d78a65667cb9d83910cda439cbe84d62
  • Loading branch information
laanwj committed Dec 23, 2017
2 parents 9bad8d6 + 3e1ee31 commit 20166f8
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Expand Up @@ -33,6 +33,7 @@ BITCOIN_TESTS =\
test/base64_tests.cpp \
test/bech32_tests.cpp \
test/bip32_tests.cpp \
test/blockchain_tests.cpp \
test/blockencodings_tests.cpp \
test/bloom_tests.cpp \
test/bswap_tests.cpp \
Expand Down
15 changes: 11 additions & 4 deletions src/rpc/blockchain.cpp
Expand Up @@ -47,18 +47,20 @@ static CUpdatedBlock latestblock;

extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);

double GetDifficulty(const CBlockIndex* blockindex)
/* Calculate the difficulty for a given block index,
* or the block index of the given chain.
*/
double GetDifficulty(const CChain& chain, const CBlockIndex* blockindex)
{
if (blockindex == nullptr)
{
if (chainActive.Tip() == nullptr)
if (chain.Tip() == nullptr)
return 1.0;
else
blockindex = chainActive.Tip();
blockindex = chain.Tip();
}

int nShift = (blockindex->nBits >> 24) & 0xff;

double dDiff =
(double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff);

Expand All @@ -76,6 +78,11 @@ double GetDifficulty(const CBlockIndex* blockindex)
return dDiff;
}

double GetDifficulty(const CBlockIndex* blockindex)
{
return GetDifficulty(chainActive, blockindex);
}

UniValue blockheaderToJSON(const CBlockIndex* blockindex)
{
AssertLockHeld(cs_main);
Expand Down
126 changes: 126 additions & 0 deletions src/test/blockchain_tests.cpp
@@ -0,0 +1,126 @@
#include <boost/test/unit_test.hpp>

#include "stdlib.h"

#include "rpc/blockchain.cpp"
#include "test/test_bitcoin.h"

/* Equality between doubles is imprecise. Comparison should be done
* with a small threshold of tolerance, rather than exact equality.
*/
bool DoubleEquals(double a, double b, double epsilon)
{
return std::abs(a - b) < epsilon;
}

CBlockIndex* CreateBlockIndexWithNbits(uint32_t nbits)
{
CBlockIndex* block_index = new CBlockIndex();
block_index->nHeight = 46367;
block_index->nTime = 1269211443;
block_index->nBits = nbits;
return block_index;
}

CChain CreateChainWithNbits(uint32_t nbits)
{
CBlockIndex* block_index = CreateBlockIndexWithNbits(nbits);
CChain chain;
chain.SetTip(block_index);
return chain;
}

void RejectDifficultyMismatch(double difficulty, double expected_difficulty) {
BOOST_CHECK_MESSAGE(
DoubleEquals(difficulty, expected_difficulty, 0.00001),
"Difficulty was " + std::to_string(difficulty)
+ " but was expected to be " + std::to_string(expected_difficulty));
}

/* Given a BlockIndex with the provided nbits,
* verify that the expected difficulty results.
*/
void TestDifficulty(uint32_t nbits, double expected_difficulty)
{
CBlockIndex* block_index = CreateBlockIndexWithNbits(nbits);
/* Since we are passing in block index explicitly,
* there is no need to set up anything within the chain itself.
*/
CChain chain;

double difficulty = GetDifficulty(chain, block_index);
delete block_index;

RejectDifficultyMismatch(difficulty, expected_difficulty);
}

BOOST_FIXTURE_TEST_SUITE(blockchain_difficulty_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(get_difficulty_for_very_low_target)
{
TestDifficulty(0x1f111111, 0.000001);
}

BOOST_AUTO_TEST_CASE(get_difficulty_for_low_target)
{
TestDifficulty(0x1ef88f6f, 0.000016);
}

BOOST_AUTO_TEST_CASE(get_difficulty_for_mid_target)
{
TestDifficulty(0x1df88f6f, 0.004023);
}

BOOST_AUTO_TEST_CASE(get_difficulty_for_high_target)
{
TestDifficulty(0x1cf88f6f, 1.029916);
}

BOOST_AUTO_TEST_CASE(get_difficulty_for_very_high_target)
{
TestDifficulty(0x12345678, 5913134931067755359633408.0);
}

// Verify that difficulty is 1.0 for an empty chain.
BOOST_AUTO_TEST_CASE(get_difficulty_for_null_tip)
{
CChain chain;
double difficulty = GetDifficulty(chain, nullptr);
RejectDifficultyMismatch(difficulty, 1.0);
}

/* Verify that if difficulty is based upon the block index
* in the chain, if no block index is explicitly specified.
*/
BOOST_AUTO_TEST_CASE(get_difficulty_for_null_block_index)
{
CChain chain = CreateChainWithNbits(0x1df88f6f);

double difficulty = GetDifficulty(chain, nullptr);
delete chain.Tip();

double expected_difficulty = 0.004023;

RejectDifficultyMismatch(difficulty, expected_difficulty);
}

/* Verify that difficulty is based upon the explicitly specified
* block index rather than being taken from the provided chain,
* when both are present.
*/
BOOST_AUTO_TEST_CASE(get_difficulty_for_block_index_overrides_tip)
{
CChain chain = CreateChainWithNbits(0x1df88f6f);
/* This block index's nbits should be used
* instead of the chain's when calculating difficulty.
*/
CBlockIndex* override_block_index = CreateBlockIndexWithNbits(0x12345678);

double difficulty = GetDifficulty(chain, override_block_index);
delete chain.Tip();
delete override_block_index;

RejectDifficultyMismatch(difficulty, 5913134931067755359633408.0);
}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 20166f8

Please sign in to comment.