Files
hush3/src/test/coins_tests.cpp
Jack Grigg e5eab182b5 Use boost::variant to represent shielded addresses and keys
libzcash::PaymentAddress has been renamed to libzcash::SproutPaymentAddress,
and a new typedef boost::variant is now libzcash::PaymentAddress. Similarly
for ViewingKey and SpendingKey.

A new class InvalidEncoding is introduced as the default boost::variant
option for each address and key type; it is used during decoding instead
of boost::optional.

All address and key storage functions in the wallet have been modified to
refer specifically to the Sprout types, as they are used very precisely.
In most other cases, the more general type is leveraged as much as possible,
and we convert to the Sprout type when necessary. This will be subsequently
wrapped in, or replaced with, context-specific functions once Sapling
types are implemented.
2018-05-11 17:14:49 -04:00

951 lines
30 KiB
C++

// Copyright (c) 2014 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 "random.h"
#include "script/standard.h"
#include "uint256.h"
#include "utilstrencodings.h"
#include "test/test_bitcoin.h"
#include "consensus/validation.h"
#include "main.h"
#include "undo.h"
#include "primitives/transaction.h"
#include "pubkey.h"
#include <vector>
#include <map>
#include <boost/test/unit_test.hpp>
#include "zcash/IncrementalMerkleTree.hpp"
namespace
{
class CCoinsViewTest : public CCoinsView
{
uint256 hashBestBlock_;
uint256 hashBestSproutAnchor_;
uint256 hashBestSaplingAnchor_;
std::map<uint256, CCoins> map_;
std::map<uint256, ZCIncrementalMerkleTree> mapSproutAnchors_;
std::map<uint256, ZCSaplingIncrementalMerkleTree> mapSaplingAnchors_;
std::map<uint256, bool> mapSproutNullifiers_;
std::map<uint256, bool> mapSaplingNullifiers_;
public:
CCoinsViewTest() {
hashBestSproutAnchor_ = ZCIncrementalMerkleTree::empty_root();
hashBestSaplingAnchor_ = ZCSaplingIncrementalMerkleTree::empty_root();
}
bool GetSproutAnchorAt(const uint256& rt, ZCIncrementalMerkleTree &tree) const {
if (rt == ZCIncrementalMerkleTree::empty_root()) {
ZCIncrementalMerkleTree new_tree;
tree = new_tree;
return true;
}
std::map<uint256, ZCIncrementalMerkleTree>::const_iterator it = mapSproutAnchors_.find(rt);
if (it == mapSproutAnchors_.end()) {
return false;
} else {
tree = it->second;
return true;
}
}
bool GetSaplingAnchorAt(const uint256& rt, ZCSaplingIncrementalMerkleTree &tree) const {
if (rt == ZCSaplingIncrementalMerkleTree::empty_root()) {
ZCSaplingIncrementalMerkleTree new_tree;
tree = new_tree;
return true;
}
std::map<uint256, ZCSaplingIncrementalMerkleTree>::const_iterator it = mapSaplingAnchors_.find(rt);
if (it == mapSaplingAnchors_.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;
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;
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.entered) {
cacheNullifiers[it->first] = true;
} else {
cacheNullifiers.erase(it->first);
}
mapNullifiers.erase(it++);
}
mapNullifiers.clear();
}
bool BatchWrite(CCoinsMap& mapCoins,
const uint256& hashBlock,
const uint256& hashSproutAnchor,
const uint256& hashSaplingAnchor,
CAnchorsSproutMap& mapSproutAnchors,
CAnchorsSaplingMap& mapSaplingAnchors,
CNullifiersMap& mapSproutNullifiers,
CNullifiersMap& mapSaplingNullifiers)
{
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
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);
}
mapCoins.erase(it++);
}
for (CAnchorsSproutMap::iterator it = mapSproutAnchors.begin(); it != mapSproutAnchors.end(); ) {
if (it->second.entered) {
std::map<uint256, ZCIncrementalMerkleTree>::iterator ret =
mapSproutAnchors_.insert(std::make_pair(it->first, ZCIncrementalMerkleTree())).first;
ret->second = it->second.tree;
} else {
mapSproutAnchors_.erase(it->first);
}
mapSproutAnchors.erase(it++);
}
for (CAnchorsSaplingMap::iterator it = mapSaplingAnchors.begin(); it != mapSaplingAnchors.end(); ) {
if (it->second.entered) {
std::map<uint256, ZCSaplingIncrementalMerkleTree>::iterator ret =
mapSaplingAnchors_.insert(std::make_pair(it->first, ZCSaplingIncrementalMerkleTree())).first;
ret->second = it->second.tree;
} else {
mapSaplingAnchors_.erase(it->first);
}
mapSaplingAnchors.erase(it++);
}
BatchWriteNullifiers(mapSproutNullifiers, mapSproutNullifiers_);
BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_);
mapCoins.clear();
mapSproutAnchors.clear();
mapSaplingAnchors.clear();
hashBestBlock_ = hashBlock;
hashBestSproutAnchor_ = hashSproutAnchor;
hashBestSaplingAnchor_ = hashSaplingAnchor;
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(cacheSproutNullifiers) +
memusage::DynamicUsage(cacheSaplingNullifiers);
for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
ret += it->second.coins.DynamicMemoryUsage();
}
BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);
}
};
class TxWithNullifiers
{
public:
CTransaction tx;
uint256 sproutNullifier;
uint256 saplingNullifier;
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);
tx = CTransaction(mutableTx);
}
};
}
uint256 appendRandomCommitment(ZCIncrementalMerkleTree &tree)
{
libzcash::SproutSpendingKey k = libzcash::SproutSpendingKey::random();
libzcash::SproutPaymentAddress addr = k.address();
libzcash::SproutNote note(addr.a_pk, 0, uint256(), uint256());
auto cm = note.cm();
tree.append(cm);
return cm;
}
BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
void checkNullifierCache(const CCoinsViewCacheTest &cache, const TxWithNullifiers &txWithNullifiers, bool shouldBeInCache) {
// Make sure the nullifiers have not gotten mixed up
BOOST_CHECK(!cache.GetNullifier(txWithNullifiers.sproutNullifier, SAPLING));
BOOST_CHECK(!cache.GetNullifier(txWithNullifiers.saplingNullifier, SPROUT));
// Check if the nullifiers either are or are not in the cache
bool containsSproutNullifier = cache.GetNullifier(txWithNullifiers.sproutNullifier, SPROUT);
bool containsSaplingNullifier = cache.GetNullifier(txWithNullifiers.saplingNullifier, SAPLING);
BOOST_CHECK(containsSproutNullifier == shouldBeInCache);
BOOST_CHECK(containsSaplingNullifier == shouldBeInCache);
}
BOOST_AUTO_TEST_CASE(nullifier_regression_test)
{
// Correct behavior:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
TxWithNullifiers txWithNullifiers;
// Insert a nullifier into the base.
cache1.SetNullifiers(txWithNullifiers.tx, true);
checkNullifierCache(cache1, txWithNullifiers, true);
cache1.Flush(); // Flush to base.
// Remove the nullifier from cache
cache1.SetNullifiers(txWithNullifiers.tx, false);
// The nullifier now should be `false`.
checkNullifierCache(cache1, txWithNullifiers, false);
}
// Also correct behavior:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
TxWithNullifiers txWithNullifiers;
// Insert a nullifier into the base.
cache1.SetNullifiers(txWithNullifiers.tx, true);
checkNullifierCache(cache1, txWithNullifiers, true);
cache1.Flush(); // Flush to base.
// Remove the nullifier from cache
cache1.SetNullifiers(txWithNullifiers.tx, false);
cache1.Flush(); // Flush to base.
// The nullifier now should be `false`.
checkNullifierCache(cache1, txWithNullifiers, false);
}
// Works because we bring it from the parent cache:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Insert a nullifier into the base.
TxWithNullifiers txWithNullifiers;
cache1.SetNullifiers(txWithNullifiers.tx, true);
checkNullifierCache(cache1, txWithNullifiers, true);
cache1.Flush(); // Empties cache.
// Create cache on top.
{
// Remove the nullifier.
CCoinsViewCacheTest cache2(&cache1);
checkNullifierCache(cache2, txWithNullifiers, true);
cache1.SetNullifiers(txWithNullifiers.tx, false);
cache2.Flush(); // Empties cache, flushes to cache1.
}
// The nullifier now should be `false`.
checkNullifierCache(cache1, txWithNullifiers, false);
}
// Was broken:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Insert a nullifier into the base.
TxWithNullifiers txWithNullifiers;
cache1.SetNullifiers(txWithNullifiers.tx, true);
cache1.Flush(); // Empties cache.
// Create cache on top.
{
// Remove the nullifier.
CCoinsViewCacheTest cache2(&cache1);
cache2.SetNullifiers(txWithNullifiers.tx, false);
cache2.Flush(); // Empties cache, flushes to cache1.
}
// The nullifier now should be `false`.
checkNullifierCache(cache1, txWithNullifiers, false);
}
}
BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
{
// Correct behavior:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Create dummy anchor/commitment
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
// Add the anchor
cache1.PushSproutAnchor(tree);
cache1.Flush();
// Remove the anchor
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache1.Flush();
// Add the anchor back
cache1.PushSproutAnchor(tree);
cache1.Flush();
// The base contains the anchor, of course!
{
ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetSproutAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root());
}
}
// Previously incorrect behavior
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Create dummy anchor/commitment
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
// Add the anchor and flush to disk
cache1.PushSproutAnchor(tree);
cache1.Flush();
// Remove the anchor, but don't flush yet!
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
{
CCoinsViewCacheTest cache2(&cache1); // Build cache on top
cache2.PushSproutAnchor(tree); // Put the same anchor back!
cache2.Flush(); // Flush to cache1
}
// cache2's flush kinda worked, i.e. cache1 thinks the
// tree is there, but it didn't bring down the correct
// treestate...
{
ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetSproutAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
}
// Flushing cache won't help either, just makes the inconsistency
// permanent.
cache1.Flush();
{
ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetSproutAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
}
}
}
BOOST_AUTO_TEST_CASE(anchor_regression_test)
{
// Correct behavior:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Insert anchor into base.
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
cache1.PushSproutAnchor(tree);
cache1.Flush();
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
}
// Also correct behavior:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Insert anchor into base.
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
cache1.PushSproutAnchor(tree);
cache1.Flush();
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache1.Flush();
BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
}
// Works because we bring the anchor in from parent cache.
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Insert anchor into base.
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
cache1.PushSproutAnchor(tree);
cache1.Flush();
{
// Pop anchor.
CCoinsViewCacheTest cache2(&cache1);
BOOST_CHECK(cache2.GetSproutAnchorAt(tree.root(), tree));
cache2.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache2.Flush();
}
BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
}
// Was broken:
{
CCoinsViewTest base;
CCoinsViewCacheTest cache1(&base);
// Insert anchor into base.
ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash();
tree.append(cm);
cache1.PushSproutAnchor(tree);
cache1.Flush();
{
// Pop anchor.
CCoinsViewCacheTest cache2(&cache1);
cache2.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache2.Flush();
}
BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
}
}
BOOST_AUTO_TEST_CASE(nullifiers_test)
{
CCoinsViewTest base;
CCoinsViewCacheTest cache(&base);
TxWithNullifiers txWithNullifiers;
checkNullifierCache(cache, txWithNullifiers, false);
cache.SetNullifiers(txWithNullifiers.tx, true);
checkNullifierCache(cache, txWithNullifiers, true);
cache.Flush();
CCoinsViewCacheTest cache2(&base);
checkNullifierCache(cache2, txWithNullifiers, true);
cache2.SetNullifiers(txWithNullifiers.tx, false);
checkNullifierCache(cache2, txWithNullifiers, false);
cache2.Flush();
CCoinsViewCacheTest cache3(&base);
checkNullifierCache(cache3, txWithNullifiers, false);
}
BOOST_AUTO_TEST_CASE(anchors_flush_test)
{
CCoinsViewTest base;
uint256 newrt;
{
CCoinsViewCacheTest cache(&base);
ZCIncrementalMerkleTree tree;
BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
appendRandomCommitment(tree);
newrt = tree.root();
cache.PushSproutAnchor(tree);
cache.Flush();
}
{
CCoinsViewCacheTest cache(&base);
ZCIncrementalMerkleTree tree;
BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
// Get the cached entry.
BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
uint256 check_rt = tree.root();
BOOST_CHECK(check_rt == newrt);
}
}
BOOST_AUTO_TEST_CASE(chained_joinsplits)
{
CCoinsViewTest base;
CCoinsViewCacheTest cache(&base);
ZCIncrementalMerkleTree tree;
JSDescription js1;
js1.anchor = tree.root();
js1.commitments[0] = appendRandomCommitment(tree);
js1.commitments[1] = appendRandomCommitment(tree);
// Although it's not possible given our assumptions, if
// two joinsplits create the same treestate twice, we should
// still be able to anchor to it.
JSDescription js1b;
js1b.anchor = tree.root();
js1b.commitments[0] = js1.commitments[0];
js1b.commitments[1] = js1.commitments[1];
JSDescription js2;
JSDescription js3;
js2.anchor = tree.root();
js3.anchor = tree.root();
js2.commitments[0] = appendRandomCommitment(tree);
js2.commitments[1] = appendRandomCommitment(tree);
js3.commitments[0] = appendRandomCommitment(tree);
js3.commitments[1] = appendRandomCommitment(tree);
{
CMutableTransaction mtx;
mtx.vjoinsplit.push_back(js2);
BOOST_CHECK(!cache.HaveJoinSplitRequirements(mtx));
}
{
// js2 is trying to anchor to js1 but js1
// appears afterwards -- not a permitted ordering
CMutableTransaction mtx;
mtx.vjoinsplit.push_back(js2);
mtx.vjoinsplit.push_back(js1);
BOOST_CHECK(!cache.HaveJoinSplitRequirements(mtx));
}
{
CMutableTransaction mtx;
mtx.vjoinsplit.push_back(js1);
mtx.vjoinsplit.push_back(js2);
BOOST_CHECK(cache.HaveJoinSplitRequirements(mtx));
}
{
CMutableTransaction mtx;
mtx.vjoinsplit.push_back(js1);
mtx.vjoinsplit.push_back(js2);
mtx.vjoinsplit.push_back(js3);
BOOST_CHECK(cache.HaveJoinSplitRequirements(mtx));
}
{
CMutableTransaction mtx;
mtx.vjoinsplit.push_back(js1);
mtx.vjoinsplit.push_back(js1b);
mtx.vjoinsplit.push_back(js2);
mtx.vjoinsplit.push_back(js3);
BOOST_CHECK(cache.HaveJoinSplitRequirements(mtx));
}
}
BOOST_AUTO_TEST_CASE(anchors_test)
{
// TODO: These tests should be more methodical.
// Or, integrate with Bitcoin's tests later.
CCoinsViewTest base;
CCoinsViewCacheTest cache(&base);
BOOST_CHECK(cache.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
{
ZCIncrementalMerkleTree tree;
BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
BOOST_CHECK(cache.GetBestAnchor(SPROUT) == tree.root());
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
ZCIncrementalMerkleTree save_tree_for_later;
save_tree_for_later = tree;
uint256 newrt = tree.root();
uint256 newrt2;
cache.PushSproutAnchor(tree);
BOOST_CHECK(cache.GetBestAnchor(SPROUT) == newrt);
{
ZCIncrementalMerkleTree confirm_same;
BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), confirm_same));
BOOST_CHECK(confirm_same.root() == newrt);
}
appendRandomCommitment(tree);
appendRandomCommitment(tree);
newrt2 = tree.root();
cache.PushSproutAnchor(tree);
BOOST_CHECK(cache.GetBestAnchor(SPROUT) == newrt2);
ZCIncrementalMerkleTree test_tree;
BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), test_tree));
BOOST_CHECK(tree.root() == test_tree.root());
{
ZCIncrementalMerkleTree test_tree2;
cache.GetSproutAnchorAt(newrt, test_tree2);
BOOST_CHECK(test_tree2.root() == newrt);
}
{
cache.PopAnchor(newrt, SPROUT);
ZCIncrementalMerkleTree obtain_tree;
assert(!cache.GetSproutAnchorAt(newrt2, obtain_tree)); // should have been popped off
assert(cache.GetSproutAnchorAt(newrt, obtain_tree));
assert(obtain_tree.root() == newrt);
}
}
}
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 CCoins 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 removed_an_entry = false;
bool updated_an_entry = false;
bool found_an_entry = false;
bool missed_an_entry = false;
// A simple map to track what we expect the cache stack to represent.
std::map<uint256, CCoins> result;
// The cache stack.
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
// Use a limited set of random transaction ids, so we do test overwriting entries.
std::vector<uint256> txids;
txids.resize(NUM_SIMULATION_ITERATIONS / 8);
for (unsigned int i = 0; i < txids.size(); i++) {
txids[i] = GetRandHash();
}
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
// Do a random modification.
{
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
CCoins& coins = result[txid];
CCoinsModifier entry = stack.back()->ModifyCoins(txid);
BOOST_CHECK(coins == *entry);
if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
if (coins.IsPruned()) {
added_an_entry = true;
} else {
updated_an_entry = true;
}
coins.nVersion = insecure_rand();
coins.vout.resize(1);
coins.vout[0].nValue = insecure_rand();
*entry = coins;
} else {
coins.Clear();
entry->Clear();
removed_an_entry = true;
}
}
// Once every 1000 iterations and at the end, verify the full cache.
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
const CCoins* coins = stack.back()->AccessCoins(it->first);
if (coins) {
BOOST_CHECK(*coins == it->second);
found_an_entry = true;
} else {
BOOST_CHECK(it->second.IsPruned());
missed_an_entry = true;
}
}
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
test->SelfTest();
}
}
if (insecure_rand() % 100 == 0) {
// Every 100 iterations, change the cache stack.
if (stack.size() > 0 && insecure_rand() % 2 == 0) {
stack.back()->Flush();
delete stack.back();
stack.pop_back();
}
if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) {
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(removed_an_entry);
BOOST_CHECK(updated_an_entry);
BOOST_CHECK(found_an_entry);
BOOST_CHECK(missed_an_entry);
}
BOOST_AUTO_TEST_CASE(coins_coinbase_spends)
{
CCoinsViewTest base;
CCoinsViewCacheTest cache(&base);
// Create coinbase transaction
CMutableTransaction mtx;
mtx.vin.resize(1);
mtx.vin[0].scriptSig = CScript() << OP_1;
mtx.vin[0].nSequence = 0;
mtx.vout.resize(1);
mtx.vout[0].nValue = 500;
mtx.vout[0].scriptPubKey = CScript() << OP_1;
CTransaction tx(mtx);
BOOST_CHECK(tx.IsCoinBase());
CValidationState state;
UpdateCoins(tx, cache, 100);
// Create coinbase spend
CMutableTransaction mtx2;
mtx2.vin.resize(1);
mtx2.vin[0].prevout = COutPoint(tx.GetHash(), 0);
mtx2.vin[0].scriptSig = CScript() << OP_1;
mtx2.vin[0].nSequence = 0;
{
CTransaction tx2(mtx2);
BOOST_CHECK(Consensus::CheckTxInputs(tx2, state, cache, 100+COINBASE_MATURITY, Params().GetConsensus()));
}
mtx2.vout.resize(1);
mtx2.vout[0].nValue = 500;
mtx2.vout[0].scriptPubKey = CScript() << OP_1;
{
CTransaction tx2(mtx2);
BOOST_CHECK(!Consensus::CheckTxInputs(tx2, state, cache, 100+COINBASE_MATURITY, Params().GetConsensus()));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-coinbase-spend-has-transparent-outputs");
}
}
BOOST_AUTO_TEST_CASE(ccoins_serialization)
{
// Good example
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION);
CCoins cc1;
ss1 >> cc1;
BOOST_CHECK_EQUAL(cc1.nVersion, 1);
BOOST_CHECK_EQUAL(cc1.fCoinBase, false);
BOOST_CHECK_EQUAL(cc1.nHeight, 203998);
BOOST_CHECK_EQUAL(cc1.vout.size(), 2);
BOOST_CHECK_EQUAL(cc1.IsAvailable(0), false);
BOOST_CHECK_EQUAL(cc1.IsAvailable(1), true);
BOOST_CHECK_EQUAL(cc1.vout[1].nValue, 60000000000ULL);
BOOST_CHECK_EQUAL(HexStr(cc1.vout[1].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
// Good example
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION);
CCoins cc2;
ss2 >> cc2;
BOOST_CHECK_EQUAL(cc2.nVersion, 1);
BOOST_CHECK_EQUAL(cc2.fCoinBase, true);
BOOST_CHECK_EQUAL(cc2.nHeight, 120891);
BOOST_CHECK_EQUAL(cc2.vout.size(), 17);
for (int i = 0; i < 17; i++) {
BOOST_CHECK_EQUAL(cc2.IsAvailable(i), i == 4 || i == 16);
}
BOOST_CHECK_EQUAL(cc2.vout[4].nValue, 234925952);
BOOST_CHECK_EQUAL(HexStr(cc2.vout[4].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("61b01caab50f1b8e9c50a5057eb43c2d9563a4ee"))))));
BOOST_CHECK_EQUAL(cc2.vout[16].nValue, 110397);
BOOST_CHECK_EQUAL(HexStr(cc2.vout[16].scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"))))));
// Smallest possible example
CDataStream ssx(SER_DISK, CLIENT_VERSION);
BOOST_CHECK_EQUAL(HexStr(ssx.begin(), ssx.end()), "");
CDataStream ss3(ParseHex("0002000600"), SER_DISK, CLIENT_VERSION);
CCoins cc3;
ss3 >> cc3;
BOOST_CHECK_EQUAL(cc3.nVersion, 0);
BOOST_CHECK_EQUAL(cc3.fCoinBase, false);
BOOST_CHECK_EQUAL(cc3.nHeight, 0);
BOOST_CHECK_EQUAL(cc3.vout.size(), 1);
BOOST_CHECK_EQUAL(cc3.IsAvailable(0), true);
BOOST_CHECK_EQUAL(cc3.vout[0].nValue, 0);
BOOST_CHECK_EQUAL(cc3.vout[0].scriptPubKey.size(), 0);
// scriptPubKey that ends beyond the end of the stream
CDataStream ss4(ParseHex("0002000800"), SER_DISK, CLIENT_VERSION);
try {
CCoins cc4;
ss4 >> cc4;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) {
}
// 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("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION);
try {
CCoins cc5;
ss5 >> cc5;
BOOST_CHECK_MESSAGE(false, "We should have thrown");
} catch (const std::ios_base::failure& e) {
}
}
BOOST_AUTO_TEST_SUITE_END()