Skip to content

Commit

Permalink
Fork out NotAllowedToFail (#223)
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Fieroni <[email protected]>
  • Loading branch information
bvbfan authored Feb 17, 2021
1 parent 0ffc91c commit ecb8662
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 94 deletions.
2 changes: 1 addition & 1 deletion src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
std::vector<unsigned char> dummy;
const auto txType = GuessCustomTxType(tx, dummy);

if (NotAllowedToFail(txType)) {
if (NotAllowedToFail(txType, nSpendHeight)) {
auto res = ApplyCustomTx(const_cast<CCustomCSView&>(*mnview), inputs, tx, chainparams.GetConsensus(), nSpendHeight, 0, true); // note for 'isCheck == true' here; 'zero' for txn is dummy value
if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) {
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-customtx", res.msg);
Expand Down
5 changes: 3 additions & 2 deletions src/masternodes/mn_checks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,9 @@ Res ApplyCustomTx(CCustomCSView & base_mnview, CCoinsViewCache const & coins, CT
default:
return Res::Ok(); // not "custom" tx
}
// list of transactions which aren't allowed to fail:
if (!res.ok && NotAllowedToFail(guess)) {
// list of transactions which aren't allowed to fail,
// post Dakota height all failed txs are marked as fatal
if (!res.ok && (NotAllowedToFail(guess, height) || height >= consensusParams.DakotaHeight)) {
res.code |= CustomTxErrCodes::Fatal;
}
} catch (std::exception& e) {
Expand Down
6 changes: 4 additions & 2 deletions src/masternodes/mn_checks.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ inline std::string ToString(CustomTxType type) {
}
}

inline bool NotAllowedToFail(CustomTxType txType) {
return txType == CustomTxType::MintToken || txType == CustomTxType::AccountToUtxos;
// it's disabled after Dakota height
inline bool NotAllowedToFail(CustomTxType txType, int height) {
return (height < Params().GetConsensus().DakotaHeight
&& (txType == CustomTxType::MintToken || txType == CustomTxType::AccountToUtxos));
}

template<typename Stream>
Expand Down
2 changes: 1 addition & 1 deletion src/test/mempool_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include <boost/test/unit_test.hpp>
#include <vector>

BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup)
BOOST_FIXTURE_TEST_SUITE(mempool_tests, BasicTestingSetup)

static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED;

Expand Down
91 changes: 40 additions & 51 deletions src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,69 +556,58 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight)
{
AssertLockHeld(cs);
std::vector<const CTxMemPoolEntry*> entries;
for (const auto& tx : vtx)
{
uint256 hash = tx->GetHash();

indexed_transaction_set::iterator i = mapTx.find(hash);
if (i != mapTx.end())
entries.push_back(&*i);
}
// 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)
{
txiter it = mapTx.find(tx->GetHash());
setEntries staged;
std::vector<const CTxMemPoolEntry*> entries;
for (const auto& tx : vtx) {
auto it = mapTx.find(tx->GetHash());
if (it != mapTx.end()) {
setEntries stage;
stage.insert(it);
RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK);
staged.insert(it);
entries.push_back(&*it);
}
removeConflicts(*tx);
ClearPrioritisation(tx->GetHash());
}
// Before the txs in the new block have been removed from the mempool, update policy estimates
if (minerPolicyEstimator) {minerPolicyEstimator->processBlock(nBlockHeight, entries);}

bool accountConflict{false};

// Check if any custom TXs are in mempool with conflict
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); ++it) {
std::vector<unsigned char> metadata;
CustomTxType txType = GuessCustomTxType(it->GetTx(), metadata);
if (NotAllowedToFail(txType)) {
auto res = ApplyCustomTx(*pcustomcsview, g_chainstate->CoinsTip(), it->GetTx(), Params().GetConsensus(), nBlockHeight, 0, true);
if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) {
accountConflict = true;
break;
}
}
for (auto& it : staged) {
auto& tx = it->GetTx();
removeConflicts(tx);
ClearPrioritisation(tx.GetHash());
}

// Account conflict, check entire mempool
if (accountConflict) {
std::set<CTransactionRef> txsToRemove;
CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(&::ChainstateActive().CoinsTip()));
RemoveStaged(staged, true, MemPoolRemovalReason::BLOCK);

if (pcustomcsview) { // can happen in tests
// check entire mempool
CAmount txfee = 0;
CCustomCSView viewDuplicate(*pcustomcsview);
CCoinsViewCache mempoolDuplicate(&::ChainstateActive().CoinsTip());

setEntries staged;
// Check custom TX consensus types are now not in conflict with account layer
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); ++it) {
auto& txsByEntryTime = mapTx.get<entry_time>();
for (auto it = txsByEntryTime.begin(); it != txsByEntryTime.end(); ++it) {
CValidationState state;
if (!Consensus::CheckTxInputs(it->GetTx(), state, mempoolDuplicate, pcustomcsview.get(), nBlockHeight, txfee, Params())) {
LogPrintf("%s: Remove conflicting TX: %s\n", __func__, it->GetTx().GetHash().GetHex());
txsToRemove.insert(it->GetSharedTx());
const auto& tx = it->GetTx();
if (!Consensus::CheckTxInputs(tx, state, mempoolDuplicate, &viewDuplicate, nBlockHeight, txfee, Params())) {
LogPrintf("%s: Remove conflicting TX: %s\n", __func__, tx.GetHash().GetHex());
staged.insert(mapTx.project<0>(it));
continue;
}
auto res = ApplyCustomTx(viewDuplicate, mempoolDuplicate, tx, Params().GetConsensus(), nBlockHeight, 0, false);
if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) {
LogPrintf("%s: Remove conflicting custom TX: %s\n", __func__, tx.GetHash().GetHex());
staged.insert(mapTx.project<0>(it));
}
}

for (auto& tx : txsToRemove) {
txiter it = mapTx.find(tx->GetHash());
if (it != mapTx.end()) {
setEntries stage;
stage.insert(it);
RemoveStaged(stage, true, MemPoolRemovalReason::CONFLICT);
}
removeConflicts(*tx);
ClearPrioritisation(tx->GetHash());
for (auto& it : staged) {
auto& tx = it->GetTx();
removeConflicts(tx);
ClearPrioritisation(tx.GetHash());
}

RemoveStaged(staged, true, MemPoolRemovalReason::BLOCK);
}

lastRollingFeeUpdate = GetTime();
Expand Down Expand Up @@ -893,7 +882,7 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
LogPrintf("PrioritiseTransaction: %s feerate += %s\n", hash.ToString(), FormatMoney(nFeeDelta));
}

