Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13711232
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
83 KB
Subscribers
None
View Options
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 ¶m,
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Apr 27, 11:04 (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5563374
Default Alt Text
(83 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment