diff --git a/configure.ac b/configure.ac index 36727004887..d7b92fcdb95 100644 --- a/configure.ac +++ b/configure.ac @@ -654,6 +654,7 @@ if test x$use_upnp != xno; then ) fi + BITCOIN_QT_INIT dnl sets $bitcoin_enable_qt, $bitcoin_enable_qt_test, $bitcoin_enable_qt_dbus @@ -741,6 +742,14 @@ if test x$use_tests = xyes; then fi fi +if test x$use_tests = xyes; then + dnl check for rapidcheck + AC_CHECK_HEADERS( + [rapidcheck.h], + [AC_CHECK_LIB([rapidcheck], [main],[], [])], + []) +fi + if test x$use_boost = xyes; then BOOST_LIBS="$BOOST_LDFLAGS $BOOST_SYSTEM_LIB $BOOST_FILESYSTEM_LIB $BOOST_PROGRAM_OPTIONS_LIB $BOOST_THREAD_LIB $BOOST_CHRONO_LIB" diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 088723ebd0d..4d2b424a7c7 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,4 +1,4 @@ -packages:=boost openssl libevent zeromq +packages:=boost openssl libevent zeromq rapidcheck native_packages := native_ccache qt_native_packages = native_protobuf diff --git a/depends/packages/rapidcheck.mk b/depends/packages/rapidcheck.mk new file mode 100644 index 00000000000..36e4e8f4955 --- /dev/null +++ b/depends/packages/rapidcheck.mk @@ -0,0 +1,13 @@ +package=rapidcheck +$(package)_version=f5d3afa +$(package)_download_path=https://bitcoin-10596.firebaseapp.com/depends +$(package)_file_name=$(package)-$($(package)_version).tar.gz +$(package)_sha256_hash=78cdb8d0185b602e32e66f4e5d1a6ceec1f801dd9641b8a9456c386b1eaaf0e5 + +define $(package)_config_cmds + cmake -DCMAKE_INSTALL_PREFIX=$(build_prefix) +endef + +define $(package)_build_cmds + $(MAKE) +endef diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ee1c11ff1f6..9b22313ee02 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -84,7 +84,20 @@ BITCOIN_TESTS =\ test/versionbits_tests.cpp \ test/uint256_tests.cpp \ test/univalue_tests.cpp \ - test/util_tests.cpp + test/util_tests.cpp \ + test/gen/crypto_gen.h \ + test/gen/script_gen.h \ + test/gen/transaction_gen.h \ + test/gen/block_gen.h \ + test/gen/merkleblock_gen.h \ + test/gen/bloom_gen.cpp \ + test/gen/bloom_gen.h \ + test/key_properties.cpp \ + test/script_properties.cpp \ + test/block_properties.cpp \ + test/merkleblock_properties.cpp \ + test/bloom_properties.cpp \ + test/transaction_properties.cpp if ENABLE_WALLET BITCOIN_TESTS += \ diff --git a/src/test/block_properties.cpp b/src/test/block_properties.cpp new file mode 100644 index 00000000000..856092fdf3f --- /dev/null +++ b/src/test/block_properties.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +#include "test/test_bitcoin.h" +#include "primitives/block.h" +#include "test/gen/block_gen.h" + + +BOOST_FIXTURE_TEST_SUITE(block_properties, BasicTestingSetup) + +RC_BOOST_PROP(blockheader_serialization_symmetry, (CBlockHeader header)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << header; + CBlockHeader header2; + ss >> header2; + CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION); + ss << header; + ss1 << header2; + RC_ASSERT(ss.str() == ss1.str()); +} + +RC_BOOST_PROP(block_serialization_symmetry, (CBlock block)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << block; + CBlock block2; + ss >> block2; + RC_ASSERT(block.GetHash() == block2.GetHash()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/bloom_properties.cpp b/src/test/bloom_properties.cpp new file mode 100644 index 00000000000..dd6aa6455e7 --- /dev/null +++ b/src/test/bloom_properties.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2012-2016 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 "key.h" + +#include "test/test_bitcoin.h" + +#include +#include +#include +#include + +#include "test/gen/bloom_gen.h" +#include "test/gen/crypto_gen.h" + +BOOST_FIXTURE_TEST_SUITE(bloom_properties, BasicTestingSetup) + +RC_BOOST_PROP(no_false_negatives, (CBloomFilter bloomFilter, uint256 hash)) { + bloomFilter.insert(hash); + RC_ASSERT(bloomFilter.contains(hash)); +} + +RC_BOOST_PROP(serialization_symmetry, (CBloomFilter bloomFilter)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << bloomFilter; + CBloomFilter bloomFilter2; + ss >> bloomFilter2; + CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION); + ss << bloomFilter; + ss1 << bloomFilter2; + RC_ASSERT(ss.str() == ss1.str()); +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/gen/block_gen.h b/src/test/gen/block_gen.h new file mode 100644 index 00000000000..4d5285ff616 --- /dev/null +++ b/src/test/gen/block_gen.h @@ -0,0 +1,57 @@ +#ifndef BITCOIN_TEST_GEN_BLOCK_GEN_H +#define BITCOIN_TEST_GEN_BLOCK_GEN_H + +#include "test/gen/crypto_gen.h" +#include "test/gen/transaction_gen.h" +#include "uint256.h" +#include "primitives/block.h" + +#include +#include + +/** Generator for the primitives of a block header */ +inline rc::Gen> blockHeaderPrimitives() { + return rc::gen::tuple(rc::gen::arbitrary(), + rc::gen::arbitrary(), rc::gen::arbitrary(), + rc::gen::arbitrary(), rc::gen::arbitrary(), rc::gen::arbitrary()); +} + +namespace rc { + + /** Generator for a new CBlockHeader */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(blockHeaderPrimitives(), [](std::tuple headerPrimitives) { + int32_t nVersion; + uint256 hashPrevBlock; + uint256 hashMerkleRoot; + uint32_t nTime; + uint32_t nBits; + uint32_t nNonce; + std::tie(nVersion,hashPrevBlock, hashMerkleRoot, nTime,nBits,nNonce) = headerPrimitives; + CBlockHeader header; + header.nVersion = nVersion; + header.hashPrevBlock = hashPrevBlock; + header.hashMerkleRoot = hashMerkleRoot; + header.nTime = nTime; + header.nBits = nBits; + header.nNonce = nNonce; + return header; + }); + }; + }; + + /** Generator for a new CBlock */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::nonEmpty>(), [](std::vector txRefs) { + CBlock block; + block.vtx = txRefs; + return block; + }); + } + }; +} +#endif diff --git a/src/test/gen/bloom_gen.h b/src/test/gen/bloom_gen.h new file mode 100644 index 00000000000..78ac3cae7dd --- /dev/null +++ b/src/test/gen/bloom_gen.h @@ -0,0 +1,43 @@ +#ifndef BITCOIN_TEST_GEN_BLOOM_GEN_H +#define BITCOIN_TEST_GEN_BLOOM_GEN_H + +#include "bloom.h" +#include "merkleblock.h" + +#include + +#include +#include + +/** Generates a double between 0,1 exclusive */ +rc::Gen BetweenZeroAndOne(); + +rc::Gen> BloomFilterPrimitives(); + +namespace rc { + + + /** Generator for a new CBloomFilter*/ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(BloomFilterPrimitives(), [](std::tuple filterPrimitives) { + unsigned int numElements; + double fpRate; + unsigned int nTweakIn; + unsigned int bloomFlag; + std::tie(numElements, fpRate, nTweakIn, bloomFlag) = filterPrimitives; + return CBloomFilter(numElements,fpRate,nTweakIn,bloomFlag); + }); + }; + }; +} + +/** Returns a bloom filter loaded with the given uint256s */ +rc::Gen>> LoadedBloomFilter(); + +/** Loads an arbitrary bloom filter with the given hashes */ +rc::Gen>> LoadBloomFilter(std::vector& hashes); + + +#endif diff --git a/src/test/gen/crypto_gen.h b/src/test/gen/crypto_gen.h new file mode 100644 index 00000000000..9e40ee5c3b7 --- /dev/null +++ b/src/test/gen/crypto_gen.h @@ -0,0 +1,53 @@ +#ifndef BITCOIN_TEST_GEN_CRYPTO_GEN_H +#define BITCOIN_TEST_GEN_CRYPTO_GEN_H + +#include "key.h" +#include "random.h" +#include "uint256.h" +#include +#include +#include +#include +namespace rc { + + /** Generator for a new CKey */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return rc::gen::map([](int x) { + CKey key; + key.MakeNewKey(true); + return key; + }); + }; + }; + + /** Generator for a CPrivKey */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::arbitrary(), [](CKey key) { + return key.GetPrivKey(); + }); + }; + }; + + /** Generator for a new CPubKey */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::arbitrary(), [](CKey key) { + return key.GetPubKey(); + }); + }; + }; + + /** Generates a arbitrary uint256 */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return rc::gen::just(GetRandHash()); + }; + }; +} +#endif diff --git a/src/test/gen/merkleblock_gen.h b/src/test/gen/merkleblock_gen.h new file mode 100644 index 00000000000..18db61d4651 --- /dev/null +++ b/src/test/gen/merkleblock_gen.h @@ -0,0 +1,89 @@ +#ifndef BITCOIN_TEST_GEN_MERKLEBLOCK_GEN_H +#define BITCOIN_TEST_GEN_MERKLEBLOCK_GEN_H + +#include +#include + +#include "merkleblock.h" +#include "test/gen/block_gen.h" + +#include +namespace rc { + /** Returns a CMerkleblock with the hashes that match inside of the CPartialMerkleTree */ + template<> + struct Arbitrary>> { + static Gen>> arbitrary() { + return gen::map(gen::arbitrary(), [](CBlock block) { + std::set hashes; + for(unsigned int i = 0; i < block.vtx.size(); i++) { + //pretty naive to include every other txid in the merkle block + //but this will work for now. + if (i % 2 == 0) { + hashes.insert(block.vtx[i]->GetHash()); + } + } + return std::make_pair(CMerkleBlock(block,hashes),hashes); + }); + }; + }; + + + Gen> betweenZeroAnd100 = gen::suchThat>([](std::vector hashes) { + return hashes.size() <= 100; + }); + + /** Returns an arbitrary CMerkleBlock */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::arbitrary>>(), + [](std::pair> merkleBlockWithTxIds) { + CMerkleBlock merkleBlock = merkleBlockWithTxIds.first; + std::set insertedHashes = merkleBlockWithTxIds.second; + return merkleBlock; + }); + }; + }; + + + + /** Generates a CPartialMerkleTree and returns the PartialMerkleTree along + * with the txids that should be matched inside of it */ + template<> + struct Arbitrary>> { + static Gen>> arbitrary() { + return gen::map(gen::arbitrary>(), [](std::vector txids) { + //note this use of 'gen::nonEmpty' above, if we have an empty vector of txids + //we will get a memory access violation when calling the CPartialMerkleTree + //constructor below. On one hand we shouldn't every have CPartialMerkleTree + //with no txids, but on the other hand, it seems we should call + //CPartialMerkleTree() inside of CPartialMerkleTree(txids,matches) + //if we have zero txids. + //Some one who knows more than me will have to elaborate if the memory access violation + //is a desirable failure mode or not... + std::vector matches; + std::vector matchedTxs; + for(unsigned int i = 0; i < txids.size(); i++) { + //pretty naive to include every other txid in the merkle block + //but this will work for now. + matches.push_back(i % 2 == 1); + if (i % 2 == 1) { + matchedTxs.push_back(txids[i]); + } + } + return std::make_pair(CPartialMerkleTree(txids,matches),matchedTxs); + }); + }; + }; + + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::arbitrary>>(), + [](std::pair> p) { + return p.first; + }); + }; + }; +} +#endif diff --git a/src/test/gen/script_gen.h b/src/test/gen/script_gen.h new file mode 100644 index 00000000000..af207aadbd3 --- /dev/null +++ b/src/test/gen/script_gen.h @@ -0,0 +1,20 @@ +#ifndef BITCOIN_TEST_GEN_SCRIPT_GEN_H +#define BITCOIN_TEST_GEN_SCRIPT_GEN_H + +#include "script/script.h" +#include +#include +#include +#include +namespace rc { + + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::arbitrary>(), [](std::vector script) { + return CScript(script); + }); + }; + }; +} +#endif diff --git a/src/test/gen/transaction_gen.h b/src/test/gen/transaction_gen.h new file mode 100644 index 00000000000..2450abfc71c --- /dev/null +++ b/src/test/gen/transaction_gen.h @@ -0,0 +1,98 @@ +#ifndef BITCOIN_TEST_GEN_TRANSACTION_GEN_H +#define BITCOIN_TEST_GEN_TRANSACTION_GEN_H + +#include "test/gen/crypto_gen.h" +#include "test/gen/script_gen.h" + +#include "script/script.h" +#include "primitives/transaction.h" +#include "amount.h" + +#include +#include +#include + +namespace rc { + /** Generator for a COutPoint */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::tuple(gen::arbitrary(), gen::arbitrary()), [](std::tuple outPointPrimitives) { + uint32_t nIn; + uint256 nHashIn; + std::tie(nHashIn, nIn) = outPointPrimitives; + return COutPoint(nHashIn, nIn); + }); + }; + }; + + /** Generator for a CTxIn */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::tuple(gen::arbitrary(), gen::arbitrary(), gen::arbitrary()), [](std::tuple txInPrimitives) { + COutPoint outpoint; + CScript script; + uint32_t sequence; + std::tie(outpoint,script,sequence) = txInPrimitives; + return CTxIn(outpoint,script,sequence); + }); + }; + }; + + /** Generator for a CAmount */ + template<> + struct Arbitrary { + static Gen arbitrary() { + //why doesn't this generator call work? It seems to cause an infinite loop. + //return gen::arbitrary(); + return gen::inRange(std::numeric_limits::min(),std::numeric_limits::max()); + }; + }; + + /** Generator for CTxOut */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::tuple(gen::arbitrary(), gen::arbitrary()), [](std::tuple txOutPrimitives) { + CAmount amount; + CScript script; + std::tie(amount,script) = txOutPrimitives; + return CTxOut(amount,script); + }); + }; + }; + + /** Generator for a CTransaction */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::tuple(gen::arbitrary(), + gen::nonEmpty>(), gen::nonEmpty>(), gen::arbitrary()), + [](std::tuple, std::vector, uint32_t> txPrimitives) { + CMutableTransaction tx; + int32_t nVersion; + std::vector vin; + std::vector vout; + uint32_t locktime; + std::tie(nVersion, vin, vout, locktime) = txPrimitives; + tx.nVersion=nVersion; + tx.vin=vin; + tx.vout=vout; + tx.nLockTime=locktime; + return CTransaction(tx); + }); + }; + }; + + /** Generator for a CTransactionRef */ + template<> + struct Arbitrary { + static Gen arbitrary() { + return gen::map(gen::arbitrary(), [](CTransaction tx) { + return MakeTransactionRef(tx); + }); + }; + }; +} +#endif diff --git a/src/test/key_properties.cpp b/src/test/key_properties.cpp new file mode 100644 index 00000000000..009578de5a1 --- /dev/null +++ b/src/test/key_properties.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2012-2016 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 "key.h" + +#include "base58.h" +#include "script/script.h" +#include "uint256.h" +#include "util.h" +#include "utilstrencodings.h" +#include "test/test_bitcoin.h" +#include +#include + +#include +#include +#include +#include + +#include "test/gen/crypto_gen.h" + +BOOST_FIXTURE_TEST_SUITE(key_properties, BasicTestingSetup) + +/** Check CKey uniqueness */ +RC_BOOST_PROP(key_uniqueness, (CKey key1, CKey key2)) { + RC_ASSERT(!(key1 == key2)); +} + +/** Verify that a private key generates the correct public key */ +RC_BOOST_PROP(key_generates_correct_pubkey, (CKey key)) { + CPubKey pubKey = key.GetPubKey(); + RC_ASSERT(key.VerifyPubKey(pubKey)); +} + +/** Serialization symmetry CKey -> CBitcoinSecret -> CKey */ +RC_BOOST_PROP(key_bitcoinsecret_symmetry, (CKey key)) { + CBitcoinSecret secret; + secret.SetKey(key); + RC_ASSERT(secret.GetKey() == key); +} + +/** Create a CKey using the 'Set' function must give us the same key */ +RC_BOOST_PROP(key_set_symmetry, (CKey key)) { + CKey key1; + key1.Set(key.begin(), key.end(), key.IsCompressed()); + RC_ASSERT(key1 == key); +} + +/** Create a CKey, sign a piece of data, then verify it with the public key */ +RC_BOOST_PROP(key_sign_symmetry, (CKey key, uint256 hash)) { + std::vector vchSig; + key.Sign(hash,vchSig,0); + CPubKey pubKey = key.GetPubKey(); + RC_ASSERT(pubKey.Verify(hash,vchSig)); +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/merkleblock_properties.cpp b/src/test/merkleblock_properties.cpp new file mode 100644 index 00000000000..7f9b326a912 --- /dev/null +++ b/src/test/merkleblock_properties.cpp @@ -0,0 +1,57 @@ +#include "test/test_bitcoin.h" + +#include +#include +#include +#include + +#include "merkleblock.h" +#include "test/gen/merkleblock_gen.h" + +#include +BOOST_FIXTURE_TEST_SUITE(merkleblock_properties, BasicTestingSetup) + +RC_BOOST_PROP(merkleblock_serialization_symmetry, (CMerkleBlock merkleBlock)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << merkleBlock; + CMerkleBlock merkleBlock2; + ss >> merkleBlock2; + CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION); + ss << merkleBlock; + ss1 << merkleBlock2; + RC_ASSERT(ss.str() == ss1.str()); +} + +/** Should find all txids we inserted in the merkle block */ +RC_BOOST_PROP(merkle_block_match_symmetry, (std::pair> t)) { + CMerkleBlock& merkleBlock = t.first; + std::set& insertedHashes = t.second; + for (unsigned int i = 0; i < merkleBlock.vMatchedTxn.size(); i++) { + const auto& h = merkleBlock.vMatchedTxn[i].second; + RC_ASSERT(insertedHashes.find(h) != insertedHashes.end()); + } +} + +RC_BOOST_PROP(partialmerkletree_serialization_symmetry, (CPartialMerkleTree tree)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << tree; + CPartialMerkleTree tree2; + ss >> tree2; + CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION); + ss << tree; + ss1 << tree2; + RC_ASSERT(ss.str() == ss1.str()); +} + + +/** Should find all txids we inserted in the PartialMerkleTree */ +RC_BOOST_PROP(partialmerkletree_extract_matches_symmetry, (std::pair> p)) { + CPartialMerkleTree tree = p.first; + std::vector expectedMatches = p.second; + std::vector matches; + std::vector indices; + tree.ExtractMatches(matches,indices); + RC_ASSERT(matches == expectedMatches); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_properties.cpp b/src/test/script_properties.cpp new file mode 100644 index 00000000000..892e773d012 --- /dev/null +++ b/src/test/script_properties.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include +#include +#include +#include + +#include "script/script.h" +#include "test/test_bitcoin.h" +#include "test/gen/script_gen.h" + +BOOST_FIXTURE_TEST_SUITE(script_properties, BasicTestingSetup) + +/** Check CScript serialization symmetry */ +RC_BOOST_PROP(cscript_serialization_symmetry, (CScript script)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << static_cast(script); + std::vector deserialized; + ss >> deserialized; + CScript script2 = CScript(deserialized.begin(), deserialized.end()); + RC_ASSERT(script == script2); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/transaction_properties.cpp b/src/test/transaction_properties.cpp new file mode 100644 index 00000000000..ffab91b63f0 --- /dev/null +++ b/src/test/transaction_properties.cpp @@ -0,0 +1,57 @@ + +#include "test/gen/transaction_gen.h" + +#include "key.h" +#include "base58.h" +#include "script/script.h" +#include "uint256.h" +#include "util.h" +#include "utilstrencodings.h" +#include "test/test_bitcoin.h" +#include "streams.h" +#include +#include + +#include +#include +#include +#include + +BOOST_FIXTURE_TEST_SUITE(transaction_properties, BasicTestingSetup) + +/** Check COutpoint serialization symmetry */ +RC_BOOST_PROP(outpoint_serialization_symmetry, (COutPoint outpoint)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << outpoint; + COutPoint outpoint2; + ss >> outpoint2; + RC_ASSERT(outpoint2 == outpoint); +} +/** Check CTxIn serialization symmetry */ +RC_BOOST_PROP(ctxin_serialization_symmetry, (CTxIn txIn)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << txIn; + CTxIn txIn2; + ss >> txIn2; + RC_ASSERT(txIn == txIn2); +} + +/** Check CTxOut serialization symmetry */ +RC_BOOST_PROP(ctxout_serialization_symmetry, (CTxOut txOut)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << txOut; + CTxOut txOut2; + ss >> txOut2; + RC_ASSERT(txOut == txOut2); +} + +/** Check CTransaction serialization symmetry */ +RC_BOOST_PROP(ctransaction_serialization_symmetry, (CTransaction tx)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << tx; + deserialize_type t; + CTransaction tx2(t,ss); + RC_ASSERT(tx == tx2); +} + +BOOST_AUTO_TEST_SUITE_END()