8000 refactor: Convert GenTxid to `std::variant` by marcofleon · Pull Request #32631 · bitcoin/bitcoin · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

refactor: Convert GenTxid to std::variant #32631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

marcofleon
Copy link
Contributor

Part of the type safety refactor.

This PR changes the GenTxid class to a variant, which holds both Txids and Wtxids. This provides compile-time type safety and eliminates the manual type check (bool m_is_wtxid). Variables that can be either a Txid or a Wtxid are now using the new GenTxid variant, instead of 8000 uint256.

@DrahtBot
Copy link
Contributor
DrahtBot commented May 28, 2025

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32631.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK dergoegge, stickies-v, instagibbs, theStack, hodlinator

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32730 (p2p: avoid traversing blocks (twice) during IBD by furszy)
  • #32189 (refactor: Txid type safety (parent PR) by marcofleon)
  • #31829 (p2p: improve TxOrphanage denial of service bounds by glozow)
  • #30116 (p2p: Fill reconciliation sets (Erlay) attempt 2 by sr-gi)
  • #29641 (scripted-diff: Use LogInfo over LogPrintf [WIP, NOMERGE, DRAFT] by maflcko)
  • #29415 (Broadcast own transactions only via short-lived Tor or I2P connections by vasild)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@marcofleon
Copy link
Contributor Author

In txrequest the ByPeer and ByTxHash indexes still sort announcements by the underlying uint256 txhash only, as opposed to using the GenTxid comparators which sort by type and then hash. The patch below can be used to make sure that there are no accidental switches from uint256 comparisons to GenTxid comparisons.

diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 011e3cd928..1a47a39dfd 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -206,6 +206,12 @@ struct QueuedBlock {
     std::unique_ptr<PartiallyDownloadedBlock> partialBlock;
 };
 
+struct CompareGenTxid {
+    bool operator()(const GenTxid& a, const GenTxid& b) const {
+        return std::tuple(a.index(), a.ToUint256()) < std::tuple(b.index(), b.ToUint256());
+    }
+};
+
 /**
  * Data structure for an individual peer. This struct is not protected by
  * cs_main since it does not contain validation-critical data.
@@ -302,7 +308,7 @@ struct Peer {
          *  non-wtxid-relay peers, wtxid for wtxid-relay peers). We use the
          *  mempool to sort transactions in dependency order before relay, so
          *  this does not have to be sorted. */
-        std::set<GenTxid> m_tx_inventory_to_send GUARDED_BY(m_tx_inventory_mutex);
+        std::set<GenTxid, CompareGenTxid> m_tx_inventory_to_send GUARDED_BY(m_tx_inventory_mutex);
         /** Whether the peer has requested us to send our complete mempool. Only
          *  permitted if the peer has NetPermissionFlags::Mempool or we advertise
          *  NODE_BLOOM. See BIP35. */
@@ -856,7 +862,7 @@ private:
     std::shared_ptr<const CBlock> m_most_recent_block GUARDED_BY(m_most_recent_block_mutex);
     std::shared_ptr<const CBlockHeaderAndShortTxIDs> m_most_recent_compact_block GUARDED_BY(m_most_recent_block_mutex);
     uint256 m_most_recent_block_hash GUARDED_BY(m_most_recent_block_mutex);
-    std::unique_ptr<const std::map<GenTxid, CTransactionRef>> m_most_recent_block_txs GUARDED_BY(m_most_recent_block_mutex);
+    std::unique_ptr<const std::map<GenTxid, CTransactionRef, CompareGenTxid>> m_most_recent_block_txs GUARDED_BY(m_most_recent_block_mutex);
 
     // Data about the low-work headers synchronization, aggregated from all peers' HeadersSyncStates.
     /** Mutex guarding the other m_headers_presync_* variables. */
@@ -2027,7 +2033,7 @@ void PeerManagerImpl::NewPoWValidBlock(const CBlockIndex *pindex, const std::sha
         std::async(std::launch::deferred, [&] { return NetMsg::Make(NetMsgType::CMPCTBLOCK, *pcmpctblock); })};
 
     {
-        auto most_recent_block_txs = std::make_unique<std::map<GenTxid, CTransactionRef>>();
+        auto most_recent_block_txs = std::make_unique<std::map<GenTxid, CTransactionRef, CompareGenTxid>>();
         for (const auto& tx : pblock->vtx) {
             most_recent_block_txs->emplace(tx->GetHash(), tx);
             most_recent_block_txs->emplace(tx->GetWitnessHash(), tx);
diff --git a/src/util/transaction_identifier.h b/src/util/transaction_identifier.h
index 3cdeb664d4..6ab5d98263 100644
--- a/src/util/transaction_identifier.h
+++ b/src/util/transaction_identifier.h
@@ -88,13 +88,12 @@ p
8000
ublic:
         return std::visit([](const auto& id) -> const uint256& { return id.ToUint256(); }, *this);
     }
 
-    friend bool operator==(const GenTxid& a, const GenTxid& b) {
-        return a.index() == b.index() && a.ToUint256() == b.ToUint256();
-    }
-
-    friend bool operator<(const GenTxid& a, const GenTxid& b) {
-        return std::tuple(a.index(), a.ToUint256()) < std::tuple(b.index(), b.ToUint256());
-    }
+    friend bool operator==(const GenTxid& a, const GenTxid& b) = delete;
+    friend bool operator!=(const GenTxid& a, const GenTxid& b) = delete;
+    friend bool operator<(const GenTxid& a, const GenTxid& b) = delete;
+    friend bool operator<=(const GenTxid& a, const GenTxid& b) = delete;
+    friend bool operator>(const GenTxid& a, const GenTxid& b) = delete;
+    friend bool operator>=(const GenTxid& a, const GenTxid& b) = delete;
 };
 
 #endif // BITCOIN_UTIL_TRANSACTION_IDENTIFIER_H
 

