Browse Source

Merge #7187: Keep reorgs fast for SequenceLocks checks

982670c Add LockPoints (Alex Morcos)
0.13
Wladimir J. van der Laan 9 years ago
parent
commit
14d6324a24
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 89
      src/main.cpp
  2. 12
      src/main.h
  3. 2
      src/test/test_bitcoin.cpp
  4. 4
      src/test/test_bitcoin.h
  5. 18
      src/txmempool.cpp
  6. 32
      src/txmempool.h

89
src/main.cpp

@ -794,7 +794,25 @@ bool SequenceLocks(const CTransaction &tx, int flags, std::vector<int>* prevHeig
return EvaluateSequenceLocks(block, CalculateSequenceLocks(tx, flags, prevHeights, block)); return EvaluateSequenceLocks(block, CalculateSequenceLocks(tx, flags, prevHeights, block));
} }
bool CheckSequenceLocks(const CTransaction &tx, int flags) bool TestLockPointValidity(const LockPoints* lp)
{
AssertLockHeld(cs_main);
assert(lp);
// If there are relative lock times then the maxInputBlock will be set
// If there are no relative lock times, the LockPoints don't depend on the chain
if (lp->maxInputBlock) {
// Check whether chainActive is an extension of the block at which the LockPoints
// calculation was valid. If not LockPoints are no longer valid
if (!chainActive.Contains(lp->maxInputBlock)) {
return false;
}
}
// LockPoints still valid
return true;
}
bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool useExistingLockPoints)
{ {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
AssertLockHeld(mempool.cs); AssertLockHeld(mempool.cs);
@ -810,25 +828,57 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags)
// *next* block, we need to use one more than chainActive.Height() // *next* block, we need to use one more than chainActive.Height()
index.nHeight = tip->nHeight + 1; index.nHeight = tip->nHeight + 1;
// pcoinsTip contains the UTXO set for chainActive.Tip() std::pair<int, int64_t> lockPair;
CCoinsViewMemPool viewMemPool(pcoinsTip, mempool); if (useExistingLockPoints) {
std::vector<int> prevheights; assert(lp);
prevheights.resize(tx.vin.size()); lockPair.first = lp->height;
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) { lockPair.second = lp->time;
const CTxIn& txin = tx.vin[txinIndex]; }
CCoins coins; else {
if (!viewMemPool.GetCoins(txin.prevout.hash, coins)) { // pcoinsTip contains the UTXO set for chainActive.Tip()
return error("%s: Missing input", __func__); CCoinsViewMemPool viewMemPool(pcoinsTip, mempool);
std::vector<int> prevheights;
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)) {
return error("%s: Missing input", __func__);
}
if (coins.nHeight == MEMPOOL_HEIGHT) {
// Assume all mempool transaction confirm in the next block
prevheights[txinIndex] = tip->nHeight + 1;
} else {
prevheights[txinIndex] = coins.nHeight;
}
} }
if (coins.nHeight == MEMPOOL_HEIGHT) { lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
// Assume all mempool transaction confirm in the next block if (lp) {
prevheights[txinIndex] = tip->nHeight + 1; lp->height = lockPair.first;
} else { lp->time = lockPair.second;
prevheights[txinIndex] = coins.nHeight; // Also store the hash of the block with the highest height of
// all the blocks which have sequence locked prevouts.
// This hash needs to still be on the chain
// for these LockPoint calculations to be valid
// Note: It is impossible to correctly calculate a maxInputBlock
// if any of the sequence locked inputs depend on unconfirmed txs,
// except in the special case where the relative lock time/height
// is 0, which is equivalent to no sequence lock. Since we assume
// input height of tip+1 for mempool txs and test the resulting
// lockPair from CalculateSequenceLocks against tip+1. We know
// EvaluateSequenceLocks will fail if there was a non-zero sequence
// lock on a mempool input, so we can use the return value of
// CheckSequenceLocks to indicate the LockPoints validity
int maxInputHeight = 0;
BOOST_FOREACH(int height, prevheights) {
// Can ignore mempool inputs since we'll fail if they had non-zero locks
if (height != tip->nHeight+1) {
maxInputHeight = std::max(maxInputHeight, height);
}
}
lp->maxInputBlock = tip->GetAncestor(maxInputHeight);
} }
} }
std::pair<int, int64_t> lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
return EvaluateSequenceLocks(index, lockPair); return EvaluateSequenceLocks(index, lockPair);
} }
@ -1017,6 +1067,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
CCoinsViewCache view(&dummy); CCoinsViewCache view(&dummy);
CAmount nValueIn = 0; CAmount nValueIn = 0;
LockPoints lp;
{ {
LOCK(pool.cs); LOCK(pool.cs);
CCoinsViewMemPool viewMemPool(pcoinsTip, pool); CCoinsViewMemPool viewMemPool(pcoinsTip, pool);
@ -1060,7 +1111,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
// be mined yet. // be mined yet.
// Must keep pool.cs for this unless we change CheckSequenceLocks to take a // Must keep pool.cs for this unless we change CheckSequenceLocks to take a
// CoinsViewCache instead of create its own // CoinsViewCache instead of create its own
if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final");
} }
@ -1092,7 +1143,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
} }
} }
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps); CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase, nSigOps, lp);
unsigned int nSize = entry.GetTxSize(); unsigned int nSize = entry.GetTxSize();
// Check that the transaction doesn't have an excessive number of // Check that the transaction doesn't have an excessive number of

12
src/main.h

@ -39,6 +39,7 @@ class CValidationInterface;
class CValidationState; class CValidationState;
struct CNodeStateStats; struct CNodeStateStats;
struct LockPoints;
/** Default for accepting alerts from the P2P network. */ /** Default for accepting alerts from the P2P network. */
static const bool DEFAULT_ALERTS = true; static const bool DEFAULT_ALERTS = true;
@ -368,6 +369,11 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime);
*/ */
bool CheckFinalTx(const CTransaction &tx, int flags = -1); bool CheckFinalTx(const CTransaction &tx, int flags = -1);
/**
* Test whether the LockPoints height and time are still valid on the current chain
*/
bool TestLockPointValidity(const LockPoints* lp);
/** /**
* Check if transaction is final per BIP 68 sequence numbers and can be included in a block. * Check if transaction is final per BIP 68 sequence numbers and can be included in a block.
* Consensus critical. Takes as input a list of heights at which tx's inputs (in order) confirmed. * Consensus critical. Takes as input a list of heights at which tx's inputs (in order) confirmed.
@ -378,10 +384,14 @@ bool SequenceLocks(const CTransaction &tx, int flags, std::vector<int>* prevHeig
* Check if transaction will be BIP 68 final in the next block to be created. * Check if transaction will be BIP 68 final in the next block to be created.
* *
* Simulates calling SequenceLocks() with data from the tip of the current active chain. * Simulates calling SequenceLocks() with data from the tip of the current active chain.
* Optionally stores in LockPoints the resulting height and time calculated and the hash
* of the block needed for calculation or skips the calculation and uses the LockPoints
* passed in for evaluation.
* The LockPoints should not be considered valid if CheckSequenceLocks returns false.
* *
* See consensus/consensus.h for flag definitions. * See consensus/consensus.h for flag definitions.
*/ */
bool CheckSequenceLocks(const CTransaction &tx, int flags); bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp = NULL, bool useExistingLockPoints = false);
/** /**
* Closure representing one script verification * Closure representing one script verification

2
src/test/test_bitcoin.cpp

@ -152,7 +152,7 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(CMutableTransaction &tx, CTxMemPo
CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0; CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0;
return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight, return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight,
hasNoDependencies, inChainValue, spendsCoinbase, sigOpCount); hasNoDependencies, inChainValue, spendsCoinbase, sigOpCount, lp);
} }
void Shutdown(void* parg) void Shutdown(void* parg)

4
src/test/test_bitcoin.h

@ -9,6 +9,7 @@
#include "key.h" #include "key.h"
#include "pubkey.h" #include "pubkey.h"
#include "txdb.h" #include "txdb.h"
#include "txmempool.h"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
@ -71,7 +72,8 @@ struct TestMemPoolEntryHelper
bool hadNoDependencies; bool hadNoDependencies;
bool spendsCoinbase; bool spendsCoinbase;
unsigned int sigOpCount; unsigned int sigOpCount;
LockPoints lp;
TestMemPoolEntryHelper() : TestMemPoolEntryHelper() :
nFee(0), nTime(0), dPriority(0.0), nHeight(1), nFee(0), nTime(0), dPriority(0.0), nHeight(1),
hadNoDependencies(false), spendsCoinbase(false), sigOpCount(1) { } hadNoDependencies(false), spendsCoinbase(false), sigOpCount(1) { }

18
src/txmempool.cpp

@ -22,10 +22,10 @@ using namespace std;
CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight, int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue, bool poolHasNoInputsOf, CAmount _inChainInputValue,
bool _spendsCoinbase, unsigned int _sigOps): bool _spendsCoinbase, unsigned int _sigOps, LockPoints lp):
tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight), tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight),
hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue), hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue),
spendsCoinbase(_spendsCoinbase), sigOpCount(_sigOps) spendsCoinbase(_spendsCoinbase), sigOpCount(_sigOps), lockPoints(lp)
{ {
nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
nModSize = tx.CalculateModifiedSize(nTxSize); nModSize = tx.CalculateModifiedSize(nTxSize);
@ -61,6 +61,11 @@ void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
feeDelta = newFeeDelta; feeDelta = newFeeDelta;
} }
void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp)
{
lockPoints = lp;
}
// Update the given tx for any in-mempool descendants. // Update the given tx for any in-mempool descendants.
// Assumes that setMemPoolChildren is correct for the given tx and all // Assumes that setMemPoolChildren is correct for the given tx and all
// descendants. // descendants.
@ -506,7 +511,11 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
list<CTransaction> transactionsToRemove; list<CTransaction> transactionsToRemove;
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
const CTransaction& tx = it->GetTx(); const CTransaction& tx = it->GetTx();
if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(tx, flags)) { LockPoints lp = it->GetLockPoints();
bool validLP = TestLockPointValidity(&lp);
if (!CheckFinalTx(tx, flags) || !CheckSequenceLocks(tx, flags, &lp, validLP)) {
// Note if CheckSequenceLocks fails the LockPoints may still be invalid
// So it's critical that we remove the tx and not depend on the LockPoints.
transactionsToRemove.push_back(tx); transactionsToRemove.push_back(tx);
} else if (it->GetSpendsCoinbase()) { } else if (it->GetSpendsCoinbase()) {
BOOST_FOREACH(const CTxIn& txin, tx.vin) { BOOST_FOREACH(const CTxIn& txin, tx.vin) {
@ -521,6 +530,9 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
} }
} }
} }
if (!validLP) {
mapTx.modify(it, update_lock_points(lp));
}
} }
BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) { BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) {
list<CTransaction> removed; list<CTransaction> removed;

32
src/txmempool.h

@ -19,6 +19,7 @@
#include "boost/multi_index/ordered_index.hpp" #include "boost/multi_index/ordered_index.hpp"
class CAutoFile; class CAutoFile;
class CBlockIndex;
inline double AllowFreeThreshold() inline double AllowFreeThreshold()
{ {
@ -35,6 +36,21 @@ inline bool AllowFree(double dPriority)
/** Fake height value used in CCoins to signify they are only in the memory pool (since 0.8) */ /** 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; static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF;
struct LockPoints
{
// Will be set to the blockchain height and median time past
// values that would be necessary to satisfy all relative locktime
// constraints (BIP68) of this tx given our view of block chain history
int height;
int64_t time;
// As long as the current chain descends from the highest height block
// containing one of the inputs used in the calculation, then the cached
// values are still valid even after a reorg.
CBlockIndex* maxInputBlock;
LockPoints() : height(0), time(0), maxInputBlock(NULL) { }
};
class CTxMemPool; class CTxMemPool;
/** \class CTxMemPoolEntry /** \class CTxMemPoolEntry
@ -70,6 +86,7 @@ private:
bool spendsCoinbase; //! keep track of transactions that spend a coinbase bool spendsCoinbase; //! keep track of transactions that spend a coinbase
unsigned int sigOpCount; //! Legacy sig ops plus P2SH sig op count unsigned int sigOpCount; //! Legacy sig ops plus P2SH sig op count
int64_t feeDelta; //! Used for determining the priority of the transaction for mining in a block int64_t feeDelta; //! Used for determining the priority of the transaction for mining in a block
LockPoints lockPoints; //! Track the height and time at which tx was final
// Information about descendants of this transaction that are in the // Information about descendants of this transaction that are in the
// mempool; if we remove this transaction we must remove all of these // mempool; if we remove this transaction we must remove all of these
@ -84,7 +101,7 @@ public:
CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight, int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue, bool spendsCoinbase, bool poolHasNoInputsOf, CAmount _inChainInputValue, bool spendsCoinbase,
unsigned int nSigOps); unsigned int nSigOps, LockPoints lp);
CTxMemPoolEntry(const CTxMemPoolEntry& other); CTxMemPoolEntry(const CTxMemPoolEntry& other);
const CTransaction& GetTx() const { return this->tx; } const CTransaction& GetTx() const { return this->tx; }
@ -101,12 +118,15 @@ public:
unsigned int GetSigOpCount() const { return sigOpCount; } unsigned int GetSigOpCount() const { return sigOpCount; }
int64_t GetModifiedFee() const { return nFee + feeDelta; } int64_t GetModifiedFee() const { return nFee + feeDelta; }
size_t DynamicMemoryUsage() const { return nUsageSize; } size_t DynamicMemoryUsage() const { return nUsageSize; }
const LockPoints& GetLockPoints() const { return lockPoints; }
// Adjusts the descendant state, if this entry is not dirty. // Adjusts the descendant state, if this entry is not dirty.
void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
// Updates the fee delta used for mining priority score, and the // Updates the fee delta used for mining priority score, and the
// modified fees with descendants. // modified fees with descendants.
void UpdateFeeDelta(int64_t feeDelta); void UpdateFeeDelta(int64_t feeDelta);
// Update the LockPoints after a reorg
void UpdateLockPoints(const LockPoints& lp);
/** We can set the entry to be dirty if doing the full calculation of in- /** We can set the entry to be dirty if doing the full calculation of in-
* mempool descendants will be too expensive, which can potentially happen * mempool descendants will be too expensive, which can potentially happen
@ -154,6 +174,16 @@ private:
int64_t feeDelta; int64_t feeDelta;
}; };
struct update_lock_points
{
update_lock_points(const LockPoints& _lp) : lp(_lp) { }
void operator() (CTxMemPoolEntry &e) { e.UpdateLockPoints(lp); }
private:
const LockPoints& lp;
};
// extracts a TxMemPoolEntry's transaction hash // extracts a TxMemPoolEntry's transaction hash
struct mempoolentry_txid struct mempoolentry_txid
{ {

Loading…
Cancel
Save