Port CCoinsViewTest to gtest suite CoinsTests

This commit is contained in:
sasha 2022-04-26 15:04:46 -07:00
parent 313dec27dd
commit d04f86b030
1 changed files with 276 additions and 0 deletions

View File

@ -24,3 +24,279 @@
#include <gtest/gtest.h>
#include "zcash/IncrementalMerkleTree.hpp"
namespace
{
class CCoinsViewTest : public CCoinsView
{
uint256 hashBestBlock_;
uint256 hashBestSproutAnchor_;
uint256 hashBestSaplingAnchor_;
uint256 hashBestOrchardAnchor_;
std::map<uint256, CCoins> map_;
std::map<uint256, SproutMerkleTree> mapSproutAnchors_;
std::map<uint256, SaplingMerkleTree> mapSaplingAnchors_;
std::map<uint256, OrchardMerkleFrontier> mapOrchardAnchors_;
std::map<uint256, bool> mapSproutNullifiers_;
std::map<uint256, bool> mapSaplingNullifiers_;
std::map<uint256, bool> mapOrchardNullifiers_;
public:
CCoinsViewTest() {
hashBestSproutAnchor_ = SproutMerkleTree::empty_root();
hashBestSaplingAnchor_ = SaplingMerkleTree::empty_root();
hashBestOrchardAnchor_ = OrchardMerkleFrontier::empty_root();
}
bool GetSproutAnchorAt(const uint256& rt, SproutMerkleTree &tree) const {
if (rt == SproutMerkleTree::empty_root()) {
SproutMerkleTree new_tree;
tree = new_tree;
return true;
}
std::map<uint256, SproutMerkleTree>::const_iterator it = mapSproutAnchors_.find(rt);
if (it == mapSproutAnchors_.end()) {
return false;
} else {
tree = it->second;
return true;
}
}
bool GetSaplingAnchorAt(const uint256& rt, SaplingMerkleTree &tree) const {
if (rt == SaplingMerkleTree::empty_root()) {
SaplingMerkleTree new_tree;
tree = new_tree;
return true;
}
std::map<uint256, SaplingMerkleTree>::const_iterator it = mapSaplingAnchors_.find(rt);
if (it == mapSaplingAnchors_.end()) {
return false;
} else {
tree = it->second;
return true;
}
}
bool GetOrchardAnchorAt(const uint256& rt, OrchardMerkleFrontier &tree) const {
if (rt == OrchardMerkleFrontier::empty_root()) {
OrchardMerkleFrontier new_tree;
tree = new_tree;
return true;
}
std::map<uint256, OrchardMerkleFrontier>::const_iterator it = mapOrchardAnchors_.find(rt);
if (it == mapOrchardAnchors_.end()) {
return false;
} else {
tree = it->second;
return true;
}
}
bool GetNullifier(const uint256 &nf, ShieldedType type) const
{
const std::map<uint256, bool>* mapToUse;
switch (type) {
case SPROUT:
mapToUse = &mapSproutNullifiers_;
break;
case SAPLING:
mapToUse = &mapSaplingNullifiers_;
break;
case ORCHARD:
mapToUse = &mapOrchardNullifiers_;
break;
default:
throw std::runtime_error("Unknown shielded type");
}
std::map<uint256, bool>::const_iterator it = mapToUse->find(nf);
if (it == mapToUse->end()) {
return false;
} else {
// The map shouldn't contain any false entries.
assert(it->second);
return true;
}
}
uint256 GetBestAnchor(ShieldedType type) const {
switch (type) {
case SPROUT:
return hashBestSproutAnchor_;
break;
case SAPLING:
return hashBestSaplingAnchor_;
break;
case ORCHARD:
return hashBestOrchardAnchor_;
break;
default:
throw std::runtime_error("Unknown shielded type");
}
}
bool GetCoins(const uint256& txid, CCoins& coins) const
{
std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
if (it == map_.end()) {
return false;
}
coins = it->second;
if (coins.IsPruned() && insecure_rand() % 2 == 0) {
// Randomly return false in case of an empty entry.
return false;
}
return true;
}
bool HaveCoins(const uint256& txid) const
{
CCoins coins;
return GetCoins(txid, coins);
}
uint256 GetBestBlock() const { return hashBestBlock_; }
void BatchWriteNullifiers(CNullifiersMap& mapNullifiers, std::map<uint256, bool>& cacheNullifiers)
{
for (CNullifiersMap::iterator it = mapNullifiers.begin(); it != mapNullifiers.end(); ) {
if (it->second.flags & CNullifiersCacheEntry::DIRTY) {
// Same optimization used in CCoinsViewDB is to only write dirty entries.
if (it->second.entered) {
cacheNullifiers[it->first] = true;
} else {
cacheNullifiers.erase(it->first);
}
}
it = mapNullifiers.erase(it);
}
}
template<typename Tree, typename Map, typename MapEntry>
void BatchWriteAnchors(Map& mapAnchors, std::map<uint256, Tree>& cacheAnchors)
{
for (auto it = mapAnchors.begin(); it != mapAnchors.end(); ) {
if (it->second.flags & MapEntry::DIRTY) {
// Same optimization used in CCoinsViewDB is to only write dirty entries.
if (it->second.entered) {
if (it->first != Tree::empty_root()) {
auto ret = cacheAnchors.insert(std::make_pair(it->first, Tree())).first;
ret->second = it->second.tree;
}
} else {
cacheAnchors.erase(it->first);
}
}
it = mapAnchors.erase(it);
}
}
bool BatchWrite(CCoinsMap& mapCoins,
const uint256& hashBlock,
const uint256& hashSproutAnchor,
const uint256& hashSaplingAnchor,
const uint256& hashOrchardAnchor,
CAnchorsSproutMap& mapSproutAnchors,
CAnchorsSaplingMap& mapSaplingAnchors,
CAnchorsOrchardMap& mapOrchardAnchors,
CNullifiersMap& mapSproutNullifiers,
CNullifiersMap& mapSaplingNullifiers,
CNullifiersMap& mapOrchardNullifiers,
CHistoryCacheMap &historyCacheMap)
{
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.coins;
if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
// Randomly delete empty entries on write.
map_.erase(it->first);
}
}
it = mapCoins.erase(it);
}
BatchWriteAnchors<SproutMerkleTree, CAnchorsSproutMap, CAnchorsSproutCacheEntry>(mapSproutAnchors, mapSproutAnchors_);
BatchWriteAnchors<SaplingMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingCacheEntry>(mapSaplingAnchors, mapSaplingAnchors_);
BatchWriteAnchors<OrchardMerkleFrontier, CAnchorsOrchardMap, CAnchorsOrchardCacheEntry>(mapOrchardAnchors, mapOrchardAnchors_);
BatchWriteNullifiers(mapSproutNullifiers, mapSproutNullifiers_);
BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_);
BatchWriteNullifiers(mapOrchardNullifiers, mapOrchardNullifiers_);
if (!hashBlock.IsNull())
hashBestBlock_ = hashBlock;
if (!hashSproutAnchor.IsNull())
hashBestSproutAnchor_ = hashSproutAnchor;
if (!hashSaplingAnchor.IsNull())
hashBestSaplingAnchor_ = hashSaplingAnchor;
if (!hashOrchardAnchor.IsNull())
hashBestOrchardAnchor_ = hashOrchardAnchor;
return true;
}
bool GetStats(CCoinsStats& stats) const { return false; }
};
class CCoinsViewCacheTest : public CCoinsViewCache
{
public:
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) +
memusage::DynamicUsage(cacheSproutAnchors) +
memusage::DynamicUsage(cacheSaplingAnchors) +
memusage::DynamicUsage(cacheOrchardAnchors) +
memusage::DynamicUsage(cacheSproutNullifiers) +
memusage::DynamicUsage(cacheSaplingNullifiers) +
memusage::DynamicUsage(cacheOrchardNullifiers) +
memusage::DynamicUsage(historyCacheMap);
for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
ret += it->second.coins.DynamicMemoryUsage();
}
EXPECT_EQ(DynamicMemoryUsage(), ret);
}
};
class TxWithNullifiers
{
public:
CTransaction tx;
uint256 sproutNullifier;
uint256 saplingNullifier;
uint256 orchardNullifier;
TxWithNullifiers()
{
CMutableTransaction mutableTx;
sproutNullifier = GetRandHash();
JSDescription jsd;
jsd.nullifiers[0] = sproutNullifier;
mutableTx.vJoinSplit.emplace_back(jsd);
saplingNullifier = GetRandHash();
SpendDescription sd;
sd.nullifier = saplingNullifier;
mutableTx.vShieldedSpend.push_back(sd);
// The Orchard bundle builder always pads to two Actions, so we can just
// use an empty builder to create a dummy Orchard bundle.
uint256 orchardAnchor;
uint256 dataToBeSigned;
auto builder = orchard::Builder(true, true, orchardAnchor);
mutableTx.orchardBundle = builder.Build().value().ProveAndSign({}, dataToBeSigned).value();
orchardNullifier = mutableTx.orchardBundle.GetNullifiers()[0];
tx = CTransaction(mutableTx);
}
};
}