MWEB: Mempool

This commit is contained in:
David Burkett 2022-01-29 21:44:33 -05:00 committed by Loshan T
parent 147ad5aefd
commit 7b05e5ca06
5 changed files with 225 additions and 100 deletions

View File

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <policy/policy.h>
#include <primitives/block.h>
#include <txmempool.h>
#include <util/system.h>
#include <util/time.h>
@ -389,9 +390,9 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
CheckSort<ancestor_score>(pool, sortedOrder);
/* after tx6 is mined, tx7 should move up in the sort */
std::vector<CTransactionRef> vtx;
vtx.push_back(MakeTransactionRef(tx6));
pool.removeForBlock(vtx, 1);
CBlock block;
block.vtx.push_back(MakeTransactionRef(tx6));
pool.removeForBlock(block, 1, nullptr);
sortedOrder.erase(sortedOrder.begin()+1);
// Ties are broken by hash
@ -546,12 +547,12 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
std::vector<CTransactionRef> vtx;
CBlock block;
SetMockTime(42);
SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
// ... we should keep the same min fee until we get a block
pool.removeForBlock(vtx, 1);
pool.removeForBlock(block, 1, nullptr);
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/2.0));
// ... then feerate should drop 1/2 each halflife

View File

@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
CFeeRate baseRate(basefee, GetVirtualTransactionSize(CTransaction(tx)), 0);
// Create a fake block
std::vector<CTransactionRef> block;
CBlock block;
int blocknum = 0;
// Loop through 200 blocks
@ -70,12 +70,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
while (txHashes[9-h].size()) {
CTransactionRef ptx = mpool.get(txHashes[9-h].back());
if (ptx)
block.push_back(ptx);
block.vtx.push_back(ptx);
txHashes[9-h].pop_back();
}
}
mpool.removeForBlock(block, ++blocknum);
block.clear();
mpool.removeForBlock(block, ++blocknum, nullptr);
block.vtx.clear();
// Check after just a few txs that combining buckets works as expected
if (blocknum == 3) {
// At this point we should need to combine 3 buckets to get enough data points
@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// Mine 50 more blocks with no transactions happening, estimates shouldn't change
// We haven't decayed the moving average enough so we still have enough data points in every bucket
while (blocknum < 250)
mpool.removeForBlock(block, ++blocknum);
mpool.removeForBlock(block, ++blocknum, nullptr);
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) {
@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
txHashes[j].push_back(hash);
}
}
mpool.removeForBlock(block, ++blocknum);
mpool.removeForBlock(block, ++blocknum, nullptr);
}
for (int i = 1; i < 10;i++) {
@ -146,12 +146,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
while(txHashes[j].size()) {
CTransactionRef ptx = mpool.get(txHashes[j].back());
if (ptx)
block.push_back(ptx);
block.vtx.push_back(ptx);
txHashes[j].pop_back();
}
}
mpool.removeForBlock(block, 266);
block.clear();
mpool.removeForBlock(block, 266, nullptr);
block.vtx.clear();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) {
BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
@ -167,12 +167,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
mpool.addUnchecked(entry.Fee(feeV[j]).Time(GetTime()).Height(blocknum).FromTx(tx));
CTransactionRef ptx = mpool.get(hash);
if (ptx)
block.push_back(ptx);
block.vtx.push_back(ptx);
}
}
mpool.removeForBlock(block, ++blocknum);
block.clear();
mpool.removeForBlock(block, ++blocknum, nullptr);
block.vtx.clear();
}
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)

View File

