mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Looks like James hardcoded a couple hashes in hard-to-find places, which caused a unit test to fail. I just changed the hash (which covers UTXO data which naturally will be different for us on account of our differently formatted CTxOuts). TODO: determine how Assumeutxo interacts with the fact that we don't save out nonces in our normal UTXO serialization. Probably we will need to remove the nonce from the CCoinStats serialization to avoid having inconsistent hashes across nodes, since these hashes are now checked in assumeutxo?
381 lines
15 KiB
C++
381 lines
15 KiB
C++
// Copyright (c) 2012-2020 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 <consensus/consensus.h>
|
|
#include <logging.h>
|
|
#include <random.h>
|
|
#include <version.h>
|
|
|
|
bool CCoinsView::GetCoin(const COutPoint &outpoint, Coin &coin) const { return false; }
|
|
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
|
|
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
|
|
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
|
|
CCoinsViewCursor *CCoinsView::Cursor() const { return nullptr; }
|
|
// ELEMENTS:
|
|
bool CCoinsView::IsPeginSpent(const std::pair<uint256, COutPoint> &outpoint) const { return false; }
|
|
|
|
bool CCoinsView::HaveCoin(const COutPoint &outpoint) const
|
|
{
|
|
Coin coin;
|
|
return GetCoin(outpoint, coin);
|
|
}
|
|
|
|
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
|
|
bool CCoinsViewBacked::GetCoin(const COutPoint &outpoint, Coin &coin) const { return base->GetCoin(outpoint, coin); }
|
|
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
|
|
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
|
|
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
|
|
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
|
|
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
|
|
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
|
|
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }
|
|
// ELEMENTS:
|
|
bool CCoinsViewBacked::IsPeginSpent(const std::pair<uint256, COutPoint> &outpoint) const { return base->IsPeginSpent(outpoint); }
|
|
|
|
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {}
|
|
|
|
size_t CCoinsViewCache::DynamicMemoryUsage() const {
|
|
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
|
|
}
|
|
|
|
// ELEMENTS:
|
|
// Create a CCoinsMapKey for non-PEGIN utxo.
|
|
static inline CCoinsMapKey native_key(const COutPoint& outpoint) {
|
|
return std::make_pair(uint256(), outpoint);
|
|
}
|
|
|
|
CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
|
|
CCoinsMap::iterator it = cacheCoins.find(native_key(outpoint));
|
|
if (it != cacheCoins.end())
|
|
return it;
|
|
Coin tmp;
|
|
if (!base->GetCoin(outpoint, tmp))
|
|
return cacheCoins.end();
|
|
CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(native_key(outpoint)),
|
|
std::forward_as_tuple(std::move(tmp))).first;
|
|
if (ret->second.coin.IsSpent()) {
|
|
// The parent only has an empty entry for this outpoint; we can consider our
|
|
// version as fresh.
|
|
ret->second.flags = CCoinsCacheEntry::FRESH;
|
|
}
|
|
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
|
|
return ret;
|
|
}
|
|
|
|
bool CCoinsViewCache::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
|
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
|
if (it != cacheCoins.end()) {
|
|
coin = it->second.coin;
|
|
return !coin.IsSpent();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possible_overwrite) {
|
|
assert(!coin.IsSpent());
|
|
if (coin.out.scriptPubKey.IsUnspendable()) return;
|
|
CCoinsMap::iterator it;
|
|
bool inserted;
|
|
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(native_key(outpoint)), std::tuple<>());
|
|
bool fresh = false;
|
|
if (!inserted) {
|
|
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
|
|
}
|
|
if (!possible_overwrite) {
|
|
if (!it->second.coin.IsSpent()) {
|
|
throw std::logic_error("Attempted to overwrite an unspent coin (when possible_overwrite is false)");
|
|
}
|
|
// If the coin exists in this cache as a spent coin and is DIRTY, then
|
|
// its spentness hasn't been flushed to the parent cache. We're
|
|
// re-adding the coin to this cache now but we can't mark it as FRESH.
|
|
// If we mark it FRESH and then spend it before the cache is flushed
|
|
// we would remove it from this cache and would never flush spentness
|
|
// to the parent cache.
|
|
//
|
|
// Re-adding a spent coin can happen in the case of a re-org (the coin
|
|
// is 'spent' when the block adding it is disconnected and then
|
|
// re-added when it is also added in a newly connected block).
|
|
//
|
|
// If the coin doesn't exist in the current cache, or is spent but not
|
|
// DIRTY, then it can be marked FRESH.
|
|
fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY);
|
|
}
|
|
it->second.coin = std::move(coin);
|
|
it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0);
|
|
cachedCoinsUsage += it->second.coin.DynamicMemoryUsage();
|
|
}
|
|
|
|
void CCoinsViewCache::EmplaceCoinInternalDANGER(COutPoint&& outpoint, Coin&& coin) {
|
|
cachedCoinsUsage += coin.DynamicMemoryUsage();
|
|
cacheCoins.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(std::move(native_key(outpoint))),
|
|
std::forward_as_tuple(std::move(coin), CCoinsCacheEntry::DIRTY));
|
|
}
|
|
|
|
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check_for_overwrite) {
|
|
bool fCoinbase = tx.IsCoinBase();
|
|
const uint256& txid = tx.GetHash();
|
|
for (size_t i = 0; i < tx.vout.size(); ++i) {
|
|
bool overwrite = check_for_overwrite ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase;
|
|
// Coinbase transactions can always be overwritten, in order to correctly
|
|
// deal with the pre-BIP30 occurrences of duplicate coinbase transactions.
|
|
cache.AddCoin(COutPoint(txid, i), Coin(tx.vout[i], nHeight, fCoinbase), overwrite);
|
|
}
|
|
}
|
|
|
|
bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
|
|
CCoinsMap::iterator it = FetchCoin(outpoint);
|
|
if (it == cacheCoins.end()) return false;
|
|
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
|
|
if (moveout) {
|
|
*moveout = std::move(it->second.coin);
|
|
}
|
|
if (it->second.flags & CCoinsCacheEntry::FRESH) {
|
|
cacheCoins.erase(it);
|
|
} else {
|
|
it->second.flags |= CCoinsCacheEntry::DIRTY;
|
|
it->second.coin.Clear();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ELEMENTS:
|
|
// Because g_con_elementsmode is only set after the moment coinEmpty is initialized,
|
|
// we have to force set it to an empty coin without the default asset commitment.
|
|
Coin generateEmptyCoin() {
|
|
Coin coin;
|
|
coin.out.nValue.vchCommitment.clear();
|
|
coin.out.nAsset.vchCommitment.clear();
|
|
return coin;
|
|
}
|
|
static const Coin coinEmpty = generateEmptyCoin();
|
|
|
|
const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
|
|
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
|
if (it == cacheCoins.end()) {
|
|
return coinEmpty;
|
|
} else {
|
|
return it->second.coin;
|
|
}
|
|
}
|
|
|
|
bool CCoinsViewCache::HaveCoin(const COutPoint &outpoint) const {
|
|
CCoinsMap::const_iterator it = FetchCoin(outpoint);
|
|
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
|
|
}
|
|
|
|
bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const {
|
|
CCoinsMap::const_iterator it = cacheCoins.find(native_key(outpoint));
|
|
return (it != cacheCoins.end() && !it->second.coin.IsSpent());
|
|
}
|
|
|
|
//
|
|
// ELEMENTS:
|
|
|
|
bool CCoinsViewCache::IsPeginSpent(const std::pair<uint256, COutPoint> &outpoint) const {
|
|
assert(!outpoint.second.hash.IsNull());
|
|
assert(!outpoint.first.IsNull());
|
|
|
|
CCoinsMap::iterator it = cacheCoins.find(outpoint);
|
|
if (it == cacheCoins.end()) {
|
|
bool inserted;
|
|
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(outpoint), std::tuple<>());
|
|
it->second.peginSpent = base->IsPeginSpent(outpoint);
|
|
it->second.flags |= CCoinsCacheEntry::PEGIN;
|
|
if (!it->second.peginSpent)
|
|
it->second.flags |= CCoinsCacheEntry::FRESH;
|
|
}
|
|
return it->second.peginSpent;
|
|
}
|
|
|
|
void CCoinsViewCache::SetPeginSpent(const std::pair<uint256, COutPoint> &outpoint, bool fSpent) {
|
|
assert(!outpoint.second.hash.IsNull());
|
|
assert(!outpoint.first.IsNull());
|
|
|
|
CCoinsMap::iterator it = cacheCoins.find(outpoint);
|
|
|
|
bool hadSpent;
|
|
if (it == cacheCoins.end())
|
|
hadSpent = base->IsPeginSpent(outpoint);
|
|
else
|
|
hadSpent = it->second.peginSpent;
|
|
|
|
// If we aren't changing spentness, don't do anything at all
|
|
if (hadSpent == fSpent)
|
|
return;
|
|
|
|
if (it == cacheCoins.end()) {
|
|
bool inserted;
|
|
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(outpoint), std::tuple<>());
|
|
if (!hadSpent)
|
|
it->second.flags = CCoinsCacheEntry::FRESH;
|
|
}
|
|
it->second.peginSpent = fSpent;
|
|
it->second.flags |= CCoinsCacheEntry::PEGIN | CCoinsCacheEntry::DIRTY;
|
|
}
|
|
|
|
// END ELEMENTS
|
|
//
|
|
|
|
uint256 CCoinsViewCache::GetBestBlock() const {
|
|
if (hashBlock.IsNull())
|
|
hashBlock = base->GetBestBlock();
|
|
return hashBlock;
|
|
}
|
|
|
|
void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
|
|
hashBlock = hashBlockIn;
|
|
}
|
|
|
|
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
|
|
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); it = mapCoins.erase(it)) {
|
|
// Ignore non-dirty entries (optimization).
|
|
if (!(it->second.flags & CCoinsCacheEntry::DIRTY)) {
|
|
continue;
|
|
}
|
|
|
|
// ELEMENTS:
|
|
bool fIsPegin = it->second.flags & CCoinsCacheEntry::PEGIN;
|
|
|
|
CCoinsMap::iterator itUs = cacheCoins.find(it->first);
|
|
if (itUs == cacheCoins.end()) {
|
|
// The parent cache does not have an entry, while the child cache does
|
|
// We can ignore it if it's both FRESH and {pruned, spent pegin} in the child
|
|
if (!((it->second.flags & CCoinsCacheEntry::FRESH) &&
|
|
(( fIsPegin && !it->second.peginSpent) ||
|
|
(!fIsPegin && it->second.coin.IsSpent())))) {
|
|
// Create the coin in the parent cache, move the data up
|
|
// and mark it as dirty.
|
|
CCoinsCacheEntry& entry = cacheCoins[it->first];
|
|
entry.flags = CCoinsCacheEntry::DIRTY;
|
|
if (fIsPegin) {
|
|
entry.peginSpent = it->second.peginSpent;
|
|
entry.flags |= CCoinsCacheEntry::PEGIN;
|
|
} else {
|
|
entry.coin = it->second.coin;
|
|
}
|
|
cachedCoinsUsage += entry.coin.DynamicMemoryUsage();
|
|
// We can mark it FRESH in the parent if it was FRESH in the child
|
|
// Otherwise it might have just been flushed from the parent's cache
|
|
// and already exist in the grandparent
|
|
if (it->second.flags & CCoinsCacheEntry::FRESH) {
|
|
entry.flags |= CCoinsCacheEntry::FRESH;
|
|
}
|
|
}
|
|
} else {
|
|
// Found the entry in the parent cache
|
|
if ((it->second.flags & CCoinsCacheEntry::FRESH) && !itUs->second.coin.IsSpent()) {
|
|
// The coin was marked FRESH in the child cache, but the coin
|
|
// exists in the parent cache. If this ever happens, it means
|
|
// the FRESH flag was misapplied and there is a logic error in
|
|
// the calling code.
|
|
throw std::logic_error("FRESH flag misapplied to coin that exists in parent cache");
|
|
}
|
|
|
|
if ((itUs->second.flags & CCoinsCacheEntry::FRESH) &&
|
|
((fIsPegin && !it->second.peginSpent) || (!fIsPegin && it->second.coin.IsSpent()))) {
|
|
// The grandparent cache does not have an entry, and the coin
|
|
// has been spent. We can just delete it from the parent cache.
|
|
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
|
|
cacheCoins.erase(itUs);
|
|
} else {
|
|
// A normal modification.
|
|
cachedCoinsUsage -= itUs->second.coin.DynamicMemoryUsage();
|
|
if (fIsPegin) {
|
|
itUs->second.peginSpent = it->second.peginSpent;
|
|
} else {
|
|
itUs->second.coin = it->second.coin;
|
|
}
|
|
cachedCoinsUsage += itUs->second.coin.DynamicMemoryUsage();
|
|
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
|
|
// NOTE: It isn't safe to mark the coin as FRESH in the parent
|
|
// cache. If it already existed and was spent in the parent
|
|
// cache then marking it FRESH would prevent that spentness
|
|
// from being flushed to the grandparent.
|
|
}
|
|
}
|
|
}
|
|
hashBlock = hashBlockIn;
|
|
return true;
|
|
}
|
|
|
|
bool CCoinsViewCache::Flush() {
|
|
bool fOk = base->BatchWrite(cacheCoins, hashBlock);
|
|
cacheCoins.clear();
|
|
cachedCoinsUsage = 0;
|
|
return fOk;
|
|
}
|
|
|
|
void CCoinsViewCache::Uncache(const COutPoint& point)
|
|
{
|
|
CCoinsMap::iterator it = cacheCoins.find(native_key(point));
|
|
if (it != cacheCoins.end() && it->second.flags == 0) {
|
|
cachedCoinsUsage -= it->second.coin.DynamicMemoryUsage();
|
|
cacheCoins.erase(it);
|
|
}
|
|
}
|
|
|
|
unsigned int CCoinsViewCache::GetCacheSize() const {
|
|
return cacheCoins.size();
|
|
}
|
|
|
|
bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
|
|
{
|
|
if (!tx.IsCoinBase()) {
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
|
if (tx.vin[i].m_is_pegin) {
|
|
continue;
|
|
}
|
|
if (!HaveCoin(tx.vin[i].prevout)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CCoinsViewCache::ReallocateCache()
|
|
{
|
|
// Cache should be empty when we're calling this.
|
|
assert(cacheCoins.size() == 0);
|
|
cacheCoins.~CCoinsMap();
|
|
::new (&cacheCoins) CCoinsMap();
|
|
}
|
|
|
|
static const size_t MIN_TRANSACTION_OUTPUT_WEIGHT = WITNESS_SCALE_FACTOR * ::GetSerializeSize(CTxOut(), PROTOCOL_VERSION);
|
|
static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_WEIGHT / MIN_TRANSACTION_OUTPUT_WEIGHT;
|
|
|
|
const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
|
|
{
|
|
COutPoint iter(txid, 0);
|
|
while (iter.n < MAX_OUTPUTS_PER_BLOCK) {
|
|
const Coin& alternate = view.AccessCoin(iter);
|
|
if (!alternate.IsSpent()) return alternate;
|
|
++iter.n;
|
|
}
|
|
return coinEmpty;
|
|
}
|
|
|
|
bool CCoinsViewErrorCatcher::GetCoin(const COutPoint &outpoint, Coin &coin) const {
|
|
try {
|
|
return CCoinsViewBacked::GetCoin(outpoint, coin);
|
|
} catch(const std::runtime_error& e) {
|
|
for (auto f : m_err_callbacks) {
|
|
f();
|
|
}
|
|
LogPrintf("Error reading from database: %s\n", e.what());
|
|
// Starting the shutdown sequence and returning false to the caller would be
|
|
// interpreted as 'entry not found' (as opposed to unable to read data), and
|
|
// could lead to invalid interpretation. Just exit immediately, as we can't
|
|
// continue anyway, and all writes should be atomic.
|
|
std::abort();
|
|
}
|
|
}
|