Last active
December 15, 2017 15:01
-
-
Save jngbng/3140c29fafc750182a6ab6cc93c8e4df to your computer and use it in GitHub Desktop.
bitcoincore undo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************** | |
* Do part | |
********************/ | |
// undo.h | |
class CTxUndo | |
{ | |
public: | |
// undo information for all txins // COMMENT: spent coin by txins | |
std::vector<Coin> vprevout; | |
// ... | |
}; | |
// validation.cpp | |
/** Apply the effects of this block (with given index) on the UTXO set represented by coins. | |
* Validity checks that depend on the UTXO set are also done; ConnectBlock() | |
* can fail if those validity checks fail (among other reasons). */ | |
static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, | |
CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) | |
{ | |
// ... | |
CBlockUndo blockundo; | |
CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size())); | |
std::vector<std::pair<uint256, CDiskTxPos> > vPos; | |
vPos.reserve(block.vtx.size()); | |
blockundo.vtxundo.reserve(block.vtx.size() - 1); | |
for (unsigned int i = 0; i < block.vtx.size(); i++) { | |
const CTransaction &tx = *(block.vtx[i]); | |
// ... | |
CTxUndo undoDummy; | |
if (i > 0) { | |
blockundo.vtxundo.push_back(CTxUndo()); | |
} | |
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight); | |
vPos.push_back(std::make_pair(tx.GetHash(), pos)); | |
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); | |
} | |
// ... | |
// Write undo information to disk | |
if (pindex->GetUndoPos().IsNull() || !pindex->IsValid(BLOCK_VALID_SCRIPTS)) { | |
if (pindex->GetUndoPos().IsNull()) { | |
CDiskBlockPos _pos; | |
if (!FindUndoPos(state, pindex->nFile, _pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 40)) | |
return error("ConnectBlock(): FindUndoPos failed"); | |
if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(), chainparams.MessageStart())) | |
return AbortNode(state, "Failed to write undo data"); | |
// update nUndoPos in block index | |
pindex->nUndoPos = _pos.nPos; | |
pindex->nStatus |= BLOCK_HAVE_UNDO; | |
} | |
// ... | |
} | |
if (fTxIndex) | |
if (!pblocktree->WriteTxIndex(vPos)) | |
return AbortNode(state, "Failed to write transaction index"); | |
// ... | |
} | |
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight) | |
{ | |
// mark inputs spent | |
if (!tx.IsCoinBase()) { | |
// COMMENT: move Coin from UTXO to CTxUndo | |
txundo.vprevout.reserve(tx.vin.size()); | |
for (const CTxIn &txin : tx.vin) { | |
txundo.vprevout.emplace_back(); | |
bool is_spent = inputs.SpendCoin(txin.prevout, &txundo.vprevout.back()); | |
assert(is_spent); | |
} | |
} | |
// add outputs | |
AddCoins(inputs, tx, nHeight); | |
} | |
/******************** | |
* Delete | |
********************/ | |
// coins.cpp | |
bool CCoinsViewCache::SpendCoin(const COutPoint &outpoint, Coin* moveout) | |
{ | |
CCoinsMap::iterator it = FetchCoin(outpoint); | |
if (it == cacheCoins.end()) return false; | |
// ... | |
if (moveout) { | |
*moveout = std::move(it->second.coin); | |
} | |
// ... | |
return true; | |
} | |
CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const | |
{ | |
// typeof cacheCoins := std::unordered_map<COutPoint, CCoinsCacheEntry, SaltedOutpointHasher> | |
CCoinsMap::iterator it = cacheCoins.find(outpoint); | |
if (it != cacheCoins.end()) | |
return it; | |
// ... | |
} | |
struct CCoinsCacheEntry | |
{ | |
Coin coin; // The actual cached data. | |
unsigned char flags; | |
// ... | |
}; | |
/******************** | |
* Create | |
********************/ | |
void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool check = false) { | |
bool fCoinbase = tx.IsCoinBase(); | |
const uint256& txid = tx.GetHash(); | |
for (size_t i = 0; i < tx.vout.size(); ++i) { | |
bool overwrite = check ? cache.HaveCoin(COutPoint(txid, i)) : fCoinbase; | |
// Always set the possible_overwrite flag to AddCoin for coinbase txn, 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); | |
} | |
} | |
/******************** | |
* Undo part | |
********************/ | |
// validation.cpp | |
/** Undo the effects of this block (with given index) on the UTXO set represented by coins. | |
* When FAILED is returned, view is left in an indeterminate state. */ | |
static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) | |
{ | |
bool fClean = true; | |
CBlockUndo blockUndo; | |
CDiskBlockPos pos = pindex->GetUndoPos(); | |
// ... | |
if (!UndoReadFromDisk(blockUndo, pos, pindex->pprev->GetBlockHash())) { | |
// ... | |
} | |
if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) { | |
// ... | |
} | |
// undo transactions in reverse order | |
for (int i = block.vtx.size() - 1; i >= 0; i--) { | |
const CTransaction &tx = *(block.vtx[i]); | |
uint256 hash = tx.GetHash(); | |
bool is_coinbase = tx.IsCoinBase(); | |
// Check that all outputs are available and match the outputs in the block itself | |
// exactly. | |
for (size_t o = 0; o < tx.vout.size(); o++) { | |
if (!tx.vout[o].scriptPubKey.IsUnspendable()) { | |
COutPoint out(hash, o); | |
Coin coin; | |
bool is_spent = view.SpendCoin(out, &coin); | |
if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) { | |
fClean = false; // transaction output mismatch | |
} | |
} | |
} | |
// restore inputs | |
if (i > 0) { // not coinbases | |
CTxUndo &txundo = blockUndo.vtxundo[i-1]; | |
// ... | |
for (unsigned int j = tx.vin.size(); j-- > 0;) { | |
// COMMENT: txundo.vprevout[j] -> spent coin | |
// COMMENT: tx.vin[j].prevout -> spent transaction output | |
// COMMENT: view -> map<COutPoint, Coin> | |
const COutPoint &out = tx.vin[j].prevout; | |
int res = ApplyTxInUndo(std::move(txundo.vprevout[j]), view, out); | |
// ... | |
} | |
// At this point, all of txundo.vprevout should have been moved out. | |
} | |
} | |
// ... | |
} | |
// validation.cpp | |
/** | |
* Restore the UTXO in a Coin at a given COutPoint | |
* @param undo The Coin to be restored. | |
* @param view The coins view to which to apply the changes. | |
* @param out The out point that corresponds to the tx input. | |
* @return A DisconnectResult as an int | |
*/ | |
int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) | |
{ | |
bool fClean = true; | |
if (view.HaveCoin(out)) fClean = false; // overwriting transaction output | |
if (undo.nHeight == 0) { | |
// COMMENT: Backward-compatibility. calculate nHeight and fCoinBase. | |
// Older versions included this information only in undo records for the last spend of a transactions' outputs. | |
// This implies that it must be present for some other output of the same tx. | |
} | |
// The potential_overwrite parameter to AddCoin is only allowed to be false if we know for | |
// sure that the coin did not already exist in the cache. As we have queried for that above | |
// using HaveCoin, we don't need to guess. When fClean is false, a coin already existed and | |
// it is an overwrite. | |
view.AddCoin(out, std::move(undo), !fClean); | |
return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; | |
} | |
/// undo.h | |
class TxInUndoSerializer | |
{ | |
const Coin* txout; | |
public: | |
template<typename Stream> | |
void Serialize(Stream &s) const { | |
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0))); | |
if (txout->nHeight > 0) { | |
// Required to maintain compatibility with older undo format. | |
::Serialize(s, (unsigned char)0); | |
} | |
::Serialize(s, CTxOutCompressor(REF(txout->out))); | |
} | |
explicit TxInUndoSerializer(const Coin* coin) : txout(coin) {} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment