Skip to content

Commit

Permalink
Conflict handling for ProRegTx in mempool
Browse files Browse the repository at this point in the history
>>> adapted from dash@cdd723ede6bf6b13ff19142e9f0e210c0bb4a139

with more recent changes merged in
  • Loading branch information
codablock authored and random-zebra committed Apr 20, 2021
1 parent ae33972 commit 48c66dc
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 1 deletion.
162 changes: 161 additions & 1 deletion src/txmempool.cpp
Expand Up @@ -7,6 +7,9 @@
#include "txmempool.h"

#include "clientversion.h"
#include "evo/deterministicmns.h"
#include "evo/specialtx.h"
#include "evo/providertx.h"
#include "policy/fees.h"
#include "reverse_iterate.h"
#include "streams.h"
Expand All @@ -19,7 +22,6 @@
#include "validationinterface.h"



CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority,
unsigned int _entryHeight, bool poolHasNoInputsOf, CAmount _inChainInputValue,
Expand Down Expand Up @@ -429,6 +431,23 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
totalTxSize += entry.GetTxSize();
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);

// Invalid ProTxes should never get this far because transactions should be
// fully checked by AcceptToMemoryPool() at this point, so we just assume that
// everything is fine here.
if (tx.nType == CTransaction::TxType::PROREG) {
const uint256& txid = tx.GetHash();
ProRegPL pl;
bool ok = GetTxPayload(tx, pl);
assert(ok);
if (!pl.collateralOutpoint.hash.IsNull()) {
mapProTxRefs.emplace(txid, pl.collateralOutpoint.hash);
mapProTxCollaterals.emplace(pl.collateralOutpoint, txid);
}
mapProTxAddresses.emplace(pl.addr, txid);
mapProTxPubKeyIDs.emplace(pl.keyIDOwner, txid);
mapProTxPubKeyIDs.emplace(pl.keyIDOperator, txid);
}

return true;
}

Expand All @@ -452,6 +471,32 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
mapSaplingNullifiers.erase(sd.nullifier);
}
}

auto eraseProTxRef = [&](const uint256& proTxHash, const uint256& txHash) {
auto its = mapProTxRefs.equal_range(proTxHash);
for (auto it = its.first; it != its.second;) {
if (it->second == txHash) {
it = mapProTxRefs.erase(it);
} else {
++it;
}
}
};

if (tx.nType == CTransaction::TxType::PROREG) {
const uint256& txid = tx.GetHash();
ProRegPL pl;
bool ok = GetTxPayload(tx, pl);
assert(ok);
if (!pl.collateralOutpoint.IsNull()) {
eraseProTxRef(txid, pl.collateralOutpoint.hash);
}
mapProTxCollaterals.erase(pl.collateralOutpoint);
mapProTxAddresses.erase(pl.addr);
mapProTxPubKeyIDs.erase(pl.keyIDOwner);
mapProTxPubKeyIDs.erase(pl.keyIDOperator);
}

totalTxSize -= it->GetTxSize();
cachedInnerUsage -= it->DynamicMemoryUsage();
cachedInnerUsage -= memusage::DynamicUsage(mapLinks[it].parents) + memusage::DynamicUsage(mapLinks[it].children);
Expand Down Expand Up @@ -607,6 +652,89 @@ void CTxMemPool::removeConflicts(const CTransaction& tx)
}
}

void CTxMemPool::removeProTxPubKeyConflicts(const CTransaction& tx, const CKeyID& keyId)
{
if (mapProTxPubKeyIDs.count(keyId)) {
const uint256& conflictHash = mapProTxPubKeyIDs.at(keyId);
if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) {
removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT);
}
}
}

void CTxMemPool::removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint)
{
if (mapProTxCollaterals.count(collateralOutpoint)) {
const uint256& conflictHash = mapProTxCollaterals.at(collateralOutpoint);
if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) {
removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT);
}
}
}

void CTxMemPool::removeProTxSpentCollateralConflicts(const CTransaction &tx)
{
// Remove TXs that refer to a MN for which the collateral was spent
auto removeSpentCollateralConflict = [&](const uint256& proTxHash) {
// Can't use equal_range here as every call to removeRecursive might invalidate iterators
while (true) {
auto it = mapProTxRefs.find(proTxHash);
if (it == mapProTxRefs.end()) {
break;
}
auto conflictIt = mapTx.find(it->second);
if (conflictIt != mapTx.end()) {
removeRecursive(conflictIt->GetTx(), MemPoolRemovalReason::CONFLICT);
} else {
// Should not happen as we track referencing TXs in addUnchecked/removeUnchecked.
// But lets be on the safe side and not run into an endless loop...
LogPrint(BCLog::MEMPOOL, "%s: ERROR: found invalid TX ref in mapProTxRefs, proTxHash=%s, txHash=%s\n", __func__, proTxHash.ToString(), it->second.ToString());
mapProTxRefs.erase(it);
}
}
};
auto mnList = deterministicMNManager->GetListAtChainTip();
for (const auto& in : tx.vin) {
auto collateralIt = mapProTxCollaterals.find(in.prevout);
if (collateralIt != mapProTxCollaterals.end()) {
// These are not yet mined ProRegTxs
removeSpentCollateralConflict(collateralIt->second);
}
auto dmn = mnList.GetMNByCollateral(in.prevout);
if (dmn) {
// These are updates refering to a mined ProRegTx
removeSpentCollateralConflict(dmn->proTxHash);
}
}
}

