v31.1-roots.1 · Commit 3: [PATCH 03/17] feat(policy): preserve cluster accounting and repair dynamic dust
src/validation.cpp
Before: src/validation.cpp · After: src/validation.cpp
File metadata
index 5a07852971bb4688ccdd1c4b26f42dbda946ba4b..f94c378f4ee1ed60ecf8b83ac37a12a277d2ecdd 100644
Download original patch · Release notes
Diff rows 1–197 of 197. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.
src/validation.cpp
CHANGED
|
@@ -839,9 +839,14 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
839
839
|
if (tx.IsCoinBase())
|
|
840
840
|
return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase");
|
|
841
841
|
|
|
842
|
+
if (tx.version == TRUC_VERSION && m_pool.m_opts.truc_policy == TRUCPolicy::Reject) {
|
|
843
|
+
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "version");
|
|
844
|
+
}
|
|
845
|
+
|
|
842
846
|
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
|
|
843
847
|
std::string reason;
|
|
844
|
-
if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts, reason)) {
|
|
848
|
+
const StandardnessOptions standardness{m_pool.GetStandardnessOptions()};
|
|
849
|
+
if (m_pool.m_opts.require_standard && !IsStandardTx(tx, standardness, reason)) {
|
|
845
850
|
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
|
|
846
851
|
}
|
|
847
852
|
|
|
@@ -874,10 +879,27 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
874
879
|
// Transaction conflicts with a mempool tx, but we're not allowing replacements in this context.
|
|
875
880
|
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
|
|
876
881
|
}
|
|
882
|
+
if ((m_pool.m_opts.rbf_policy == RBFPolicy::Never ||
|
|
883
|
+
(m_pool.m_opts.rbf_policy == RBFPolicy::OptIn && !SignalsOptInRBF(*ptxConflicting) && ptxConflicting->version != TRUC_VERSION))) {
|
|
884
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
|
|
885
|
+
}
|
|
877
886
|
ws.m_conflicts.insert(ptxConflicting->GetHash());
|
|
878
887
|
}
|
|
879
888
|
}
|
|
880
889
|
|
|
890
|
+
std::map<CScript, unsigned> script_claims;
|
|
891
|
+
std::set<Txid> script_conflicts;
|
|
892
|
+
if (m_pool.m_opts.reject_spk_reuse) {
|
|
893
|
+
for (const auto& output : tx.vout) {
|
|
894
|
+
if (script_claims.contains(output.scriptPubKey)) {
|
|
895
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-spk-reused-twinoutputs");
|
|
896
|
+
}
|
|
897
|
+
script_claims[output.scriptPubKey] |= 2;
|
|
898
|
+
const auto conflicts = m_pool.GetScriptConflicts(output.scriptPubKey, true);
|
|
899
|
+
script_conflicts.insert(conflicts.begin(), conflicts.end());
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
881
903
|
m_view.SetBackend(m_viewmempool);
|
|
882
904
|
|
|
883
905
|
const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
|
|
@@ -948,6 +970,23 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
948
970
|
if (coinblocks < m_pool.m_opts.minrelaycoinblocks) {
|
|
949
971
|
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-input-immature-coinblocks");
|
|
950
972
|
}
|
|
973
|
+
if (m_pool.m_opts.reject_spk_reuse) {
|
|
974
|
+
for (const auto& input : tx.vin) {
|
|
975
|
+
const auto& script = m_view.AccessCoin(input.prevout).out.scriptPubKey;
|
|
976
|
+
if (script_claims[script] & 2) {
|
|
977
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-spk-reused-change");
|
|
978
|
+
}
|
|
979
|
+
script_claims[script] |= 1;
|
|
980
|
+
const auto conflicts = m_pool.GetScriptConflicts(script, false);
|
|
981
|
+
script_conflicts.insert(conflicts.begin(), conflicts.end());
|
|
982
|
+
}
|
|
983
|
+
{
|
|
984
|
+
if (!args.m_allow_replacement && !script_conflicts.empty()) {
|
|
985
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
|
|
986
|
+
}
|
|
987
|
+
ws.m_conflicts.insert(script_conflicts.begin(), script_conflicts.end());
|
|
988
|
+
}
|
|
989
|
+
}
|
|
951
990
|
if (m_pool.m_opts.datacarrier_fullcount || !m_pool.m_opts.accept_non_std_datacarrier) {
|
|
952
991
|
const auto [standard_bytes, nonstandard_bytes] = DatacarrierBytes(tx, m_view);
|
|
953
992
|
if (nonstandard_bytes && !m_pool.m_opts.accept_non_std_datacarrier) {
|
|
@@ -958,7 +997,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
958
997
|
}
|
|
959
998
|
}
|
|
960
999
|
|
|
961
|
-
if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view, m_pool.m_opts, "bad-txns-input-", reason)) {
|
|
1000
|
+
if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view, standardness, "bad-txns-input-", reason)) {
|
|
962
1001
|
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason);
|
|
963
1002
|
}
|
|
964
1003
|
|
|
@@ -986,7 +1025,51 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
986
1025
|
if (!m_subpackage.m_changeset) {
|
|
987
1026
|
m_subpackage.m_changeset = m_pool.GetChangeSet();
|
|
988
1027
|
}
|
|
989
|
-
ws.m_tx_handle = m_subpackage.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value());
|
|
1028
|
+
if (!bypass_limits) {
|
|
1029
|
+
for (const auto& [script, claims] : script_claims) {
|
|
1030
|
+
if (m_subpackage.m_changeset->HasStagedScriptConflict(script, claims & 2)) {
|
|
1031
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-spk-reused-package");
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const int32_t extra_weight{CalculateExtraTxWeight(tx, m_view, m_pool.m_opts.weight_per_data_byte)};
|
|
1036
|
+
// Bound sigop cost before multiplying by the initialized unsigned policy
|
|
1037
|
+
// factor. This is the same local limit checked below, moved ahead of math.
|
|
1038
|
+
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) {
|
|
1039
|
+
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", strprintf("%d", nSigOpsCost));
|
|
1040
|
+
}
|
|
1041
|
+
const int64_t adjusted_weight{GetSigOpsAdjustedWeight(GetTransactionWeight(tx) + int64_t{extra_weight}, nSigOpsCost, nBytesPerSigOp)};
|
|
1042
|
+
const int64_t policy_vsize{(adjusted_weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR};
|
|
1043
|
+
// CFeeRate accepts int32 vsize. Extreme native bytespersigop settings must
|
|
1044
|
+
// fail the graph limit before an implicit narrowing can wrap that argument.
|
|
1045
|
+
if (policy_vsize > INT32_MAX) {
|
|
1046
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-large-cluster");
|
|
1047
|
+
}
|
|
1048
|
+
CAmount dust_penalty{0};
|
|
1049
|
+
if (m_pool.m_opts.subdustfeepenalty) {
|
|
1050
|
+
for (const auto& output : tx.vout) {
|
|
1051
|
+
const CAmount threshold{GetDustThreshold(output, standardness.dust_relay_feerate)};
|
|
1052
|
+
if (output.nValue < threshold) dust_penalty = SaturatingAdd(dust_penalty, threshold - output.nValue);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
CAmount delta{0};
|
|
1056
|
+
m_pool.ApplyDelta(hash, delta);
|
|
1057
|
+
const CAmount modified_fee{SaturatingAdd(SaturatingAdd(ws.m_base_fees, delta), -dust_penalty)};
|
|
1058
|
+
ws.m_modified_fees = modified_fee;
|
|
1059
|
+
ws.m_vsize = policy_vsize;
|
|
1060
|
+
// Reject ordinary low-fee transactions before placing extreme deficit/weight
|
|
1061
|
+
// values in the native graph. TRUC sponsorship is checked at package level.
|
|
1062
|
+
const bool sponsored{tx.version == TRUC_VERSION && m_pool.m_opts.truc_policy == TRUCPolicy::Enforce};
|
|
1063
|
+
if (!bypass_limits && !sponsored && modified_fee < m_pool.m_opts.min_relay_feerate.GetFee(policy_vsize)) {
|
|
1064
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", strprintf("%d < %d", modified_fee, m_pool.m_opts.min_relay_feerate.GetFee(policy_vsize)));
|
|
1065
|
+
}
|
|
1066
|
+
if (!m_pool.CanAddModifiedFee(modified_fee)) {
|
|
1067
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool-fee-overflow");
|
|
1068
|
+
}
|
|
1069
|
+
if (!m_pool.CanAddPolicyWeight(adjusted_weight)) {
|
|
1070
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-large-cluster");
|
|
1071
|
+
}
|
|
1072
|
+
ws.m_tx_handle = m_subpackage.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value(), extra_weight, dust_penalty, std::move(script_claims));
|
|
990
1073
|
|
|
991
1074
|
// ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
|
|
992
1075
|
ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee();
|
|
@@ -995,14 +1078,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
995
1078
|
|
|
996
1079
|
// Enforces 0-fee for dust transactions, no incentive to be mined alone
|
|
997
1080
|
if (m_pool.m_opts.require_standard) {
|
|
998
|
-
if (!PreCheckEphemeralTx(*ptx, m_pool.m_opts.dust_relay_feerate, ws.m_base_fees, ws.m_modified_fees, state)) {
|
|
1081
|
+
if (!PreCheckEphemeralTx(*ptx, standardness.dust_relay_feerate, ws.m_base_fees, ws.m_modified_fees, state)) {
|
|
999
1082
|
return false; // state filled in by PreCheckEphemeralTx
|
|
1000
1083
|
}
|
|
1001
1084
|
}
|
|
1002
1085
|
|
|
1003
|
-
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST ||
|
|
1004
|
-
(nBytesPerSigOpStrict && GetAccurateTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS) >
|
|
1005
|
-
ws.m_vsize * WITNESS_SCALE_FACTOR / nBytesPerSigOpStrict))
|
|
1086
|
+
if (nBytesPerSigOpStrict && GetAccurateTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS) >
|
|
1087
|
+
ws.m_vsize * WITNESS_SCALE_FACTOR / nBytesPerSigOpStrict)
|
|
1006
1088
|
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
|
|
1007
1089
|
strprintf("%d", nSigOpsCost));
|
|
1008
1090
|
|
|
@@ -1015,11 +1097,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
1015
1097
|
|
|
1016
1098
|
ws.m_parents = m_pool.GetParents(*ws.m_tx_handle);
|
|
1017
1099
|
|
|
1018
|
-
if (!args.m_bypass_limits) {
|
|
1100
|
+
if (!args.m_bypass_limits && m_pool.m_opts.truc_policy == TRUCPolicy::Enforce) {
|
|
1019
1101
|
// Perform the TRUC checks, using the in-mempool parents.
|
|
1020
1102
|
if (const auto err{SingleTRUCChecks(m_pool, ws.m_ptx, ws.m_parents, ws.m_conflicts, ws.m_vsize)}) {
|
|
1021
1103
|
// Single transaction contexts only.
|
|
1022
|
-
if (args.m_allow_sibling_eviction && err->second != nullptr) {
|
|
1104
|
+
if (args.m_allow_sibling_eviction && m_pool.m_opts.rbf_policy != RBFPolicy::Never && err->second != nullptr) {
|
|
1023
1105
|
// We should only be considering where replacement is considered valid as well.
|
|
1024
1106
|
Assume(args.m_allow_replacement);
|
|
1025
1107
|
// Potential sibling eviction. Add the sibling to our list of mempool conflicts to be
|
|
@@ -1040,6 +1122,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|
|
1040
1122
|
}
|
|
1041
1123
|
}
|
|
1042
1124
|
|
|
1125
|
+
if (!script_conflicts.empty()) {
|
|
1126
|
+
const auto ancestors = m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle);
|
|
1127
|
+
for (const auto& ancestor : ancestors) {
|
|
1128
|
+
if (script_conflicts.contains(ancestor->GetTx().GetHash())) {
|
|
1129
|
+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-spk-reused-chained");
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1043
1134
|
// We want to detect conflicts in any tx in a package to trigger package RBF logic
|
|
1044
1135
|
m_subpackage.m_rbf |= !ws.m_conflicts.empty();
|
|
1045
1136
|
return true;
|
|
@@ -1436,7 +1527,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransactionInternal(const CTransa
|
|
|
1436
1527
|
|
|
1437
1528
|
if (!args.m_bypass_limits && m_pool.m_opts.require_standard) {
|
|
1438
1529
|
Wtxid dummy_wtxid;
|
|
1439
|
-
if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.m_opts.dust_relay_feerate, m_pool, ws.m_state, dummy_wtxid)) {
|
|
1530
|
+
if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.GetDustRelayFee(), m_pool, ws.m_state, dummy_wtxid)) {
|
|
1440
1531
|
return MempoolAcceptResult::Failure(ws.m_state);
|
|
1441
1532
|
}
|
|
1442
1533
|
}
|
|
@@ -1541,9 +1632,9 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(con
|
|
|
1541
1632
|
}
|
|
1542
1633
|
|
|
1543
1634
|
// At this point we have all in-mempool parents, and we know every transaction's vsize.
|
|
1544
|
-
// Run the TRUC checks on the package.
|
|
1635
|
+
// Run the configured TRUC checks on the package.
|
|
1545
1636
|
for (Workspace& ws : workspaces) {
|
|
1546
|
-
if (auto err{PackageTRUCChecks(m_pool, ws.m_ptx, ws.m_vsize, txns, ws.m_parents)}) {
|
|
1637
|
+
if (auto err{m_pool.m_opts.truc_policy == TRUCPolicy::Enforce ? PackageTRUCChecks(m_pool, ws.m_ptx, ws.m_vsize, txns, ws.m_parents) : std::nullopt}) {
|
|
1547
1638
|
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "TRUC-violation", err.value());
|
|
1548
1639
|
return PackageMempoolAcceptResult(package_state, {});
|
|
1549
1640
|
}
|
|
@@ -1590,7 +1681,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(con
|
|
|
1590
1681
|
if (m_pool.m_opts.require_standard) {
|
|
1591
1682
|
TxValidationState child_state;
|
|
1592
1683
|
Wtxid child_wtxid;
|
|
1593
|
-
if (!CheckEphemeralSpends(txns, m_pool.m_opts.dust_relay_feerate, m_pool, child_state, child_wtxid)) {
|
|
1684
|
+
if (!CheckEphemeralSpends(txns, m_pool.GetDustRelayFee(), m_pool, child_state, child_wtxid)) {
|
|
1594
1685
|
package_state.Invalid(PackageValidationResult::PCKG_TX, "unspent-dust");
|
|
1595
1686
|
results.emplace(child_wtxid, MempoolAcceptResult::Failure(child_state));
|
|
1596
1687
|
return PackageMempoolAcceptResult(package_state, std::move(results));
|