Skip to content

Commit 1147e00

Browse files
committed
[validation] change package-fee-too-low, return wtxid(s) and effective feerate
With subpackage evaluation and de-duplication, it's not always the entire package that is used in CheckFeerate. To be more helpful to the caller, specify which transactions were included in the evaluation and what the feerate was. Instead of PCKG_POLICY (which is supposed to be for package-wide errors), use PCKG_TX.
1 parent 10dd9f2 commit 1147e00

File tree

5 files changed

+81
-20
lines changed

5 files changed

+81
-20
lines changed

src/test/fuzz/tx_pool.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,15 @@ void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, b
154154
// It may be already in the mempool since in ATMP cases we don't set MEMPOOL_ENTRY or DIFFERENT_WITNESS
155155
Assert(!res.m_state.IsValid());
156156
Assert(res.m_state.IsInvalid());
157+
158+
const bool is_reconsiderable{res.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE};
157159
Assert(!res.m_replaced_transactions);
158160
Assert(!res.m_vsize);
159161
Assert(!res.m_base_fees);
160-
// Unable or unwilling to calculate fees
161-
Assert(!res.m_effective_feerate);
162-
Assert(!res.m_wtxids_fee_calculations);
162+
// Fee information is provided if the failure is TX_RECONSIDERABLE.
163+
// In other cases, validation may be unable or unwilling to calculate the fees.
164+
Assert(res.m_effective_feerate.has_value() == is_reconsiderable);
165+
Assert(res.m_wtxids_fee_calculations.has_value() == is_reconsiderable);
163166
Assert(!res.m_other_wtxid);
164167
break;
165168
}

src/test/txpackage_tests.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,15 +753,26 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
753753
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)) > parent_fee + child_fee);
754754
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
755755

756-
// Cheap package should fail with package-fee-too-low.
756+
// Cheap package should fail for being too low fee.
757757
{
758758
const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
759759
package_still_too_low, /*test_accept=*/false);
760-
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
761-
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low");
762760
if (auto err_package_too_low{CheckPackageMempoolAcceptResult(package_still_too_low, submit_package_too_low, /*expect_valid=*/false, m_node.mempool.get())}) {
763761
BOOST_ERROR(err_package_too_low.value());
762+
} else {
763+
// Individual feerate of parent is too low.
764+
BOOST_CHECK_EQUAL(submit_package_too_low.m_tx_results.at(tx_parent_cheap->GetWitnessHash()).m_state.GetResult(),
765+
TxValidationResult::TX_RECONSIDERABLE);
766+
BOOST_CHECK(submit_package_too_low.m_tx_results.at(tx_parent_cheap->GetWitnessHash()).m_effective_feerate.value() ==
767+
CFeeRate(parent_fee, GetVirtualTransactionSize(*tx_parent_cheap)));
768+
// Package feerate of parent + child is too low.
769+
BOOST_CHECK_EQUAL(submit_package_too_low.m_tx_results.at(tx_child_cheap->GetWitnessHash()).m_state.GetResult(),
770+
TxValidationResult::TX_RECONSIDERABLE);
771+
BOOST_CHECK(submit_package_too_low.m_tx_results.at(tx_child_cheap->GetWitnessHash()).m_effective_feerate.value() ==
772+
CFeeRate(parent_fee + child_fee, GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)));
764773
}
774+
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_TX);
775+
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "transaction failed");
765776
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
766777
}
767778

src/test/util/txmempool.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,14 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
8989
}
9090