void CTxMemPool::removeProTxConflicts(const CTransaction &tx)
{
removeProTxSpentCollateralConflicts(tx);

if (tx.nType == CTransaction::TxType::PROREG) {
ProRegPL pl;
if (!GetTxPayload(tx, pl)) {
LogPrint(BCLog::MEMPOOL, "%s: ERROR: Invalid transaction payload, tx: %s", __func__, tx.ToString());
return;
}

const uint256& txid = tx.GetHash();

if (mapProTxAddresses.count(pl.addr)) {
const uint256& conflictHash = mapProTxAddresses.at(pl.addr);
if (conflictHash != txid && mapTx.count(conflictHash)) {
removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT);
}
}
removeProTxPubKeyConflicts(tx, pl.keyIDOwner);
removeProTxPubKeyConflicts(tx, pl.keyIDOperator);
if (!pl.collateralOutpoint.hash.IsNull()) {
removeProTxCollateralConflicts(tx, pl.collateralOutpoint);
}
}
}

/**
* Called when a block is connected. Removes from mempool and updates the miner fee estimator.
*/
Expand All @@ -629,6 +757,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK);
}
removeConflicts(*tx);
removeProTxConflicts(*tx);
ClearPrioritisation(tx->GetHash());
}
// After the txs in the new block have been removed from the mempool, update policy estimates
Expand All @@ -643,6 +772,8 @@ void CTxMemPool::_clear()
mapLinks.clear();
mapTx.clear();
mapNextTx.clear();
mapProTxAddresses.clear();
mapProTxPubKeyIDs.clear();
totalTxSize = 0;
cachedInnerUsage = 0;
lastRollingFeeUpdate = GetTime();
Expand Down Expand Up @@ -918,6 +1049,35 @@ TxMempoolInfo CTxMemPool::info(const uint256& hash) const
return GetInfo(i);
}

bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
LOCK(cs);

if (tx.nType != CTransaction::TxType::PROREG)
return false;

ProRegPL pl;
if (!GetTxPayload(tx, pl)) {
LogPrint(BCLog::MEMPOOL, "%s: ERROR: Invalid transaction payload, tx: %s", __func__, tx.ToString());
return true; // i.e. can't decode payload == conflict
}

if (mapProTxAddresses.count(pl.addr) || mapProTxPubKeyIDs.count(pl.keyIDOwner) || mapProTxPubKeyIDs.count(pl.keyIDOperator)) {
return true;
}

if (!pl.collateralOutpoint.hash.IsNull()) {
if (mapProTxCollaterals.count(pl.collateralOutpoint)) {
// there is another ProRegTx that refers to the same collateral
return true;
}
if (mapNextTx.count(pl.collateralOutpoint)) {
// there is another tx that spends the collateral
return true;
}
}
return false;
}

CFeeRate CTxMemPool::estimateFee(int nBlocks) const
{
LOCK(cs);
Expand Down
12 changes: 12 additions & 0 deletions src/txmempool.h
Expand Up @@ -18,6 +18,7 @@
#include "primitives/transaction.h"
#include "sync.h"
#include "random.h"
#include "netaddress.h"

#include "boost/multi_index_container.hpp"
#include "boost/multi_index/ordered_index.hpp"
Expand Down Expand Up @@ -511,6 +512,11 @@ class CTxMemPool
typedef std::map<txiter, TxLinks, CompareIteratorByHash> txlinksMap;
txlinksMap mapLinks;

std::multimap<uint256, uint256> mapProTxRefs; // proTxHash -> transaction (all TXs that refer to an existing proTx)
std::map<CService, uint256> mapProTxAddresses;
std::map<CKeyID, uint256> mapProTxPubKeyIDs;
std::map<COutPoint, uint256> mapProTxCollaterals;

void UpdateParent(txiter entry, txiter parent, bool add);
void UpdateChild(txiter entry, txiter child, bool add);

Expand Down Expand Up @@ -551,6 +557,10 @@ class CTxMemPool
void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags);
void removeWithAnchor(const uint256& invalidRoot);
void removeConflicts(const CTransaction& tx);
void removeProTxPubKeyConflicts(const CTransaction &tx, const CKeyID &keyId);
void removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint);
void removeProTxSpentCollateralConflicts(const CTransaction &tx);
void removeProTxConflicts(const CTransaction &tx);
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight,
bool fCurrentEstimate = true);

Expand Down Expand Up @@ -658,6 +668,8 @@ class CTxMemPool
TxMempoolInfo info(const uint256& hash) const;
std::vector<TxMempoolInfo> infoAll() const;

bool existsProviderTxConflict(const CTransaction &tx) const;

/** Estimate fee rate needed to get into the next nBlocks
* If no answer can be given at nBlocks, return an estimate
* at the lowest number of blocks where one can be given
Expand Down
4 changes: 4 additions & 0 deletions src/validation.cpp
Expand Up @@ -433,6 +433,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C
if (tx.IsCoinStake())
return state.DoS(100, false, REJECT_INVALID, "coinstake");

if (pool.existsProviderTxConflict(tx)) {
return state.DoS(0, false, REJECT_DUPLICATE, "protx-dup");
}

// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
Expand Down

0 comments on commit 48c66dc

Please sign in to comment.