Then run cmake --build build --target bitcoind. To check all tests as well, comment out the unit and fuzz tests for txrequest, as these tests directly compare GenTxid objects.

Copy link
Member
@dergoegge dergoegge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

I'm not sure if the intermediate commits make things better here. Looking at the final diff seems simple enough.

Copy link
Contributor
@stickies-v stickies-v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, the new GenTxid allows us to better benefit from the type safety of Txid and Wtxid and this PR takes a clear and structured approach that seems straightforward to review.

Approach-wise, I think I have a slightly different view. I find code generally easier to understand when we use the underlying types {Txid, Wtxid} directly and as such would prefer functions to be overloaded with those types, pushing the handling of variants to the edge as much as possible. This also has the side benefit of gently nudging users of those functions to not using variants if they don't have to. I have left 2 such comments on the first commits, but I suspect it applies to later commits too (did not investigate that in detail yet). This is largely a style preference, and I don't want to block the PRs progress over it if other reviewers disagree.

@instagibbs
Copy link
Member

concept ACK

@marcofleon marcofleon force-pushed the 2025/05/gentxid-variant branch from 98358c4 to f7c8f3f Compare June 3, 2025 17:20
@DrahtBot
Copy link
Contributor
DrahtBot commented Jun 3, 2025

🚧 At least one of the CI tasks failed.
Task TSan, depends, gui: https://github.com/bitcoin/bitcoin/runs/43405323838
LLM reason (✨ experimental): The CI failure is caused by compilation errors due to missing 'util::Overloaded' and 'gtxid.ToVariant' members, indicating code incompatibility or missing definitions.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@marcofleon marcofleon force-pushed the 2025/05/gentxid-variant branch 2 times, most recently from a3286b9 to a853dcb Compare June 3, 2025 18:00
@theStack
Copy link
Contributor
theStack commented Jun 4, 2025

Concept ACK

@marcofleon marcofleon force-pushed the 2025/05/gentxid-variant branch 2 times, most recently from 17af8a7 to b740b6f Compare June 4, 2025 13:58
@marcofleon
Copy link
Contributor Author

Thanks for reviewing @stickies-v! I took your suggestion for mempool's exists, info, and info_for_relay. These are all fairly simple functions where I agree that the separate txid/wtxid overloads make the code clearer.

I looked at txrequest and txdownloadman_impl to see where txid and wtxid could be used internally in place of the variant, but it seems to be a less straightfoward refactor. Maybe AddTxAnnouncement could be a good candidate? If you see something that I'm missing (or isn't as complicated as I might think it is), let me know and I'll give it a go, if not in this PR then in a follow-up.

@DrahtBot DrahtBot removed the CI failed label Jun 4, 2025
Copy link
Contributor
@hodlinator hodlinator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK b740b6f

Comment on lines +806 to +809
indexed_transaction_set::const_iterator j = std::visit(util::Overloaded{
[this](const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs) { return get_iter_from_wtxid(wtxid); },
[this](const Txid& txid) EXCLUSIVE_LOCKS_REQUIRED(cs) { return mapTx.find(txid); }
}, hashb);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit in ea58f45:

At first it seems more optimal to keep taking bool wtxid as a function parameter, and assume our GenTxidVariants are of the expected type. The current way gives the impression that we may be mixing Txid with Wtxid for the same peer.

Suggested change
indexed_transaction_set::const_iterator j = std::visit(util::Overloaded{
[this](const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs) { return get_iter_from_wtxid(wtxid); },
[this](const Txid& txid) EXCLUSIVE_LOCKS_REQUIRED(cs) { return mapTx.find(txid); }
}, hashb);
indexed_transaction_set::const_iterator j = wtxid ? get_iter_from_wtxid(std::get<Wtxid>(hashb)) : mapTx.find(std::get<Txid>(hashb));

A strong argument against reverting to that is that std::get still checks the index matches internally, and std::variant has no zero-overhead alternative. So we might as well use std::visit and drop the function parameter as you have done.

Might be worth adding something to the commit message such as:
"Now that we are storing CTxMemPool::CompareDepthAndScore parameters using std::variant we have no portable zero-overhead way of accessing them, so use std::visit and drop bool wtxid in-parameter."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing. I don't think I can improve upon your suggested commit message, so hope you don't mind that I took this directly.

@marcofleon marcofleon force-pushed the 2025/05/gentxid-variant branch 3 times, most recently from 67fbc3a to d5a6e7a Compare June 10, 2025 16:01
Copy link
Contributor
@hodlinator hodlinator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review d5a6e7a

