Skip to content
Bitcoin Roots

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));