evo: Fix two mempool issues (#4154)

* evo: Remove all protx-es that refer to a ProRegTx removed from mempool

* tests: Check that removal of ProRegTx causes removal of other protx-es that refer to it

* evo: Consider tx itself a collateral in mempool maps when payload collateral hash is null

* tests: Should not allow a ProRegTx which uses another ProRegTx as an external collateral to enter mempool

Signed-off-by: pasta <pasta@dashboost.org>
This commit is contained in:
UdjinM6 2021-05-14 20:55:03 +03:00 committed by pasta
parent 027c4653ba
commit 9f9a08ae80
2 changed files with 155 additions and 0 deletions

View File

@ -14,6 +14,7 @@
#include <policy/policy.h>
#include <keystore.h>
#include <spork.h>
#include <txmempool.h>
#include <evo/specialtx.h>
#include <evo/providertx.h>
@ -438,6 +439,138 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChainDIP3Setup)
const_cast<Consensus::Params&>(Params().GetConsensus()).DIP0003EnforcementHeight = DIP0003EnforcementHeightBackup;
}
BOOST_FIXTURE_TEST_CASE(dip3_test_mempool_reorg, TestChainDIP3Setup)
{
int nHeight = chainActive.Height();
auto utxos = BuildSimpleUtxoMap(coinbaseTxns);
CKey ownerKey;
CKey payoutKey;
CKey collateralKey;
CBLSSecretKey operatorKey;
ownerKey.MakeNewKey(true);
payoutKey.MakeNewKey(true);
collateralKey.MakeNewKey(true);
operatorKey.MakeNewKey();
auto scriptPayout = GetScriptForDestination(payoutKey.GetPubKey().GetID());
auto scriptCollateral = GetScriptForDestination(collateralKey.GetPubKey().GetID());
// Create a MN with an external collateral
CMutableTransaction tx_collateral;
FundTransaction(tx_collateral, utxos, scriptCollateral, 1000 * COIN, coinbaseKey);
SignTransaction(tx_collateral, coinbaseKey);
auto block = std::make_shared<CBlock>(CreateBlock({tx_collateral}, coinbaseKey));
BOOST_ASSERT(ProcessNewBlock(Params(), block, true, nullptr));
deterministicMNManager->UpdatedBlockTip(chainActive.Tip());
BOOST_ASSERT(chainActive.Height() == nHeight + 1);
BOOST_ASSERT(block->GetHash() == chainActive.Tip()->GetBlockHash());
CProRegTx payload;
payload.addr = LookupNumeric("1.1.1.1", 1);
payload.keyIDOwner = ownerKey.GetPubKey().GetID();
payload.pubKeyOperator = operatorKey.GetPublicKey();
payload.keyIDVoting = ownerKey.GetPubKey().GetID();
payload.scriptPayout = scriptPayout;
for (size_t i = 0; i < tx_collateral.vout.size(); ++i) {
if (tx_collateral.vout[i].nValue == 1000 * COIN) {
payload.collateralOutpoint = COutPoint(tx_collateral.GetHash(), i);
break;
}
}
CMutableTransaction tx_reg;
tx_reg.nVersion = 3;
tx_reg.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx_reg, utxos, scriptPayout, 1000 * COIN, coinbaseKey);
payload.inputsHash = CalcTxInputsHash(tx_reg);
CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
SetTxPayload(tx_reg, payload);
SignTransaction(tx_reg, coinbaseKey);
CTxMemPool testPool;
TestMemPoolEntryHelper entry;
LOCK(testPool.cs);
// Create ProUpServ and test block reorg which double-spend ProRegTx
auto tx_up_serv = CreateProUpServTx(utxos, tx_reg.GetHash(), operatorKey, 2, CScript(), coinbaseKey);
testPool.addUnchecked(tx_up_serv.GetHash(), entry.FromTx(tx_up_serv));
// A disconnected block would insert ProRegTx back into mempool
testPool.addUnchecked(tx_reg.GetHash(), entry.FromTx(tx_reg));
BOOST_CHECK_EQUAL(testPool.size(), 2U);
// Create a tx that will double-spend ProRegTx
CMutableTransaction tx_reg_ds;
tx_reg_ds.vin = tx_reg.vin;
tx_reg_ds.vout.emplace_back(0, CScript() << OP_RETURN);
SignTransaction(tx_reg_ds, coinbaseKey);
// Check mempool as if a new block with tx_reg_ds was connected instead of the old one with tx_reg
std::vector<CTransactionRef> block_reorg;
block_reorg.emplace_back(std::make_shared<CTransaction>(tx_reg_ds));
testPool.removeForBlock(block_reorg, nHeight + 2);
BOOST_CHECK_EQUAL(testPool.size(), 0U);
}
BOOST_FIXTURE_TEST_CASE(dip3_test_mempool_dual_proregtx, TestChainDIP3Setup)
{
int nHeight = chainActive.Height();
auto utxos = BuildSimpleUtxoMap(coinbaseTxns);
// Create a MN
CKey ownerKey1;
CBLSSecretKey operatorKey1;
auto tx_reg1 = CreateProRegTx(utxos, 1, GenerateRandomAddress(), coinbaseKey, ownerKey1, operatorKey1);
// Create a MN with an external collateral that references tx_reg1
CKey ownerKey;
CKey payoutKey;
CKey collateralKey;
CBLSSecretKey operatorKey;
ownerKey.MakeNewKey(true);
payoutKey.MakeNewKey(true);
collateralKey.MakeNewKey(true);
operatorKey.MakeNewKey();
auto scriptPayout = GetScriptForDestination(payoutKey.GetPubKey().GetID());
auto scriptCollateral = GetScriptForDestination(collateralKey.GetPubKey().GetID());
CProRegTx payload;
payload.addr = LookupNumeric("1.1.1.1", 2);
payload.keyIDOwner = ownerKey.GetPubKey().GetID();
payload.pubKeyOperator = operatorKey.GetPublicKey();
payload.keyIDVoting = ownerKey.GetPubKey().GetID();
payload.scriptPayout = scriptPayout;
for (size_t i = 0; i < tx_reg1.vout.size(); ++i) {
if (tx_reg1.vout[i].nValue == 1000 * COIN) {
payload.collateralOutpoint = COutPoint(tx_reg1.GetHash(), i);
break;
}
}
CMutableTransaction tx_reg2;
tx_reg2.nVersion = 3;
tx_reg2.nType = TRANSACTION_PROVIDER_REGISTER;
FundTransaction(tx_reg2, utxos, scriptPayout, 1000 * COIN, coinbaseKey);
payload.inputsHash = CalcTxInputsHash(tx_reg2);
CMessageSigner::SignMessage(payload.MakeSignString(), payload.vchSig, collateralKey);
SetTxPayload(tx_reg2, payload);
SignTransaction(tx_reg2, coinbaseKey);
CTxMemPool testPool;
TestMemPoolEntryHelper entry;
LOCK(testPool.cs);
testPool.addUnchecked(tx_reg1.GetHash(), entry.FromTx(tx_reg1));
BOOST_CHECK_EQUAL(testPool.size(), 1U);
BOOST_CHECK(testPool.existsProviderTxConflict(tx_reg2));
}
BOOST_FIXTURE_TEST_CASE(dip3_verify_db, TestChainDIP3Setup)
{
int nHeight = chainActive.Height();

View File

@ -426,6 +426,8 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
mapProTxBlsPubKeyHashes.emplace(proTx.pubKeyOperator.GetHash(), tx.GetHash());
if (!proTx.collateralOutpoint.hash.IsNull()) {
mapProTxCollaterals.emplace(proTx.collateralOutpoint, tx.GetHash());
} else {
mapProTxCollaterals.emplace(COutPoint(tx.GetHash(), proTx.collateralOutpoint.n), tx.GetHash());
}
} else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx;
@ -656,6 +658,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
mapProTxPubKeyIDs.erase(proTx.keyIDOwner);
mapProTxBlsPubKeyHashes.erase(proTx.pubKeyOperator.GetHash());
mapProTxCollaterals.erase(proTx.collateralOutpoint);
mapProTxCollaterals.erase(COutPoint(it->GetTx().GetHash(), proTx.collateralOutpoint.n));
} else if (it->GetTx().nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx;
if (!GetTxPayload(it->GetTx(), proTx)) {
@ -797,6 +800,23 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
const CTransaction &txConflict = *it->second;
if (txConflict != tx)
{
if (txConflict.nType == TRANSACTION_PROVIDER_REGISTER) {
// Remove all other protxes which refer to this protx
// NOTE: Can't use equal_range here as every call to removeRecursive might invalidate iterators
while (true) {
auto itPro = mapProTxRefs.find(txConflict.GetHash());
if (itPro == mapProTxRefs.end()) {
break;
}
auto txit = mapTx.find(itPro->second);
if (txit != mapTx.end()) {
ClearPrioritisation(txit->GetTx().GetHash());
removeRecursive(txit->GetTx(), MemPoolRemovalReason::CONFLICT);
} else {
mapProTxRefs.erase(itPro);
}
}
}
ClearPrioritisation(txConflict.GetHash());
removeRecursive(txConflict, MemPoolRemovalReason::CONFLICT);
}
@ -909,6 +929,8 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx)
removeProTxPubKeyConflicts(tx, proTx.pubKeyOperator);
if (!proTx.collateralOutpoint.hash.IsNull()) {
removeProTxCollateralConflicts(tx, proTx.collateralOutpoint);
} else {
removeProTxCollateralConflicts(tx, COutPoint(tx.GetHash(), proTx.collateralOutpoint.n));
}
} else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
CProUpServTx proTx;