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:
Pieter Wuille
2017-04-25 11:29:39 -07:00
parent 4ec0d9e794
commit 5083079688
15 changed files with 363 additions and 373 deletions

View File

@@ -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) {