Page MenuHomePhabricator

No OneTemporary

diff --git a/src/rest.cpp b/src/rest.cpp
index 159595fb2..5d1282a74 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -1,686 +1,686 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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 <attributes.h>
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <core_io.h>
#include <httpserver.h>
#include <index/txindex.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/blockchain.h>
#include <rpc/server.h>
#include <streams.h>
#include <sync.h>
#include <txmempool.h>
#include <util/strencodings.h>
#include <validation.h>
#include <version.h>
#include <boost/algorithm/string.hpp>
#include <univalue.h>
// Allow a max of 15 outpoints to be queried at once.
static const size_t MAX_GETUTXOS_OUTPOINTS = 15;
enum class RetFormat {
UNDEF,
BINARY,
HEX,
JSON,
};
static const struct {
enum RetFormat rf;
const char *name;
} rf_names[] = {
{RetFormat::UNDEF, ""},
{RetFormat::BINARY, "bin"},
{RetFormat::HEX, "hex"},
{RetFormat::JSON, "json"},
};
struct CCoin {
uint32_t nHeight;
CTxOut out;
CCoin() : nHeight(0) {}
explicit CCoin(Coin in)
: nHeight(in.GetHeight()), out(std::move(in.GetTxOut())) {}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
uint32_t nTxVerDummy = 0;
READWRITE(nTxVerDummy);
READWRITE(nHeight);
READWRITE(out);
}
};
static bool RESTERR(HTTPRequest *req, enum HTTPStatusCode status,
std::string message) {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(status, message + "\r\n");
return false;
}
static enum RetFormat ParseDataFormat(std::string &param,
const std::string &strReq) {
const std::string::size_type pos = strReq.rfind('.');
if (pos == std::string::npos) {
param = strReq;
return rf_names[0].rf;
}
param = strReq.substr(0, pos);
const std::string suff(strReq, pos + 1);
for (size_t i = 0; i < ARRAYLEN(rf_names); i++) {
if (suff == rf_names[i].name) {
return rf_names[i].rf;
}
}
/* If no suffix is found, return original string. */
param = strReq;
return rf_names[0].rf;
}
static std::string AvailableDataFormatsString() {
std::string formats;
for (size_t i = 0; i < ARRAYLEN(rf_names); i++) {
if (strlen(rf_names[i].name) > 0) {
formats.append(".");
formats.append(rf_names[i].name);
formats.append(", ");
}
}
if (formats.length() > 0) {
return formats.substr(0, formats.length() - 2);
}
return formats;
}
static bool ParseHashStr(const std::string &strReq, uint256 &v) {
if (!IsHex(strReq) || (strReq.size() != 64)) {
return false;
}
v.SetHex(strReq);
return true;
}
static bool CheckWarmup(HTTPRequest *req) {
std::string statusmessage;
if (RPCIsInWarmup(&statusmessage)) {
return RESTERR(req, HTTP_SERVICE_UNAVAILABLE,
"Service temporarily unavailable: " + statusmessage);
}
return true;
}
static bool rest_headers(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> path;
boost::split(path, param, boost::is_any_of("/"));
if (path.size() != 2) {
return RESTERR(req, HTTP_BAD_REQUEST,
"No header count specified. Use "
"/rest/headers/<count>/<hash>.<ext>.");
}
long count = strtol(path[0].c_str(), nullptr, 10);
if (count < 1 || count > 2000) {
return RESTERR(req, HTTP_BAD_REQUEST,
"Header count out of range: " + path[0]);
}
std::string hashStr = path[1];
uint256 rawHash;
if (!ParseHashStr(hashStr, rawHash)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const BlockHash hash(rawHash);
const CBlockIndex *tip = nullptr;
std::vector<const CBlockIndex *> headers;
headers.reserve(count);
{
LOCK(cs_main);
tip = chainActive.Tip();
const CBlockIndex *pindex = LookupBlockIndex(hash);
while (pindex != nullptr && chainActive.Contains(pindex)) {
headers.push_back(pindex);
if (headers.size() == size_t(count)) {
break;
}
pindex = chainActive.Next(pindex);
}
}
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
}
switch (rf) {
case RetFormat::BINARY: {
std::string binaryHeader = ssHeader.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryHeader);
return true;
}
case RetFormat::HEX: {
std::string strHex =
HexStr(ssHeader.begin(), ssHeader.end()) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue jsonHeaders(UniValue::VARR);
for (const CBlockIndex *pindex : headers) {
jsonHeaders.push_back(blockheaderToJSON(tip, pindex));
}
std::string strJSON = jsonHeaders.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: .bin, .hex)");
}
}
}
static bool rest_block(const Config &config, HTTPRequest *req,
const std::string &strURIPart, bool showTxDetails) {
if (!CheckWarmup(req)) {
return false;
}
std::string hashStr;
const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 rawHash;
if (!ParseHashStr(hashStr, rawHash)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const BlockHash hash(rawHash);
CBlock block;
CBlockIndex *pblockindex = nullptr;
CBlockIndex *tip = nullptr;
{
LOCK(cs_main);
tip = chainActive.Tip();
pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
if (IsBlockPruned(pblockindex)) {
return RESTERR(req, HTTP_NOT_FOUND,
hashStr + " not available (pruned data)");
}
if (!ReadBlockFromDisk(block, pblockindex,
config.GetChainParams().GetConsensus())) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
}
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
switch (rf) {
case RetFormat::BINARY: {
std::string binaryBlock = ssBlock.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryBlock);
return true;
}
case RetFormat::HEX: {
std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objBlock =
blockToJSON(block, tip, pblockindex, showTxDetails);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static bool rest_block_extended(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
return rest_block(config, req, strURIPart, true);
}
static bool rest_block_notxdetails(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
return rest_block(config, req, strURIPart, false);
}
static bool rest_chaininfo(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
JSONRPCRequest jsonRequest;
jsonRequest.params = UniValue(UniValue::VARR);
UniValue chainInfoObject = getblockchaininfo(config, jsonRequest);
std::string strJSON = chainInfoObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: json)");
}
}
}
static bool rest_mempool_info(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
UniValue mempoolInfoObject = MempoolInfoToJSON(::g_mempool);
std::string strJSON = mempoolInfoObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: json)");
}
}
}
static bool rest_mempool_contents(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
UniValue mempoolObject = MempoolToJSON(::g_mempool, true);
std::string strJSON = mempoolObject.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: json)");
}
}
}
static bool rest_tx(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string hashStr;
const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
}
const TxId txid(hash);
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
CTransactionRef tx;
BlockHash hashBlock;
if (!GetTransaction(txid, tx, config.GetChainParams().GetConsensus(),
hashBlock, true)) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
switch (rf) {
case RetFormat::BINARY: {
std::string binaryTx = ssTx.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryTx);
return true;
}
case RetFormat::HEX: {
std::string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, hashBlock, objTx);
std::string strJSON = objTx.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static bool rest_getutxos(Config &config, HTTPRequest *req,
const std::string &strURIPart) {
if (!CheckWarmup(req)) {
return false;
}
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> uriParts;
if (param.length() > 1) {
std::string strUriParams = param.substr(1);
boost::split(uriParts, strUriParams, boost::is_any_of("/"));
}
// throw exception in case of an empty request
std::string strRequestMutable = req->ReadBody();
if (strRequestMutable.length() == 0 && uriParts.size() == 0) {
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
bool fInputParsed = false;
bool fCheckMemPool = false;
std::vector<COutPoint> vOutPoints;
// parse/deserialize input
// input-format = output-format, rest/getutxos/bin requires binary input,
// gives binary output, ...
if (uriParts.size() > 0) {
// inputs is sent over URI scheme
// (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
if (uriParts[0] == "checkmempool") {
fCheckMemPool = true;
}
for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++) {
int32_t nOutput;
std::string strTxid = uriParts[i].substr(0, uriParts[i].find('-'));
std::string strOutput =
uriParts[i].substr(uriParts[i].find('-') + 1);
if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid)) {
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
}
TxId txid;
txid.SetHex(strTxid);
vOutPoints.push_back(COutPoint(txid, uint32_t(nOutput)));
}
if (vOutPoints.size() > 0) {
fInputParsed = true;
} else {
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
}
switch (rf) {
case RetFormat::HEX: {
// convert hex to bin, continue then with bin part
std::vector<uint8_t> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
}
// FALLTHROUGH
case RetFormat::BINARY: {
try {
// deserialize only if user sent a request
if (strRequestMutable.size() > 0) {
// don't allow sending input over URI and HTTP RAW DATA
if (fInputParsed) {
return RESTERR(req, HTTP_BAD_REQUEST,
"Combination of URI scheme inputs and "
"raw post data is not allowed");
}
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
oss << strRequestMutable;
oss >> fCheckMemPool;
oss >> vOutPoints;
}
- } catch (const std::ios_base::failure &e) {
+ } catch (const std::ios_base::failure &) {
// abort in case of unreadable binary data
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
}
break;
}
case RetFormat::JSON: {
if (!fInputParsed) {
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
break;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
// limit max outpoints
if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS) {
return RESTERR(
req, HTTP_BAD_REQUEST,
strprintf("Error: max outpoints exceeded (max: %d, tried: %d)",
MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
}
// check spentness and form a bitmap (as well as a JSON capable
// human-readable string representation)
std::vector<uint8_t> bitmap;
std::vector<CCoin> outs;
std::string bitmapStringRepresentation;
std::vector<bool> hits;
bitmap.resize((vOutPoints.size() + 7) / 8);
{
auto process_utxos = [&vOutPoints, &outs,
&hits](const CCoinsView &view,
const CTxMemPool &mempool) {
for (const COutPoint &vOutPoint : vOutPoints) {
Coin coin;
bool hit = !mempool.isSpent(vOutPoint) &&
view.GetCoin(vOutPoint, coin);
hits.push_back(hit);
if (hit) {
outs.emplace_back(std::move(coin));
}
}
};
if (fCheckMemPool) {
// use db+mempool as cache backend in case user likes to query
// mempool
LOCK2(cs_main, g_mempool.cs);
CCoinsViewCache &viewChain = *pcoinsTip;
CCoinsViewMemPool viewMempool(&viewChain, g_mempool);
process_utxos(viewMempool, g_mempool);
} else {
// no need to lock mempool!
LOCK(cs_main);
process_utxos(*pcoinsTip, CTxMemPool());
}
for (size_t i = 0; i < hits.size(); ++i) {
const bool hit = hits[i];
// form a binary string representation (human-readable for json
// output)
bitmapStringRepresentation.append(hit ? "1" : "0");
bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
}
}
switch (rf) {
case RetFormat::BINARY: {
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainActive.Height()
<< chainActive.Tip()->GetBlockHash() << bitmap
<< outs;
std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
return true;
}
case RetFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainActive.Height()
<< chainActive.Tip()->GetBlockHash() << bitmap
<< outs;
std::string strHex =
HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) +
"\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objGetUTXOResponse(UniValue::VOBJ);
// pack in some essentials
// use more or less the same output as mentioned in Bip64
objGetUTXOResponse.pushKV("chainHeight", chainActive.Height());
objGetUTXOResponse.pushKV(
"chaintipHash", chainActive.Tip()->GetBlockHash().GetHex());
objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
UniValue utxos(UniValue::VARR);
for (const CCoin &coin : outs) {
UniValue utxo(UniValue::VOBJ);
utxo.pushKV("height", int32_t(coin.nHeight));
utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
// include the script in a json output
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
utxo.pushKV("scriptPubKey", o);
utxos.push_back(utxo);
}
objGetUTXOResponse.pushKV("utxos", utxos);
// return json string
std::string strJSON = objGetUTXOResponse.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND,
"output format not found (available: " +
AvailableDataFormatsString() + ")");
}
}
}
static const struct {
const char *prefix;
bool (*handler)(Config &config, HTTPRequest *req,
const std::string &strReq);
} uri_prefixes[] = {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
{"/rest/chaininfo", rest_chaininfo},
{"/rest/mempool/info", rest_mempool_info},
{"/rest/mempool/contents", rest_mempool_contents},
{"/rest/headers/", rest_headers},
{"/rest/getutxos", rest_getutxos},
};
void StartREST() {
for (size_t i = 0; i < ARRAYLEN(uri_prefixes); i++) {
RegisterHTTPHandler(uri_prefixes[i].prefix, false,
uri_prefixes[i].handler);
}
}
void InterruptREST() {}
void StopREST() {
for (size_t i = 0; i < ARRAYLEN(uri_prefixes); i++) {
UnregisterHTTPHandler(uri_prefixes[i].prefix, false);
}
}
diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp
index afd1f8c03..8979dbafa 100644
--- a/src/test/coins_tests.cpp
+++ b/src/test/coins_tests.cpp
@@ -1,912 +1,912 @@
// Copyright (c) 2014-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 <coins.h>
#include <clientversion.h>
#include <consensus/validation.h>
#include <script/standard.h>
#include <streams.h>
#include <undo.h>
#include <util/strencodings.h>
#include <validation.h>
#include <test/test_bitcoin.h>
#include <boost/test/unit_test.hpp>
#include <map>
#include <vector>
namespace {
//! equality test
bool operator==(const Coin &a, const Coin &b) {
// Empty Coin objects are always equal.
if (a.IsSpent() && b.IsSpent()) {
return true;
}
return a.IsCoinBase() == b.IsCoinBase() && a.GetHeight() == b.GetHeight() &&
a.GetTxOut() == b.GetTxOut();
}
class CCoinsViewTest : public CCoinsView {
BlockHash hashBestBlock_;
std::map<COutPoint, Coin> map_;
public:
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override {
std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
if (it == map_.end()) {
return false;
}
coin = it->second;
if (coin.IsSpent() && InsecureRandBool() == 0) {
// Randomly return false in case of an empty entry.
return false;
}
return true;
}
BlockHash GetBestBlock() const override { return hashBestBlock_; }
bool BatchWrite(CCoinsMap &mapCoins, const BlockHash &hashBlock) override {
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
// Same optimization used in CCoinsViewDB is to only write dirty
// entries.
map_[it->first] = it->second.coin;
if (it->second.coin.IsSpent() && InsecureRandRange(3) == 0) {
// Randomly delete empty entries on write.
map_.erase(it->first);
}
}
mapCoins.erase(it++);
}
if (!hashBlock.IsNull()) {
hashBestBlock_ = hashBlock;
}
return true;
}
};
class CCoinsViewCacheTest : public CCoinsViewCache {
public:
explicit CCoinsViewCacheTest(CCoinsView *_base) : CCoinsViewCache(_base) {}
void SelfTest() const {
// Manually recompute the dynamic usage of the whole data, and compare
// it.
size_t ret = memusage::DynamicUsage(cacheCoins);
size_t count = 0;
for (const auto &entry : cacheCoins) {
ret += entry.second.coin.DynamicMemoryUsage();
count++;
}
BOOST_CHECK_EQUAL(GetCacheSize(), count);
BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
}
CCoinsMap &map() const { return cacheCoins; }
size_t &usage() const { return cachedCoinsUsage; }
};
} // namespace
BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;
// This is a large randomized insert/remove simulation test on a variable-size
// stack of caches on top of CCoinsViewTest.
//
// It will randomly create/update/delete Coin entries to a tip of caches, with
// txids picked from a limited list of random 256-bit hashes. Occasionally, a
// new tip is added to the stack of caches, or the tip is flushed and removed.
//
// During the process, booleans are kept to make sure that the randomized
// operation hits all branches.
BOOST_AUTO_TEST_CASE(coins_cache_simulation_test) {
// Various coverage trackers.
bool removed_all_caches = false;
bool reached_4_caches = false;
bool added_an_entry = false;
bool added_an_unspendable_entry = false;
bool removed_an_entry = false;
bool updated_an_entry = false;
bool found_an_entry = false;
bool missed_an_entry = false;
bool uncached_an_entry = false;
// A simple map to track what we expect the cache stack to represent.
std::map<COutPoint, Coin> result;
// The cache stack.
// A CCoinsViewTest at the bottom.
CCoinsViewTest base;
// A stack of CCoinsViewCaches on top.
std::vector<CCoinsViewCacheTest *> stack;
// Start with one cache.
stack.push_back(new CCoinsViewCacheTest(&base));
// Use a limited set of random transaction ids, so we do test overwriting
// entries.
std::vector<TxId> txids;
txids.resize(NUM_SIMULATION_ITERATIONS / 8);
for (size_t i = 0; i < txids.size(); i++) {
txids[i] = TxId(InsecureRand256());
}
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
// Do a random modification.
{
// txid we're going to modify in this iteration.
const TxId txid = txids[InsecureRandRange(txids.size())];
Coin &coin = result[COutPoint(txid, 0)];
// Determine whether to test HaveCoin before or after Access* (or
// both). As these functions can influence each other's behaviour by
// pulling things into the cache, all combinations are tested.
bool test_havecoin_before = InsecureRandBits(2) == 0;
bool test_havecoin_after = InsecureRandBits(2) == 0;
bool result_havecoin =
test_havecoin_before
? stack.back()->HaveCoin(COutPoint(txid, 0))
: false;
const Coin &entry =
(InsecureRandRange(500) == 0)
? AccessByTxid(*stack.back(), txid)
: stack.back()->AccessCoin(COutPoint(txid, 0));
BOOST_CHECK(coin == entry);
BOOST_CHECK(!test_havecoin_before ||
result_havecoin == !entry.IsSpent());
if (test_havecoin_after) {
bool ret = stack.back()->HaveCoin(COutPoint(txid, 0));
BOOST_CHECK(ret == !entry.IsSpent());
}
if (InsecureRandRange(5) == 0 || coin.IsSpent()) {
CTxOut txout;
txout.nValue = int64_t(InsecureRand32()) * SATOSHI;
if (InsecureRandRange(16) == 0 && coin.IsSpent()) {
txout.scriptPubKey.assign(1 + InsecureRandBits(6),
OP_RETURN);
BOOST_CHECK(txout.scriptPubKey.IsUnspendable());
added_an_unspendable_entry = true;
} else {
// Random sizes so we can test memory usage accounting
txout.scriptPubKey.assign(InsecureRandBits(6), 0);
(coin.IsSpent() ? added_an_entry : updated_an_entry) = true;
coin = Coin(txout, 1, false);
}
Coin newcoin(txout, 1, false);
stack.back()->AddCoin(COutPoint(txid, 0), newcoin,
!coin.IsSpent() || InsecureRand32() & 1);
} else {
removed_an_entry = true;
coin.Clear();
stack.back()->SpendCoin(COutPoint(txid, 0));
}
}
// One every 10 iterations, remove a random entry from the cache
if (InsecureRandRange(10) == 0) {
COutPoint out(txids[InsecureRand32() % txids.size()], 0);
int cacheid = InsecureRand32() % stack.size();
stack[cacheid]->Uncache(out);
uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);
}
// Once every 1000 iterations and at the end, verify the full cache.
if (InsecureRandRange(1000) == 1 ||
i == NUM_SIMULATION_ITERATIONS - 1) {
for (const auto &entry : result) {
bool have = stack.back()->HaveCoin(entry.first);
const Coin &coin = stack.back()->AccessCoin(entry.first);
BOOST_CHECK(have == !coin.IsSpent());
BOOST_CHECK(coin == entry.second);
if (coin.IsSpent()) {
missed_an_entry = true;
} else {
BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));
found_an_entry = true;
}
}
for (const CCoinsViewCacheTest *test : stack) {
test->SelfTest();
}
}
// Every 100 iterations, flush an intermediate cache
if (InsecureRandRange(100) == 0) {
if (stack.size() > 1 && InsecureRandBool() == 0) {
unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
stack[flushIndex]->Flush();
}
}
if (InsecureRandRange(100) == 0) {
// Every 100 iterations, change the cache stack.
if (stack.size() > 0 && InsecureRandBool() == 0) {
// Remove the top cache
stack.back()->Flush();
delete stack.back();
stack.pop_back();
}
if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
// Add a new cache
CCoinsView *tip = &base;
if (stack.size() > 0) {
tip = stack.back();
} else {
removed_all_caches = true;
}
stack.push_back(new CCoinsViewCacheTest(tip));
if (stack.size() == 4) {
reached_4_caches = true;
}
}
}
}
// Clean up the stack.
while (stack.size() > 0) {
delete stack.back();
stack.pop_back();
}
// Verify coverage.
BOOST_CHECK(removed_all_caches);
BOOST_CHECK(reached_4_caches);
BOOST_CHECK(added_an_entry);
BOOST_CHECK(added_an_unspendable_entry);
BOOST_CHECK(removed_an_entry);
BOOST_CHECK(updated_an_entry);
BOOST_CHECK(found_an_entry);
BOOST_CHECK(missed_an_entry);
BOOST_CHECK(uncached_an_entry);
}
// Store of all necessary tx and undo data for next test
typedef std::map<COutPoint, std::tuple<CTransaction, CTxUndo, Coin>> UtxoData;
UtxoData utxoData;
UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
assert(utxoSet.size());
auto utxoSetIt = utxoSet.lower_bound(COutPoint(TxId(InsecureRand256()), 0));
if (utxoSetIt == utxoSet.end()) {
utxoSetIt = utxoSet.begin();
}
auto utxoDataIt = utxoData.find(*utxoSetIt);
assert(utxoDataIt != utxoData.end());
return utxoDataIt;
}
// This test is similar to the previous test except the emphasis is on testing
// the functionality of UpdateCoins random txs are created and UpdateCoins is
// used to update the cache stack. In particular it is tested that spending a
// duplicate coinbase tx has the expected effect (the other duplicate is
// overwritten at all cache levels)
BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) {
bool spent_a_duplicate_coinbase = false;
// A simple map to track what we expect the cache stack to represent.
std::map<COutPoint, Coin> result;
// The cache stack.
// A CCoinsViewTest at the bottom.
CCoinsViewTest base;
// A stack of CCoinsViewCaches on top.
std::vector<CCoinsViewCacheTest *> stack;
// Start with one cache.
stack.push_back(new CCoinsViewCacheTest(&base));
// Track the txids we've used in various sets
std::set<COutPoint> coinbase_coins;
std::set<COutPoint> disconnected_coins;
std::set<COutPoint> duplicate_coins;
std::set<COutPoint> utxoset;
for (int64_t i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
uint32_t randiter = InsecureRand32();
// 19/20 txs add a new transaction
if (randiter % 20 < 19) {
CMutableTransaction tx;
tx.vin.resize(1);
tx.vout.resize(1);
// Keep txs unique unless intended to duplicate.
tx.vout[0].nValue = i * SATOSHI;
// Random sizes so we can test memory usage accounting
tx.vout[0].scriptPubKey.assign(InsecureRand32() & 0x3F, 0);
unsigned int height = InsecureRand32();
Coin old_coin;
// 2/20 times create a new coinbase
if (randiter % 20 < 2 || coinbase_coins.size() < 10) {
// 1/10 of those times create a duplicate coinbase
if (InsecureRandRange(10) == 0 && coinbase_coins.size()) {
auto utxod = FindRandomFrom(coinbase_coins);
// Reuse the exact same coinbase
tx = CMutableTransaction{std::get<0>(utxod->second)};
// shouldn't be available for reconnection if it's been
// duplicated
disconnected_coins.erase(utxod->first);
duplicate_coins.insert(utxod->first);
} else {
coinbase_coins.insert(COutPoint(tx.GetId(), 0));
}
assert(CTransaction(tx).IsCoinBase());
}
// 17/20 times reconnect previous or add a regular tx
else {
COutPoint prevout;
// 1/20 times reconnect a previously disconnected tx
if (randiter % 20 == 2 && disconnected_coins.size()) {
auto utxod = FindRandomFrom(disconnected_coins);
tx = CMutableTransaction{std::get<0>(utxod->second)};
prevout = tx.vin[0].prevout;
if (!CTransaction(tx).IsCoinBase() &&
!utxoset.count(prevout)) {
disconnected_coins.erase(utxod->first);
continue;
}
// If this tx is already IN the UTXO, then it must be a
// coinbase, and it must be a duplicate
if (utxoset.count(utxod->first)) {
assert(CTransaction(tx).IsCoinBase());
assert(duplicate_coins.count(utxod->first));
}
disconnected_coins.erase(utxod->first);
}
// 16/20 times create a regular tx
else {
auto utxod = FindRandomFrom(utxoset);
prevout = utxod->first;
// Construct the tx to spend the coins of prevouthash
tx.vin[0].prevout = COutPoint(prevout.GetTxId(), 0);
assert(!CTransaction(tx).IsCoinBase());
}
// In this simple test coins only have two states, spent or
// unspent, save the unspent state to restore
old_coin = result[prevout];
// Update the expected result of prevouthash to know these coins
// are spent
result[prevout].Clear();
utxoset.erase(prevout);
// The test is designed to ensure spending a duplicate coinbase
// will work properly if that ever happens and not resurrect the
// previously overwritten coinbase
if (duplicate_coins.count(prevout)) {
spent_a_duplicate_coinbase = true;
}
}
// Update the expected result to know about the new output coins
assert(tx.vout.size() == 1);
const COutPoint outpoint(tx.GetId(), 0);
result[outpoint] =
Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase());
// Call UpdateCoins on the top cache
CTxUndo undo;
UpdateCoins(*(stack.back()), CTransaction(tx), undo, height);
// Update the utxo set for future spends
utxoset.insert(outpoint);
// Track this tx and undo info to use later
utxoData.emplace(outpoint,
std::make_tuple(CTransaction(tx), undo, old_coin));
}
// 1/20 times undo a previous transaction
else if (utxoset.size()) {
auto utxod = FindRandomFrom(utxoset);
CTransaction &tx = std::get<0>(utxod->second);
CTxUndo &undo = std::get<1>(utxod->second);
Coin &orig_coin = std::get<2>(utxod->second);
// Update the expected result
// Remove new outputs
result[utxod->first].Clear();
// If not coinbase restore prevout
if (!tx.IsCoinBase()) {
result[tx.vin[0].prevout] = orig_coin;
}
// Disconnect the tx from the current UTXO
// See code in DisconnectBlock
// remove outputs
stack.back()->SpendCoin(utxod->first);
// restore inputs
if (!tx.IsCoinBase()) {
const COutPoint &out = tx.vin[0].prevout;
UndoCoinSpend(undo.vprevout[0], *(stack.back()), out);
}
// Store as a candidate for reconnection
disconnected_coins.insert(utxod->first);
// Update the utxoset
utxoset.erase(utxod->first);
if (!tx.IsCoinBase()) {
utxoset.insert(tx.vin[0].prevout);
}
}
// Once every 1000 iterations and at the end, verify the full cache.
if (InsecureRandRange(1000) == 1 ||
i == NUM_SIMULATION_ITERATIONS - 1) {
for (const auto &entry : result) {
bool have = stack.back()->HaveCoin(entry.first);
const Coin &coin = stack.back()->AccessCoin(entry.first);
BOOST_CHECK(have == !coin.IsSpent());
BOOST_CHECK(coin == entry.second);
}
}
// One every 10 iterations, remove a random entry from the cache
if (utxoset.size() > 1 && InsecureRandRange(30) == 0) {
stack[InsecureRand32() % stack.size()]->Uncache(
FindRandomFrom(utxoset)->first);
}
if (disconnected_coins.size() > 1 && InsecureRandRange(30) == 0) {
stack[InsecureRand32() % stack.size()]->Uncache(
FindRandomFrom(disconnected_coins)->first);
}
if (duplicate_coins.size() > 1 && InsecureRandRange(30) == 0) {
stack[InsecureRand32() % stack.size()]->Uncache(
FindRandomFrom(duplicate_coins)->first);
}
if (InsecureRandRange(100) == 0) {
// Every 100 iterations, flush an intermediate cache
if (stack.size() > 1 && InsecureRandBool() == 0) {
unsigned int flushIndex = InsecureRandRange(stack.size() - 1);
stack[flushIndex]->Flush();
}
}
if (InsecureRandRange(100) == 0) {
// Every 100 iterations, change the cache stack.
if (stack.size() > 0 && InsecureRandBool() == 0) {
stack.back()->Flush();
delete stack.back();
stack.pop_back();
}
if (stack.size() == 0 || (stack.size() < 4 && InsecureRandBool())) {
CCoinsView *tip = &base;
if (stack.size() > 0) {
tip = stack.back();
}
stack.push_back(new CCoinsViewCacheTest(tip));
}
}
}
// Clean up the stack.
while (stack.size() > 0) {
delete stack.back();
stack.pop_back();
}
// Verify coverage.
BOOST_CHECK(spent_a_duplicate_coinbase);
}
BOOST_AUTO_TEST_CASE(coin_serialization) {
// Good example
CDataStream ss1(
ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"),
SER_DISK, CLIENT_VERSION);
Coin c1;
ss1 >> c1;
BOOST_CHECK_EQUAL(c1.IsCoinBase(), false);
BOOST_CHECK_EQUAL(c1.GetHeight(), 203998U);
BOOST_CHECK_EQUAL(c1.GetTxOut().nValue, int64_t(60000000000) * SATOSHI);
BOOST_CHECK_EQUAL(HexStr(c1.GetTxOut().scriptPubKey),
HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex(
"816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
// Good example
CDataStream ss2(
ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"),
SER_DISK, CLIENT_VERSION);
Coin c2;
ss2 >> c2;
BOOST_CHECK_EQUAL(c2.IsCoinBase(), true);
BOOST_CHECK_EQUAL(c2.GetHeight(), 120891U);
BOOST_CHECK_EQUAL(c2.GetTxOut().nValue, 110397 * SATOSHI);
BOOST_CHECK_EQUAL(HexStr(c2.GetTxOut().scriptPubKey),
HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex(
"8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
// Smallest possible example
CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
Coin c3;
ss3 >> c3;
BOOST_CHECK_EQUAL(c3.IsCoinBase(), false);
BOOST_CHECK_EQUAL(c3.GetHeight(), 0U);
BOOST_CHECK_EQUAL(c3.GetTxOut().nValue, Amount::zero());
BOOST_CHECK_EQUAL(c3.GetTxOut().scriptPubKey.size(), 0U);
// scriptPubKey that ends beyond the end of the stream
CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
try {
Coin c4;
ss4 >> c4;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
- } catch (const std::ios_base::failure &e) {
+ } catch (const std::ios_base::failure &) {
}
// Very large scriptPubKey (3*10^9 bytes) past the end of the stream
CDataStream tmp(SER_DISK, CLIENT_VERSION);
uint64_t x = 3000000000ULL;
tmp << VARINT(x);
BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
try {
Coin c5;
ss5 >> c5;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
- } catch (const std::ios_base::failure &e) {
+ } catch (const std::ios_base::failure &) {
}
}
static const COutPoint OUTPOINT;
static const Amount PRUNED(-1 * SATOSHI);
static const Amount ABSENT(-2 * SATOSHI);
static const Amount FAIL(-3 * SATOSHI);
static const Amount VALUE1(100 * SATOSHI);
static const Amount VALUE2(200 * SATOSHI);
static const Amount VALUE3(300 * SATOSHI);
static const char DIRTY = CCoinsCacheEntry::DIRTY;
static const char FRESH = CCoinsCacheEntry::FRESH;
static const char NO_ENTRY = -1;
static const auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
static const auto CLEAN_FLAGS = {char(0), FRESH};
static const auto ABSENT_FLAGS = {NO_ENTRY};
static void SetCoinValue(const Amount value, Coin &coin) {
assert(value != ABSENT);
coin.Clear();
assert(coin.IsSpent());
if (value != PRUNED) {
CTxOut out;
out.nValue = value;
coin = Coin(std::move(out), 1, false);
assert(!coin.IsSpent());
}
}
static size_t InsertCoinMapEntry(CCoinsMap &map, const Amount value,
char flags) {
if (value == ABSENT) {
assert(flags == NO_ENTRY);
return 0;
}
assert(flags != NO_ENTRY);
CCoinsCacheEntry entry;
entry.flags = flags;
SetCoinValue(value, entry.coin);
auto inserted = map.emplace(OUTPOINT, std::move(entry));
assert(inserted.second);
return inserted.first->second.coin.DynamicMemoryUsage();
}
void GetCoinMapEntry(const CCoinsMap &map, Amount &value, char &flags) {
auto it = map.find(OUTPOINT);
if (it == map.end()) {
value = ABSENT;
flags = NO_ENTRY;
} else {
if (it->second.coin.IsSpent()) {
value = PRUNED;
} else {
value = it->second.coin.GetTxOut().nValue;
}
flags = it->second.flags;
assert(flags != NO_ENTRY);
}
}
void WriteCoinViewEntry(CCoinsView &view, const Amount value, char flags) {
CCoinsMap map;
InsertCoinMapEntry(map, value, flags);
view.BatchWrite(map, BlockHash());
}
class SingleEntryCacheTest {
public:
SingleEntryCacheTest(const Amount base_value, const Amount cache_value,
char cache_flags) {
WriteCoinViewEntry(base, base_value,
base_value == ABSENT ? NO_ENTRY : DIRTY);
cache.usage() +=
InsertCoinMapEntry(cache.map(), cache_value, cache_flags);
}
CCoinsView root;
CCoinsViewCacheTest base{&root};
CCoinsViewCacheTest cache{&base};
};
static void CheckAccessCoin(const Amount base_value, const Amount cache_value,
const Amount expected_value, char cache_flags,
char expected_flags) {
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
test.cache.AccessCoin(OUTPOINT);
test.cache.SelfTest();
Amount result_value;
char result_flags;
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
}
BOOST_AUTO_TEST_CASE(coin_access) {
/* Check AccessCoin behavior, requesting a coin from a cache view layered on
* top of a base view, and checking the resulting entry in the cache after
* the access.
*
* Base Cache Result Cache Result
* Value Value Value Flags Flags
*/
CheckAccessCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckAccessCoin(ABSENT, PRUNED, PRUNED, 0, 0);
CheckAccessCoin(ABSENT, PRUNED, PRUNED, FRESH, FRESH);
CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY, DIRTY);
CheckAccessCoin(ABSENT, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, 0, 0);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, FRESH, FRESH);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY, DIRTY);
CheckAccessCoin(ABSENT, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(PRUNED, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckAccessCoin(PRUNED, PRUNED, PRUNED, 0, 0);
CheckAccessCoin(PRUNED, PRUNED, PRUNED, FRESH, FRESH);
CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY);
CheckAccessCoin(PRUNED, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(PRUNED, VALUE2, VALUE2, 0, 0);
CheckAccessCoin(PRUNED, VALUE2, VALUE2, FRESH, FRESH);
CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY);
CheckAccessCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(VALUE1, ABSENT, VALUE1, NO_ENTRY, 0);
CheckAccessCoin(VALUE1, PRUNED, PRUNED, 0, 0);
CheckAccessCoin(VALUE1, PRUNED, PRUNED, FRESH, FRESH);
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY);
CheckAccessCoin(VALUE1, PRUNED, PRUNED, DIRTY | FRESH, DIRTY | FRESH);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, 0, 0);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, FRESH, FRESH);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY);
CheckAccessCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH);
}
static void CheckSpendCoin(Amount base_value, Amount cache_value,
Amount expected_value, char cache_flags,
char expected_flags) {
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
test.cache.SpendCoin(OUTPOINT);
test.cache.SelfTest();
Amount result_value;
char result_flags;
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
};
BOOST_AUTO_TEST_CASE(coin_spend) {
/**
* Check SpendCoin behavior, requesting a coin from a cache view layered on
* top of a base view, spending, and then checking the resulting entry in
* the cache after the modification.
*
* Base Cache Result Cache Result
* Value Value Value Flags Flags
*/
CheckSpendCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckSpendCoin(ABSENT, PRUNED, PRUNED, 0, DIRTY);
CheckSpendCoin(ABSENT, PRUNED, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(ABSENT, PRUNED, PRUNED, DIRTY, DIRTY);
CheckSpendCoin(ABSENT, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(ABSENT, VALUE2, PRUNED, 0, DIRTY);
CheckSpendCoin(ABSENT, VALUE2, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(ABSENT, VALUE2, PRUNED, DIRTY, DIRTY);
CheckSpendCoin(ABSENT, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(PRUNED, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY);
CheckSpendCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY);
CheckSpendCoin(PRUNED, PRUNED, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY);
CheckSpendCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(PRUNED, VALUE2, PRUNED, 0, DIRTY);
CheckSpendCoin(PRUNED, VALUE2, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(PRUNED, VALUE2, PRUNED, DIRTY, DIRTY);
CheckSpendCoin(PRUNED, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, ABSENT, PRUNED, NO_ENTRY, DIRTY);
CheckSpendCoin(VALUE1, PRUNED, PRUNED, 0, DIRTY);
CheckSpendCoin(VALUE1, PRUNED, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY);
CheckSpendCoin(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, VALUE2, PRUNED, 0, DIRTY);
CheckSpendCoin(VALUE1, VALUE2, ABSENT, FRESH, NO_ENTRY);
CheckSpendCoin(VALUE1, VALUE2, PRUNED, DIRTY, DIRTY);
CheckSpendCoin(VALUE1, VALUE2, ABSENT, DIRTY | FRESH, NO_ENTRY);
}
static void CheckAddCoinBase(Amount base_value, Amount cache_value,
Amount modify_value, Amount expected_value,
char cache_flags, char expected_flags,
bool coinbase) {
SingleEntryCacheTest test(base_value, cache_value, cache_flags);
Amount result_value;
char result_flags;
try {
CTxOut output;
output.nValue = modify_value;
test.cache.AddCoin(OUTPOINT, Coin(std::move(output), 1, coinbase),
coinbase);
test.cache.SelfTest();
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
- } catch (std::logic_error &e) {
+ } catch (std::logic_error &) {
result_value = FAIL;
result_flags = NO_ENTRY;
}
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
}
// Simple wrapper for CheckAddCoinBase function above that loops through
// different possible base_values, making sure each one gives the same results.
// This wrapper lets the coin_add test below be shorter and less repetitive,
// while still verifying that the CoinsViewCache::AddCoin implementation ignores
// base values.
template <typename... Args> static void CheckAddCoin(Args &&... args) {
for (const Amount &base_value : {ABSENT, PRUNED, VALUE1}) {
CheckAddCoinBase(base_value, std::forward<Args>(args)...);
}
}
BOOST_AUTO_TEST_CASE(coin_add) {
/**
* Check AddCoin behavior, requesting a new coin from a cache view, writing
* a modification to the coin, and then checking the resulting entry in the
* cache after the modification. Verify behavior with the with the AddCoin
* potential_overwrite argument set to false, and to true.
*
* Cache Write Result Cache Result potential_overwrite
* Value Value Value Flags Flags
*/
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY | FRESH, false);
CheckAddCoin(ABSENT, VALUE3, VALUE3, NO_ENTRY, DIRTY, true);
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0, DIRTY | FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, 0, DIRTY, true);
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY, DIRTY, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY, DIRTY, true);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, false);
CheckAddCoin(PRUNED, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, 0, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, 0, DIRTY, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, FRESH, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, FRESH, DIRTY | FRESH, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY, DIRTY, true);
CheckAddCoin(VALUE2, VALUE3, FAIL, DIRTY | FRESH, NO_ENTRY, false);
CheckAddCoin(VALUE2, VALUE3, VALUE3, DIRTY | FRESH, DIRTY | FRESH, true);
}
void CheckWriteCoin(Amount parent_value, Amount child_value,
Amount expected_value, char parent_flags, char child_flags,
char expected_flags) {
SingleEntryCacheTest test(ABSENT, parent_value, parent_flags);
Amount result_value;
char result_flags;
try {
WriteCoinViewEntry(test.cache, child_value, child_flags);
test.cache.SelfTest();
GetCoinMapEntry(test.cache.map(), result_value, result_flags);
- } catch (std::logic_error &e) {
+ } catch (std::logic_error &) {
result_value = FAIL;
result_flags = NO_ENTRY;
}
BOOST_CHECK_EQUAL(result_value, expected_value);
BOOST_CHECK_EQUAL(result_flags, expected_flags);
}
BOOST_AUTO_TEST_CASE(coin_write) {
/* Check BatchWrite behavior, flushing one entry from a child cache to a
* parent cache, and checking the resulting entry in the parent cache
* after the write.
*
* Parent Child Result Parent Child Result
* Value Value Value Flags Flags Flags
*/
CheckWriteCoin(ABSENT, ABSENT, ABSENT, NO_ENTRY, NO_ENTRY, NO_ENTRY);
CheckWriteCoin(ABSENT, PRUNED, PRUNED, NO_ENTRY, DIRTY, DIRTY);
CheckWriteCoin(ABSENT, PRUNED, ABSENT, NO_ENTRY, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY, DIRTY);
CheckWriteCoin(ABSENT, VALUE2, VALUE2, NO_ENTRY, DIRTY | FRESH,
DIRTY | FRESH);
CheckWriteCoin(PRUNED, ABSENT, PRUNED, 0, NO_ENTRY, 0);
CheckWriteCoin(PRUNED, ABSENT, PRUNED, FRESH, NO_ENTRY, FRESH);
CheckWriteCoin(PRUNED, ABSENT, PRUNED, DIRTY, NO_ENTRY, DIRTY);
CheckWriteCoin(PRUNED, ABSENT, PRUNED, DIRTY | FRESH, NO_ENTRY,
DIRTY | FRESH);
CheckWriteCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY, DIRTY);
CheckWriteCoin(PRUNED, PRUNED, PRUNED, 0, DIRTY | FRESH, DIRTY);
CheckWriteCoin(PRUNED, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(PRUNED, PRUNED, ABSENT, FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(PRUNED, PRUNED, PRUNED, DIRTY, DIRTY | FRESH, DIRTY);
CheckWriteCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(PRUNED, PRUNED, ABSENT, DIRTY | FRESH, DIRTY | FRESH,
NO_ENTRY);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, 0, DIRTY, DIRTY);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, 0, DIRTY | FRESH, DIRTY);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, FRESH, DIRTY | FRESH, DIRTY | FRESH);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY, DIRTY | FRESH, DIRTY);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(PRUNED, VALUE2, VALUE2, DIRTY | FRESH, DIRTY | FRESH,
DIRTY | FRESH);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, 0, NO_ENTRY, 0);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, FRESH, NO_ENTRY, FRESH);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY, NO_ENTRY, DIRTY);
CheckWriteCoin(VALUE1, ABSENT, VALUE1, DIRTY | FRESH, NO_ENTRY,
DIRTY | FRESH);
CheckWriteCoin(VALUE1, PRUNED, PRUNED, 0, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, PRUNED, FAIL, 0, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, PRUNED, ABSENT, FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(VALUE1, PRUNED, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, PRUNED, PRUNED, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, PRUNED, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, PRUNED, ABSENT, DIRTY | FRESH, DIRTY, NO_ENTRY);
CheckWriteCoin(VALUE1, PRUNED, FAIL, DIRTY | FRESH, DIRTY | FRESH,
NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, 0, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, VALUE2, FAIL, 0, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(VALUE1, VALUE2, FAIL, FRESH, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY, DIRTY, DIRTY);
CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY, DIRTY | FRESH, NO_ENTRY);
CheckWriteCoin(VALUE1, VALUE2, VALUE2, DIRTY | FRESH, DIRTY, DIRTY | FRESH);
CheckWriteCoin(VALUE1, VALUE2, FAIL, DIRTY | FRESH, DIRTY | FRESH,
NO_ENTRY);
// The checks above omit cases where the child flags are not DIRTY, since
// they would be too repetitive (the parent cache is never updated in these
// cases). The loop below covers these cases and makes sure the parent cache
// is always left unchanged.
for (const Amount &parent_value : {ABSENT, PRUNED, VALUE1}) {
for (const Amount &child_value : {ABSENT, PRUNED, VALUE2}) {
for (const char parent_flags :
parent_value == ABSENT ? ABSENT_FLAGS : FLAGS) {
for (const char child_flags :
child_value == ABSENT ? ABSENT_FLAGS : CLEAN_FLAGS) {
CheckWriteCoin(parent_value, child_value, parent_value,
parent_flags, child_flags, parent_flags);
}
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp
index 18bb92500..a260db49a 100644
--- a/src/test/dbwrapper_tests.cpp
+++ b/src/test/dbwrapper_tests.cpp
@@ -1,342 +1,342 @@
// 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 <dbwrapper.h>
#include <random.h>
#include <uint256.h>
#include <test/test_bitcoin.h>
#include <boost/test/unit_test.hpp>
#include <memory>
// Test if a string consists entirely of null characters
static bool is_null_key(const std::vector<uint8_t> &key) {
bool isnull = true;
for (unsigned int i = 0; i < key.size(); i++) {
isnull &= (key[i] == '\x00');
}
return isnull;
}
BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(dbwrapper) {
// Perform tests both obfuscated and non-obfuscated.
for (const bool obfuscate : {false, true}) {
fs::path ph = SetDataDir(
std::string("dbwrapper").append(obfuscate ? "_true" : "_false"));
CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
char key = 'k';
uint256 in = InsecureRand256();
uint256 res;
// Ensure that we're doing real obfuscation when obfuscate=true
BOOST_CHECK(obfuscate !=
is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
BOOST_CHECK(dbw.Write(key, in));
BOOST_CHECK(dbw.Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
}
}
// Test batch operations
BOOST_AUTO_TEST_CASE(dbwrapper_batch) {
// Perform tests both obfuscated and non-obfuscated.
for (const bool obfuscate : {false, true}) {
fs::path ph = SetDataDir(std::string("dbwrapper_batch")
.append(obfuscate ? "_true" : "_false"));
CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
char key = 'i';
uint256 in = InsecureRand256();
char key2 = 'j';
uint256 in2 = InsecureRand256();
char key3 = 'k';
uint256 in3 = InsecureRand256();
uint256 res;
CDBBatch batch(dbw);
batch.Write(key, in);
batch.Write(key2, in2);
batch.Write(key3, in3);
// Remove key3 before it's even been written
batch.Erase(key3);
dbw.WriteBatch(batch);
BOOST_CHECK(dbw.Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
BOOST_CHECK(dbw.Read(key2, res));
BOOST_CHECK_EQUAL(res.ToString(), in2.ToString());
// key3 should've never been written
BOOST_CHECK(dbw.Read(key3, res) == false);
}
}
BOOST_AUTO_TEST_CASE(dbwrapper_iterator) {
// Perform tests both obfuscated and non-obfuscated.
for (const bool obfuscate : {false, true}) {
fs::path ph = SetDataDir(std::string("dbwrapper_iterator")
.append(obfuscate ? "_true" : "_false"));
CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
// The two keys are intentionally chosen for ordering
char key = 'j';
uint256 in = InsecureRand256();
BOOST_CHECK(dbw.Write(key, in));
char key2 = 'k';
uint256 in2 = InsecureRand256();
BOOST_CHECK(dbw.Write(key2, in2));
std::unique_ptr<CDBIterator> it(
const_cast<CDBWrapper &>(dbw).NewIterator());
// Be sure to seek past the obfuscation key (if it exists)
it->Seek(key);
char key_res;
uint256 val_res;
it->GetKey(key_res);
it->GetValue(val_res);
BOOST_CHECK_EQUAL(key_res, key);
BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString());
it->Next();
it->GetKey(key_res);
it->GetValue(val_res);
BOOST_CHECK_EQUAL(key_res, key2);
BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString());
it->Next();
BOOST_CHECK_EQUAL(it->Valid(), false);
}
}
// Test that we do not obfuscation if there is existing data.
BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) {
// We're going to share this fs::path between two wrappers
fs::path ph = SetDataDir("existing_data_no_obfuscate");
create_directories(ph);
// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw =
std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false);
char key = 'k';
uint256 in = InsecureRand256();
uint256 res;
BOOST_CHECK(dbw->Write(key, in));
BOOST_CHECK(dbw->Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
// Call the destructor to free leveldb LOCK
dbw.reset();
// Now, set up another wrapper that wants to obfuscate the same directory
CDBWrapper odbw(ph, (1 << 10), false, false, true);
// Check that the key/val we wrote with unobfuscated wrapper exists and
// is readable.
uint256 res2;
BOOST_CHECK(odbw.Read(key, res2));
BOOST_CHECK_EQUAL(res2.ToString(), in.ToString());
// There should be existing data
BOOST_CHECK(!odbw.IsEmpty());
// The key should be an empty string
BOOST_CHECK(is_null_key(dbwrapper_private::GetObfuscateKey(odbw)));
uint256 in2 = InsecureRand256();
uint256 res3;
// Check that we can write successfully
BOOST_CHECK(odbw.Write(key, in2));
BOOST_CHECK(odbw.Read(key, res3));
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
}
// Ensure that we start obfuscating during a reindex.
BOOST_AUTO_TEST_CASE(existing_data_reindex) {
// We're going to share this fs::path between two wrappers
fs::path ph = SetDataDir("existing_data_reindex");
create_directories(ph);
// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw =
std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false);
char key = 'k';
uint256 in = InsecureRand256();
uint256 res;
BOOST_CHECK(dbw->Write(key, in));
BOOST_CHECK(dbw->Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
// Call the destructor to free leveldb LOCK
dbw.reset();
// Simulate a -reindex by wiping the existing data store
CDBWrapper odbw(ph, (1 << 10), false, true, true);
// Check that the key/val we wrote with unobfuscated wrapper doesn't exist
uint256 res2;
BOOST_CHECK(!odbw.Read(key, res2));
BOOST_CHECK(!is_null_key(dbwrapper_private::GetObfuscateKey(odbw)));
uint256 in2 = InsecureRand256();
uint256 res3;
// Check that we can write successfully
BOOST_CHECK(odbw.Write(key, in2));
BOOST_CHECK(odbw.Read(key, res3));
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
}
BOOST_AUTO_TEST_CASE(iterator_ordering) {
fs::path ph = SetDataDir("iterator_ordering");
CDBWrapper dbw(ph, (1 << 20), true, false, false);
for (int x = 0x00; x < 256; ++x) {
uint8_t key = x;
uint32_t value = x * x;
if (!(x & 1)) {
BOOST_CHECK(dbw.Write(key, value));
}
}
// Check that creating an iterator creates a snapshot
std::unique_ptr<CDBIterator> it(
const_cast<CDBWrapper &>(dbw).NewIterator());
for (unsigned int x = 0x00; x < 256; ++x) {
uint8_t key = x;
uint32_t value = x * x;
if (x & 1) {
BOOST_CHECK(dbw.Write(key, value));
}
}
for (const int seek_start : {0x00, 0x80}) {
it->Seek((uint8_t)seek_start);
for (unsigned int x = seek_start; x < 255; ++x) {
uint8_t key;
uint32_t value;
BOOST_CHECK(it->Valid());
// Avoid spurious errors about invalid iterator's key and value in
// case of failure
if (!it->Valid()) {
break;
}
BOOST_CHECK(it->GetKey(key));
if (x & 1) {
BOOST_CHECK_EQUAL(key, x + 1);
continue;
}
BOOST_CHECK(it->GetValue(value));
BOOST_CHECK_EQUAL(key, x);
BOOST_CHECK_EQUAL(value, x * x);
it->Next();
}
BOOST_CHECK(!it->Valid());
}
}
struct StringContentsSerializer {
// Used to make two serialized objects the same while letting them have
// different lengths. This is a terrible idea.
std::string str;
StringContentsSerializer() {}
explicit StringContentsSerializer(const std::string &inp) : str(inp) {}
StringContentsSerializer &operator+=(const std::string &s) {
str += s;
return *this;
}
StringContentsSerializer &operator+=(const StringContentsSerializer &s) {
return *this += s.str;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
if (ser_action.ForRead()) {
str.clear();
char c = 0;
while (true) {
try {
READWRITE(c);
str.push_back(c);
- } catch (const std::ios_base::failure &e) {
+ } catch (const std::ios_base::failure &) {
break;
}
}
} else {
for (size_t i = 0; i < str.size(); i++) {
READWRITE(str[i]);
}
}
}
};
BOOST_AUTO_TEST_CASE(iterator_string_ordering) {
char buf[10];
fs::path ph = SetDataDir("iterator_string_ordering");
CDBWrapper dbw(ph, (1 << 20), true, false, false);
for (int x = 0x00; x < 10; ++x) {
for (int y = 0; y < 10; y++) {
snprintf(buf, sizeof(buf), "%d", x);
StringContentsSerializer key(buf);
for (int z = 0; z < y; z++) {
key += key;
}
uint32_t value = x * x;
BOOST_CHECK(dbw.Write(key, value));
}
}
std::unique_ptr<CDBIterator> it(
const_cast<CDBWrapper &>(dbw).NewIterator());
for (const int seek_start : {0, 5}) {
snprintf(buf, sizeof(buf), "%d", seek_start);
StringContentsSerializer seek_key(buf);
it->Seek(seek_key);
for (unsigned int x = seek_start; x < 10; ++x) {
for (int y = 0; y < 10; y++) {
snprintf(buf, sizeof(buf), "%d", x);
std::string exp_key(buf);
for (int z = 0; z < y; z++) {
exp_key += exp_key;
}
StringContentsSerializer key;
uint32_t value;
BOOST_CHECK(it->Valid());
// Avoid spurious errors about invalid iterator's key and value
// in case of failure
if (!it->Valid()) {
break;
}
BOOST_CHECK(it->GetKey(key));
BOOST_CHECK(it->GetValue(value));
BOOST_CHECK_EQUAL(key.str, exp_key);
BOOST_CHECK_EQUAL(value, x * x);
it->Next();
}
}
BOOST_CHECK(!it->Valid());
}
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
index d733e35f2..f0508ac95 100644
--- a/src/test/net_tests.cpp
+++ b/src/test/net_tests.cpp
@@ -1,343 +1,343 @@
// Copyright (c) 2012-2016 The Bitcoin Core developers
// Copyright (c) 2017-2018 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <net.h>
#include <addrman.h>
#include <chainparams.h>
#include <clientversion.h>
#include <config.h>
#include <hash.h>
#include <netbase.h>
#include <serialize.h>
#include <streams.h>
#include <test/test_bitcoin.h>
#include <boost/test/unit_test.hpp>
#include <memory>
#include <string>
class CAddrManSerializationMock : public CAddrMan {
public:
virtual void Serialize(CDataStream &s) const = 0;
//! Ensure that bucket placement is always the same for testing purposes.
void MakeDeterministic() {
nKey.SetNull();
insecure_rand = FastRandomContext(true);
}
};
class CAddrManUncorrupted : public CAddrManSerializationMock {
public:
void Serialize(CDataStream &s) const override { CAddrMan::Serialize(s); }
};
class CAddrManCorrupted : public CAddrManSerializationMock {
public:
void Serialize(CDataStream &s) const override {
// Produces corrupt output that claims addrman has 20 addrs when it only
// has one addr.
uint8_t nVersion = 1;
s << nVersion;
s << uint8_t(32);
s << nKey;
s << 10; // nNew
s << 10; // nTried
int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
s << nUBuckets;
CService serv;
Lookup("252.1.1.1", serv, 7777, false);
CAddress addr = CAddress(serv, NODE_NONE);
CNetAddr resolved;
LookupHost("252.2.2.2", resolved, false);
CAddrInfo info = CAddrInfo(addr, resolved);
s << info;
}
};
class NetTestConfig : public DummyConfig {
public:
bool SetMaxBlockSize(uint64_t maxBlockSize) override {
nMaxBlockSize = maxBlockSize;
return true;
}
uint64_t GetMaxBlockSize() const override { return nMaxBlockSize; }
private:
uint64_t nMaxBlockSize;
};
static CDataStream AddrmanToStream(CAddrManSerializationMock &_addrman) {
CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION);
ssPeersIn << Params().DiskMagic();
ssPeersIn << _addrman;
std::string str = ssPeersIn.str();
std::vector<uint8_t> vchData(str.begin(), str.end());
return CDataStream(vchData, SER_DISK, CLIENT_VERSION);
}
BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(cnode_listen_port) {
// test default
unsigned short port = GetListenPort();
BOOST_CHECK(port == Params().GetDefaultPort());
// test set port
unsigned short altPort = 12345;
gArgs.SoftSetArg("-port", std::to_string(altPort));
port = GetListenPort();
BOOST_CHECK(port == altPort);
}
BOOST_AUTO_TEST_CASE(caddrdb_read) {
SetDataDir("caddrdb_read");
CAddrManUncorrupted addrmanUncorrupted;
addrmanUncorrupted.MakeDeterministic();
CService addr1, addr2, addr3;
Lookup("250.7.1.1", addr1, 8333, false);
Lookup("250.7.2.2", addr2, 9999, false);
Lookup("250.7.3.3", addr3, 9999, false);
// Add three addresses to new table.
CService source;
Lookup("252.5.1.1", source, 8333, false);
addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source);
addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source);
addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source);
// Test that the de-serialization does not throw an exception.
CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted);
bool exceptionThrown = false;
CAddrMan addrman1;
BOOST_CHECK(addrman1.size() == 0);
try {
uint8_t pchMsgTmp[4];
ssPeers1 >> pchMsgTmp;
ssPeers1 >> addrman1;
- } catch (const std::exception &e) {
+ } catch (const std::exception &) {
exceptionThrown = true;
}
BOOST_CHECK(addrman1.size() == 3);
BOOST_CHECK(exceptionThrown == false);
// Test that CAddrDB::Read creates an addrman with the correct number of
// addrs.
CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted);
CAddrMan addrman2;
CAddrDB adb(Params());
BOOST_CHECK(addrman2.size() == 0);
adb.Read(addrman2, ssPeers2);
BOOST_CHECK(addrman2.size() == 3);
}
BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) {
SetDataDir("caddrdb_read_corrupted");
CAddrManCorrupted addrmanCorrupted;
addrmanCorrupted.MakeDeterministic();
// Test that the de-serialization of corrupted addrman throws an exception.
CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted);
bool exceptionThrown = false;
CAddrMan addrman1;
BOOST_CHECK(addrman1.size() == 0);
try {
uint8_t pchMsgTmp[4];
ssPeers1 >> pchMsgTmp;
ssPeers1 >> addrman1;
- } catch (const std::exception &e) {
+ } catch (const std::exception &) {
exceptionThrown = true;
}
// Even through de-serialization failed addrman is not left in a clean
// state.
BOOST_CHECK(addrman1.size() == 1);
BOOST_CHECK(exceptionThrown);
// Test that CAddrDB::Read leaves addrman in a clean state if
// de-serialization fails.
CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted);
CAddrMan addrman2;
CAddrDB adb(Params());
BOOST_CHECK(addrman2.size() == 0);
adb.Read(addrman2, ssPeers2);
BOOST_CHECK(addrman2.size() == 0);
}
BOOST_AUTO_TEST_CASE(cnode_simple_test) {
SOCKET hSocket = INVALID_SOCKET;
NodeId id = 0;
int height = 0;
in_addr ipv4Addr;
ipv4Addr.s_addr = 0xa0b0c001;
CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK);
std::string pszDest;
bool fInboundIn = false;
// Test that fFeeler is false by default.
auto pnode1 =
std::make_unique<CNode>(id++, NODE_NETWORK, height, hSocket, addr, 0, 0,
CAddress(), pszDest, fInboundIn);
BOOST_CHECK(pnode1->fInbound == false);
BOOST_CHECK(pnode1->fFeeler == false);
fInboundIn = true;
auto pnode2 =
std::make_unique<CNode>(id++, NODE_NETWORK, height, hSocket, addr, 1, 1,
CAddress(), pszDest, fInboundIn);
BOOST_CHECK(pnode2->fInbound == true);
BOOST_CHECK(pnode2->fFeeler == false);
}
BOOST_AUTO_TEST_CASE(test_getSubVersionEB) {
BOOST_CHECK_EQUAL(getSubVersionEB(13800000000), "13800.0");
BOOST_CHECK_EQUAL(getSubVersionEB(3800000000), "3800.0");
BOOST_CHECK_EQUAL(getSubVersionEB(14000000), "14.0");
BOOST_CHECK_EQUAL(getSubVersionEB(1540000), "1.5");
BOOST_CHECK_EQUAL(getSubVersionEB(1560000), "1.5");
BOOST_CHECK_EQUAL(getSubVersionEB(210000), "0.2");
BOOST_CHECK_EQUAL(getSubVersionEB(10000), "0.0");
BOOST_CHECK_EQUAL(getSubVersionEB(0), "0.0");
}
BOOST_AUTO_TEST_CASE(test_userAgent) {
NetTestConfig config;
config.SetMaxBlockSize(8000000);
const std::string uacomment = "A very nice comment";
gArgs.ForceSetMultiArg("-uacomment", uacomment);
const std::string versionMessage =
"/Bitcoin ABC:" + std::to_string(CLIENT_VERSION_MAJOR) + "." +
std::to_string(CLIENT_VERSION_MINOR) + "." +
std::to_string(CLIENT_VERSION_REVISION) + "(EB8.0; " + uacomment + ")/";
BOOST_CHECK_EQUAL(userAgent(config), versionMessage);
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) {
BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true);
BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true);
SetReachable(NET_IPV4, false);
SetReachable(NET_IPV6, false);
SetReachable(NET_ONION, false);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), false);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), false);
BOOST_CHECK_EQUAL(IsReachable(NET_ONION), false);
SetReachable(NET_IPV4, true);
SetReachable(NET_IPV6, true);
SetReachable(NET_ONION, true);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true);
BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true);
BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true);
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) {
BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true);
BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true);
SetReachable(NET_UNROUTABLE, false);
SetReachable(NET_INTERNAL, false);
// Ignored for both networks
BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true);
BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true);
}
CNetAddr UtilBuildAddress(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4) {
uint8_t ip[] = {p1, p2, p3, p4};
struct sockaddr_in sa;
// initialize the memory block
memset(&sa, 0, sizeof(sockaddr_in));
memcpy(&(sa.sin_addr), &ip, sizeof(ip));
return CNetAddr(sa.sin_addr);
}
BOOST_AUTO_TEST_CASE(LimitedAndReachable_CNetAddr) {
// 1.1.1.1
CNetAddr addr = UtilBuildAddress(0x001, 0x001, 0x001, 0x001);
SetReachable(NET_IPV4, true);
BOOST_CHECK_EQUAL(IsReachable(addr), true);
SetReachable(NET_IPV4, false);
BOOST_CHECK_EQUAL(IsReachable(addr), false);
// have to reset this, because this is stateful.
SetReachable(NET_IPV4, true);
}
BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) {
// 2.1.1.1:1000
CService addr =
CService(UtilBuildAddress(0x002, 0x001, 0x001, 0x001), 1000);
SetReachable(NET_IPV4, true);
BOOST_CHECK_EQUAL(IsLocal(addr), false);
BOOST_CHECK_EQUAL(AddLocal(addr, 1000), true);
BOOST_CHECK_EQUAL(IsLocal(addr), true);
RemoveLocal(addr);
BOOST_CHECK_EQUAL(IsLocal(addr), false);
}
// prior to PR #14728, this test triggers an undefined behavior
BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) {
// set up local addresses; all that's necessary to reproduce the bug is
// that a normal IPv4 address is among the entries, but if this address is
// !IsRoutable the undefined behavior is easier to trigger deterministically
{
LOCK(cs_mapLocalHost);
in_addr ipv4AddrLocal;
ipv4AddrLocal.s_addr = 0x0100007f;
CNetAddr addr = CNetAddr(ipv4AddrLocal);
LocalServiceInfo lsi;
lsi.nScore = 23;
lsi.nPort = 42;
mapLocalHost[addr] = lsi;
}
// create a peer with an IPv4 address
in_addr ipv4AddrPeer;
ipv4AddrPeer.s_addr = 0xa0b0c001;
CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK);
std::unique_ptr<CNode> pnode =
std::make_unique<CNode>(0, NODE_NETWORK, 0, INVALID_SOCKET, addr, 0, 0,
CAddress{}, std::string{}, false);
pnode->fSuccessfullyConnected.store(true);
// the peer claims to be reaching us via IPv6
in6_addr ipv6AddrLocal;
memset(ipv6AddrLocal.s6_addr, 0, 16);
ipv6AddrLocal.s6_addr[0] = 0xcc;
CAddress addrLocal = CAddress(CService(ipv6AddrLocal, 7777), NODE_NETWORK);
pnode->SetAddrLocal(addrLocal);
// before patch, this causes undefined behavior detectable with clang's
// -fsanitize=memory
AdvertiseLocal(&*pnode);
// suppress no-checks-run warning; if this test fails, it's by triggering a
// sanitizer
BOOST_CHECK(1);
}
BOOST_AUTO_TEST_SUITE_END()

File Metadata

Mime Type
text/x-diff
Expires
Sun, Apr 27, 12:44 (21 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5563374
Default Alt Text
(83 KB)

Event Timeline