Repository: bitcoin/bitcoin
File: src/rpc/mempool.cpp (C++)

Code before:
                    const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
                    if (max_raw_tx_fee && fee > max_raw_tx_fee) {
                        result_inner.pushKV("allowed", false);
                        result_inner.pushKV("reject-reason", "max-fee-exceeded");
                        exit_early = true;
                    } else {
                        // Only return the fee and vsize if the transaction would pass ATMP.
                        // These can be used to calculate the feerate.
                        result_inner.pushKV("allowed", true);
                        result_inner.pushKV("vsize", virtual_size);
                        UniValue fees(UniValue::VOBJ);
                        fees.pushKV("base", ValueFromAmount(fee));
                        fees.pushKV("effective-feerate", ValueFromAmount(tx_result.m_effective_feerate.value().GetFeePerK()));
                        UniValue effective_includes_res(UniValue::VARR);
                        for (const auto& wtxid : tx_result.m_wtxids_fee_calculations.value()) {
                            effective_includes_res.push_back(wtxid.ToString());
                        }
                        fees.pushKV("effective-includes", std::move(effective_includes_res));
                        result_inner.pushKV("fees", std::move(fees));
                    }
                } else {
                    result_inner.pushKV("allowed", false);
                    const TxValidationState state = tx_result.m_state;
                    if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
                        result_inner.pushKV("reject-reason", "missing-inputs");
                    } else {
                        result_inner.pushKV("reject-reason", state.GetRejectReason());
                    }
                }
                rpc_result.push_back(std::move(result_inner));
            }
            return rpc_result;
        },
    };
}

static std::vector<RPCResult> MempoolEntryDescription()
{
    return {
        RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
        RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
        RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
        RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
        RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
        RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
        RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
        RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
        RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
        RPCResult{RPCResult::Type::OBJ, "fees", "",
            {
                RPCResult{RPCResult::Type::STR_AMOUNT, "base", "transaction fee, denominated in " + CURRENCY_UNIT},

Review comment (bug):
A simpler way to do this might just be `ToString()`.

It ensures the field exists as long as reject-reason exists. That'd be a simpler interface to understand, as the current one depends on very specific internal stuff - I can't really tell you off the top of my head where we have/don't add a debug string.

It does mean the (very short) first part of the error string is repeated in both places, but perhaps that is convenient for users that want the more verbose one.

Also nit, I heard underscores are better for json parsers
```suggestion
                        result_inner.pushKV("reject_details", state.ToString());
```

Code after:
                    const int64_t virtual_size = tx_result.m_vsize.value();
                    const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
                    if (max_raw_tx_fee && fee > max_raw_tx_fee) {
                        result_inner.pushKV("allowed", false);
                        result_inner.pushKV("reject-reason", "max-fee-exceeded");
                        exit_early = true;
                    } else {
                        // Only return the fee and vsize if the transaction would pass ATMP.
                        // These can be used to calculate the feerate.
                        result_inner.pushKV("allowed", true);
                        result_inner.pushKV("vsize", virtual_size);
                        UniValue fees(UniValue::VOBJ);
                        fees.pushKV("base", ValueFromAmount(fee));
                        fees.pushKV("effective-feerate", ValueFromAmount(tx_result.m_effective_feerate.value().GetFeePerK()));
                        UniValue effective_includes_res(UniValue::VARR);
                        for (const auto& wtxid : tx_result.m_wtxids_fee_calculations.value()) {
                            effective_includes_res.push_back(wtxid.ToString());
                        }
                        fees.pushKV("effective-includes", std::move(effective_includes_res));
                        result_inner.pushKV("fees", std::move(fees));
                    }
                } else {
                    result_inner.pushKV("allowed", false);
                    const TxValidationState state = tx_result.m_state;
                    if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
                        result_inner.pushKV("reject-reason", "missing-inputs");
                    } else {
                        result_inner.pushKV("reject-reason", state.GetRejectReason());
                        result_inner.pushKV("reject-details", state.ToString());
                    }
                }
                rpc_result.push_back(std::move(result_inner));
            }
            return rpc_result;
        },
    };
}

static std::vector<RPCResult> MempoolEntryDescription()
{
    return {
        RPCResult{RPCResult::Type::NUM, "vsize", "virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted."},
        RPCResult{RPCResult::Type::NUM, "weight", "transaction weight as defined in BIP 141."},
        RPCResult{RPCResult::Type::NUM_TIME, "time", "local time transaction entered pool in seconds since 1 Jan 1970 GMT"},
        RPCResult{RPCResult::Type::NUM, "height", "block height when transaction entered pool"},
        RPCResult{RPCResult::Type::NUM, "descendantcount", "number of in-mempool descendant transactions (including this one)"},
        RPCResult{RPCResult::Type::NUM, "descendantsize", "virtual transaction size of in-mempool descendants (including this one)"},
        RPCResult{RPCResult::Type::NUM, "ancestorcount", "number of in-mempool ancestor transactions (including this one)"},
        RPCResult{RPCResult::Type::NUM, "ancestorsize", "virtual transaction size of in-mempool ancestors (including this one)"},
        RPCResult{RPCResult::Type::STR_HEX, "wtxid", "hash of serialized transaction, including witness data"},
        RPCResult{RPCResult::Type::OBJ, "fees", "",