Switch CCoinsView and chainstate db from per-txid to per-txout
This patch makes several related changes: * Changes the CCoinsView virtual methods (GetCoins, HaveCoins, ...) to be COutPoint/Coin-based rather than txid/CCoins-based. * Changes the chainstate db to a new incompatible format that is also COutPoint/Coin based. * Implements reconstruction code for hash_serialized_2. * Adapts the coins_tests unit tests (thanks to Russell Yanofsky). A side effect of the new CCoinsView model is that we can no longer use the (unreliable) test for transaction outputs in the UTXO set to determine whether we already have a particular transaction.
This commit is contained in:
parent
4ec0d9e794
commit
5083079688
15 changed files with 363 additions and 373 deletions
112
src/coins.cpp
112
src/coins.cpp
|
@ -42,41 +42,40 @@ bool CCoins::Spend(uint32_t nPos)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
|
||||
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
|
||||
bool CCoinsView::GetCoins(const COutPoint &outpoint, Coin &coin) const { return false; }
|
||||
bool CCoinsView::HaveCoins(const COutPoint &outpoint) const { return false; }
|
||||
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
|
||||
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
|
||||
CCoinsViewCursor *CCoinsView::Cursor() const { return 0; }
|
||||
|
||||
|
||||
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
|
||||
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
|
||||
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
|
||||
bool CCoinsViewBacked::GetCoins(const COutPoint &outpoint, Coin &coin) const { return base->GetCoins(outpoint, coin); }
|
||||
bool CCoinsViewBacked::HaveCoins(const COutPoint &outpoint) const { return base->HaveCoins(outpoint); }
|
||||
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
|
||||
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(); }
|
||||
|
||||
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
|
||||
SaltedOutpointHasher::SaltedOutpointHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
|
||||
|
||||
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) { }
|
||||
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), cachedCoinsUsage(0) {}
|
||||
|
||||
size_t CCoinsViewCache::DynamicMemoryUsage() const {
|
||||
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
|
||||
}
|
||||
|
||||
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
|
||||
CCoinsMap::iterator it = cacheCoins.find(txid);
|
||||
CCoinsMap::iterator CCoinsViewCache::FetchCoins(const COutPoint &outpoint) const {
|
||||
CCoinsMap::iterator it = cacheCoins.find(outpoint);
|
||||
if (it != cacheCoins.end())
|
||||
return it;
|
||||
CCoins tmp;
|
||||
if (!base->GetCoins(txid, tmp))
|
||||
Coin tmp;
|
||||
if (!base->GetCoins(outpoint, tmp))
|
||||
return cacheCoins.end();
|
||||
CCoinsMap::iterator ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())).first;
|
||||
tmp.swap(ret->second.coins);
|
||||
CCoinsMap::iterator ret = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::forward_as_tuple(std::move(tmp))).first;
|
||||
if (ret->second.coins.IsPruned()) {
|
||||
// The parent only has an empty entry for this txid; we can consider our
|
||||
// The parent only has an empty entry for this outpoint; we can consider our
|
||||
// version as fresh.
|
||||
ret->second.flags = CCoinsCacheEntry::FRESH;
|
||||
}
|
||||
|
@ -84,10 +83,10 @@ CCoinsMap::iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const {
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(txid);
|
||||
bool CCoinsViewCache::GetCoins(const COutPoint &outpoint, Coin &coin) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(outpoint);
|
||||
if (it != cacheCoins.end()) {
|
||||
coins = it->second.coins;
|
||||
coin = it->second.coins;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -98,23 +97,18 @@ void CCoinsViewCache::AddCoin(const COutPoint &outpoint, Coin&& coin, bool possi
|
|||
if (coin.out.scriptPubKey.IsUnspendable()) return;
|
||||
CCoinsMap::iterator it;
|
||||
bool inserted;
|
||||
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint.hash), std::tuple<>());
|
||||
std::tie(it, inserted) = cacheCoins.emplace(std::piecewise_construct, std::forward_as_tuple(outpoint), std::tuple<>());
|
||||
bool fresh = false;
|
||||
if (!inserted) {
|
||||
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
|
||||
}
|
||||
if (!possible_overwrite) {
|
||||
if (it->second.coins.IsAvailable(outpoint.n)) {
|
||||
if (!it->second.coins.IsPruned()) {
|
||||
throw std::logic_error("Adding new coin that replaces non-pruned entry");
|
||||
}
|
||||
fresh = it->second.coins.IsPruned() && !(it->second.flags & CCoinsCacheEntry::DIRTY);
|
||||
fresh = !(it->second.flags & CCoinsCacheEntry::DIRTY);
|
||||
}
|
||||
if (it->second.coins.vout.size() <= outpoint.n) {
|
||||
it->second.coins.vout.resize(outpoint.n + 1);
|
||||
}
|
||||
it->second.coins.vout[outpoint.n] = std::move(coin.out);
|
||||
it->second.coins.nHeight = coin.nHeight;
|
||||
it->second.coins.fCoinBase = coin.fCoinBase;
|
||||
it->second.coins = std::move(coin);
|
||||
it->second.flags |= CCoinsCacheEntry::DIRTY | (fresh ? CCoinsCacheEntry::FRESH : 0);
|
||||
cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
|
||||
}
|
||||
|
@ -130,58 +124,38 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight) {
|
|||
}
|
||||
|
||||
void CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) {
|
||||
CCoinsMap::iterator it = FetchCoins(outpoint.hash);
|
||||
CCoinsMap::iterator it = FetchCoins(outpoint);
|
||||
if (it == cacheCoins.end()) return;
|
||||
cachedCoinsUsage -= it->second.coins.DynamicMemoryUsage();
|
||||
if (moveout && it->second.coins.IsAvailable(outpoint.n)) {
|
||||
*moveout = Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase);
|
||||
if (moveout) {
|
||||
*moveout = std::move(it->second.coins);
|
||||
}
|
||||
it->second.coins.Spend(outpoint.n); // Ignore return value: SpendCoin has no effect if no UTXO found.
|
||||
if (it->second.coins.IsPruned() && it->second.flags & CCoinsCacheEntry::FRESH) {
|
||||
if (it->second.flags & CCoinsCacheEntry::FRESH) {
|
||||
cacheCoins.erase(it);
|
||||
} else {
|
||||
cachedCoinsUsage += it->second.coins.DynamicMemoryUsage();
|
||||
it->second.flags |= CCoinsCacheEntry::DIRTY;
|
||||
}
|
||||
}
|
||||
|
||||
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(txid);
|
||||
if (it == cacheCoins.end()) {
|
||||
return NULL;
|
||||
} else {
|
||||
return &it->second.coins;
|
||||
it->second.coins.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static const Coin coinEmpty;
|
||||
|
||||
const Coin CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(outpoint.hash);
|
||||
if (it == cacheCoins.end() || !it->second.coins.IsAvailable(outpoint.n)) {
|
||||
const Coin& CCoinsViewCache::AccessCoin(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(outpoint);
|
||||
if (it == cacheCoins.end()) {
|
||||
return coinEmpty;
|
||||
} else {
|
||||
return Coin(it->second.coins.vout[outpoint.n], it->second.coins.nHeight, it->second.coins.fCoinBase);
|
||||
return it->second.coins;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CCoinsViewCache::HaveCoins(const uint256 &txid) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(txid);
|
||||
// We're using vtx.empty() instead of IsPruned here for performance reasons,
|
||||
// as we only care about the case where a transaction was replaced entirely
|
||||
// in a reorganization (which wipes vout entirely, as opposed to spending
|
||||
// which just cleans individual outputs).
|
||||
return (it != cacheCoins.end() && !it->second.coins.vout.empty());
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::HaveCoins(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = FetchCoins(outpoint.hash);
|
||||
return (it != cacheCoins.end() && it->second.coins.IsAvailable(outpoint.n));
|
||||
CCoinsMap::const_iterator it = FetchCoins(outpoint);
|
||||
return (it != cacheCoins.end() && !it->second.coins.IsPruned());
|
||||
}
|
||||
|
||||
bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const {
|
||||
CCoinsMap::const_iterator it = cacheCoins.find(txid);
|
||||
bool CCoinsViewCache::HaveCoinsInCache(const COutPoint &outpoint) const {
|
||||
CCoinsMap::const_iterator it = cacheCoins.find(outpoint);
|
||||
return it != cacheCoins.end();
|
||||
}
|
||||
|
||||
|
@ -206,7 +180,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
|
|||
// Otherwise we will need to create it in the parent
|
||||
// and move the data up and mark it as dirty
|
||||
CCoinsCacheEntry& entry = cacheCoins[it->first];
|
||||
entry.coins.swap(it->second.coins);
|
||||
entry.coins = std::move(it->second.coins);
|
||||
cachedCoinsUsage += entry.coins.DynamicMemoryUsage();
|
||||
entry.flags = CCoinsCacheEntry::DIRTY;
|
||||
// We can mark it FRESH in the parent if it was FRESH in the child
|
||||
|
@ -233,7 +207,7 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
|
|||
} else {
|
||||
// A normal modification.
|
||||
cachedCoinsUsage -= itUs->second.coins.DynamicMemoryUsage();
|
||||
itUs->second.coins.swap(it->second.coins);
|
||||
itUs->second.coins = std::move(it->second.coins);
|
||||
cachedCoinsUsage += itUs->second.coins.DynamicMemoryUsage();
|
||||
itUs->second.flags |= CCoinsCacheEntry::DIRTY;
|
||||
// NOTE: It is possible the child has a FRESH flag here in
|
||||
|
@ -258,7 +232,7 @@ bool CCoinsViewCache::Flush() {
|
|||
return fOk;
|
||||
}
|
||||
|
||||
void CCoinsViewCache::Uncache(const uint256& hash)
|
||||
void CCoinsViewCache::Uncache(const COutPoint& hash)
|
||||
{
|
||||
CCoinsMap::iterator it = cacheCoins.find(hash);
|
||||
if (it != cacheCoins.end() && it->second.flags == 0) {
|
||||
|
@ -273,9 +247,9 @@ unsigned int CCoinsViewCache::GetCacheSize() const {
|
|||
|
||||
const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const
|
||||
{
|
||||
const CCoins* coins = AccessCoins(input.prevout.hash);
|
||||
assert(coins && coins->IsAvailable(input.prevout.n));
|
||||
return coins->vout[input.prevout.n];
|
||||
const Coin& coin = AccessCoin(input.prevout);
|
||||
assert(!coin.IsPruned());
|
||||
return coin.out;
|
||||
}
|
||||
|
||||
CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
|
||||
|
@ -294,9 +268,7 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
|
|||
{
|
||||
if (!tx.IsCoinBase()) {
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
const COutPoint &prevout = tx.vin[i].prevout;
|
||||
const CCoins* coins = AccessCoins(prevout.hash);
|
||||
if (!coins || !coins->IsAvailable(prevout.n)) {
|
||||
if (!HaveCoins(tx.vin[i].prevout)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -304,13 +276,9 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
|
|||
return true;
|
||||
}
|
||||
|
||||
CCoinsViewCursor::~CCoinsViewCursor()
|
||||
{
|
||||
}
|
||||
|
||||
static const size_t MAX_OUTPUTS_PER_BLOCK = MAX_BLOCK_BASE_SIZE / ::GetSerializeSize(CTxOut(), SER_NETWORK, PROTOCOL_VERSION); // TODO: merge with similar definition in undo.h.
|
||||
|
||||
const Coin AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
|
||||
const Coin& AccessByTxid(const CCoinsViewCache& view, const uint256& txid)
|
||||
{
|
||||
COutPoint iter(txid, 0);
|
||||
while (iter.n < MAX_OUTPUTS_PER_BLOCK) {
|
||||
|
|
74
src/coins.h
74
src/coins.h
|
@ -299,28 +299,28 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class SaltedTxidHasher
|
||||
class SaltedOutpointHasher
|
||||
{
|
||||
private:
|
||||
/** Salt */
|
||||
const uint64_t k0, k1;
|
||||
|
||||
public:
|
||||
SaltedTxidHasher();
|
||||
SaltedOutpointHasher();
|
||||
|
||||
/**
|
||||
* This *must* return size_t. With Boost 1.46 on 32-bit systems the
|
||||
* unordered_map will behave unpredictably if the custom hasher returns a
|
||||
* uint64_t, resulting in failures when syncing the chain (#4634).
|
||||
*/
|
||||
size_t operator()(const uint256& txid) const {
|
||||
return SipHashUint256(k0, k1, txid);
|
||||
size_t operator()(const COutPoint& id) const {
|
||||
return SipHashUint256Extra(k0, k1, id.hash, id.n);
|
||||
}
|
||||
};
|
||||
|
||||
struct CCoinsCacheEntry
|
||||
{
|
||||
CCoins coins; // The actual cached data.
|
||||
Coin coins; // The actual cached data.
|
||||
unsigned char flags;
|
||||
|
||||
enum Flags {
|
||||
|
@ -333,20 +333,21 @@ struct CCoinsCacheEntry
|
|||
*/
|
||||
};
|
||||
|
||||
CCoinsCacheEntry() : coins(), flags(0) {}
|
||||
CCoinsCacheEntry() : flags(0) {}
|
||||
explicit CCoinsCacheEntry(Coin&& coin_) : coins(std::move(coin_)), flags(0) {}
|
||||
};
|
||||
|
||||
typedef std::unordered_map<uint256, CCoinsCacheEntry, SaltedTxidHasher> CCoinsMap;
|
||||
typedef std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher> CCoinsMap;
|
||||
|
||||
/** Cursor for iterating over CoinsView state */
|
||||
class CCoinsViewCursor
|
||||
{
|
||||
public:
|
||||
CCoinsViewCursor(const uint256 &hashBlockIn): hashBlock(hashBlockIn) {}
|
||||
virtual ~CCoinsViewCursor();
|
||||
virtual ~CCoinsViewCursor() {}
|
||||
|
||||
virtual bool GetKey(uint256 &key) const = 0;
|
||||
virtual bool GetValue(CCoins &coins) const = 0;
|
||||
virtual bool GetKey(COutPoint &key) const = 0;
|
||||
virtual bool GetValue(Coin &coin) const = 0;
|
||||
virtual unsigned int GetValueSize() const = 0;
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
|
@ -362,17 +363,17 @@ private:
|
|||
class CCoinsView
|
||||
{
|
||||
public:
|
||||
//! Retrieve the CCoins (unspent transaction outputs) for a given txid
|
||||
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
//! Retrieve the Coin (unspent transaction output) for a given outpoint.
|
||||
virtual bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
|
||||
|
||||
//! Just check whether we have data for a given txid.
|
||||
//! This may (but cannot always) return true for fully spent transactions
|
||||
virtual bool HaveCoins(const uint256 &txid) const;
|
||||
//! Just check whether we have data for a given outpoint.
|
||||
//! This may (but cannot always) return true for spent outputs.
|
||||
virtual bool HaveCoins(const COutPoint &outpoint) const;
|
||||
|
||||
//! Retrieve the block hash whose state this CCoinsView currently represents
|
||||
virtual uint256 GetBestBlock() const;
|
||||
|
||||
//! Do a bulk modification (multiple CCoins changes + BestBlock change).
|
||||
//! Do a bulk modification (multiple Coin changes + BestBlock change).
|
||||
//! The passed mapCoins can be modified.
|
||||
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
|
||||
|
@ -395,12 +396,12 @@ protected:
|
|||
|
||||
public:
|
||||
CCoinsViewBacked(CCoinsView *viewIn);
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
uint256 GetBestBlock() const;
|
||||
bool GetCoins(const COutPoint &outpoint, Coin &coin) const override;
|
||||
bool HaveCoins(const COutPoint &outpoint) const override;
|
||||
uint256 GetBestBlock() const override;
|
||||
void SetBackend(CCoinsView &viewIn);
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
CCoinsViewCursor *Cursor() const;
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
|
||||
CCoinsViewCursor *Cursor() const override;
|
||||
size_t EstimateSize() const override;
|
||||
};
|
||||
|
||||
|
@ -416,41 +417,32 @@ protected:
|
|||
mutable uint256 hashBlock;
|
||||
mutable CCoinsMap cacheCoins;
|
||||
|
||||
/* Cached dynamic memory usage for the inner CCoins objects. */
|
||||
/* Cached dynamic memory usage for the inner Coin objects. */
|
||||
mutable size_t cachedCoinsUsage;
|
||||
|
||||
public:
|
||||
CCoinsViewCache(CCoinsView *baseIn);
|
||||
|
||||
// Standard CCoinsView methods
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
|
||||
bool HaveCoins(const COutPoint &outpoint) const;
|
||||
uint256 GetBestBlock() const;
|
||||
void SetBestBlock(const uint256 &hashBlock);
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
|
||||
/**
|
||||
* Check if we have the given tx already loaded in this cache.
|
||||
* Check if we have the given utxo already loaded in this cache.
|
||||
* The semantics are the same as HaveCoins(), but no calls to
|
||||
* the backing CCoinsView are made.
|
||||
*/
|
||||
bool HaveCoinsInCache(const uint256 &txid) const;
|
||||
bool HaveCoinsInCache(const COutPoint &outpoint) const;
|
||||
|
||||
/**
|
||||
* Return a pointer to CCoins in the cache, or NULL if not found. This is
|
||||
* Return a reference to Coin in the cache, or a pruned one if not found. This is
|
||||
* more efficient than GetCoins. Modifications to other cache entries are
|
||||
* allowed while accessing the returned pointer.
|
||||
*/
|
||||
const CCoins* AccessCoins(const uint256 &txid) const;
|
||||
|
||||
/**
|
||||
* Return a copy of a Coin in the cache, or a pruned one if not found. This is
|
||||
* more efficient than GetCoins. Modifications to other cache entries are
|
||||
* allowed while accessing the returned pointer.
|
||||
* TODO: return a reference to a Coin after changing CCoinsViewCache storage.
|
||||
*/
|
||||
const Coin AccessCoin(const COutPoint &output) const;
|
||||
const Coin& AccessCoin(const COutPoint &output) const;
|
||||
|
||||
/**
|
||||
* Add a coin. Set potential_overwrite to true if a non-pruned version may
|
||||
|
@ -473,12 +465,12 @@ public:
|
|||
bool Flush();
|
||||
|
||||
/**
|
||||
* Removes the transaction with the given hash from the cache, if it is
|
||||
* Removes the UTXO with the given outpoint from the cache, if it is
|
||||
* not modified.
|
||||
*/
|
||||
void Uncache(const uint256 &txid);
|
||||
void Uncache(const COutPoint &outpoint);
|
||||
|
||||
//! Calculate the size of the cache (in number of transactions)
|
||||
//! Calculate the size of the cache (in number of transaction outputs)
|
||||
unsigned int GetCacheSize() const;
|
||||
|
||||
//! Calculate the size of the cache (in bytes)
|
||||
|
@ -500,7 +492,7 @@ public:
|
|||
const CTxOut &GetOutputFor(const CTxIn& input) const;
|
||||
|
||||
private:
|
||||
CCoinsMap::iterator FetchCoins(const uint256 &txid) const;
|
||||
CCoinsMap::iterator FetchCoins(const COutPoint &outpoint) const;
|
||||
|
||||
/**
|
||||
* By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache.
|
||||
|
@ -515,6 +507,6 @@ private:
|
|||
void AddCoins(CCoinsViewCache& cache, const CTransaction& tx, int nHeight);
|
||||
|
||||
//! Utility function to find any unspent output with a given txid.
|
||||
const Coin AccessByTxid(const CCoinsViewCache& cache, const uint256& txid);
|
||||
const Coin& AccessByTxid(const CCoinsViewCache& cache, const uint256& txid);
|
||||
|
||||
#endif // BITCOIN_COINS_H
|
||||
|
|
|
@ -146,9 +146,9 @@ class CCoinsViewErrorCatcher : public CCoinsViewBacked
|
|||
{
|
||||
public:
|
||||
CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
bool GetCoins(const COutPoint &outpoint, Coin &coin) const override {
|
||||
try {
|
||||
return CCoinsViewBacked::GetCoins(txid, coins);
|
||||
return CCoinsViewBacked::GetCoins(outpoint, coin);
|
||||
} catch(const std::runtime_error& e) {
|
||||
uiInterface.ThreadSafeMessageBox(_("Error reading from database, shutting down."), "", CClientUIInterface::MSG_ERROR);
|
||||
LogPrintf("Error reading from database: %s\n", e.what());
|
||||
|
|
|
@ -911,12 +911,11 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
|||
recentRejects->reset();
|
||||
}
|
||||
|
||||
// Use pcoinsTip->HaveCoinsInCache as a quick approximation to exclude
|
||||
// requesting or processing some txs which have already been included in a block
|
||||
return recentRejects->contains(inv.hash) ||
|
||||
mempool.exists(inv.hash) ||
|
||||
mapOrphanTransactions.count(inv.hash) ||
|
||||
pcoinsTip->HaveCoinsInCache(inv.hash);
|
||||
pcoinsTip->HaveCoinsInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1
|
||||
pcoinsTip->HaveCoinsInCache(COutPoint(inv.hash, 1));
|
||||
}
|
||||
case MSG_BLOCK:
|
||||
case MSG_WITNESS_BLOCK:
|
||||
|
|
|
@ -293,13 +293,12 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
|
|||
{
|
||||
COutPoint prevout = txin.prevout;
|
||||
|
||||
CCoins prev;
|
||||
if(pcoinsTip->GetCoins(prevout.hash, prev))
|
||||
Coin prev;
|
||||
if(pcoinsTip->GetCoins(prevout, prev))
|
||||
{
|
||||
if (prevout.n < prev.vout.size())
|
||||
{
|
||||
strHTML += "<li>";
|
||||
const CTxOut &vout = prev.vout[prevout.n];
|
||||
const CTxOut &vout = prev.out;
|
||||
CTxDestination address;
|
||||
if (ExtractDestination(vout.scriptPubKey, address))
|
||||
{
|
||||
|
|
20
src/rest.cpp
20
src/rest.cpp
|
@ -47,6 +47,9 @@ struct CCoin {
|
|||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
CCoin() : nHeight(0) {}
|
||||
CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action)
|
||||
{
|
||||
|
@ -509,20 +512,11 @@ static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart)
|
|||
view.SetBackend(viewMempool); // switch cache backend to db+mempool in case user likes to query mempool
|
||||
|
||||
for (size_t i = 0; i < vOutPoints.size(); i++) {
|
||||
CCoins coins;
|
||||
uint256 hash = vOutPoints[i].hash;
|
||||
bool hit = false;
|
||||
if (view.GetCoins(hash, coins)) {
|
||||
if (coins.IsAvailable(vOutPoints[i].n) && !mempool.isSpent(vOutPoints[i])) {
|
||||
hit = true;
|
||||
// Safe to index into vout here because IsAvailable checked if it's off the end of the array, or if
|
||||
// n is valid but points to an already spent output (IsNull).
|
||||
CCoin coin;
|
||||
coin.nHeight = coins.nHeight;
|
||||
coin.out = coins.vout.at(vOutPoints[i].n);
|
||||
assert(!coin.out.IsNull());
|
||||
outs.push_back(coin);
|
||||
}
|
||||
Coin coin;
|
||||
if (view.GetCoins(vOutPoints[i], coin) && !mempool.isSpent(vOutPoints[i])) {
|
||||
hit = true;
|
||||
outs.emplace_back(std::move(coin));
|
||||
}
|
||||
|
||||
hits.push_back(hit);
|
||||
|
|
|
@ -816,24 +816,27 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
|
|||
stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight;
|
||||
}
|
||||
ss << stats.hashBlock;
|
||||
uint256 prevkey;
|
||||
std::map<uint32_t, Coin> outputs;
|
||||
while (pcursor->Valid()) {
|
||||
boost::this_thread::interruption_point();
|
||||
uint256 key;
|
||||
CCoins coins;
|
||||
if (pcursor->GetKey(key) && pcursor->GetValue(coins)) {
|
||||
std::map<uint32_t, Coin> outputs;
|
||||
for (unsigned int i=0; i<coins.vout.size(); i++) {
|
||||
CTxOut &out = coins.vout[i];
|
||||
if (!out.IsNull()) {
|
||||
outputs[i] = Coin(std::move(out), coins.nHeight, coins.fCoinBase);
|
||||
}
|
||||
COutPoint key;
|
||||
Coin coin;
|
||||
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
|
||||
if (!outputs.empty() && key.hash != prevkey) {
|
||||
ApplyStats(stats, ss, prevkey, outputs);
|
||||
outputs.clear();
|
||||
}
|
||||
ApplyStats(stats, ss, key, outputs);
|
||||
prevkey = key.hash;
|
||||
outputs[key.n] = std::move(coin);
|
||||
} else {
|
||||
return error("%s: unable to read value", __func__);
|
||||
}
|
||||
pcursor->Next();
|
||||
}
|
||||
if (!outputs.empty()) {
|
||||
ApplyStats(stats, ss, prevkey, outputs);
|
||||
}
|
||||
stats.hashSerialized = ss.GetHash();
|
||||
stats.nDiskSize = view->EstimateSize();
|
||||
return true;
|
||||
|
@ -973,35 +976,37 @@ UniValue gettxout(const JSONRPCRequest& request)
|
|||
std::string strHash = request.params[0].get_str();
|
||||
uint256 hash(uint256S(strHash));
|
||||
int n = request.params[1].get_int();
|
||||
COutPoint out(hash, n);
|
||||
bool fMempool = true;
|
||||
if (request.params.size() > 2)
|
||||
fMempool = request.params[2].get_bool();
|
||||
|
||||
CCoins coins;
|
||||
Coin coin;
|
||||
if (fMempool) {
|
||||
LOCK(mempool.cs);
|
||||
CCoinsViewMemPool view(pcoinsTip, mempool);
|
||||
if (!view.GetCoins(hash, coins) || mempool.isSpent(COutPoint(hash, n))) // TODO: this should be done by the CCoinsViewMemPool
|
||||
if (!view.GetCoins(out, coin) || mempool.isSpent(out)) { // TODO: filtering spent coins should be done by the CCoinsViewMemPool
|
||||
return NullUniValue;
|
||||
}
|
||||
} else {
|
||||
if (!pcoinsTip->GetCoins(hash, coins))
|
||||
if (!pcoinsTip->GetCoins(out, coin)) {
|
||||
return NullUniValue;
|
||||
}
|
||||
}
|
||||
if (n<0 || (unsigned int)n>=coins.vout.size() || coins.vout[n].IsNull())
|
||||
return NullUniValue;
|
||||
|
||||
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
|
||||
CBlockIndex *pindex = it->second;
|
||||
ret.push_back(Pair("bestblock", pindex->GetBlockHash().GetHex()));
|
||||
if ((unsigned int)coins.nHeight == MEMPOOL_HEIGHT)
|
||||
if (coin.nHeight == MEMPOOL_HEIGHT) {
|
||||
ret.push_back(Pair("confirmations", 0));
|
||||
else
|
||||
ret.push_back(Pair("confirmations", pindex->nHeight - coins.nHeight + 1));
|
||||
ret.push_back(Pair("value", ValueFromAmount(coins.vout[n].nValue)));
|
||||
} else {
|
||||
ret.push_back(Pair("confirmations", (int64_t)(pindex->nHeight - coin.nHeight + 1)));
|
||||
}
|
||||
ret.push_back(Pair("value", ValueFromAmount(coin.out.nValue)));
|
||||
UniValue o(UniValue::VOBJ);
|
||||
ScriptPubKeyToUniv(coins.vout[n].scriptPubKey, o, true);
|
||||
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
|
||||
ret.push_back(Pair("scriptPubKey", o));
|
||||
ret.push_back(Pair("coinbase", coins.fCoinBase));
|
||||
ret.push_back(Pair("coinbase", (bool)coin.fCoinBase));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -219,9 +219,10 @@ UniValue gettxoutproof(const JSONRPCRequest& request)
|
|||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
pblockindex = mapBlockIndex[hashBlock];
|
||||
} else {
|
||||
CCoins coins;
|
||||
if (pcoinsTip->GetCoins(oneTxid, coins) && coins.nHeight > 0 && coins.nHeight <= chainActive.Height())
|
||||
pblockindex = chainActive[coins.nHeight];
|
||||
const Coin& coin = AccessByTxid(*pcoinsTip, oneTxid);
|
||||
if (!coin.IsPruned() && coin.nHeight > 0 && coin.nHeight <= chainActive.Height()) {
|
||||
pblockindex = chainActive[coin.nHeight];
|
||||
}
|
||||
}
|
||||
|
||||
if (pblockindex == NULL)
|
||||
|
|
|
@ -34,27 +34,27 @@ bool operator==(const Coin &a, const Coin &b) {
|
|||
class CCoinsViewTest : public CCoinsView
|
||||
{
|
||||
uint256 hashBestBlock_;
|
||||
std::map<uint256, CCoins> map_;
|
||||
std::map<COutPoint, Coin> map_;
|
||||
|
||||
public:
|
||||
bool GetCoins(const uint256& txid, CCoins& coins) const
|
||||
bool GetCoins(const COutPoint& outpoint, Coin& coin) const
|
||||
{
|
||||
std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
|
||||
std::map<COutPoint, Coin>::const_iterator it = map_.find(outpoint);
|
||||
if (it == map_.end()) {
|
||||
return false;
|
||||
}
|
||||
coins = it->second;
|
||||
if (coins.IsPruned() && insecure_rand() % 2 == 0) {
|
||||
coin = it->second;
|
||||
if (coin.IsPruned() && insecure_rand() % 2 == 0) {
|
||||
// Randomly return false in case of an empty entry.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HaveCoins(const uint256& txid) const
|
||||
bool HaveCoins(const COutPoint& outpoint) const
|
||||
{
|
||||
CCoins coins;
|
||||
return GetCoins(txid, coins);
|
||||
Coin coin;
|
||||
return GetCoins(outpoint, coin);
|
||||
}
|
||||
|
||||
uint256 GetBestBlock() const { return hashBestBlock_; }
|
||||
|
@ -106,7 +106,7 @@ 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
|
||||
// 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.
|
||||
//
|
||||
|
@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
bool missed_an_entry = false;
|
||||
|
||||
// A simple map to track what we expect the cache stack to represent.
|
||||
std::map<uint256, CCoins> result;
|
||||
std::map<COutPoint, Coin> result;
|
||||
|
||||
// The cache stack.
|
||||
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
|
||||
|
@ -142,39 +142,38 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
// Do a random modification.
|
||||
{
|
||||
uint256 txid = txids[insecure_rand() % txids.size()]; // txid we're going to modify in this iteration.
|
||||
CCoins& coins = result[txid];
|
||||
Coin& coin = result[COutPoint(txid, 0)];
|
||||
const Coin& entry = stack.back()->AccessCoin(COutPoint(txid, 0));
|
||||
BOOST_CHECK((entry.IsPruned() && coins.IsPruned()) || entry == Coin(coins.vout[0], coins.nHeight, coins.fCoinBase));
|
||||
BOOST_CHECK(coin == entry);
|
||||
|
||||
if (insecure_rand() % 5 == 0 || coins.IsPruned()) {
|
||||
if (coins.IsPruned()) {
|
||||
if (insecure_rand() % 5 == 0 || coin.IsPruned()) {
|
||||
if (coin.IsPruned()) {
|
||||
added_an_entry = true;
|
||||
} else {
|
||||
updated_an_entry = true;
|
||||
}
|
||||
coins.vout.resize(1);
|
||||
coins.vout[0].nValue = insecure_rand();
|
||||
coin.out.nValue = insecure_rand();
|
||||
coin.nHeight = 1;
|
||||
} else {
|
||||
coins.Clear();
|
||||
coin.Clear();
|
||||
removed_an_entry = true;
|
||||
}
|
||||
if (coins.IsPruned()) {
|
||||
if (coin.IsPruned()) {
|
||||
stack.back()->SpendCoin(COutPoint(txid, 0));
|
||||
} else {
|
||||
stack.back()->AddCoin(COutPoint(txid, 0), Coin(coins.vout[0], coins.nHeight, coins.fCoinBase), true);
|
||||
stack.back()->AddCoin(COutPoint(txid, 0), Coin(coin), 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());
|
||||
for (auto it = result.begin(); it != result.end(); it++) {
|
||||
const Coin& coin = stack.back()->AccessCoin(it->first);
|
||||
BOOST_CHECK(coin == it->second);
|
||||
if (coin.IsPruned()) {
|
||||
missed_an_entry = true;
|
||||
} else {
|
||||
found_an_entry = true;
|
||||
}
|
||||
}
|
||||
BOOST_FOREACH(const CCoinsViewCacheTest *test, stack) {
|
||||
|
@ -229,19 +228,19 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
|
|||
BOOST_CHECK(missed_an_entry);
|
||||
}
|
||||
|
||||
typedef std::tuple<CTransaction,CTxUndo,CCoins> TxData;
|
||||
// Store of all necessary tx and undo data for next test
|
||||
std::map<uint256, TxData> alltxs;
|
||||
typedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;
|
||||
UtxoData utxoData;
|
||||
|
||||
TxData &FindRandomFrom(const std::set<uint256> &txidset) {
|
||||
assert(txidset.size());
|
||||
std::set<uint256>::iterator txIt = txidset.lower_bound(GetRandHash());
|
||||
if (txIt == txidset.end()) {
|
||||
txIt = txidset.begin();
|
||||
UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) {
|
||||
assert(utxoSet.size());
|
||||
auto utxoSetIt = utxoSet.lower_bound(COutPoint(GetRandHash(), 0));
|
||||
if (utxoSetIt == utxoSet.end()) {
|
||||
utxoSetIt = utxoSet.begin();
|
||||
}
|
||||
std::map<uint256, TxData>::iterator txdit = alltxs.find(*txIt);
|
||||
assert(txdit != alltxs.end());
|
||||
return txdit->second;
|
||||
auto utxoDataIt = utxoData.find(*utxoSetIt);
|
||||
assert(utxoDataIt != utxoData.end());
|
||||
return utxoDataIt;
|
||||
}
|
||||
|
||||
|
||||
|
@ -254,7 +253,7 @@ 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<uint256, CCoins> result;
|
||||
std::map<COutPoint, Coin> result;
|
||||
|
||||
// The cache stack.
|
||||
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
|
||||
|
@ -262,10 +261,10 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
|
||||
|
||||
// Track the txids we've used in various sets
|
||||
std::set<uint256> coinbaseids;
|
||||
std::set<uint256> disconnectedids;
|
||||
std::set<uint256> duplicateids;
|
||||
std::set<uint256> utxoset;
|
||||
std::set<COutPoint> coinbaseids;
|
||||
std::set<COutPoint> disconnectedids;
|
||||
std::set<COutPoint> duplicateids;
|
||||
std::set<COutPoint> utxoset;
|
||||
|
||||
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
|
||||
uint32_t randiter = insecure_rand();
|
||||
|
@ -277,22 +276,22 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
tx.vout.resize(1);
|
||||
tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
|
||||
unsigned int height = insecure_rand();
|
||||
CCoins oldcoins;
|
||||
Coin oldcoins;
|
||||
|
||||
// 2/20 times create a new coinbase
|
||||
if (randiter % 20 < 2 || coinbaseids.size() < 10) {
|
||||
// 1/10 of those times create a duplicate coinbase
|
||||
if (insecure_rand() % 10 == 0 && coinbaseids.size()) {
|
||||
TxData &txd = FindRandomFrom(coinbaseids);
|
||||
auto utxod = FindRandomFrom(coinbaseids);
|
||||
// Reuse the exact same coinbase
|
||||
tx = std::get<0>(txd);
|
||||
tx = std::get<0>(utxod->second);
|
||||
// shouldn't be available for reconnection if its been duplicated
|
||||
disconnectedids.erase(tx.GetHash());
|
||||
disconnectedids.erase(utxod->first);
|
||||
|
||||
duplicateids.insert(tx.GetHash());
|
||||
duplicateids.insert(utxod->first);
|
||||
}
|
||||
else {
|
||||
coinbaseids.insert(tx.GetHash());
|
||||
coinbaseids.insert(COutPoint(tx.GetHash(), 0));
|
||||
}
|
||||
assert(CTransaction(tx).IsCoinBase());
|
||||
}
|
||||
|
@ -300,85 +299,82 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
// 17/20 times reconnect previous or add a regular tx
|
||||
else {
|
||||
|
||||
uint256 prevouthash;
|
||||
COutPoint prevout;
|
||||
// 1/20 times reconnect a previously disconnected tx
|
||||
if (randiter % 20 == 2 && disconnectedids.size()) {
|
||||
TxData &txd = FindRandomFrom(disconnectedids);
|
||||
tx = std::get<0>(txd);
|
||||
prevouthash = tx.vin[0].prevout.hash;
|
||||
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevouthash)) {
|
||||
disconnectedids.erase(tx.GetHash());
|
||||
auto utxod = FindRandomFrom(disconnectedids);
|
||||
tx = std::get<0>(utxod->second);
|
||||
prevout = tx.vin[0].prevout;
|
||||
if (!CTransaction(tx).IsCoinBase() && !utxoset.count(prevout)) {
|
||||
disconnectedids.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(tx.GetHash())) {
|
||||
if (utxoset.count(utxod->first)) {
|
||||
assert(CTransaction(tx).IsCoinBase());
|
||||
assert(duplicateids.count(tx.GetHash()));
|
||||
assert(duplicateids.count(utxod->first));
|
||||
}
|
||||
disconnectedids.erase(tx.GetHash());
|
||||
disconnectedids.erase(utxod->first);
|
||||
}
|
||||
|
||||
// 16/20 times create a regular tx
|
||||
else {
|
||||
TxData &txd = FindRandomFrom(utxoset);
|
||||
prevouthash = std::get<0>(txd).GetHash();
|
||||
auto utxod = FindRandomFrom(utxoset);
|
||||
prevout = utxod->first;
|
||||
|
||||
// Construct the tx to spend the coins of prevouthash
|
||||
tx.vin[0].prevout.hash = prevouthash;
|
||||
tx.vin[0].prevout.n = 0;
|
||||
tx.vin[0].prevout = prevout;
|
||||
assert(!CTransaction(tx).IsCoinBase());
|
||||
}
|
||||
// In this simple test coins only have two states, spent or unspent, save the unspent state to restore
|
||||
oldcoins = result[prevouthash];
|
||||
oldcoins = result[prevout];
|
||||
// Update the expected result of prevouthash to know these coins are spent
|
||||
result[prevouthash].Clear();
|
||||
result[prevout].Clear();
|
||||
|
||||
utxoset.erase(prevouthash);
|
||||
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 (duplicateids.count(prevouthash)) {
|
||||
if (duplicateids.count(prevout)) {
|
||||
spent_a_duplicate_coinbase = true;
|
||||
}
|
||||
|
||||
}
|
||||
// Update the expected result to know about the new output coins
|
||||
result[tx.GetHash()].FromTx(tx, height);
|
||||
assert(tx.vout.size() == 1);
|
||||
const COutPoint outpoint(tx.GetHash(), 0);
|
||||
result[outpoint] = Coin(tx.vout[0], height, CTransaction(tx).IsCoinBase());
|
||||
|
||||
// Call UpdateCoins on the top cache
|
||||
CTxUndo undo;
|
||||
UpdateCoins(tx, *(stack.back()), undo, height);
|
||||
|
||||
// Update the utxo set for future spends
|
||||
utxoset.insert(tx.GetHash());
|
||||
utxoset.insert(outpoint);
|
||||
|
||||
// Track this tx and undo info to use later
|
||||
alltxs.insert(std::make_pair(tx.GetHash(),std::make_tuple(tx,undo,oldcoins)));
|
||||
utxoData.emplace(outpoint, std::make_tuple(tx,undo,oldcoins));
|
||||
} else if (utxoset.size()) {
|
||||
//1/20 times undo a previous transaction
|
||||
TxData &txd = FindRandomFrom(utxoset);
|
||||
auto utxod = FindRandomFrom(utxoset);
|
||||
|
||||
CTransaction &tx = std::get<0>(txd);
|
||||
CTxUndo &undo = std::get<1>(txd);
|
||||
CCoins &origcoins = std::get<2>(txd);
|
||||
|
||||
uint256 undohash = tx.GetHash();
|
||||
CTransaction &tx = std::get<0>(utxod->second);
|
||||
CTxUndo &undo = std::get<1>(utxod->second);
|
||||
Coin &origcoins = std::get<2>(utxod->second);
|
||||
|
||||
// Update the expected result
|
||||
// Remove new outputs
|
||||
result[undohash].Clear();
|
||||
result[utxod->first].Clear();
|
||||
// If not coinbase restore prevout
|
||||
if (!tx.IsCoinBase()) {
|
||||
result[tx.vin[0].prevout.hash] = origcoins;
|
||||
result[tx.vin[0].prevout] = origcoins;
|
||||
}
|
||||
|
||||
// Disconnect the tx from the current UTXO
|
||||
// See code in DisconnectBlock
|
||||
// remove outputs
|
||||
{
|
||||
stack.back()->SpendCoin(COutPoint(undohash, 0));
|
||||
}
|
||||
stack.back()->SpendCoin(utxod->first);
|
||||
// restore inputs
|
||||
if (!tx.IsCoinBase()) {
|
||||
const COutPoint &out = tx.vin[0].prevout;
|
||||
|
@ -386,23 +382,19 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
ApplyTxInUndo(std::move(coin), *(stack.back()), out);
|
||||
}
|
||||
// Store as a candidate for reconnection
|
||||
disconnectedids.insert(undohash);
|
||||
disconnectedids.insert(utxod->first);
|
||||
|
||||
// Update the utxoset
|
||||
utxoset.erase(undohash);
|
||||
utxoset.erase(utxod->first);
|
||||
if (!tx.IsCoinBase())
|
||||
utxoset.insert(tx.vin[0].prevout.hash);
|
||||
utxoset.insert(tx.vin[0].prevout);
|
||||
}
|
||||
|
||||
// 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);
|
||||
} else {
|
||||
BOOST_CHECK(it->second.IsPruned());
|
||||
}
|
||||
for (auto it = result.begin(); it != result.end(); it++) {
|
||||
const Coin& coin = stack.back()->AccessCoin(it->first);
|
||||
BOOST_CHECK(coin == it->second);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -443,50 +435,36 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
|
|||
BOOST_AUTO_TEST_CASE(ccoins_serialization)
|
||||
{
|
||||
// Good example
|
||||
CDataStream ss1(ParseHex("0104835800816115944e077fe7c803cfa57f29b36bf87c1d358bb85e"), SER_DISK, CLIENT_VERSION);
|
||||
CCoins cc1;
|
||||
CDataStream ss1(ParseHex("97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"), SER_DISK, CLIENT_VERSION);
|
||||
Coin cc1;
|
||||
ss1 >> cc1;
|
||||
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"))))));
|
||||
BOOST_CHECK_EQUAL(cc1.out.nValue, 60000000000ULL);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(CKeyID(uint160(ParseHex("816115944e077fe7c803cfa57f29b36bf87c1d35"))))));
|
||||
|
||||
// Good example
|
||||
CDataStream ss2(ParseHex("0109044086ef97d5790061b01caab50f1b8e9c50a5057eb43c2d9563a4eebbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa486af3b"), SER_DISK, CLIENT_VERSION);
|
||||
CCoins cc2;
|
||||
CDataStream ss2(ParseHex("8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"), SER_DISK, CLIENT_VERSION);
|
||||
Coin cc2;
|
||||
ss2 >> cc2;
|
||||
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"))))));
|
||||
BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);
|
||||
BOOST_CHECK_EQUAL(HexStr(cc2.out.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;
|
||||
CDataStream ss3(ParseHex("000006"), SER_DISK, CLIENT_VERSION);
|
||||
Coin cc3;
|
||||
ss3 >> cc3;
|
||||
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);
|
||||
BOOST_CHECK_EQUAL(cc3.out.nValue, 0);
|
||||
BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0);
|
||||
|
||||
// scriptPubKey that ends beyond the end of the stream
|
||||
CDataStream ss4(ParseHex("0002000800"), SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ss4(ParseHex("000007"), SER_DISK, CLIENT_VERSION);
|
||||
try {
|
||||
CCoins cc4;
|
||||
Coin cc4;
|
||||
ss4 >> cc4;
|
||||
BOOST_CHECK_MESSAGE(false, "We should have thrown");
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
|
@ -497,17 +475,16 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
|
|||
uint64_t x = 3000000000ULL;
|
||||
tmp << VARINT(x);
|
||||
BOOST_CHECK_EQUAL(HexStr(tmp.begin(), tmp.end()), "8a95c0bb00");
|
||||
CDataStream ss5(ParseHex("0002008a95c0bb0000"), SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ss5(ParseHex("00008a95c0bb00"), SER_DISK, CLIENT_VERSION);
|
||||
try {
|
||||
CCoins cc5;
|
||||
Coin cc5;
|
||||
ss5 >> cc5;
|
||||
BOOST_CHECK_MESSAGE(false, "We should have thrown");
|
||||
} catch (const std::ios_base::failure& e) {
|
||||
}
|
||||
}
|
||||
|
||||
const static uint256 TXID;
|
||||
const static COutPoint OUTPOINT = {uint256(), 0};
|
||||
const static COutPoint OUTPOINT;
|
||||
const static CAmount PRUNED = -1;
|
||||
const static CAmount ABSENT = -2;
|
||||
const static CAmount FAIL = -3;
|
||||
|
@ -522,15 +499,15 @@ const static auto FLAGS = {char(0), FRESH, DIRTY, char(DIRTY | FRESH)};
|
|||
const static auto CLEAN_FLAGS = {char(0), FRESH};
|
||||
const static auto ABSENT_FLAGS = {NO_ENTRY};
|
||||
|
||||
void SetCoinsValue(CAmount value, CCoins& coins)
|
||||
void SetCoinsValue(CAmount value, Coin& coin)
|
||||
{
|
||||
assert(value != ABSENT);
|
||||
coins.Clear();
|
||||
assert(coins.IsPruned());
|
||||
coin.Clear();
|
||||
assert(coin.IsPruned());
|
||||
if (value != PRUNED) {
|
||||
coins.vout.emplace_back();
|
||||
coins.vout.back().nValue = value;
|
||||
assert(!coins.IsPruned());
|
||||
coin.out.nValue = value;
|
||||
coin.nHeight = 1;
|
||||
assert(!coin.IsPruned());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -544,24 +521,22 @@ size_t InsertCoinsMapEntry(CCoinsMap& map, CAmount value, char flags)
|
|||
CCoinsCacheEntry entry;
|
||||
entry.flags = flags;
|
||||
SetCoinsValue(value, entry.coins);
|
||||
auto inserted = map.emplace(TXID, std::move(entry));
|
||||
auto inserted = map.emplace(OUTPOINT, std::move(entry));
|
||||
assert(inserted.second);
|
||||
return inserted.first->second.coins.DynamicMemoryUsage();
|
||||
}
|
||||
|
||||
void GetCoinsMapEntry(const CCoinsMap& map, CAmount& value, char& flags)
|
||||
{
|
||||
auto it = map.find(TXID);
|
||||
auto it = map.find(OUTPOINT);
|
||||
if (it == map.end()) {
|
||||
value = ABSENT;
|
||||
flags = NO_ENTRY;
|
||||
} else {
|
||||
if (it->second.coins.IsPruned()) {
|
||||
assert(it->second.coins.vout.size() == 0);
|
||||
value = PRUNED;
|
||||
} else {
|
||||
assert(it->second.coins.vout.size() == 1);
|
||||
value = it->second.coins.vout[0].nValue;
|
||||
value = it->second.coins.out.nValue;
|
||||
}
|
||||
flags = it->second.flags;
|
||||
assert(flags != NO_ENTRY);
|
||||
|
|
|
@ -168,8 +168,8 @@ int do_fuzz()
|
|||
{
|
||||
try
|
||||
{
|
||||
CCoins block;
|
||||
ds >> block;
|
||||
Coin coin;
|
||||
ds >> coin;
|
||||
} catch (const std::ios_base::failure& e) {return 0;}
|
||||
break;
|
||||
}
|
||||
|
|
66
src/txdb.cpp
66
src/txdb.cpp
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
static const char DB_COIN = 'C';
|
||||
static const char DB_COINS = 'c';
|
||||
static const char DB_BLOCK_FILES = 'f';
|
||||
static const char DB_TXINDEX = 't';
|
||||
|
@ -24,17 +25,40 @@ static const char DB_FLAG = 'F';
|
|||
static const char DB_REINDEX_FLAG = 'R';
|
||||
static const char DB_LAST_BLOCK = 'l';
|
||||
|
||||
namespace {
|
||||
|
||||
struct CoinsEntry {
|
||||
COutPoint* outpoint;
|
||||
char key;
|
||||
CoinsEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN) {}
|
||||
|
||||
template<typename Stream>
|
||||
void Serialize(Stream &s) const {
|
||||
s << key;
|
||||
s << outpoint->hash;
|
||||
s << VARINT(outpoint->n);
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
void Unserialize(Stream& s) {
|
||||
s >> key;
|
||||
s >> outpoint->hash;
|
||||
s >> VARINT(outpoint->n);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true)
|
||||
{
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
return db.Read(std::make_pair(DB_COINS, txid), coins);
|
||||
bool CCoinsViewDB::GetCoins(const COutPoint &outpoint, Coin &coin) const {
|
||||
return db.Read(CoinsEntry(&outpoint), coin);
|
||||
}
|
||||
|
||||
bool CCoinsViewDB::HaveCoins(const uint256 &txid) const {
|
||||
return db.Exists(std::make_pair(DB_COINS, txid));
|
||||
bool CCoinsViewDB::HaveCoins(const COutPoint &outpoint) const {
|
||||
return db.Exists(CoinsEntry(&outpoint));
|
||||
}
|
||||
|
||||
uint256 CCoinsViewDB::GetBestBlock() const {
|
||||
|
@ -50,10 +74,11 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
|
|||
size_t changed = 0;
|
||||
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
|
||||
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
|
||||
CoinsEntry entry(&it->first);
|
||||
if (it->second.coins.IsPruned())
|
||||
batch.Erase(std::make_pair(DB_COINS, it->first));
|
||||
batch.Erase(entry);
|
||||
else
|
||||
batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins);
|
||||
batch.Write(entry, it->second.coins);
|
||||
changed++;
|
||||
}
|
||||
count++;
|
||||
|
@ -63,13 +88,14 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
|
|||
if (!hashBlock.IsNull())
|
||||
batch.Write(DB_BEST_BLOCK, hashBlock);
|
||||
|
||||
LogPrint(BCLog::COINDB, "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
|
||||
return db.WriteBatch(batch);
|
||||
bool ret = db.WriteBatch(batch);
|
||||
LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t CCoinsViewDB::EstimateSize() const
|
||||
{
|
||||
return db.EstimateSize(DB_COINS, (char)(DB_COINS+1));
|
||||
return db.EstimateSize(DB_COIN, (char)(DB_COIN+1));
|
||||
}
|
||||
|
||||
CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) {
|
||||
|
@ -101,29 +127,31 @@ CCoinsViewCursor *CCoinsViewDB::Cursor() const
|
|||
/* It seems that there are no "const iterators" for LevelDB. Since we
|
||||
only need read operations on it, use a const-cast to get around
|
||||
that restriction. */
|
||||
i->pcursor->Seek(DB_COINS);
|
||||
i->pcursor->Seek(DB_COIN);
|
||||
// Cache key of first record
|
||||
if (i->pcursor->Valid()) {
|
||||
i->pcursor->GetKey(i->keyTmp);
|
||||
CoinsEntry entry(&i->keyTmp.second);
|
||||
i->pcursor->GetKey(entry);
|
||||
i->keyTmp.first = entry.key;
|
||||
} else {
|
||||
i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool CCoinsViewDBCursor::GetKey(uint256 &key) const
|
||||
bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
|
||||
{
|
||||
// Return cached key
|
||||
if (keyTmp.first == DB_COINS) {
|
||||
if (keyTmp.first == DB_COIN) {
|
||||
key = keyTmp.second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CCoinsViewDBCursor::GetValue(CCoins &coins) const
|
||||
bool CCoinsViewDBCursor::GetValue(Coin &coin) const
|
||||
{
|
||||
return pcursor->GetValue(coins);
|
||||
return pcursor->GetValue(coin);
|
||||
}
|
||||
|
||||
unsigned int CCoinsViewDBCursor::GetValueSize() const
|
||||
|
@ -133,14 +161,18 @@ unsigned int CCoinsViewDBCursor::GetValueSize() const
|
|||
|
||||
bool CCoinsViewDBCursor::Valid() const
|
||||
{
|
||||
return keyTmp.first == DB_COINS;
|
||||
return keyTmp.first == DB_COIN;
|
||||
}
|
||||
|
||||
void CCoinsViewDBCursor::Next()
|
||||
{
|
||||
pcursor->Next();
|
||||
if (!pcursor->Valid() || !pcursor->GetKey(keyTmp))
|
||||
CoinsEntry entry(&keyTmp.second);
|
||||
if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
|
||||
keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
|
||||
} else {
|
||||
keyTmp.first = entry.key;
|
||||
}
|
||||
}
|
||||
|
||||
bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {
|
||||
|
|
16
src/txdb.h
16
src/txdb.h
|
@ -73,11 +73,11 @@ protected:
|
|||
public:
|
||||
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
|
||||
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
uint256 GetBestBlock() const;
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
|
||||
CCoinsViewCursor *Cursor() const;
|
||||
bool GetCoins(const COutPoint &outpoint, Coin &coin) const override;
|
||||
bool HaveCoins(const COutPoint &outpoint) const override;
|
||||
uint256 GetBestBlock() const override;
|
||||
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) override;
|
||||
CCoinsViewCursor *Cursor() const override;
|
||||
|
||||
size_t EstimateSize() const override;
|
||||
};
|
||||
|
@ -88,8 +88,8 @@ class CCoinsViewDBCursor: public CCoinsViewCursor
|
|||
public:
|
||||
~CCoinsViewDBCursor() {}
|
||||
|
||||
bool GetKey(uint256 &key) const;
|
||||
bool GetValue(CCoins &coins) const;
|
||||
bool GetKey(COutPoint &key) const;
|
||||
bool GetValue(Coin &coin) const;
|
||||
unsigned int GetValueSize() const;
|
||||
|
||||
bool Valid() const;
|
||||
|
@ -99,7 +99,7 @@ private:
|
|||
CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256 &hashBlockIn):
|
||||
CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
|
||||
std::unique_ptr<CDBIterator> pcursor;
|
||||
std::pair<char, uint256> keyTmp;
|
||||
std::pair<char, COutPoint> keyTmp;
|
||||
|
||||
friend class CCoinsViewDB;
|
||||
};
|
||||
|
|
|
@ -524,9 +524,9 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
|
|||
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
|
||||
if (it2 != mapTx.end())
|
||||
continue;
|
||||
const CCoins *coins = pcoins->AccessCoins(txin.prevout.hash);
|
||||
if (nCheckFrequency != 0) assert(coins);
|
||||
if (!coins || (coins->IsCoinBase() && ((signed long)nMemPoolHeight) - coins->nHeight < COINBASE_MATURITY)) {
|
||||
const Coin &coin = pcoins->AccessCoin(txin.prevout);
|
||||
if (nCheckFrequency != 0) assert(!coin.IsPruned());
|
||||
if (coin.IsPruned() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
|
||||
txToRemove.insert(it);
|
||||
break;
|
||||
}
|
||||
|
@ -654,8 +654,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
|
|||
parentSigOpCost += it2->GetSigOpCost();
|
||||
}
|
||||
} else {
|
||||
const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash);
|
||||
assert(coins && coins->IsAvailable(txin.prevout.n));
|
||||
assert(pcoins->HaveCoins(txin.prevout));
|
||||
}
|
||||
// Check whether its inputs are marked in mapNextTx.
|
||||
auto it3 = mapNextTx.find(txin.prevout);
|
||||
|
@ -891,20 +890,24 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
|
|||
|
||||
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
|
||||
|
||||
bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) const {
|
||||
bool CCoinsViewMemPool::GetCoins(const COutPoint &outpoint, Coin &coin) const {
|
||||
// If an entry in the mempool exists, always return that one, as it's guaranteed to never
|
||||
// conflict with the underlying cache, and it cannot have pruned entries (as it contains full)
|
||||
// transactions. First checking the underlying cache risks returning a pruned entry instead.
|
||||
CTransactionRef ptx = mempool.get(txid);
|
||||
CTransactionRef ptx = mempool.get(outpoint.hash);
|
||||
if (ptx) {
|
||||
coins = CCoins(*ptx, MEMPOOL_HEIGHT);
|
||||
return true;
|
||||
if (outpoint.n < ptx->vout.size()) {
|
||||
coin = Coin(ptx->vout[outpoint.n], MEMPOOL_HEIGHT, false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (base->GetCoins(txid, coins) && !coins.IsPruned());
|
||||
return (base->GetCoins(outpoint, coin) && !coin.IsPruned());
|
||||
}
|
||||
|
||||
bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const {
|
||||
return mempool.exists(txid) || base->HaveCoins(txid);
|
||||
bool CCoinsViewMemPool::HaveCoins(const COutPoint &outpoint) const {
|
||||
return mempool.exists(outpoint) || base->HaveCoins(outpoint);
|
||||
}
|
||||
|
||||
size_t CTxMemPool::DynamicMemoryUsage() const {
|
||||
|
@ -1015,7 +1018,7 @@ void CTxMemPool::trackPackageRemoved(const CFeeRate& rate) {
|
|||
}
|
||||
}
|
||||
|
||||
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining) {
|
||||
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining) {
|
||||
LOCK(cs);
|
||||
|
||||
unsigned nTxnRemoved = 0;
|
||||
|
@ -1046,11 +1049,10 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRe
|
|||
if (pvNoSpendsRemaining) {
|
||||
BOOST_FOREACH(const CTransaction& tx, txn) {
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
|
||||
if (exists(txin.prevout.hash))
|
||||
continue;
|
||||
auto iter = mapNextTx.lower_bound(COutPoint(txin.prevout.hash, 0));
|
||||
if (iter == mapNextTx.end() || iter->first->hash != txin.prevout.hash)
|
||||
pvNoSpendsRemaining->push_back(txin.prevout.hash);
|
||||
if (exists(txin.prevout.hash)) continue;
|
||||
if (!mapNextTx.count(txin.prevout)) {
|
||||
pvNoSpendsRemaining->push_back(txin.prevout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1067,3 +1069,5 @@ bool CTxMemPool::TransactionWithinChainLimit(const uint256& txid, size_t chainLi
|
|||
return it == mapTx.end() || (it->GetCountWithAncestors() < chainLimit &&
|
||||
it->GetCountWithDescendants() < chainLimit);
|
||||
}
|
||||
|
||||
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
class CAutoFile;
|
||||
class CBlockIndex;
|
||||
|
||||
/** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */
|
||||
static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF;
|
||||
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
|
||||
static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
|
||||
|
||||
struct LockPoints
|
||||
{
|
||||
|
@ -321,6 +321,20 @@ enum class MemPoolRemovalReason {
|
|||
REPLACED //! Removed for replacement
|
||||
};
|
||||
|
||||
class SaltedTxidHasher
|
||||
{
|
||||
private:
|
||||
/** Salt */
|
||||
const uint64_t k0, k1;
|
||||
|
||||
public:
|
||||
SaltedTxidHasher();
|
||||
|
||||
size_t operator()(const uint256& txid) const {
|
||||
return SipHashUint256(k0, k1, txid);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CTxMemPool stores valid-according-to-the-current-best-chain transactions
|
||||
* that may be included in the next block.
|
||||
|
@ -570,10 +584,10 @@ public:
|
|||
CFeeRate GetMinFee(size_t sizelimit) const;
|
||||
|
||||
/** Remove transactions from the mempool until its dynamic size is <= sizelimit.
|
||||
* pvNoSpendsRemaining, if set, will be populated with the list of transactions
|
||||
* pvNoSpendsRemaining, if set, will be populated with the list of outpoints
|
||||
* which are not in mempool which no longer have any spends in this mempool.
|
||||
*/
|
||||
void TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining=NULL);
|
||||
void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining=NULL);
|
||||
|
||||
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
|
||||
int Expire(int64_t time);
|
||||
|
@ -599,6 +613,13 @@ public:
|
|||
return (mapTx.count(hash) != 0);
|
||||
}
|
||||
|
||||
bool exists(const COutPoint& outpoint) const
|
||||
{
|
||||
LOCK(cs);
|
||||
auto it = mapTx.find(outpoint.hash);
|
||||
return (it != mapTx.end() && outpoint.n < it->GetTx().vout.size());
|
||||
}
|
||||
|
||||
CTransactionRef get(const uint256& hash) const;
|
||||
TxMempoolInfo info(const uint256& hash) const;
|
||||
std::vector<TxMempoolInfo> infoAll() const;
|
||||
|
@ -658,8 +679,8 @@ protected:
|
|||
|
||||
public:
|
||||
CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn);
|
||||
bool GetCoins(const uint256 &txid, CCoins &coins) const;
|
||||
bool HaveCoins(const uint256 &txid) const;
|
||||
bool GetCoins(const COutPoint &outpoint, Coin &coin) const;
|
||||
bool HaveCoins(const COutPoint &outpoint) const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_TXMEMPOOL_H
|
||||
|
|
|
@ -268,15 +268,15 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool
|
|||
prevheights.resize(tx.vin.size());
|
||||
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
|
||||
const CTxIn& txin = tx.vin[txinIndex];
|
||||
CCoins coins;
|
||||
if (!viewMemPool.GetCoins(txin.prevout.hash, coins)) {
|
||||
Coin coin;
|
||||
if (!viewMemPool.GetCoins(txin.prevout, coin)) {
|
||||
return error("%s: Missing input", __func__);
|
||||
}
|
||||
if (coins.nHeight == MEMPOOL_HEIGHT) {
|
||||
if (coin.nHeight == MEMPOOL_HEIGHT) {
|
||||
// Assume all mempool transaction confirm in the next block
|
||||
prevheights[txinIndex] = tip->nHeight + 1;
|
||||
} else {
|
||||
prevheights[txinIndex] = coins.nHeight;
|
||||
prevheights[txinIndex] = coin.nHeight;
|
||||
}
|
||||
}
|
||||
lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
|
||||
|
@ -315,9 +315,9 @@ void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) {
|
|||
LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired);
|
||||
}
|
||||
|
||||
std::vector<uint256> vNoSpendsRemaining;
|
||||
std::vector<COutPoint> vNoSpendsRemaining;
|
||||
pool.TrimToSize(limit, &vNoSpendsRemaining);
|
||||
BOOST_FOREACH(const uint256& removed, vNoSpendsRemaining)
|
||||
BOOST_FOREACH(const COutPoint& removed, vNoSpendsRemaining)
|
||||
pcoinsTip->Uncache(removed);
|
||||
}
|
||||
|
||||
|
@ -344,7 +344,7 @@ static bool IsCurrentForFeeEstimation()
|
|||
|
||||
bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx, bool fLimitFree,
|
||||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<uint256>& vHashTxnToUncache)
|
||||
bool fOverrideMempoolLimit, const CAmount& nAbsurdFee, std::vector<COutPoint>& vHashTxnToUncache)
|
||||
{
|
||||
const CTransaction& tx = *ptx;
|
||||
const uint256 hash = tx.GetHash();
|
||||
|
@ -437,29 +437,29 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
|||
view.SetBackend(viewMemPool);
|
||||
|
||||
// do we already have it?
|
||||
bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(hash);
|
||||
if (view.HaveCoins(hash)) {
|
||||
if (!fHadTxInCache)
|
||||
vHashTxnToUncache.push_back(hash);
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
|
||||
}
|
||||
|
||||
// do all inputs exist?
|
||||
// Note that this does not check for the presence of actual outputs (see the next check for that),
|
||||
// and only helps with filling in pfMissingInputs (to determine missing vs spent).
|
||||
BOOST_FOREACH(const CTxIn txin, tx.vin) {
|
||||
if (!pcoinsTip->HaveCoinsInCache(txin.prevout.hash))
|
||||
vHashTxnToUncache.push_back(txin.prevout.hash);
|
||||
if (!view.HaveCoins(txin.prevout.hash)) {
|
||||
if (pfMissingInputs)
|
||||
*pfMissingInputs = true;
|
||||
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
|
||||
for (size_t out = 0; out < tx.vout.size(); out++) {
|
||||
COutPoint outpoint(hash, out);
|
||||
bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(outpoint);
|
||||
if (view.HaveCoins(outpoint)) {
|
||||
if (!fHadTxInCache) {
|
||||
vHashTxnToUncache.push_back(outpoint);
|
||||
}
|
||||
return state.Invalid(false, REJECT_ALREADY_KNOWN, "txn-already-known");
|
||||
}
|
||||
}
|
||||
|
||||
// are the actual inputs available?
|
||||
if (!view.HaveInputs(tx))
|
||||
return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
|
||||
// do all inputs exist?
|
||||
BOOST_FOREACH(const CTxIn txin, tx.vin) {
|
||||
if (!pcoinsTip->HaveCoinsInCache(txin.prevout)) {
|
||||
vHashTxnToUncache.push_back(txin.prevout);
|
||||
}
|
||||
if (!view.HaveCoins(txin.prevout)) {
|
||||
if (pfMissingInputs) {
|
||||
*pfMissingInputs = true;
|
||||
}
|
||||
return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
|
||||
}
|
||||
}
|
||||
|
||||
// Bring the best block into scope
|
||||
view.GetBestBlock();
|
||||
|
@ -763,10 +763,10 @@ bool AcceptToMemoryPoolWithTime(CTxMemPool& pool, CValidationState &state, const
|
|||
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool fOverrideMempoolLimit, const CAmount nAbsurdFee)
|
||||
{
|
||||
std::vector<uint256> vHashTxToUncache;
|
||||
std::vector<COutPoint> vHashTxToUncache;
|
||||
bool res = AcceptToMemoryPoolWorker(pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime, plTxnReplaced, fOverrideMempoolLimit, nAbsurdFee, vHashTxToUncache);
|
||||
if (!res) {
|
||||
BOOST_FOREACH(const uint256& hashTx, vHashTxToUncache)
|
||||
BOOST_FOREACH(const COutPoint& hashTx, vHashTxToUncache)
|
||||
pcoinsTip->Uncache(hashTx);
|
||||
}
|
||||
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
|
||||
|
@ -1762,12 +1762,12 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n
|
|||
}
|
||||
// Flush best chain related state. This can only be done if the blocks / block index write was also done.
|
||||
if (fDoFullFlush) {
|
||||
// Typical CCoins structures on disk are around 128 bytes in size.
|
||||
// Typical Coin structures on disk are around 48 bytes in size.
|
||||
// Pushing a new one to the database can cause it to be written
|
||||
// twice (once in the log, and once in the tables). This is already
|
||||
// an overestimation, as most will delete an existing entry or
|
||||
// overwrite one. Still, use a conservative safety factor of 2.
|
||||
if (!CheckDiskSpace(128 * 2 * 2 * pcoinsTip->GetCacheSize()))
|
||||
if (!CheckDiskSpace(48 * 2 * 2 * pcoinsTip->GetCacheSize()))
|
||||
return state.Error("out of disk space");
|
||||
// Flush the chainstate (which may refer to block index entries).
|
||||
if (!pcoinsTip->Flush())
|
||||
|
@ -1848,7 +1848,7 @@ void static UpdateTip(CBlockIndex *pindexNew, const CChainParams& chainParams) {
|
|||
}
|
||||
}
|
||||
}
|
||||
LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utx)", __func__,
|
||||
LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__,
|
||||
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), chainActive.Tip()->nVersion,
|
||||
log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
|
||||
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
|
||||
|
|
Loading…
Reference in a new issue