Thanks for incorporating most of my feedback!

Mostly focused on reviewing non-test code.

@@ -435,7 +426,7 @@ class TxRequestTracker::Impl {
auto it_prev = std::prev(it);
// The next best CANDIDATE_READY, if any, immediately precedes the REQUESTED or CANDIDATE_BEST
A93C // announcement in the ByTxHash index.
if (it_prev->m_txhash == it->m_txhash && it_prev->GetState() == State::CANDIDATE_READY) {
if (it_prev->m_txhash.ToUint256() == it->m_txhash.ToUint256() && it_prev->GetState() == State::CANDIDATE_READY) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for segwit peers we use Wtxid and for non-segwit peers we use Txid?
Scary that the variant fails comparison although the hash is the same, so we have to manually find places where it shouldn't matter and do this kind of thing. :\

Might be easier to ACK this PR if you peel off txrequest.cpp/h into it's own PR.

Copy link
Contributor Author
@marcofleon marcofleon Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the inv should match the peer's wtxid relay setting. The announcements are converted to GenTxids in ProcessMessage and that's what's passed into AddTxAnnouncement and used throughout txdownloadman_impl and txrequest.

I would prefer to keep it all in this PR, as I don't want both the old GenTxid class and the new variant in master. But I agree it's a bit iffy to have to manually ensure that we didn't miss a place where a previous uint256 comparison is now suddenly a GenTxid comparison. The patch I provide at the top should catch anywhere that I might've missed.

Yes, with this PR's change, we would have be intentional about when we're comparing GenTxid objects (type first, then hash) and when we're comparing only the underlying hash. Another option would be to make the patch an actual part of the code, deleting the GenTxid comparators and having custom comparators wherever GenTxids are involved. This felt like overkill to me though, and I think one of the goals of this type safety refactor is to, from now on, differentiate between the transaction identifiers and the underlying transaction hash. In that case, it makes sense to me to have a standard way to compare GenTxids and then make the conversion to uint256 explicit if comparison of the hash is needed.

Copy link
Contributor
@hodlinator hodlinator Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a closer look at TxRequestTracker.

  1. I support replacing uint256 Announcement::m_txhash+bool Announcement::m_is_wtxid with a GenTxId, but would prefer renaming the field just to force out any issues.

  2. On master the interface seems to deliberately use either GenTxId or uint256 depending on what we want to match on:

    bitcoin/src/txrequest.h

    Lines 135 to 178 in 1be688f

    void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,
    std::chrono::microseconds reqtime);
    /** Deletes all announcements for a given peer.
    *
    * It should be called when a peer goes offline.
    */
    void DisconnectedPeer(NodeId peer);
    /** Deletes all announcements for a given txhash (both txid and wtxid ones).
    *
    * This should be called when a transaction is no longer needed. The caller should ensure that new announcements
    * for the same txhash will not trigger new ReceivedInv calls, at least in the short term after this call.
    */
    void ForgetTxHash(const uint256& txhash);
    /** Find the txids to request now from peer.
    *
    * It does the following:
    * - Convert all REQUESTED announcements (for all txhashes/peers) with (expiry <= now) to COMPLETED ones.
    * These are returned in expired, if non-nullptr.
    * - Requestable announcements are selected: CANDIDATE announcements from the specified peer with
    * (reqtime <= now) for which no existing REQUESTED announcement with the same txhash from a different peer
    * exists, and for which the specified peer is the best choice among all (reqtime <= now) CANDIDATE
    * announcements with the same txhash (subject to preferredness rules, and tiebreaking using a deterministic
    * salted hash of peer and txhash).
    * - The selected announcements are converted to GenTxids using their is_wtxid flag, and returned in
    * announcement order (even if multiple were added at the same time, or when the clock went backwards while
    * they were being added). This is done to minimize disruption from dependent transactions being requested
    * out of order: if multiple dependent transactions are announced simultaneously by one peer, and end up
    * being requested from them, the requests will happen in announcement order.
    */
    std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now,
    std::vector<std::pair<NodeId, GenTxid>>* expired = nullptr);
    /** Marks a transaction as requested, with a specified expiry.
    *
    * If no CANDIDATE announcement for the provided peer and txhash exists, this call has no effect. Otherwise:
    * - That announcement is converted to REQUESTED.
    * - If any other REQUESTED announcement for the same txhash already existed, it means an unexpected request
    * was made (GetRequestable will never advise doing so). In this case it is converted to COMPLETED, as we're
    * no longer waiting for a response to it.
    */
    void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds expiry);

    All of the below are changed in the current version of this PR to use GenTxId instead of uint256, but then only end up using the uint256-part internally:

    • ForgetTxHash()
    • RequestedTx()
    • ReceivedResponse()
    • GetCandidatePeers()
    • ComputePriority()

    It feels safer to at least drop those kind of changes on principle, even if they currently do not introduce bugs. We don't want to accidentally introduce cases where a Wtxid of a transaction without any witness data does not match a Txid with the same exact hash.