9191
// m_effective_feerate and m_wtxids_fee_calculations should exist iff the result was valid
92-
if (atmp_result.m_effective_feerate.has_value() != valid) {
92+
// or if the failure was TX_RECONSIDERABLE
93+
const bool valid_or_reconsiderable{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID ||
94+
atmp_result.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE};
95+
if (atmp_result.m_effective_feerate.has_value() != valid_or_reconsiderable) {
9396
return strprintf("tx %s result should %shave m_effective_feerate",
9497
wtxid.ToString(), valid ? "" : "not ");
9598
}
96-
if (atmp_result.m_wtxids_fee_calculations.has_value() != valid) {
99+
if (atmp_result.m_wtxids_fee_calculations.has_value() != valid_or_reconsiderable) {
97100
return strprintf("tx %s result should %shave m_effective_feerate",
98101
wtxid.ToString(), valid ? "" : "not ");
99102
}

src/validation.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,13 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
12371237
Workspace ws(ptx);
12381238
const std::vector<Wtxid> single_wtxid{ws.m_ptx->GetWitnessHash()};
12391239

1240-
if (!PreChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
1240+
if (!PreChecks(args, ws)) {
1241+
if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) {
1242+
// Failed for fee reasons. Provide the effective feerate and which tx was included.
1243+
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_wtxid);
1244+
}
1245+
return MempoolAcceptResult::Failure(ws.m_state);
1246+
}
12411247

12421248
if (m_rbf && !ReplacementChecks(ws)) return MempoolAcceptResult::Failure(ws.m_state);
12431249

@@ -1254,7 +1260,12 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
12541260
ws.m_base_fees, effective_feerate, single_wtxid);
12551261
}
12561262

1257-
if (!Finalize(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
1263+
if (!Finalize(args, ws)) {
1264+
// The only possible failure reason is fee-related (mempool full).
1265+
// Failed for fee reasons. Provide the effective feerate and which txns were included.
1266+
Assume(ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE);
1267+
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()});
1268+
}
12581269

12591270
GetMainSignals().TransactionAddedToMempool(ptx, m_pool.GetAndIncrementSequence());
12601271

@@ -1308,11 +1319,16 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
13081319
const auto m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0},
13091320
[](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; });
13101321
const CFeeRate package_feerate(m_total_modified_fees, m_total_vsize);
1322+
std::vector<Wtxid> all_package_wtxids;
1323+
all_package_wtxids.reserve(workspaces.size());
1324+
std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids),
1325+
[](const auto& ws) { return ws.m_ptx->GetWitnessHash(); });
13111326
TxValidationState placeholder_state;
13121327
if (args.m_package_feerates &&
13131328
!CheckFeeRate(m_total_vsize, m_total_modified_fees, placeholder_state)) {
1314-
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-fee-too-low");
1315-
return PackageMempoolAcceptResult(package_state, {});
1329+
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
1330+
return PackageMempoolAcceptResult(package_state, {{workspaces.back().m_ptx->GetWitnessHash(),
1331+
MempoolAcceptResult::FeeFailure(placeholder_state, CFeeRate(m_total_modified_fees, m_total_vsize), all_package_wtxids)}});
13161332
}
13171333

13181334
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
@@ -1323,10 +1339,6 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
13231339
return PackageMempoolAcceptResult(package_state, std::move(results));
13241340
}
13251341

