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
+26 -21
View File
@@ -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;
}