Skip to content

Instantly share code, notes, and snippets.

@jngbng
Last active December 15, 2017 15:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jngbng/3140c29fafc750182a6ab6cc93c8e4df to your computer and use it in GitHub Desktop.
Save jngbng/3140c29fafc750182a6ab6cc93c8e4df to your computer and use it in GitHub Desktop.
bitcoincore undo
/********************
* 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