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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

src/validation.cpp CHANGED
@@ -839,9 +839,14 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
839
  if (tx.IsCoinBase())
840
  return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase");
841
 
 
 
 
 
842
  // Rather not work on nonstandard transactions (unless -testnet/-regtest)
843
  std::string reason;
844
- if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts, reason)) {
 
845
  return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
846
  }
847
 
@@ -874,10 +879,27 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
874
  // Transaction conflicts with a mempool tx, but we're not allowing replacements in this context.
875
  return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
876
  }
 
 
 
 
877
  ws.m_conflicts.insert(ptxConflicting->GetHash());
878
  }
879
  }
880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  m_view.SetBackend(m_viewmempool);
882
 
883
  const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
@@ -948,6 +970,23 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
948
  if (coinblocks < m_pool.m_opts.minrelaycoinblocks) {
949
  return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-input-immature-coinblocks");
950
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  if (m_pool.m_opts.datacarrier_fullcount || !m_pool.m_opts.accept_non_std_datacarrier) {
952
  const auto [standard_bytes, nonstandard_bytes] = DatacarrierBytes(tx, m_view);
953
  if (nonstandard_bytes && !m_pool.m_opts.accept_non_std_datacarrier) {
@@ -958,7 +997,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
958
  }
959
  }
960
 
961
- if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view, m_pool.m_opts, "bad-txns-input-", reason)) {
962
  return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason);
963
  }
964
 
@@ -986,7 +1025,51 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
986
  if (!m_subpackage.m_changeset) {
987
  m_subpackage.m_changeset = m_pool.GetChangeSet();
988
  }
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());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
990
 
991
  // ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
992
  ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee();
@@ -995,14 +1078,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
995
 
996
  // Enforces 0-fee for dust transactions, no incentive to be mined alone
997
  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)) {
999
  return false; // state filled in by PreCheckEphemeralTx
1000
  }
1001
  }
1002
 
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))
1006
  return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
1007
  strprintf("%d", nSigOpsCost));
1008
 
@@ -1015,11 +1097,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
1015
 
1016
  ws.m_parents = m_pool.GetParents(*ws.m_tx_handle);
1017
 
1018
- if (!args.m_bypass_limits) {
1019
  // Perform the TRUC checks, using the in-mempool parents.
1020
  if (const auto err{SingleTRUCChecks(m_pool, ws.m_ptx, ws.m_parents, ws.m_conflicts, ws.m_vsize)}) {
1021
  // Single transaction contexts only.
1022
- if (args.m_allow_sibling_eviction && err->second != nullptr) {
1023
  // We should only be considering where replacement is considered valid as well.
1024
  Assume(args.m_allow_replacement);
1025
  // 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
  }
1041
  }
1042
 
 
 
 
 
 
 
 
 
 
1043
  // We want to detect conflicts in any tx in a package to trigger package RBF logic
1044
  m_subpackage.m_rbf |= !ws.m_conflicts.empty();
1045
  return true;
@@ -1436,7 +1527,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransactionInternal(const CTransa
1436
 
1437
  if (!args.m_bypass_limits && m_pool.m_opts.require_standard) {
1438
  Wtxid dummy_wtxid;
1439
- if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.m_opts.dust_relay_feerate, m_pool, ws.m_state, dummy_wtxid)) {
1440
  return MempoolAcceptResult::Failure(ws.m_state);
1441
  }
1442
  }
@@ -1541,9 +1632,9 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(con
1541
  }
1542
 
1543
  // At this point we have all in-mempool parents, and we know every transaction's vsize.
