Skip to content

Commit

Permalink
[validation] change package-fee-too-low, return wtxid(s) and effectiv…
Browse files Browse the repository at this point in the history
…e 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.
  • Loading branch information
glozow committed Nov 3, 2023
1 parent 3407009 commit 6b0dc66
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 22 deletions.
9 changes: 6 additions & 3 deletions src/test/fuzz/tx_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,15 @@ void CheckATMPInvariants(const MempoolAcceptResult& res, bool txid_in_mempool, b
// It may be already in the mempool since in ATMP cases we don't set MEMPOOL_ENTRY or DIFFERENT_WITNESS
Assert(!res.m_state.IsValid());
Assert(res.m_state.IsInvalid());

const bool is_single_failure{res.m_state.GetResult() == TxValidationResult::TX_SINGLE_FAILURE};
Assert(!res.m_replaced_transactions);
Assert(!res.m_vsize);
Assert(!res.m_base_fees);
// Unable or unwilling to calculate fees
Assert(!res.m_effective_feerate);
Assert(!res.m_wtxids_fee_calculations);
// Fee information is provided if the failure is TX_SINGLE_FAILURE.
// In other cases, validation may be unable or unwilling to calculate the fees.
Assert(res.m_effective_feerate.has_value() == is_single_failure);
Assert(res.m_wtxids_fee_calculations.has_value() == is_single_failure);
Assert(!res.m_other_wtxid);
break;
}
Expand Down
17 changes: 13 additions & 4 deletions src/test/txpackage_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,16 +730,25 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_child_cheap)) <= child_fee);
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)) > parent_fee + child_fee);

// Cheap package should fail with package-fee-too-low.
// Cheap package should fail for being too low fee.
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_still_too_low, /*test_accept=*/false);
BOOST_CHECK_MESSAGE(submit_package_too_low.m_state.IsInvalid(), "Package validation unexpectedly succeeded");
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low");
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_TX);
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "transaction failed");
auto err_package_too_low{CheckPackageMempoolAcceptResult(package_still_too_low, submit_package_too_low, /*expect_valid=*/false, m_node.mempool.get())};
BOOST_CHECK_MESSAGE(err_package_too_low == std::nullopt, *err_package_too_low);
// Individual feerate of parent is too low.
BOOST_CHECK_EQUAL(submit_package_too_low.m_tx_results.at(tx_parent_cheap->GetWitnessHash()).m_state.GetResult(),
TxValidationResult::TX_SINGLE_FAILURE);
BOOST_CHECK(submit_package_too_low.m_tx_results.at(tx_parent_cheap->GetWitnessHash()).m_effective_feerate.value() ==
CFeeRate(parent_fee, GetVirtualTransactionSize(*tx_parent_cheap)));
// Package feerate of parent + child is too low.
BOOST_CHECK_EQUAL(submit_package_too_low.m_tx_results.at(tx_child_cheap->GetWitnessHash()).m_state.GetResult(),
TxValidationResult::TX_SINGLE_FAILURE);
BOOST_CHECK(submit_package_too_low.m_tx_results.at(tx_child_cheap->GetWitnessHash()).m_effective_feerate.value() ==
CFeeRate(parent_fee + child_fee, GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)));
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
}

Expand Down
7 changes: 5 additions & 2 deletions src/test/util/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,14 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
}

// m_effective_feerate and m_wtxids_fee_calculations should exist iff the result was valid
if (atmp_result.m_effective_feerate.has_value() != valid) {
// or if the failure was TX_SINGLE_FAILURE
const bool valid_or_single_failure{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID ||
atmp_result.m_state.GetResult() == TxValidationResult::TX_SINGLE_FAILURE};
if (atmp_result.m_effective_feerate.has_value() != valid_or_single_failure) {
return strprintf("tx %s result should %shave m_effective_feerate",
wtxid.ToString(), valid ? "" : "not ");
}
if (atmp_result.m_wtxids_fee_calculations.has_value() != valid) {
if (atmp_result.m_wtxids_fee_calculations.has_value() != valid_or_single_failure) {
return strprintf("tx %s result should %shave m_effective_feerate",
wtxid.ToString(), valid ? "" : "not ");
}
Expand Down
28 changes: 20 additions & 8 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,13 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef

Workspace ws(ptx);

if (!PreChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
if (!PreChecks(args, ws)) {
if (ws.m_state.GetResult() == TxValidationResult::TX_SINGLE_FAILURE) {
// Failed for fee reasons. Provide the effective feerate and which txns were included.
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()});
}
return MempoolAcceptResult::Failure(ws.m_state);
}

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

Expand All @@ -1253,7 +1259,12 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
ws.m_base_fees, effective_feerate, single_wtxid);
}

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

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

Expand Down Expand Up @@ -1307,11 +1318,16 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
const auto m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0},
[](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; });
const CFeeRate package_feerate(m_total_modified_fees, m_total_vsize);
std::vector<uint256> all_package_wtxids;
all_package_wtxids.reserve(workspaces.size());
std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids),
[](const auto& ws) { return ws.m_ptx->GetWitnessHash(); });
TxValidationState placeholder_state;
if (args.m_package_feerates &&
!CheckFeeRate(m_total_vsize, m_total_modified_fees, placeholder_state)) {
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-fee-too-low");
return PackageMempoolAcceptResult(package_state, {});
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
return PackageMempoolAcceptResult(package_state, {{workspaces.back().m_ptx->GetWitnessHash(),
MempoolAcceptResult::FeeFailure(placeholder_state, CFeeRate(m_total_modified_fees, m_total_vsize), all_package_wtxids)}});
}

// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
Expand All @@ -1322,10 +1338,6 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
return PackageMempoolAcceptResult(package_state, std::move(results));
}

std::vector<uint256> all_package_wtxids;
all_package_wtxids.reserve(workspaces.size());
std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids),
[](const auto& ws) { return ws.m_ptx->GetWitnessHash(); });
for (Workspace& ws : workspaces) {
ws.m_package_feerate = package_feerate;
if (!PolicyScriptChecks(args, ws)) {
Expand Down
42 changes: 37 additions & 5 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,27 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex* pin
void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight);

/**
* Validation result for a single transaction mempool acceptance.
* Validation result for a transaction evaluated by MemPoolAccept (single or package).
* Here are the expected fields and properties of a result depending on its ResultType, applicable to
* results returned from package evaluation:
*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
*| Field or property | VALID | INVALID | MEMPOOL_ENTRY | DIFFERENT_WITNESS |
*| | |--------------------------------------| | |
*| | | TX_SINGLE_FAILURE | Other | | |
*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
*| txid in mempool? | yes | no | no* | yes | yes |
*| wtxid in mempool? | yes | no | no* | yes | no |
*| m_state | yes, IsValid() | yes, IsInvalid() | yes, IsInvalid() | yes, IsValid() | yes, IsValid() |
*| m_replaced_transactions | yes | no | no | no | no |
*| m_vsize | yes | no | no | yes | no |
*| m_base_fees | yes | no | no | yes | no |
*| m_effective_feerate | yes | yes | no | no | no |
*| m_wtxids_fee_calculations | yes | yes | no | no | no |
*| m_other_wtxid | no | no | no | no | yes |
*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+
* (*) Individual transaction acceptance doesn't return MEMPOOL_ENTRY and DIFFERENT_WITNESS. It returns
* INVALID, with the errors txn-already-in-mempool and txn-same-nonwitness-data-in-mempool
* respectively. In those cases, the txid or wtxid may be in the mempool for a TX_CONFLICT.
*/
struct MempoolAcceptResult {
/** Used to indicate the results of mempool validation. */
Expand All @@ -130,7 +150,6 @@ struct MempoolAcceptResult {
/** Contains information about why the transaction failed. */
const TxValidationState m_state;

// The following fields are only present when m_result_type = ResultType::VALID or MEMPOOL_ENTRY
/** Mempool transactions replaced by the tx. */
const std::optional<std::list<CTransactionRef>> m_replaced_transactions;
/** Virtual size as used by the mempool, calculated using serialized size and sigops. */
Expand All @@ -141,24 +160,28 @@ struct MempoolAcceptResult {
* using prioritisetransaction (i.e. modified fees). If this transaction was submitted as a
* package, this is the package feerate, which may also include its descendants and/or
* ancestors (see m_wtxids_fee_calculations below).
* Only present when m_result_type = ResultType::VALID.
*/
const std::optional<CFeeRate> m_effective_feerate;
/** Contains the wtxids of the transactions used for fee-related checks. Includes this
* transaction's wtxid and may include others if this transaction was validated as part of a
* package. This is not necessarily equivalent to the list of transactions passed to
* ProcessNewPackage().
* Only present when m_result_type = ResultType::VALID. */
*/
const std::optional<std::vector<uint256>> m_wtxids_fee_calculations;

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

static MempoolAcceptResult Failure(TxValidationState state) {
return MempoolAcceptResult(state);
}

static MempoolAcceptResult FeeFailure(TxValidationState state,
CFeeRate effective_feerate,
const std::vector<uint256>& wtxids_fee_calculations) {
return MempoolAcceptResult(state, effective_feerate, wtxids_fee_calculations);
}

static MempoolAcceptResult Success(std::list<CTransactionRef>&& replaced_txns,
int64_t vsize,
CAmount fees,
Expand Down Expand Up @@ -197,6 +220,15 @@ struct MempoolAcceptResult {
m_effective_feerate(effective_feerate),
m_wtxids_fee_calculations(wtxids_fee_calculations) {}

/** Constructor for fee-related failure case */
explicit MempoolAcceptResult(TxValidationState state,
CFeeRate effective_feerate,
const std::vector<uint256>& wtxids_fee_calculations)
: m_result_type(ResultType::INVALID),
m_state(state),
m_effective_feerate(effective_feerate),
m_wtxids_fee_calculations(wtxids_fee_calculations) {}

/** Constructor for already-in-mempool case. It wouldn't replace any transactions. */
explicit MempoolAcceptResult(int64_t vsize, CAmount fees)
: m_result_type(ResultType::MEMPOOL_ENTRY), m_vsize{vsize}, m_base_fees(fees) {}
Expand Down

0 comments on commit 6b0dc66

Please sign in to comment.