@ -131,21 +131,24 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
if (it == mapTx.end()) {
continue;
}
auto iter = mapNextTx.lower_bound(COutPoint(hash, 0));
// First calculate the children, and update CTxMemPool::m_children to
// include them, and update their CTxMemPoolEntry::m_parents to include this tx.
// we cache the in-mempool children to avoid duplicate updates
{
const auto epoch = GetFreshEpoch();
for (; iter != mapNextTx.end() && iter->first->hash == hash; ++iter) {
const uint256 &childHash = iter->second->GetHash();
txiter childIter = mapTx.find(childHash);
assert(childIter != mapTx.end());
// We can skip updating entries we've encountered before or that
// are in the block (which are already accounted for).
if (!visited(childIter) && !setAlreadyIncluded.count(childHash)) {
UpdateChild(it, childIter, true);
UpdateParent(childIter, it, true);
for (const CTxOutput& output : it->GetTx().GetOutputs()) {
auto iter = mapNextTx.find(output.GetIndex());
if (iter != mapNextTx.end()) {
const uint256& childHash = iter->second->GetHash();
txiter childIter = mapTx.find(childHash);
assert(childIter != mapTx.end());
// We can skip updating entries we've encountered before or that
// are in the block (which are already accounted for).
if (!visited(childIter) && !setAlreadyIncluded.count(childHash)) {
UpdateChild(it, childIter, true);
UpdateParent(childIter, it, true);
}
}
}
} // release epoch guard for UpdateForDescendants
@ -322,7 +325,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifyMWEBWeight)
{
nSizeWithDescendants += modifySize;
assert(int64_t(nSizeWithDescendants) > 0);
assert(int64_t(nSizeWithDescendants) >= 0);
nModFeesWithDescendants += modifyFee;
nCountWithDescendants += modifyCount;
assert(int64_t(nCountWithDescendants) > 0);
@ -354,7 +357,7 @@ CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator)
nCheckFrequency = 0;
}
bool CTxMemPool::isSpent(const COutPoint& outpoint) const
bool CTxMemPool::isSpent(const OutputIndex& outpoint) const
{
LOCK(cs);
return mapNextTx.count(outpoint);
@ -393,10 +396,24 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
const CTransaction& tx = newit->GetTx();
std::set<uint256> setParentTransactions;
for (unsigned int i = 0; i < tx.vin.size(); i++) {
mapNextTx.insert(std::make_pair(&tx.vin[i].prevout, &tx));
setParentTransactions.insert(tx.vin[i].prevout.hash);
for (const CTxInput& input : tx.GetInputs()) {
mapNextTx.insert(std::make_pair(input.GetIndex(), &tx));
if (input.IsMWEB()) {
auto parentIter = mapTxOutputs_MWEB.find(input.ToMWEB());
if (parentIter != mapTxOutputs_MWEB.end()) {
setParentTransactions.insert(parentIter->second->GetHash());
}
} else {
setParentTransactions.insert(input.GetTxIn().prevout.hash);
}
}
// MWEB: Add transaction to mapTxOutputs_MWEB for each output
for (const mw::Hash& output_id : tx.mweb_tx.GetOutputIDs()) {
mapTxOutputs_MWEB.insert(std::make_pair(output_id, &tx));
}
// Don't bother worrying about child transactions of this one.
// Normal case of a new transaction arriving is that there can't be any
// children, because such children would be orphans.
@ -433,9 +450,24 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence);
}
const uint256 hash = it->GetTx().GetHash();
for (const CTxIn& txin : it->GetTx().vin)
mapNextTx.erase(txin.prevout);
CTransactionRef ptx = it->GetSharedTx();
const uint256 hash = ptx->GetHash();
for (const CTxInput& txin : ptx->GetInputs())
mapNextTx.erase(txin.GetIndex());
// MWEB: Remove transaction from mapTxOutputs_MWEB for each output
for (const mw::Hash& output_id : ptx->mweb_tx.GetOutputIDs()) {
mapTxOutputs_MWEB.erase(output_id);
}
// MWEB: When removing MWEB transactions from the mempool after a block is connected,
// cache the original tx in recentTxsByKernel, in case we need to replay it during a reorg.
if (reason == MemPoolRemovalReason::BLOCK || reason == MemPoolRemovalReason::REORG) {
for (const mw::Hash& kernel_id : ptx->mweb_tx.GetKernelIDs()) {
recentTxsByKernel.Put(kernel_id, ptx);
}
}
RemoveUnbroadcastTx(hash, true /* add logging because unchecked */ );
@ -490,30 +522,31 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso
{
// Remove transaction from memory pool
AssertLockHeld(cs);
setEntries txToRemove;
txiter origit = mapTx.find(origTx.GetHash());
if (origit != mapTx.end()) {
txToRemove.insert(origit);
} else {
// When recursively removing but origTx isn't in the mempool
// be sure to remove any children that are in the pool. This can
// happen during chain re-orgs if origTx isn't re-accepted into
// the mempool for any reason.
for (unsigned int i = 0; i < origTx.vout.size(); i++) {
auto it = mapNextTx.find(COutPoint(origTx.GetHash(), i));
if (it == mapNextTx.end())
continue;
txiter nextit = mapTx.find(it->second->GetHash());
assert(nextit != mapTx.end());
txToRemove.insert(nextit);
}
}
setEntries setAllRemoves;
for (txiter it : txToRemove) {
CalculateDescendants(it, setAllRemoves);
setEntries txToRemove;
txiter origit = mapTx.find(origTx.GetHash());
if (origit != mapTx.end()) {
txToRemove.insert(origit);
} else {
// When recursively removing but origTx isn't in the mempool
// be sure to remove any children that are in the pool. This can
// happen during chain re-orgs if origTx isn't re-accepted into
// the mempool for any reason.
for (const CTxOutput& output : origTx.GetOutputs()) {
auto it = mapNextTx.find(output.GetIndex());
if (it == mapNextTx.end())
continue;
txiter nextit = mapTx.find(it->second->GetHash());
assert(nextit != mapTx.end());
txToRemove.insert(nextit);
}
}
setEntries setAllRemoves;
for (txiter it : txToRemove) {
CalculateDescendants(it, setAllRemoves);
}
RemoveStaged(setAllRemoves, false, reason);
RemoveStaged(setAllRemoves, false, reason);
}
void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags)
@ -563,8 +596,8 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
{
// Remove transactions which depend on inputs of tx, recursively
AssertLockHeld(cs);
for (const CTxIn &txin : tx.vin) {
auto it = mapNextTx.find(txin.prevout);
for (const CTxInput& input : tx.GetInputs()) {
auto it = mapNextTx.find(input.GetIndex());
if (it != mapNextTx.end()) {
const CTransaction &txConflict = *it->second;
if (txConflict != tx)
@ -579,21 +612,42 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
/**
* Called when a block is connected. Removes from mempool and updates the miner fee estimator.
*/
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight)
void CTxMemPool::removeForBlock(const CBlock& block, unsigned int nBlockHeight, DisconnectedBlockTransactions* disconnectpool)
{
AssertLockHeld(cs);
std::vector<const CTxMemPoolEntry*> entries;
for (const auto& tx : vtx)
for (const auto& tx : block.vtx)
{
uint256 hash = tx->GetHash();
indexed_transaction_set::iterator i = mapTx.find(hash);
indexed_transaction_set::iterator i = mapTx.find(tx->GetHash());
if (i != mapTx.end())
entries.push_back(&*i);
}
// MWEB: Check for transactions with kernels included in the block.
// If we add a map of txs by kernel hash in the future, this can be made more efficient.
std::vector<CTransactionRef> txs = block.vtx;
if (!block.mweb_block.IsNull()) {
auto block_kernels = block.mweb_block.GetKernelIDs();
for (txiter it = mapTx.begin(); it != mapTx.end(); ++it) {
CTransactionRef ptx = it->GetSharedTx();
if (!ptx->HasMWEBTx()) continue;
const auto& tx_kernels = ptx->mweb_tx.GetKernelIDs();
bool remove_tx = std::any_of(tx_kernels.begin(), tx_kernels.end(),
[&block_kernels](const mw::Hash& kernel_id) {
return block_kernels.count(kernel_id) != 0;
}
);
if (remove_tx) {
entries.push_back(&*it);
txs.push_back(ptx);
}
}
}
// Before the txs in the new block have been removed from the mempool, update policy estimates
if (minerPolicyEstimator) {minerPolicyEstimator->processBlock(nBlockHeight, entries);}
for (const auto& tx : vtx)
for (const auto& tx : txs)
{
txiter it = mapTx.find(tx->GetHash());
if (it != mapTx.end()) {
@ -604,14 +658,20 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
removeConflicts(*tx);
ClearPrioritisation(tx->GetHash());
}
lastRollingFeeUpdate = GetTime();
blockSinceLastRollingFeeBump = true;
if (disconnectpool) {
disconnectpool->removeForBlock(txs);
}
}
void CTxMemPool::_clear()
{
mapTx.clear();
mapNextTx.clear();
mapTxOutputs_MWEB.clear();
totalTxSize = 0;
cachedInnerUsage = 0;
lastRollingFeeUpdate = GetTime();
@ -675,7 +735,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
// Check whether its inputs are marked in mapNextTx.
auto it3 = mapNextTx.find(txin.prevout);
assert(it3 != mapNextTx.end());
assert(it3->first == &txin.prevout);
assert(it3->first == OutputIndex(txin.prevout));
assert(it3->second == &tx);
i++;
}
@ -707,13 +767,15 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
// Check children against mapNextTx
CTxMemPoolEntry::Children setChildrenCheck;
auto iter = mapNextTx.lower_bound(COutPoint(it->GetTx().GetHash(), 0));
uint64_t child_sizes = 0;
for (; iter != mapNextTx.end() && iter->first->hash == it->GetTx().GetHash(); ++iter) {
txiter childit = mapTx.find(iter->second->GetHash());
assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions
if (setChildrenCheck.insert(*childit).second) {
child_sizes += childit->GetTxSize();
for (const CTxOutput& output : it->GetTx().GetOutputs()) {
auto iter = mapNextTx.find(output.GetIndex());
if (iter != mapNextTx.end()) {
txiter childit = mapTx.find(iter->second->GetHash());
assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions
if (setChildrenCheck.insert(*childit).second) {
child_sizes += childit->GetTxSize();
}
}
}
assert(setChildrenCheck.size() == it->GetMemPoolChildrenConst().size());
@ -895,7 +957,7 @@ void CTxMemPool::ClearPrioritisation(const uint256& hash)
mapDeltas.erase(hash);
}
const CTransaction* CTxMemPool::GetConflictTx(const COutPoint& prevout) const
const CTransaction* CTxMemPool::GetConflictTx(const OutputIndex& prevout) const
{
const auto it = mapNextTx.find(prevout);
return it == mapNextTx.end() ? nullptr : it->second;
@ -920,9 +982,16 @@ CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set<uint256>& hashes) c
bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
{
for (unsigned int i = 0; i < tx.vin.size(); i++)
if (exists(tx.vin[i].prevout.hash))
for (const CTxInput& input : tx.GetInputs()) {
if (input.IsMWEB()) {
if (mapTxOutputs_MWEB.find(input.ToMWEB()) != mapTxOutputs_MWEB.end()) {
return false;
}
} else if (exists(input.GetTxIn().prevout.hash)) {
return false;
}
}
return true;
}
@ -947,7 +1016,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const {
size_t CTxMemPool::DynamicMemoryUsage() const {
LOCK(cs);
// Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented.
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapTxOutputs_MWEB) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
}
void CTxMemPool::RemoveUnbroadcastTx(const uint256& txid, const bool unchecked) {

View File

@ -27,13 +27,19 @@
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <caches/Cache.h>
class CBlock;
class CBlockIndex;
struct DisconnectedBlockTransactions;
extern RecursiveMutex cs_main;
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
/** Default size of CMemPool's recentTxsByKernel cache */
static const unsigned int DEFAULT_MEMPOOL_MWEB_CACHE_SIZE = 1000;
struct LockPoints
{
// Will be set to the blockchain height and median time past
@ -608,7 +614,21 @@ private:
std::set<uint256> m_unbroadcast_txids GUARDED_BY(cs);
public:
indirectmap<COutPoint, const CTransaction*> mapNextTx GUARDED_BY(cs);
/**
* Maps outputs to mempool transactions that spend them.
*/
std::map<OutputIndex, const CTransaction*> mapNextTx GUARDED_BY(cs);
/**
* Maps MWEB output IDs to mempool transactions that create them.
*/
std::map<mw::Hash, const CTransaction*> mapTxOutputs_MWEB GUARDED_BY(cs);
/**
* FIFO cache of txs recently removed from the mempool keyed by kernel ID.
*/
FIFOCache<mw::Hash, CTransactionRef> recentTxsByKernel GUARDED_BY(cs){DEFAULT_MEMPOOL_MWEB_CACHE_SIZE};
std::map<uint256, CAmount> mapDeltas;
/** Create a new CTxMemPool.
@ -637,13 +657,13 @@ public:
void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs);
void removeForBlock(const CBlock& block, unsigned int nBlockHeight, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs);
void clear();
void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid=false);
void queryHashes(std::vector<uint256>& vtxid) const;
bool isSpent(const COutPoint& outpoint) const;
bool isSpent(const OutputIndex& outpoint) const;
unsigned int GetTransactionsUpdated() const;
void AddTransactionsUpdated(unsigned int n);
/**
@ -658,7 +678,7 @@ public:
void ClearPrioritisation(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs);
/** Get the transaction in the pool that spends the same prevout */
const CTransaction* GetConflictTx(const COutPoint& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs);
const CTransaction* GetConflictTx(const OutputIndex& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs);
/** Returns an iterator to the given hash, if found */
Optional<txiter> GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs);
@ -754,6 +774,17 @@ public:
}
bool exists(const uint256& txid) const { return exists(GenTxid{false, txid}); }
bool GetCreatedTx(const mw::Hash& output_id, uint256& hash) const
{
LOCK(cs);
auto iter = mapTxOutputs_MWEB.find(output_id);
if (iter != mapTxOutputs_MWEB.end()) {
hash = iter->second->GetHash();
return true;
}
return false;
}
CTransactionRef get(const uint256& hash) const;
txiter get_iter_from_wtxid(const uint256& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(cs)
{
@ -968,8 +999,10 @@ struct DisconnectedBlockTransactions {
void addTransaction(const CTransactionRef& tx)
{
queuedTx.insert(tx);
cachedInnerUsage += RecursiveDynamicUsage(tx);
if (queuedTx.find(tx->GetHash()) == queuedTx.end()) {
queuedTx.insert(tx);
cachedInnerUsage += RecursiveDynamicUsage(tx);
}
}
// Remove entries based on txid_index, and update memory usage.

View File

@ -385,7 +385,7 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact
while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) {
// ignore validation errors in resurrected transactions
TxValidationState stateDummy;
if (!fAddToMempool || (*it)->IsCoinBase() ||
if (!fAddToMempool || (*it)->IsCoinBase() || (*it)->IsHogEx() ||
!AcceptToMemoryPool(mempool, stateDummy, *it,
nullptr /* plTxnReplaced */, true /* bypass_limits */)) {
// If the transaction doesn't make it in to the mempool, remove any
@ -473,7 +473,7 @@ public:
* additions if the associated transaction ends up being rejected by
* the mempool.
*/
std::vector<COutPoint>& m_coins_to_uncache;
std::vector<OutputIndex>& m_coins_to_uncache;
const bool m_test_accept;
CAmount* m_fee_out;
};
@ -560,7 +560,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
TxValidationState &state = args.m_state;
const int64_t nAcceptTime = args.m_accept_time;
const bool bypass_limits = args.m_bypass_limits;
std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
std::vector<OutputIndex>& coins_to_uncache = args.m_coins_to_uncache;
// Alias what we need out of ws
std::set<uint256>& setConflicts = ws.m_conflicts;
@ -576,10 +576,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return false; // state filled in by CheckTransaction
}
// MWEB: Check MWEB tx
if (!MWEB::Node::CheckTransaction(tx, state)) {
return false; // state filled in by CheckTransaction
}
// Coinbase is only valid in a block, not as a loose transaction
if (tx.IsCoinBase())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase");
// HogEx is only valid in a block, not as a loose transaction
if (tx.IsHogEx())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "hogex");
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
std::string reason;
if (fRequireStandard && !IsStandardTx(tx, reason))
@ -606,9 +615,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
// Check for conflicts with in-memory transactions
for (const CTxIn &txin : tx.vin)
for (const CTxInput& txin : tx.GetInputs())
{
const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout);
const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.GetIndex());
if (ptxConflicting) {
if (!setConflicts.count(ptxConflicting->GetHash()))
{
@ -651,19 +660,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
// do all inputs exist?
for (const CTxIn& txin : tx.vin) {
if (!coins_cache.HaveCoinInCache(txin.prevout)) {
coins_to_uncache.push_back(txin.prevout);
for (const CTxInput& txin : tx.GetInputs()) {
if (!coins_cache.HaveCoinInCache(txin.GetIndex())) {
coins_to_uncache.push_back(txin.GetIndex());
}
// Note: this call may add txin.prevout to the coins cache
// (coins_cache.cacheCoins) by way of FetchCoin(). It should be removed
// later (via coins_to_uncache) if this tx turns out to be invalid.
if (!m_view.HaveCoin(txin.prevout)) {
if (!m_view.HaveCoin(txin.GetIndex())) {
// Are inputs missing because we already have the tx?
for (size_t out = 0; out < tx.vout.size(); out++) {
for (const CTxOutput& txout : tx.GetOutputs()) {
// Optimistically just do efficient check of cache for outputs
if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) {
if (coins_cache.HaveCoinInCache(txout.GetIndex())) {
return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-known");
}
}
@ -854,9 +863,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
oldFeeRate.ToString()));
}
for (const CTxIn &txin : mi->GetTx().vin)
{
setConflictsParents.insert(txin.prevout.hash);
for (const CTxInput& txin : mi->GetTx().GetInputs()) {
if (txin.IsMWEB()) {
auto parent_iter = m_pool.mapTxOutputs_MWEB.find(txin.ToMWEB());
if (parent_iter != m_pool.mapTxOutputs_MWEB.end()) {
setConflictsParents.insert(parent_iter->second->GetHash());
}
} else {
setConflictsParents.insert(txin.GetTxIn().prevout.hash);
}
}
nConflictingCount += mi->GetCountWithDescendants();
@ -1073,7 +1088,7 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool bypass_limits, bool test_accept, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
std::vector<COutPoint> coins_to_uncache;
std::vector<OutputIndex> coins_to_uncache;
MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, coins_to_uncache, test_accept, fee_out };
bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
if (!res) {
@ -1082,7 +1097,7 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
// invalid transactions that attempt to overrun the in-memory coins cache
// (`CCoinsViewCache::cacheCoins`).
for (const COutPoint& hashTx : coins_to_uncache)
for (const OutputIndex& hashTx : coins_to_uncache)
::ChainstateActive().CoinsTip().Uncache(hashTx);
}
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
@ -2574,6 +2589,14 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams&
return false;
if (disconnectpool) {
// MWEB: For each kernel, lookup kernel's txs in FIFO cache and add them back to the mempool.
for (const mw::Hash& kernel_id : block.mweb_block.GetKernelIDs()) {
if (m_mempool.recentTxsByKernel.Cached(kernel_id)) {
CTransactionRef ptx = m_mempool.recentTxsByKernel.Get(kernel_id);
disconnectpool->addTransaction(ptx);
}
}
// Save transactions to re-add to mempool at end of reorg
for (auto it = block.vtx.rbegin(); it != block.vtx.rend(); ++it) {
disconnectpool->addTransaction(*it);
@ -2692,8 +2715,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal);
// Remove conflicting transactions from the mempool.;
m_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
disconnectpool.removeForBlock(blockConnecting.vtx);
m_mempool.removeForBlock(blockConnecting, pindexNew->nHeight, &disconnectpool);
// Update m_chain & related variables.
m_chain.SetTip(pindexNew);
UpdateTip(m_mempool, pindexNew, chainparams);