@@ -630,7 +630,8 @@ class MemPoolAccept
630
630
explicit Workspace (const CTransactionRef& ptx) : m_ptx(ptx), m_hash(ptx->GetHash ()) {}
631
631
/* * Txids of mempool transactions that this transaction directly conflicts with or may
632
632
* replace via sibling eviction. */
633
- std::set<Txid> m_conflicts;
633
+ /* * .second=true is a consensus conflict, and .second=false is a policy conflict. */
634
+ std::map<Txid, bool > m_conflicts_incl_policy;
634
635
/* * Iterators to mempool entries that this transaction directly conflicts with or may
635
636
* replace via sibling eviction. */
636
637
CTxMemPool::setEntries m_iters_conflicting;
@@ -852,7 +853,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
852
853
// Transaction conflicts with a mempool tx, but we're not allowing replacements.
853
854
return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY, " bip125-replacement-disallowed" );
854
855
}
855
- if (!ws.m_conflicts .count (ptxConflicting->GetHash ()))
856
+ if (!ws.m_conflicts_incl_policy .count (ptxConflicting->GetHash ()))
856
857
{
857
858
// Transactions that don't explicitly signal replaceability are
858
859
// *not* replaceable with the current logic, even if one of their
@@ -878,16 +879,22 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
878
879
return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY, " txn-mempool-conflict" );
879
880
}
880
881
881
- ws.m_conflicts . insert (ptxConflicting->GetHash ());
882
+ ws.m_conflicts_incl_policy . emplace (ptxConflicting->GetHash (), true );
882
883
}
883
884
}
884
885
}
885
886
886
887
if (spk_reuse_mode != SRM_ALLOW) {
887
888
for (const CTxOut& txout : tx.vout ) {
888
889
uint160 hashSPK = ScriptHashkey (txout.scriptPubKey );
889
- if (m_pool.mapUsedSPK .find (hashSPK) != m_pool.mapUsedSPK .end ()) {
890
- return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY, " txn-mempool-spk-reused" );
890
+ const auto & SPKUsedIn = m_pool.mapUsedSPK .find (hashSPK);
891
+ if (SPKUsedIn != m_pool.mapUsedSPK .end ()) {
892
+ if (SPKUsedIn->second .first ) {
893
+ ws.m_conflicts_incl_policy .emplace (SPKUsedIn->second .first ->GetHash (), false );
894
+ }
895
+ if (SPKUsedIn->second .second ) {
896
+ ws.m_conflicts_incl_policy .emplace (SPKUsedIn->second .second ->GetHash (), false );
897
+ }
891
898
}
892
899
if (mapSPK.find (hashSPK) != mapSPK.end ()) {
893
900
return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY, " txn-spk-reused-twinoutputs" );
@@ -962,7 +969,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
962
969
const auto & SPKit = m_pool.mapUsedSPK .find (hashSPK);
963
970
if (SPKit != m_pool.mapUsedSPK .end ()) {
964
971
if (SPKit->second .second /* Spent */ ) {
965
- return state. Invalid (TxValidationResult::TX_MEMPOOL_POLICY, " txn-mempool-spk-reused-spend " );
972
+ ws. m_conflicts_incl_policy . emplace (SPKit-> second . second -> GetHash (), false );
966
973
}
967
974
}
968
975
mapSPK[hashSPK] = MemPool_SPK_State (mapSPK[hashSPK] | MSS_SPENT);
@@ -1024,14 +1031,18 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
1024
1031
// feerate later.
1025
1032
if (!args.m_package_feerates && !CheckFeeRate (ws.m_vsize , ws.m_modified_fees , state, args.m_ignore_rejects )) return false ;
1026
1033
1027
- ws.m_iters_conflicting = m_pool.GetIterSet (ws.m_conflicts );
1034
+ std::set<Txid> conflicts_as_a_set;
1035
+ std::transform (ws.m_conflicts_incl_policy .begin (), ws.m_conflicts_incl_policy .end (),
1036
+ std::inserter (conflicts_as_a_set, conflicts_as_a_set.end ()),
1037
+ [](const std::pair<Txid, bool >& pair){ return pair.first ; });
1038
+ ws.m_iters_conflicting = m_pool.GetIterSet (conflicts_as_a_set);
1028
1039
1029
1040
// Note that these modifications are only applicable to single transaction scenarios;
1030
1041
// carve-outs are disabled for multi-transaction evaluations.
1031
1042
CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts .limits ;
1032
1043
1033
1044
// Calculate in-mempool ancestors, up to a limit.
1034
- if (ws.m_conflicts .size () == 1 && args.m_allow_carveouts ) {
1045
+ if (ws.m_conflicts_incl_policy .size () == 1 && args.m_allow_carveouts ) {
1035
1046
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
1036
1047
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
1037
1048
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
@@ -1115,15 +1126,16 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
1115
1126
// check using the full ancestor set here because it's more convenient to use what we have
1116
1127
// already calculated.
1117
1128
if (m_pool.m_opts .truc_policy == TRUCPolicy::Enforce) {
1118
- if (const auto err{SingleTRUCChecks (ws.m_ptx , " truc-" , reason, ignore_rejects, ws.m_ancestors , ws. m_conflicts , ws.m_vsize )}) {
1129
+ if (const auto err{SingleTRUCChecks (ws.m_ptx , " truc-" , reason, ignore_rejects, ws.m_ancestors , conflicts_as_a_set , ws.m_vsize )}) {
1119
1130
// Single transaction contexts only.
1120
1131
if (args.m_allow_sibling_eviction && err->second != nullptr ) {
1121
1132
// We should only be considering where replacement is considered valid as well.
1122
1133
Assume (args.m_allow_replacement );
1123
1134
1124
1135
// Potential sibling eviction. Add the sibling to our list of mempool conflicts to be
1125
1136
// included in RBF checks.
1126
- ws.m_conflicts .insert (err->second ->GetHash ());
1137
+ ws.m_conflicts_incl_policy .emplace (err->second ->GetHash (), false );
1138
+ conflicts_as_a_set.insert (err->second ->GetHash ());
1127
1139
// Adding the sibling to m_iters_conflicting here means that it doesn't count towards
1128
1140
// RBF Carve Out above. This is correct, since removing to-be-replaced transactions from
1129
1141
// the descendant count is done separately in SingleTRUCChecks for TRUC transactions.
@@ -1142,14 +1154,18 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
1142
1154
// that we have the set of all ancestors we can detect this
1143
1155
// pathological case by making sure ws.m_conflicts and ws.m_ancestors don't
1144
1156
// intersect.
1145
- if (const auto err_string{EntriesAndTxidsDisjoint (ws.m_ancestors , ws.m_conflicts , hash)}) {
1157
+ bool has_policy_conflict{false };
1158
+ if (const auto err_string{EntriesAndTxidsDisjoint (ws.m_ancestors , ws.m_conflicts_incl_policy , hash, &has_policy_conflict)}) {
1146
1159
// We classify this as a consensus error because a transaction depending on something it
1147
1160
// conflicts with would be inconsistent.
1148
1161
return state.Invalid (TxValidationResult::TX_CONSENSUS, " bad-txns-spends-conflicting-tx" , *err_string);
1149
1162
}
1163
+ if (has_policy_conflict) {
1164
+ return state.Invalid (TxValidationResult::TX_MEMPOOL_POLICY, " txn-spk-reused-chained" );
1165
+ }
1150
1166
1151
1167
// We want to detect conflicts in any tx in a package to trigger package RBF logic
1152
- m_subpackage.m_rbf |= !ws.m_conflicts .empty ();
1168
+ m_subpackage.m_rbf |= !ws.m_conflicts_incl_policy .empty ();
1153
1169
return true ;
1154
1170
}
1155
1171
0 commit comments