MWEB: Mempool
This commit is contained in:
parent
147ad5aefd
commit
7b05e5ca06
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user