Reimplements the GenTxid class as a variant for better type safety.
Also adds two temporary functions to the old GenTxid class that
convert to and from the new variant.
Now that we are storing `CTxMemPool::CompareDepthAndScore` parameters using
`std::variant` we have no portable zero-overhead way of accessing them,
so use `std::visit` and drop `bool wtxid` in-parameter.
Convert all of `txdownloadman_impl` to the new variant except for
`GetRequestsToSend`, which will be easier to switch at the same
time as `txrequest`.
Switch all instances of GenTxid to the new variant
in `txrequest` and complete `txdownloadman_impl` by
converting `GetRequestsToSend`.
-BEGIN VERIFY SCRIPT-
sed -i 's/GenTxidVariant/GenTxid/g' $(git grep -l 'GenTxidVariant')
-END VERIFY SCRIPT-
@@ -435,7 +426,7 @@ class TxRequestTracker::Impl {
auto it_prev = std::prev(it);
// The next best CANDIDATE_READY, if any, immediately precedes the REQUESTED or CANDIDATE_BEST
// announcement in the ByTxHash index.
if (it_prev->m_txhash == it->m_txhash && it_prev->GetState() == State::CANDIDATE_READY) {
if (it_prev->m_txhash.ToUint256() == it->m_txhash.ToUint256() && it_prev->GetState() == State::CANDIDATE_READY) {
Copy link
Contributor
@hodlinator hodlinator Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a closer look at TxRequestTracker.

  1. I support replacing uint256 Announcement::m_txhash+bool Announcement::m_is_wtxid with a GenTxId, but would prefer renaming the field just to force out any issues.

  2. On master the interface seems to deliberately use either GenTxId or uint256 depending on what we want to match on:

    bitcoin/src/txrequest.h

    Lines 135 to 178 in 1be688f

    void ReceivedInv(NodeId peer, const GenTxid& gtxid, bool preferred,
    std::chrono::microseconds reqtime);
    /** Deletes all announcements for a given peer.
    *
    * It should be called when a peer goes offline.
    */
    void DisconnectedPeer(NodeId peer);
    /** Deletes all announcements for a given txhash (both txid and wtxid ones).
    *
    * This should be called when a transaction is no longer needed. The caller should ensure that new announcements
    * for the same txhash will not trigger new ReceivedInv calls, at least in the short term after this call.
    */
    void ForgetTxHash(const uint256& txhash);
    /** Find the txids to request now from peer.
    *
    * It does the following:
    * - Convert all REQUESTED announcements (for all txhashes/peers) with (expiry <= now) to COMPLETED ones.
    * These are returned in expired, if non-nullptr.
    * - Requestable announcements are selected: CANDIDATE announcements from the specified peer with
    * (reqtime <= now) for which no existing REQUESTED announcement with the same txhash from a different peer
    * exists, and for which the specified peer is the best choice among all (reqtime <= now) CANDIDATE
    * announcements with the same txhash (subject to preferredness rules, and tiebreaking using a deterministic
    * salted hash of peer and txhash).
    * - The selected announcements are converted to GenTxids using their is_wtxid flag, and returned in
    * announcement order (even if multiple were added at the same time, or when the clock went backwards while
    * they were being added). This is done to minimize disruption from dependent transactions being requested
    * out of order: if multiple dependent transactions are announced simultaneously by one peer, and end up
    * being requested from them, the requests will happen in announcement order.
    */
    std::vector<GenTxid> GetRequestable(NodeId peer, std::chrono::microseconds now,
    std::vector<std::pair<NodeId, GenTxid>>* expired = nullptr);
    /** Marks a transaction as requested, with a specified expiry.
    *
    * If no CANDIDATE announcement for the provided peer and txhash exists, this call has no effect. Otherwise:
    * - That announcement is converted to REQUESTED.
    * - If any other REQUESTED announcement for the same txhash already existed, it means an unexpected request
    * was made (GetRequestable will never advise doing so). In this case it is converted to COMPLETED, as we're
    * no longer waiting for a response to it.
    */
    void RequestedTx(NodeId peer, const uint256& txhash, std::chrono::microseconds expiry);

    All of the below are changed in the current version of this PR to use GenTxId instead of uint256, but then only end up using the uint256-part internally:

    • ForgetTxHash()
    • RequestedTx()
    • ReceivedResponse()
    • GetCandidatePeers()
    • ComputePriority()

    It feels safer to at least drop those kind of changes on principle, even if they currently do not introduce bugs. We don't want to accidentally introduce cases where a Wtxid of a transaction without any witness data does not match a Txid with the same exact hash.


bool IsWtxid() const { return std::holds_alternative<Wtxid>(*this); }

const uint256& ToUint256() const LIFETIMEBOUND {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Braces should be on newline for functions as per developer-notes.md, here and for the next one:

Suggested change
const uint256& ToUint256() const LIFETIMEBOUND {
const uint256& ToUint256() const LIFETIMEBOUND
{

Copy link
Contributor
@stickies-v stickies-v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the changes here can be simplified quite a bit by first replacing get_iter_from_wtxid with a GetIter(const wtxid&) overload, see my suggestions here and here (or full branch master...stickies-v:bitcoin:2025-06/overload-getiter).

There are also quite a few clang-tidy violations that would be helpful to address here, left per-commit comments.

@@ -76,4 +80,21 @@ using Txid = transaction_identifier<false>;
/** Wtxid commits to all transaction fields including the witness. */
using Wtxid = transaction_identifier<true>;

/** A generic txid reference (txid or wtxid). */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this docstring imo doesn't add anything useful. Would either remove or beef up (e.g. explaining when (not) to use this).

public:
explicit CompareInvMempoolOrder(CTxMemPool *_mempool, bool use_wtxid)
explicit CompareInvMempoolOrder(CTxMemPool *_mempool)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy nit:

Suggested change
explicit CompareInvMempoolOrder(CTxMemPool *_mempool)
explicit CompareInvMempoolOrder(CTxMemPool* _mempool)

@@ -437,6 +437,19 @@ class GenTxid
const uint256& GetHash() const LIFETIMEBOUND { return m_hash; }
friend bool operator==(const GenTxid& a, const GenTxid& b) { return a.m_is_wtxid == b.m_is_wtxid && a.m_hash == b.m_hash; }
friend bool operator<(const GenTxid& a, const GenTxid& b) { return std::tie(a.m_is_wtxid, a.m_hash) < std::tie(b.m_is_wtxid, b.m_hash); }

GenTxidVariant ToVariant() const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy nit for 5b5abf8:

git diff on a6f1af5f55
diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h
index 54c2fb578f..adadb2a3e7 100644
--- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -438,17 +438,18 @@ public:
     friend bool operator==(const GenTxid& a, const GenTxid& b) { return a.m_is_wtxid == b.m_is_wtxid && a.m_hash == b.m_hash; }
     friend bool operator<(const GenTxid& a, const GenTxid& b) { return std::tie(a.m_is_wtxid, a.m_hash) < std::tie(b.m_is_wtxid, b.m_hash); }
 
-    GenTxidVariant ToVariant() const {
+    GenTxidVariant ToVariant() const
+    {
         return m_is_wtxid ?
-            GenTxidVariant{Wtxid::FromUint256(m_hash)} :
-            GenTxidVariant{Txid::FromUint256(m_hash)};
+                   GenTxidVariant{Wtxid::FromUint256(m_hash)} :
+                   GenTxidVariant{Txid::FromUint256(m_hash)};
     }
 
-    static GenTxid FromVariant(const GenTxidVariant& variant) {
+    static GenTxid FromVariant(const GenTxidVariant& variant)
+    {
         return GenTxid{
             std::holds_alternative<::Wtxid>(variant),
-            variant.ToUint256()
-        };
+            variant.ToUint256()};
     }
 };
 
diff --git a/src/util/transaction_identifier.h b/src/util/transaction_identifier.h
index 4137dd7040..39104a1a5c 100644
--- a/src/util/transaction_identifier.h
+++ b/src/util/transaction_identifier.h
@@ -88,11 +88,13 @@ public:
 
     bool IsWtxid() const { return std::holds_alternative<Wtxid>(*this); }
 
-    const uint256& ToUint256() const LIFETIMEBOUND {
+    const uint256& ToUint256() const LIFETIMEBOUND
+    {
         return std::visit([](const auto& id) -> const uint256& { return id.ToUint256(); }, *this);
     }
 
-    friend auto operator<=>(const GenTxidVariant& a, const GenTxidVariant& b) {
+    friend auto operator<=>(const GenTxidVariant& a, const GenTxidVariant& b)
+    {
         return std::tuple(a.IsWtxid(), a.ToUint256()) <=> std::tuple(b.IsWtxid(), b.ToUint256());
     }
 };

@@ -2437,7 +2439,9 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
continue;
}

CTransactionRef tx = FindTxForGetData(*tx_relay, ToGenTxid(inv));
CTransactionRef tx{inv.IsMsgWtx() ?
FindTxForGetData(*tx_relay, Wtxid::FromUint256(inv.hash)) :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy nit for da99271:

git diff on 07822f5386
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index df66d4c93d..a0ed1e2620 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2440,8 +2440,8 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic
         }
 
         CTransactionRef tx{inv.IsMsgWtx() ?
-                                FindTxForGetData(*tx_relay, Wtxid::FromUint256(inv.hash)) :
-                                FindTxForGetData(*tx_relay, Txid::FromUint256(inv.hash))};
+                               FindTxForGetData(*tx_relay, Wtxid::FromUint256(inv.hash)) :
+                               FindTxForGetData(*tx_relay, Txid::FromUint256(inv.hash))};
         if (tx) {
             // WTX and WITNESS_TX imply we serialize with witness
             const auto maybe_with_witness = (inv.IsMsgTx() ? TX_NO_WITNESS : TX_WITH_WITNESS);
diff --git a/src/validation.cpp b/src/validation.cpp
index 9d788b50f6..c0bc5f28fc 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1135,8 +1135,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
     AssertLockHeld(m_pool.cs);
 
     // CheckPackageLimits expects the package transactions to not already be in the mempool.
-    assert(std::all_of(txns.cbegin(), txns.cend(), [this](const auto& tx)
-                       { return !m_pool.exists(tx->GetHash());}));
+    assert(std::all_of(txns.cbegin(), txns.cend(), [this](const auto& tx) { return !m_pool.exists(tx->GetHash()); }));
 
     assert(txns.size() == workspaces.size());
 
@@ -1346,8 +1345,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
     AssertLockHeld(m_pool.cs);
     // Sanity check: none of the transactions should be in the mempool, and none of the transactions
     // should have a same-txid-different-witness equivalent in the mempool.
-    assert(std::all_of(workspaces.cbegin(), workspaces.cend(), [this](const auto& ws){
-        return !m_pool.exists(ws.m_ptx->GetHash()); }));
+    assert(std::all_of(workspaces.cbegin(), workspaces.cend(), [this](const auto& ws) { return !m_pool.exists(ws.m_ptx->GetHash()); }));
 
     bool all_submitted = true;
     FinalizeSubpackage(args);

const uint256& hash = gtxid.GetHash();

if (gtxid.IsWtxid()) {
bool in_orphanage = std::visit(util::Overloaded{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy nit in 151ab0e:

git diff on 44c6c811ba
diff --git a/src/node/txdownloadman_impl.cpp b/src/node/txdownloadman_impl.cpp
index c169c3bf41..054c8fb7dd 100644
--- a/src/node/txdownloadman_impl.cpp
+++ b/src/node/txdownloadman_impl.cpp
@@ -126,21 +126,21 @@ void TxDownloadManagerImpl::BlockDisconnected()
 bool TxDownloadManagerImpl::AlreadyHaveTx(const GenTxidVariant& gtxid, bool include_reconsiderable)
 {
     bool in_orphanage = std::visit(util::Overloaded{
-        // Normal query by wtxid.
-        [this](const Wtxid& wtxid) { return m_orphanage.HaveTx(wtxid); },
-        // Never query by txid: it is possible that the transaction in the orphanage has the same
-        // txid but a different witness, which would give us a false positive result. If we decided
-        // not to request the transaction based on this result, an attacker could prevent us from
-        // downloading a transaction by intentionally creating a malleated version of it.  While
-        // only one (or none!) of these transactions can ultimately be confirmed, we have no way of
-        // discerning which one that is, so the orphanage can store multiple transactions with the
-        // same txid.
-        //
-        // While we won't query by txid, we can try to "guess" what the wtxid is based on the txid.
-        // A non-segwit transaction's txid == wtxid. Query this txid "casted" to a wtxid. This will
-        // help us find non-segwit transactions, saving bandwidth, and should have no false positives.
-        [this](const Txid& txid) { return m_orphanage.HaveTx(Wtxid::FromUint256(txid.ToUint256())); }
-    }, gtxid);
+                                       // Normal query by wtxid.
+                                       [this](const Wtxid& wtxid) { return m_orphanage.HaveTx(wtxid); },
+                                       // Never query by txid: it is possible that the transaction in the orphanage has the same
+                                       // txid but a different witness, which would give us a false positive result. If we decided
+                                       // not to request the transaction based on this result, an attacker could prevent us from
+                                       // downloading a transaction by intentionally creating a malleated version of it.  While
+                                       // only one (or none!) of these transactions can ultimately be confirmed, we have no way of
+                                       // discerning which one that is, so the orphanage can store multiple transactions with the
+                                       // same txid.
+                                       //
+                                       // While we won't query by txid, we can try to "guess" what the wtxid is based on the txid.
+                                       // A non-segwit transaction's txid == wtxid. Query this txid "casted" to a wtxid. This will
+                                       // help us find non-segwit transactions, saving bandwidth, and should have no false positives.
+                                       [this](const Txid& txid) { return m_orphanage.HaveTx(Wtxid::FromUint256(txid.ToUint256())); }},
+                                   gtxid);
 
     if (in_orphanage) return true;
 
@@ -183,7 +183,7 @@ bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxidVariant&
     if (const auto* wtxid = std::get_if<Wtxid>(&gtxid)) {
         if (auto orphan_tx{m_orphanage.GetTx(*wtxid)}) {
             auto unique_parents{GetUniqueParents(*orphan_tx)};
-            std::erase_if(unique_parents, [&](const auto& txid){
+            std::erase_if(unique_parents, [&](const auto& txid) {
                 return AlreadyHaveTx(txid, /*include_reconsiderable=*/false);
             });
 
@@ -398,7 +398,7 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
                 // Filter parents that we already have.
                 // Exclude m_lazy_recent_rejects_reconsiderable: the missing parent may have been
                 // previously rejected for being too low feerate. This orphan might CPFP it.
-                std::erase_if(unique_parents, [&](const auto& txid){
+                std::erase_if(unique_parents, [&](const auto& txid) {
                     return AlreadyHaveTx(txid, /*include_reconsiderable=*/false);
                 });
                 const auto now{GetTime<std::chrono::microseconds>()};
diff --git a/src/test/fuzz/txdownloadman.cpp b/src/test/fuzz/txdownloadman.cpp
index bdb38242f6..f2fb186b8e 100644
--- a/src/test/fuzz/txdownloadman.cpp
+++ b/src/test/fuzz/txdownloadman.cpp
@@ -263,8 +263,7 @@ FUZZ_TARGET(txdownloadman, .init = initialize)
                     // returned true.
                     Assert(expect_work);
                 }
-            }
-        );
+            });
         // Jump forwards or backwards
         auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS);
         if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1;
@@ -438,8 +437,7 @@ FUZZ_TARGET(txdownloadman_impl, .init = initialize)
                     state_missing_inputs.Invalid(TxValidationResult::TX_MISSING_INPUTS, "");
                     txdownload_impl.MempoolRejectedTx(ptx, state_missing_inputs, rand_peer, fuzzed_data_provider.ConsumeBool());
                 }
-            }
-        );
+            });
 
         auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS);
         if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1;

auto requestable = m_txrequest.GetRequestable(nodeid, current_time, &expired);
for (const auto& entry : expired) {
LogDebug(BCLog::NET, "timeout of inflight %s %s from peer=%d\n", entry.second.IsWtxid() ? "wtx" : "tx",
entry.second.GetHash().ToString(), entry.first);
entry.second.ToUint256().ToString(), entry.first);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy nit in 6c2ba58:

git diff on 50c04bce87
diff --git a/src/node/txdownloadman_impl.cpp b/src/node/txdownloadman_impl.cpp
index 8db30d21d1..a514ac8143 100644
--- a/src/node/txdownloadman_impl.cpp
+++ b/src/node/txdownloadman_impl.cpp
@@ -275,12 +275,12 @@ std::vector<GenTxidVariant> TxDownloadManagerImpl::GetRequestsToSend(NodeId node
     auto requestable = m_txrequest.GetRequestable(nodeid, current_time, &expired);
     for (const auto& entry : expired) {
         LogDebug(BCLog::NET, "timeout of inflight %s %s from peer=%d\n", entry.second.IsWtxid() ? "wtx" : "tx",
-            entry.second.ToUint256().ToString(), entry.first);
+                 entry.second.ToUint256().ToString(), entry.first);
     }
     for (const GenTxidVariant& gtxid : requestable) {
         if (!AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)) {
             LogDebug(BCLog::NET, "Requesting %s %s peer=%d\n", gtxid.IsWtxid() ? "wtx" : "tx",
-                gtxid.ToUint256().ToString(), nodeid);
+                     gtxid.ToUint256().ToString(), nodeid);
             requests.emplace_back(gtxid);
             m_txrequest.RequestedTx(nodeid, gtxid, current_time + GETDATA_TX_INTERVAL);
         } else {
diff --git a/src/test/txrequest_tests.cpp b/src/test/txrequest_tests.cpp
index 298586db37..e6b3e8bc15 100644
--- a/src/test/txrequest_tests.cpp
+++ b/src/test/txrequest_tests.cpp
@@ -161,17 +161,18 @@ public:
      *                   backwards (but note that the ordering of this event only follows the scenario's m_now.
      */
     void Check(NodeId peer, const std::vector<GenTxidVariant>& expected, size_t candidates, size_t inflight,
-        size_t completed, const std::string& checkname,
-        std::chrono::microseconds offset = std::chrono::microseconds{0})
+               size_t completed, const std::string& checkname,
+               std::chrono::microseconds offset = std::chrono::microseconds{0})
     {
         const auto comment = m_testname + " " + checkname;
         auto& runner = m_runner;
         const auto now = m_now;
         assert(offset.count() <= 0);
-        runner.actions.emplace_back(m_now, [=,&runner]() {
+        runner.actions.emplace_back(m_now, [=, &runner]() {
             std::vector<std::pair<NodeId, GenTxidVariant>> expired_now;
             auto ret = runner.txrequest.GetRequestable(peer, now + offset, &expired_now);
-            for (const auto& entry : expired_now) runner.expired.insert(entry);
+            for (const auto& entry : expired_now)
+                runner.expired.insert(entry);
             runner.txrequest.SanityCheck();
             runner.txrequest.PostGetRequestableSanityCheck(now + offset);
             size_t total = candidates + inflight + completed;
@@ -193,7 +194,7 @@ public:
     {
         const auto& testname = m_testname;
         auto& runner = m_runner;
-        runner.actions.emplace_back(m_now, [=,&runner]() {
+        runner.actions.emplace_back(m_now, [=, &runner]() {
             auto it = runner.expired.find(std::pair<NodeId, GenTxidVariant>{peer, gtxid});
             BOOST_CHECK_MESSAGE(it != runner.expired.end(), "[" + testname + "] missing expiration");
             if (it != runner.expired.end()) runner.expired.erase(it);
diff --git a/src/txrequest.cpp b/src/txrequest.cpp
index ec2ebe978b..0d0e7543d1 100644
--- a/src/txrequest.cpp
+++ b/src/txrequest.cpp
@@ -575,7 +575,7 @@ public:
     }
 
     void ReceivedInv(NodeId peer, const GenTxidVariant& gtxid, bool preferred,
-        std::chrono::microseconds reqtime)
+                     std::chrono::microseconds reqtime)
     {
         // Bail out if we already have a CANDIDATE_BEST announcement for this (txhash, peer) combination. The case
         // where there is a non-CANDIDATE_BEST announcement already will be caught by the uniqueness property of the
@@ -595,7 +595,7 @@ public:
 
     //! Find the GenTxids to request now from peer.
     std::vector<GenTxidVariant> GetRequestable(NodeId peer, std::chrono::microseconds now,
-        std::vector<std::pair<NodeId, GenTxidVariant>>* expired)
+                                               std::vector<std::pair<NodeId, GenTxidVariant>>* expired)
     {
         // Move time.
         SetTimePoint(now, expired);
@@ -730,7 +730,7 @@ void TxRequestTracker::PostGetRequestableSanityCheck(std::chrono::microseconds n
 }
 
 void TxRequestTracker::ReceivedInv(NodeId peer, const GenTxidVariant& gtxid, bool preferred,
-    std::chrono::microseconds reqtime)
+                                   std::chrono::microseconds reqtime)
 {
     m_impl->ReceivedInv(peer, gtxid, preferred, reqtime);
 }
@@ -746,7 +746,7 @@ void TxRequestTracker::ReceivedResponse(NodeId peer, const GenTxidVariant& txhas
 }
 
 std::vector<GenTxidVariant> TxRequestTracker::GetRequestable(NodeId peer, std::chrono::microseconds now,
-    std::vector<std::pair<NodeId, GenTxidVariant>>* expired)
+                                                             std::vector<std::pair<NodeId, GenTxidVariant>>* expired)
 {
     return m_impl->GetRequestable(peer, now, expired);
 }
diff --git a/src/txrequest.h b/src/txrequest.h
index f32fb1f2b9..3f2acef3a3 100644
--- a/src/txrequest.h
+++ b/src/txrequest.h
@@ -133,7 +133,7 @@ public:
      * from the specified gtxid.
      */
     void ReceivedInv(NodeId peer, const GenTxidVariant& gtxid, bool preferred,
-        std::chrono::microseconds reqtime);
+                     std::chrono::microseconds reqtime);
 
     /** Deletes all announcements for a given peer.
      *
@@ -164,7 +164,7 @@ public:
      *    simultaneously by one peer, and end up being requested from them, the requests will happen in announcement order.
      */
     std::vector<GenTxidVariant> GetRequestable(NodeId peer, std::chrono::microseconds now,
-        std::vector<std::pair<NodeId, GenTxidVariant>>* expired = nullptr);
+                                               std::vector<std::pair<NodeId, GenTxidVariant>>* expired = nullptr);
 
     /** Marks a transaction as requested, with a specified expiry.
      *

Comment on lines +806 to +809
indexed_transaction_set::const_iterator j = std::visit(util::Overloaded{
[this](const Wtxid& wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs) { return get_iter_from_wtxid(wtxid); },
[this](const Txid& txid) EXCLUSIVE_LOCKS_REQUIRED(cs) { return mapTx.find(txid); }
}, hashb);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 3e378fd:

These visitors can be cleaned up quite a bit by first replacing get_iter_from_wtxid with a new GetIter(const Wtxid&) overload (see stickies-v@2585f12 and stickies-v@589357b). This code block then becomes quite a bit more concise. It increases the diff a bit in other places, but part of that is from increased type safety. Overall, I think it's sensible to make this change in this PR?

    auto j{std::visit([&](const auto& id) EXCLUSIVE_LOCKS_REQUIRED(cs) { return GetIter(id); }, hashb)};
    if (!j.has_value()) return false;
    auto i{std::visit([&](const auto& id) EXCLUSIVE_LOCKS_REQUIRED(cs) { return GetIter(id); }, hasha)};
    if (!i.has_value()) return true;
    uint64_t counta = i.value()->GetCountWithAncestors();
    uint64_t countb = j.value()->GetCountWithAncestors();

See the diff here (from branch master...stickies-v:bitcoin:2025-06/overload-getiter)

}

TxMempoolInfo CTxMemPool::info_for_relay(const GenTxid& gtxid, uint64_t last_sequence) const
TxMempoolInfo CTxMemPool::info(const Wtxid& wtxid) const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In da99271:

With my other suggestion to implement GetIter(const Wtxid&), the info and info_for_relay duplication can be avoided, because they can now be trivially templated (moving them into txmempool.h). It does also require moving GetInfo to the header (see stickies-v@8d62f6e), but I think that's okay (ideally that function would just be replaced with a TxMempoolInfo(txiter) ctor but that's a much bigger diff).

These two functions can then be implemented as

    template <typename T>
        requires std::is_same_v<T, Txid> || std::is_same_v<T, Wtxid>
    TxMempoolInfo info(const T& txid) const
    {
        LOCK(cs);
        auto i{GetIter(txid)};
        return i.has_value() ? GetInfo(*i) : TxMempoolInfo{};
    }

    /** Returns info for a transaction if its entry_sequence < last_sequence */
    template <typename T>
        requires std::is_same_v<T, Txid> || std::is_same_v<T, Wtxid>
    TxMempoolInfo info_for_relay(const T& txid, uint64_t last_sequence) const
    {
        LOCK(cs);
        auto i{GetIter(txid)};
        return (i.has_value() && i.value()->GetSequence() < last_sequence) ? GetInfo(*i) : TxMempoolInfo{};
    }

See the diff here as part of branch master...stickies-v:bitcoin:2025-06/overload-getiter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0