1544
- // Run the TRUC checks on the package.
1545
  for (Workspace& ws : workspaces) {
1546
- if (auto err{PackageTRUCChecks(m_pool, ws.m_ptx, ws.m_vsize, txns, ws.m_parents)}) {
1547
  package_state.Invalid(PackageValidationResult::PCKG_POLICY, "TRUC-violation", err.value());
1548
  return PackageMempoolAcceptResult(package_state, {});
1549
  }
@@ -1590,7 +1681,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(con
1590
  if (m_pool.m_opts.require_standard) {
1591
  TxValidationState child_state;
1592
  Wtxid child_wtxid;
1593
- if (!CheckEphemeralSpends(txns, m_pool.m_opts.dust_relay_feerate, m_pool, child_state, child_wtxid)) {
1594
  package_state.Invalid(PackageValidationResult::PCKG_TX, "unspent-dust");
1595
  results.emplace(child_wtxid, MempoolAcceptResult::Failure(child_state));
1596
  return PackageMempoolAcceptResult(package_state, std::move(results));
 
839
  if (tx.IsCoinBase())
840
  return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase");
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
+
846
  // Rather not work on nonstandard transactions (unless -testnet/-regtest)
847
  std::string reason;
848
+ const StandardnessOptions standardness{m_pool.GetStandardnessOptions()};
849
+ if (m_pool.m_opts.require_standard && !IsStandardTx(tx, standardness, reason)) {
850
  return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
851
  }
852
 
 
879
  // Transaction conflicts with a mempool tx, but we're not allowing replacements in this context.
880
  return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed");
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
+ }
886
  ws.m_conflicts.insert(ptxConflicting->GetHash());
887
  }
888
  }
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
+
903
  m_view.SetBackend(m_viewmempool);
904
 
905
  const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
 
970
  if (coinblocks < m_pool.m_opts.minrelaycoinblocks) {
971
  return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-input-immature-coinblocks");
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
+ }
990
  if (m_pool.m_opts.datacarrier_fullcount || !m_pool.m_opts.accept_non_std_datacarrier) {
991
  const auto [standard_bytes, nonstandard_bytes] = DatacarrierBytes(tx, m_view);
992
  if (nonstandard_bytes && !m_pool.m_opts.accept_non_std_datacarrier) {
 
997
  }
998
  }
999
 
1000
+ if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view, standardness, "bad-txns-input-", reason)) {
1001
  return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason);
1002
  }
1003
 
 
1025
  if (!m_subpackage.m_changeset) {
1026
  m_subpackage.m_changeset = m_pool.GetChangeSet();
1027
  }
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));
1073
 
1074
  // ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
1075
  ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee();
 
1078
 
1079
  // Enforces 0-fee for dust transactions, no incentive to be mined alone
1080
  if (m_pool.m_opts.require_standard) {
1081
+ if (!PreCheckEphemeralTx(*ptx, standardness.dust_relay_feerate, ws.m_base_fees, ws.m_modified_fees, state)) {
1082
  return false; // state filled in by PreCheckEphemeralTx
1083
  }
1084
  }
1085
 
1086
+ if (nBytesPerSigOpStrict && GetAccurateTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS) >
1087
+ ws.m_vsize * WITNESS_SCALE_FACTOR / nBytesPerSigOpStrict)
 
1088
  return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
1089
  strprintf("%d", nSigOpsCost));
1090
 
 
1097
 
1098
  ws.m_parents = m_pool.GetParents(*ws.m_tx_handle);
1099
 
1100
+ if (!args.m_bypass_limits && m_pool.m_opts.truc_policy == TRUCPolicy::Enforce) {
1101
  // Perform the TRUC checks, using the in-mempool parents.
1102
  if (const auto err{SingleTRUCChecks(m_pool, ws.m_ptx, ws.m_parents, ws.m_conflicts, ws.m_vsize)}) {
1103
  // Single transaction contexts only.
1104
+ if (args.m_allow_sibling_eviction && m_pool.m_opts.rbf_policy != RBFPolicy::Never && err->second != nullptr) {
1105
  // We should only be considering where replacement is considered valid as well.
1106
  Assume(args.m_allow_replacement);
1107
  // Potential sibling eviction. Add the sibling to our list of mempool conflicts to be
 
1122
  }
1123
  }
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
+
1134
  // We want to detect conflicts in any tx in a package to trigger package RBF logic
1135
  m_subpackage.m_rbf |= !ws.m_conflicts.empty();
1136
  return true;
 
1527
 
1528
  if (!args.m_bypass_limits && m_pool.m_opts.require_standard) {
1529
  Wtxid dummy_wtxid;
1530
+ if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.GetDustRelayFee(), m_pool, ws.m_state, dummy_wtxid)) {
1531
  return MempoolAcceptResult::Failure(ws.m_state);
1532
  }
1533
  }
 
1632
  }
1633
 
1634
  // At this point we have all in-mempool parents, and we know every transaction's vsize.
1635
+ // Run the configured TRUC checks on the package.
1636
  for (Workspace& ws : workspaces) {
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}) {
1638
  package_state.Invalid(PackageValidationResult::PCKG_POLICY, "TRUC-violation", err.value());
1639
  return PackageMempoolAcceptResult(package_state, {});
1640
  }
 
1681
  if (m_pool.m_opts.require_standard) {
1682
  TxValidationState child_state;
1683
  Wtxid child_wtxid;
1684
+ if (!CheckEphemeralSpends(txns, m_pool.GetDustRelayFee(), m_pool, child_state, child_wtxid)) {
1685
  package_state.Invalid(PackageValidationResult::PCKG_TX, "unspent-dust");
1686
  results.emplace(child_wtxid, MempoolAcceptResult::Failure(child_state));
1687
  return PackageMempoolAcceptResult(package_state, std::move(results));