void CTxMemPool::ApplyDelta(const uint256 hash, CAmount &nFeeDelta) const
void CTxMemPool::ApplyDelta(const uint256& hash, CAmount &nFeeDelta) const
{
LOCK(cs);
std::map<uint256, CAmount>::const_iterator pos = mapDeltas.find(hash);
Expand All @@ -903,7 +892,7 @@ void CTxMemPool::ApplyDelta(const uint256 hash, CAmount &nFeeDelta) const
nFeeDelta += delta;
}

void CTxMemPool::ClearPrioritisation(const uint256 hash)
void CTxMemPool::ClearPrioritisation(const uint256& hash)
{
LOCK(cs);
mapDeltas.erase(hash);
Expand Down Expand Up @@ -964,7 +953,7 @@ size_t CTxMemPool::DynamicMemoryUsage() const {
return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage;
}

void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) {
void CTxMemPool::RemoveStaged(const setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) {
AssertLockHeld(cs);
UpdateForRemoveFromMempool(stage, updateDescendants);
for (txiter it : stage) {
Expand Down
6 changes: 3 additions & 3 deletions src/txmempool.h
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,8 @@ class CTxMemPool

/** Affect CreateNewBlock prioritisation of transactions */
void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta);
void ApplyDelta(const uint256 hash, CAmount &nFeeDelta) const;
void ClearPrioritisation(const uint256 hash);
void ApplyDelta(const uint256& hash, CAmount &nFeeDelta) const;
void ClearPrioritisation(const uint256& hash);

/** Get the transaction in the pool that spends the same prevout */
const CTransaction* GetConflictTx(const COutPoint& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs);
Expand All @@ -614,7 +614,7 @@ class CTxMemPool
* Set updateDescendants to true when removing a tx that was in a block, so
* that any in-mempool descendants have their ancestor state updated.
*/
void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
void RemoveStaged(const setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);

/** When adding transactions from a disconnected block back to the mempool,
* new mempool entries may have children in the mempool (which is generally
Expand Down
29 changes: 15 additions & 14 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,23 +609,29 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
for (const auto& e : mempool.mapTx.get<entry_time>()) {
const auto& tx = e.GetTx();
auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height, 0, false);
assert(res.ok || !(res.code & CustomTxErrCodes::Fatal)); // inconsistent mempool
// we don't need contract anynore furthermore transition to new hardfork will broke it
if (height < chainparams.GetConsensus().DakotaHeight) {
assert(res.ok || !(res.code & CustomTxErrCodes::Fatal));
}
}

CAmount nFees = 0;
if (!Consensus::CheckTxInputs(tx, state, view, &mnview, height, nFees, chainparams)) {
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
}

// NOTE Consensus::CheckTxInputs will check for NotAllowToFail
std::vector<unsigned char> metadata;
const auto txType = GuessCustomTxType(tx, metadata);
if (nAbsurdFee && nFees > nAbsurdFee) {
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_HIGHFEE, "absurdly-high-fee", strprintf("%d > %d", nFees, nAbsurdFee));
}

if (!NotAllowedToFail(txType)) {
auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height, 0, false);
if (!res.ok || (res.code & CustomTxErrCodes::Fatal)) {
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INVALID, res.msg);
}
// let make sure we have needed coins
if (view.GetValueIn(tx) < nFees) {
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INVALID, "bad-txns-inputs-below-tx-fee");
}

auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height, 0, false);
if (!res.ok || (res.code & CustomTxErrCodes::Fatal)) {
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INVALID, res.msg);
}

// we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool
Expand Down Expand Up @@ -688,11 +694,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize)));
}

if (nAbsurdFee && nFees > nAbsurdFee)
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false,
REJECT_HIGHFEE, "absurdly-high-fee",
strprintf("%d > %d", nFees, nAbsurdFee));

// Calculate in-mempool ancestors, up to a limit.
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
Expand Down
53 changes: 53 additions & 0 deletions test/functional/feature_mempool_dakota.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Copyright (c) DeFi Blockchain Developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test account mining behaviour"""

from test_framework.test_framework import DefiTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)

class MempoolDakotaTest(DefiTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.setup_clean_chain = True
self.extra_args = [
['-txnotokens=0', '-amkheight=50', '-dakotaheight=100'],
['-txnotokens=0', '-amkheight=50', '-dakotaheight=100'],
['-txnotokens=0', '-amkheight=50', '-dakotaheight=100'],
]

def run_test(self):
node = self.nodes[0]
node1 = self.nodes[1]
node.generate(101)
self.sync_all()

assert_equal(node.getblockcount(), 101) # Dakota height

# Get addresses and set up account
wallet1_addr = node1.getnewaddress("", "legacy")
node.sendtoaddress(wallet1_addr, "3.1")
node.generate(1)
self.sync_all()
collateral = node1.createmasternode(wallet1_addr)
node.generate(1)
self.sync_all()
assert_raises_rpc_error(-26, "collateral-locked", node1.utxostoaccount, {wallet1_addr: "0.09@0"})
node.generate(1)
self.sync_all()
assert_equal(node1.listmasternodes({}, False)[collateral], "PRE_ENABLED")
assert_equal(node1.getmasternode(collateral)[collateral]["state"], "PRE_ENABLED")
node.generate(10)
assert_equal(node.listmasternodes({}, False)[collateral], "ENABLED")

node1.utxostoaccount({wallet1_addr: "0.09@0"})
node.generate(1)
self.sync_all()

if __name__ == '__main__':
MempoolDakotaTest().main ()
27 changes: 12 additions & 15 deletions test/functional/feature_mine_cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,23 @@ def set_test_params(self):
def run_test(self):
assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI

print("Generating initial chain...")
tokens = [
{
"wallet": self.nodes[0],
"symbol": "GOLD",
"name": "shiny gold",
"collateralAddress": self.nodes[0].get_genesis_keys().ownerAuthAddress,
"amount": 30
},
]
# inside this function "tokenId" and "symbolId" will be assigned for each token obj
self.setup_tokens(tokens)
self.nodes[0].generate(101)
self.sync_all()

token0_symbol = tokens[0]["symbolId"]
wallet0_addr = self.nodes[0].getnewaddress("", "legacy")
self.nodes[0].utxostoaccount({wallet0_addr: "10@0"})
self.nodes[0].generate(1)
self.sync_all()

to = {}
wallet1_addr1 = self.nodes[1].getnewaddress("", "legacy")
to[wallet1_addr1] = ["10@" + token0_symbol, "10@0"]
wallet1_addr = self.nodes[1].getnewaddress("", "legacy")
to[wallet1_addr] = ["10@0"]

assert_raises_rpc_error(-5, None, self.nodes[0].sendtokenstoaddress, {}, to)

self.nodes[0].importprivkey(self.nodes[1].dumpprivkey(wallet1_addr))

self.nodes[0].sendtokenstoaddress({}, to)

if __name__ == '__main__':
IsMineCachedTest().main()
2 changes: 1 addition & 1 deletion test/functional/feature_poolpair_liquidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ def run_test(self):
assert_equal(self.nodes[0].getaccount(accountGold, {}, True)[idGold], initialGold)
assert_equal(self.nodes[0].getaccount(accountSilver, {}, True)[idSilver], initialSilver)

assert_equal(len(self.nodes[0].getrawmempool()), 13) # 13 txs
# assert_equal(len(self.nodes[0].getrawmempool()), 13) # removed txs for block


if __name__ == '__main__':
Expand Down
9 changes: 6 additions & 3 deletions test/functional/rpc_mn_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ def run_test(self):
# Funding auth address and successful resign
fundingTx = self.nodes[0].sendtoaddress(collateral0, 1)
self.nodes[0].generate(1)
resignTx = self.nodes[0].resignmasternode(idnode0)
# resignTx
self.nodes[0].resignmasternode(idnode0)
self.nodes[0].generate(1)
assert_equal(self.nodes[0].listmasternodes()[idnode0]['state'], "PRE_RESIGNED")
self.nodes[0].generate(10)
Expand Down Expand Up @@ -129,7 +130,8 @@ def run_test(self):
# print ("ResignTx", resignTx)
# print ("FundingTx", fundingTx)
# print ("SpendTx", sendedTxHash)
assert_equal(self.nodes[0].getrawmempool(), [fundingTx, resignTx])
# resignTx is removed for a block
assert_equal(self.nodes[0].getrawmempool(), [fundingTx])
assert_equal(self.nodes[0].listmasternodes()[idnode0]['state'], "ENABLED")

# Revert creation!
Expand All @@ -139,7 +141,8 @@ def run_test(self):
connect_nodes_bi(self.nodes, 0, 2)
self.sync_blocks(self.nodes[0:3])
assert_equal(len(self.nodes[0].listmasternodes()), 8)
assert_equal(self.nodes[0].getrawmempool(), [idnode0, fundingTx, resignTx])
# fundingTx is removed for a block
assert_equal(self.nodes[0].getrawmempool(), [idnode0])

collateral0 = self.nodes[0].getnewaddress("", "legacy")
self.nodes[0].createmasternode(collateral0)
Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
'feature_prevent_bad_tx_propagation.py',
'feature_masternode_operator.py',
'feature_mine_cached.py',
'feature_mempool_dakota.py',
'interface_http.py',
'interface_rpc.py',
'rpc_psbt.py',
Expand Down
5 changes: 4 additions & 1 deletion test/functional/wallet_bumpfee.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,10 @@ def test_unconfirmed_not_spendable(rbf_node, rbf_node_address):

# Call abandon to make sure the wallet doesn't attempt to resubmit
# the bump tx and hope the wallet does not rebroadcast before we call.
rbf_node.abandontransaction(bumpid)
try:
rbf_node.abandontransaction(bumpid)
except:
pass # make sure test not fail due to missing tx
assert bumpid not in rbf_node.getrawmempool()
assert rbfid in rbf_node.getrawmempool()

Expand Down

0 comments on commit ecb8662

Please sign in to comment.