1326-
std::vector<Wtxid> all_package_wtxids;
1327-
all_package_wtxids.reserve(workspaces.size());
1328-
std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids),
1329-
[](const auto& ws) { return ws.m_ptx->GetWitnessHash(); });
13301342
for (Workspace& ws : workspaces) {
13311343
ws.m_package_feerate = package_feerate;
13321344
if (!PolicyScriptChecks(args, ws)) {

src/validation.h

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,27 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pin
114114
void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight);
115115

116116
/**
117-
* Validation result for a single transaction mempool acceptance.
117+
* Validation result for a transaction evaluated by MemPoolAccept (single or package).
118+
* Here are the expected fields and properties of a result depending on its ResultType, applicable to
119+
* results returned from package evaluation:
120+
*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
121+
*| Field or property | VALID | INVALID | MEMPOOL_ENTRY | DIFFERENT_WITNESS |
122+
*| | |--------------------------------------| | |
123+
*| | | TX_RECONSIDERABLE | Other | | |
124+
*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
125+
*| txid in mempool? | yes | no | no* | yes | yes |
126+
*| wtxid in mempool? | yes | no | no* | yes | no |
127+
*| m_state | yes, IsValid() | yes, IsInvalid() | yes, IsInvalid() | yes, IsValid() | yes, IsValid() |
128+
*| m_replaced_transactions | yes | no | no | no | no |
129+
*| m_vsize | yes | no | no | yes | no |
130+
*| m_base_fees | yes | no | no | yes | no |
131+
*| m_effective_feerate | yes | yes | no | no | no |
132+
*| m_wtxids_fee_calculations | yes | yes | no | no | no |
133+
*| m_other_wtxid | no | no | no | no | yes |
134+
*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
135+
* (*) Individual transaction acceptance doesn't return MEMPOOL_ENTRY and DIFFERENT_WITNESS. It returns
136+
* INVALID, with the errors txn-already-in-mempool and txn-same-nonwitness-data-in-mempool
137+
* respectively. In those cases, the txid or wtxid may be in the mempool for a TX_CONFLICT.
118138
*/
119139
struct MempoolAcceptResult {
120140
/** Used to indicate the results of mempool validation. */
@@ -130,7 +150,6 @@ struct MempoolAcceptResult {
130150
/** Contains information about why the transaction failed. */
131151
const TxValidationState m_state;
132152

133-
// The following fields are only present when m_result_type = ResultType::VALID or MEMPOOL_ENTRY
134153
/** Mempool transactions replaced by the tx. */
135154
const std::optional<std::list<CTransactionRef>> m_replaced_transactions;
136155
/** Virtual size as used by the mempool, calculated using serialized size and sigops. */
@@ -141,7 +160,6 @@ struct MempoolAcceptResult {
141160
* using prioritisetransaction (i.e. modified fees). If this transaction was submitted as a
142161
* package, this is the package feerate, which may also include its descendants and/or
143162
* ancestors (see m_wtxids_fee_calculations below).
144-
* Only present when m_result_type = ResultType::VALID.
145163
*/
146164
const std::optional<CFeeRate> m_effective_feerate;
147165
/** Contains the wtxids of the transactions used for fee-related checks. Includes this
@@ -151,14 +169,19 @@ struct MempoolAcceptResult {
151169
* Only present when m_result_type = ResultType::VALID. */
152170
const std::optional<std::vector<Wtxid>> m_wtxids_fee_calculations;
153171

154-
// The following field is only present when m_result_type = ResultType::DIFFERENT_WITNESS
155172
/** The wtxid of the transaction in the mempool which has the same txid but different witness. */
156173
const std::optional<uint256> m_other_wtxid;
157174

158175
static MempoolAcceptResult Failure(TxValidationState state) {
159176
return MempoolAcceptResult(state);
160177
}
161178

179+
static MempoolAcceptResult FeeFailure(TxValidationState state,
180+
CFeeRate effective_feerate,
181+
const std::vector<Wtxid>& wtxids_fee_calculations) {
182+
return MempoolAcceptResult(state, effective_feerate, wtxids_fee_calculations);
183+
}
184+
162185
static MempoolAcceptResult Success(std::list<CTransactionRef>&& replaced_txns,
163186
int64_t vsize,
164187
CAmount fees,
@@ -197,6 +220,15 @@ struct MempoolAcceptResult {
197220
m_effective_feerate(effective_feerate),
198221
m_wtxids_fee_calculations(wtxids_fee_calculations) {}
199222

223+
/** Constructor for fee-related failure case */
224+
explicit MempoolAcceptResult(TxValidationState state,
225+
CFeeRate effective_feerate,
226+
const std::vector<Wtxid>& wtxids_fee_calculations)
227+
: m_result_type(ResultType::INVALID),
228+
m_state(state),
229+
m_effective_feerate(effective_feerate),
230+
m_wtxids_fee_calculations(wtxids_fee_calculations) {}
231+
200232
/** Constructor for already-in-mempool case. It wouldn't replace any transactions. */
201233
explicit MempoolAcceptResult(int64_t vsize, CAmount fees)
202234
: m_result_type(ResultType::MEMPOOL_ENTRY), m_vsize{vsize}, m_base_fees(fees) {}

0 commit comments

Comments
 (0)