LCOV - code coverage report
Current view: top level - src/coinjoin - coinjoin.h (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 63 119 52.9 %
Date: 2026-06-25 07:23:43 Functions: 51 110 46.4 %

          Line data    Source code
       1             : // Copyright (c) 2014-2025 The Dash Core developers
       2             : // Distributed under the MIT/X11 software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #ifndef BITCOIN_COINJOIN_COINJOIN_H
       6             : #define BITCOIN_COINJOIN_COINJOIN_H
       7             : 
       8             : #include <coinjoin/common.h>
       9             : 
      10             : #include <util/helpers.h>
      11             : 
      12             : #include <core_io.h>
      13             : #include <netaddress.h>
      14             : #include <primitives/block.h>
      15             : #include <primitives/transaction.h>
      16             : #include <sync.h>
      17             : #include <timedata.h>
      18             : #include <util/translation.h>
      19             : #include <version.h>
      20             : 
      21             : #include <atomic>
      22             : #include <map>
      23             : #include <optional>
      24             : #include <utility>
      25             : 
      26             : #include <univalue.h>
      27             : 
      28             : class CChainState;
      29             : class CBLSPublicKey;
      30             : class CBlockIndex;
      31             : class ChainstateManager;
      32             : class CTxMemPool;
      33             : 
      34             : namespace chainlock {
      35             : class Chainlocks;
      36             : } // namespace chainlock
      37             : 
      38             : namespace llmq {
      39             : class CInstantSendManager;
      40             : } // namespace llmq
      41             : 
      42             : extern RecursiveMutex cs_main; // NOLINT(readability-redundant-declaration)
      43             : 
      44             : // timeouts
      45             : static constexpr int COINJOIN_AUTO_TIMEOUT_MIN = 5;
      46             : static constexpr int COINJOIN_AUTO_TIMEOUT_MAX = 15;
      47             : static constexpr int COINJOIN_QUEUE_TIMEOUT = 30;
      48             : static constexpr int COINJOIN_SIGNING_TIMEOUT = 15;
      49             : 
      50             : static constexpr size_t COINJOIN_ENTRY_MAX_SIZE = 9;
      51             : 
      52             : // pool responses
      53             : enum PoolMessage : int32_t {
      54             :     ERR_ALREADY_HAVE,
      55             :     ERR_DENOM,
      56             :     ERR_ENTRIES_FULL,
      57             :     ERR_EXISTING_TX,
      58             :     ERR_FEES,
      59             :     ERR_INVALID_COLLATERAL,
      60             :     ERR_INVALID_INPUT,
      61             :     ERR_INVALID_SCRIPT,
      62             :     ERR_INVALID_TX,
      63             :     ERR_MAXIMUM,
      64             :     ERR_MN_LIST,
      65             :     ERR_MODE,
      66             :     ERR_NON_STANDARD_PUBKEY, // not used
      67             :     ERR_NOT_A_MN, // not used
      68             :     ERR_QUEUE_FULL,
      69             :     ERR_RECENT,
      70             :     ERR_SESSION,
      71             :     ERR_MISSING_TX,
      72             :     ERR_VERSION,
      73             :     MSG_NOERR,
      74             :     MSG_SUCCESS,
      75             :     MSG_ENTRIES_ADDED,
      76             :     ERR_SIZE_MISMATCH,
      77             :     MSG_POOL_MIN = ERR_ALREADY_HAVE,
      78             :     MSG_POOL_MAX = ERR_SIZE_MISMATCH
      79             : };
      80             : template<> struct is_serializable_enum<PoolMessage> : std::true_type {};
      81             : 
      82             : // pool states
      83             : enum PoolState : int32_t {
      84             :     POOL_STATE_IDLE,
      85             :     POOL_STATE_QUEUE,
      86             :     POOL_STATE_ACCEPTING_ENTRIES,
      87             :     POOL_STATE_SIGNING,
      88             :     POOL_STATE_ERROR,
      89             :     POOL_STATE_MIN = POOL_STATE_IDLE,
      90             :     POOL_STATE_MAX = POOL_STATE_ERROR
      91             : };
      92             : template<> struct is_serializable_enum<PoolState> : std::true_type {};
      93             : 
      94             : // status update message constants
      95             : enum PoolStatusUpdate : int32_t {
      96             :     STATUS_REJECTED,
      97             :     STATUS_ACCEPTED
      98             : };
      99             : template<> struct is_serializable_enum<PoolStatusUpdate> : std::true_type {};
     100             : 
     101             : class CCoinJoinStatusUpdate
     102             : {
     103             : public:
     104           1 :     int nSessionID{0};
     105           1 :     PoolState nState{POOL_STATE_IDLE};
     106           1 :     int nEntriesCount{0}; // deprecated, kept for backwards compatibility
     107           1 :     PoolStatusUpdate nStatusUpdate{STATUS_ACCEPTED};
     108           1 :     PoolMessage nMessageID{MSG_NOERR};
     109             : 
     110           3 :     constexpr CCoinJoinStatusUpdate() = default;
     111             : 
     112           2 :     constexpr CCoinJoinStatusUpdate(int nSessionID, PoolState nState, int nEntriesCount, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) :
     113           1 :         nSessionID(nSessionID),
     114           1 :         nState(nState),
     115           1 :         nEntriesCount(nEntriesCount),
     116           1 :         nStatusUpdate(nStatusUpdate),
     117           2 :         nMessageID(nMessageID) {};
     118             : 
     119           0 :     SERIALIZE_METHODS(CCoinJoinStatusUpdate, obj)
     120             :     {
     121           0 :         READWRITE(obj.nSessionID, obj.nState, obj.nStatusUpdate, obj.nMessageID);
     122           0 :     }
     123             : };
     124             : 
     125             : class CCoinJoinAccept
     126             : {
     127             : public:
     128           7 :     int nDenom{0};
     129             :     CMutableTransaction txCollateral;
     130             : 
     131          21 :     CCoinJoinAccept() = default;
     132             : 
     133           0 :     CCoinJoinAccept(int nDenom, CMutableTransaction txCollateral) :
     134           0 :         nDenom(nDenom),
     135           0 :         txCollateral(std::move(txCollateral)){};
     136             : 
     137           0 :     SERIALIZE_METHODS(CCoinJoinAccept, obj)
     138             :     {
     139           0 :         READWRITE(obj.nDenom, obj.txCollateral);
     140           0 :     }
     141             : 
     142           4 :     friend bool operator==(const CCoinJoinAccept& a, const CCoinJoinAccept& b)
     143             :     {
     144           8 :         return a.nDenom == b.nDenom && CTransaction(a.txCollateral) == CTransaction(b.txCollateral);
     145           0 :     }
     146             : };
     147             : 
     148             : // A client's transaction in the mixing pool
     149           0 : class CCoinJoinEntry
     150             : {
     151             : public:
     152             :     std::vector<CTxDSIn> vecTxDSIn;
     153             :     std::vector<CTxOut> vecTxOut;
     154             :     CTransactionRef txCollateral;
     155             :     // memory only
     156             :     CService addr;
     157             : 
     158           0 :     CCoinJoinEntry() :
     159           0 :         txCollateral(MakeTransactionRef(CMutableTransaction{}))
     160           0 :     {
     161           0 :     }
     162             : 
     163           0 :     CCoinJoinEntry(std::vector<CTxDSIn> vecTxDSIn, std::vector<CTxOut> vecTxOut, const CTransaction& txCollateral) :
     164           0 :             vecTxDSIn(std::move(vecTxDSIn)),
     165           0 :             vecTxOut(std::move(vecTxOut)),
     166           0 :             txCollateral(MakeTransactionRef(txCollateral))
     167           0 :     {
     168           0 :     }
     169             : 
     170           0 :     SERIALIZE_METHODS(CCoinJoinEntry, obj)
     171             :     {
     172           0 :         READWRITE(obj.vecTxDSIn, obj.txCollateral, obj.vecTxOut);
     173           0 :     }
     174             : 
     175             :     bool AddScriptSig(const CTxIn& txin);
     176             : };
     177             : 
     178             : 
     179             : /**
     180             :  * A currently in progress mixing merge and denomination information
     181             :  */
     182             : class CCoinJoinQueue
     183             : {
     184             : public:
     185          11 :     int nDenom{0};
     186             :     COutPoint masternodeOutpoint;
     187             :     uint256 m_protxHash;
     188          11 :     int64_t nTime{0};
     189          11 :     bool fReady{false}; //ready for submit
     190             :     std::vector<unsigned char> vchSig;
     191             :     // memory only
     192          11 :     bool fTried{false};
     193             : 
     194          55 :     CCoinJoinQueue() = default;
     195             : 
     196           0 :     CCoinJoinQueue(int nDenom, const COutPoint& outpoint, const uint256& proTxHash, int64_t nTime, bool fReady) :
     197           0 :         nDenom(nDenom),
     198           0 :         masternodeOutpoint(outpoint),
     199           0 :         m_protxHash(proTxHash),
     200           0 :         nTime(nTime),
     201           0 :         fReady(fReady)
     202           0 :     {
     203           0 :     }
     204             : 
     205          18 :     SERIALIZE_METHODS(CCoinJoinQueue, obj)
     206             :     {
     207           6 :         READWRITE(obj.nDenom, obj.m_protxHash, obj.nTime, obj.fReady);
     208           6 :         if (!(s.GetType() & SER_GETHASH)) {
     209           2 :             READWRITE(obj.vchSig);
     210           2 :         }
     211           6 :     }
     212             : 
     213             :     [[nodiscard]] uint256 GetHash() const;
     214             :     [[nodiscard]] uint256 GetSignatureHash() const;
     215             : 
     216             :     /// Check if we have a valid Masternode address
     217             :     [[nodiscard]] bool CheckSignature(const CBLSPublicKey& blsPubKey) const;
     218             : 
     219             :     /// Check if a queue is too old or too far into the future
     220             :     [[nodiscard]] bool IsTimeOutOfBounds(int64_t current_time = GetAdjustedTime()) const;
     221             : 
     222             :     [[nodiscard]] std::string ToString() const;
     223             : 
     224           1 :     friend bool operator==(const CCoinJoinQueue& a, const CCoinJoinQueue& b)
     225             :     {
     226           1 :         return a.nDenom == b.nDenom && a.masternodeOutpoint == b.masternodeOutpoint && a.nTime == b.nTime && a.fReady == b.fReady;
     227             :     }
     228             : };
     229             : 
     230             : /** Helper class to store mixing transaction (tx) information.
     231             :  */
     232          12 : class CCoinJoinBroadcastTx
     233             : {
     234             : private:
     235             :     // memory only
     236             :     // when corresponding tx is 0-confirmed or conflicted, nConfirmedHeight is std::nullopt
     237      198815 :     std::optional<int> nConfirmedHeight{std::nullopt};
     238             : 
     239             : public:
     240             :     CTransactionRef tx;
     241             :     COutPoint masternodeOutpoint;
     242             :     uint256 m_protxHash;
     243             :     std::vector<unsigned char> vchSig;
     244      198795 :     int64_t sigTime{0};
     245      397633 :     CCoinJoinBroadcastTx() :
     246      198815 :         tx(MakeTransactionRef(CMutableTransaction{}))
     247      198818 :     {
     248      397633 :     }
     249             : 
     250           0 :     CCoinJoinBroadcastTx(CTransactionRef _tx, const COutPoint& _outpoint, const uint256& proTxHash, int64_t _sigTime) :
     251           0 :         tx(std::move(_tx)),
     252           0 :         masternodeOutpoint(_outpoint),
     253           0 :         m_protxHash(proTxHash),
     254           0 :         sigTime(_sigTime)
     255           0 :     {
     256           0 :     }
     257             : 
     258         612 :     SERIALIZE_METHODS(CCoinJoinBroadcastTx, obj)
     259             :     {
     260         204 :         READWRITE(obj.tx, obj.m_protxHash);
     261             : 
     262         204 :         if (!(s.GetType() & SER_GETHASH)) {
     263         204 :             READWRITE(obj.vchSig);
     264         204 :         }
     265         204 :         READWRITE(obj.sigTime);
     266         204 :     }
     267             : 
     268       88765 :     friend bool operator==(const CCoinJoinBroadcastTx& a, const CCoinJoinBroadcastTx& b)
     269             :     {
     270       88765 :         return *a.tx == *b.tx;
     271             :     }
     272       88767 :     friend bool operator!=(const CCoinJoinBroadcastTx& a, const CCoinJoinBroadcastTx& b)
     273             :     {
     274       88767 :         return !(a == b);
     275             :     }
     276       88767 :     explicit operator bool() const
     277             :     {
     278       88767 :         return *this != CCoinJoinBroadcastTx();
     279           0 :     }
     280             : 
     281             :     [[nodiscard]] uint256 GetSignatureHash() const;
     282             : 
     283             :     [[nodiscard]] bool CheckSignature(const CBLSPublicKey& blsPubKey) const;
     284             : 
     285           5 :     [[nodiscard]] const std::optional<int>& GetConfirmedHeight() const { return nConfirmedHeight; }
     286           3 :     void SetConfirmedHeight(std::optional<int> nConfirmedHeightIn) { assert(nConfirmedHeightIn == std::nullopt || *nConfirmedHeightIn > 0); nConfirmedHeight = nConfirmedHeightIn; }
     287             :     [[nodiscard]] bool IsValidStructure() const;
     288             : };
     289             : 
     290             : // base class
     291             : class CCoinJoinBaseSession
     292             : {
     293             : protected:
     294             :     mutable Mutex cs_coinjoin;
     295             : 
     296             :     std::vector<CCoinJoinEntry> vecEntries GUARDED_BY(cs_coinjoin); // Masternode/clients entries
     297             : 
     298         660 :     std::atomic<PoolState> nState{POOL_STATE_IDLE}; // should be one of the POOL_STATE_XXX values
     299         660 :     std::atomic<int64_t> nTimeLastSuccessfulStep{0}; // the time when last successful mixing step was performed
     300             : 
     301         660 :     std::atomic<int> nSessionID{0}; // 0 if no mixing session is active
     302             : 
     303             :     CMutableTransaction finalMutableTransaction GUARDED_BY(cs_coinjoin); // the finalized transaction ready for signing
     304             : 
     305             :     virtual void SetNull() EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin);
     306             : 
     307             :     bool IsValidInOuts(CChainState& active_chainstate, const llmq::CInstantSendManager& isman,
     308             :                        const CTxMemPool& mempool, const std::vector<CTxIn>& vin, const std::vector<CTxOut>& vout,
     309             :                        PoolMessage& nMessageIDRet, bool* fConsumeCollateralRet) const;
     310             : 
     311             : public:
     312         660 :     int nSessionDenom{0}; // Users must submit a denom matching this
     313             : 
     314        1320 :     CCoinJoinBaseSession() = default;
     315         660 :     virtual ~CCoinJoinBaseSession() = default;
     316             : 
     317           0 :     int GetState() const { return nState; }
     318             :     std::string GetStateString() const;
     319             : 
     320       55208 :     int GetEntriesCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin) { LOCK(cs_coinjoin); return vecEntries.size(); }
     321             :     int GetEntriesCountLocked() const EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin) { return vecEntries.size(); }
     322             : };
     323             : 
     324             : class CoinJoinQueueManager
     325             : {
     326             : private:
     327             :     mutable Mutex cs_vecqueue;
     328             : 
     329             :     // The current mixing sessions in progress on the network
     330             :     std::vector<CCoinJoinQueue> vecCoinJoinQueue GUARDED_BY(cs_vecqueue);
     331             : 
     332             : public:
     333             :     void SetNull() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue);
     334             : 
     335             :     //! Remove timed-out queue entries. Call periodically (e.g. every second).
     336             :     void CheckQueue() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue);
     337             : 
     338          24 :     int GetQueueSize() const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue) { LOCK(cs_vecqueue); return vecCoinJoinQueue.size(); }
     339             :     bool GetQueueItemAndTry(CCoinJoinQueue& dsqRet) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue);
     340             : 
     341           0 :     bool HasQueue(const uint256& queueHash) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue)
     342             :     {
     343           0 :         LOCK(cs_vecqueue);
     344           0 :         return std::any_of(vecCoinJoinQueue.begin(), vecCoinJoinQueue.end(),
     345           0 :                            [&queueHash](auto q) { return q.GetHash() == queueHash; });
     346           0 :     }
     347           0 :     std::optional<CCoinJoinQueue> GetQueueFromHash(const uint256& queueHash) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue)
     348             :     {
     349           0 :         LOCK(cs_vecqueue);
     350           0 :         return util::find_if_opt(vecCoinJoinQueue, [&queueHash](const auto& q) { return q.GetHash() == queueHash; });
     351           0 :     }
     352             : 
     353             :     //! True if any queue entry matches the given masternode outpoint and readiness state.
     354             :     //! Used to detect when a masternode is broadcasting queues too quickly.
     355           0 :     bool HasQueueFromMasternode(const COutPoint& outpoint, bool fReady) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue)
     356             :     {
     357           0 :         LOCK(cs_vecqueue);
     358           0 :         return std::any_of(vecCoinJoinQueue.begin(), vecCoinJoinQueue.end(),
     359           0 :                            [&](const auto& q) { return q.masternodeOutpoint == outpoint && q.fReady == fReady; });
     360           0 :     }
     361             :     //! TRY_LOCK variant: returns nullopt if lock can't be acquired; true if any queue entry has this
     362             :     //! outpoint (any readiness).
     363             :     [[nodiscard]] std::optional<bool> TryHasQueueFromMasternode(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue);
     364             :     //! TRY_LOCK combined duplicate check: returns nullopt if lock can't be acquired; true if dsq is
     365             :     //! an exact duplicate or the masternode is sending too many dsqs with the same readiness.
     366             :     [[nodiscard]] std::optional<bool> TryCheckDuplicate(const CCoinJoinQueue& dsq) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue);
     367             : 
     368             :     //! Append a queue entry (caller must have already checked for duplicates).
     369           3 :     void AddQueue(CCoinJoinQueue dsq) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue)
     370             :     {
     371           3 :         LOCK(cs_vecqueue);
     372           3 :         vecCoinJoinQueue.push_back(std::move(dsq));
     373           3 :     }
     374             :     //! TRY_LOCK variant of AddQueue: returns false if the lock cannot be acquired.
     375             :     bool TryAddQueue(CCoinJoinQueue dsq) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue);
     376             : };
     377             : 
     378             : // Various helpers and dstx manager implementation
     379             : namespace CoinJoin
     380             : {
     381             :     bilingual_str GetMessageByID(PoolMessage nMessageID);
     382             : 
     383             :     /// Get the minimum/maximum number of participants for the pool
     384             :     int GetMinPoolParticipants();
     385             :     int GetMaxPoolParticipants();
     386             : 
     387           0 :     constexpr CAmount GetMaxPoolAmount() { return COINJOIN_ENTRY_MAX_SIZE * vecStandardDenominations.front(); }
     388             : 
     389             :     /// If the collateral is valid given by a client
     390             :     bool IsCollateralValid(ChainstateManager& chainman, const llmq::CInstantSendManager& isman,
     391             :                            const CTxMemPool& mempool, const CTransaction& txCollateral);
     392             : }
     393             : 
     394             : class CDSTXManager
     395             : {
     396             :     const chainlock::Chainlocks& m_chainlocks;
     397             :     Mutex cs_mapdstx;
     398             :     std::map<uint256, CCoinJoinBroadcastTx> mapDSTX GUARDED_BY(cs_mapdstx);
     399             : 
     400             : public:
     401             :     CDSTXManager(const CDSTXManager&) = delete;
     402             :     CDSTXManager& operator=(const CDSTXManager&) = delete;
     403             :     CDSTXManager(const chainlock::Chainlocks& chainlocks);
     404             :     ~CDSTXManager();
     405             : 
     406             :     void AddDSTX(const CCoinJoinBroadcastTx& dstx) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     407             :     CCoinJoinBroadcastTx GetDSTX(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     408             : 
     409             :     // CDSNotificationInterface
     410             :     void UpdatedBlockTip(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     411             :     void NotifyChainLock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     412             :     void TransactionAddedToMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     413             :     void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
     414             :         EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     415             :     void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex*)
     416             :         EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     417             : 
     418             : private:
     419             :     bool IsTxExpired(const CCoinJoinBroadcastTx& tx, const CBlockIndex* pindex) const EXCLUSIVE_LOCKS_REQUIRED(cs_mapdstx);
     420             :     void CheckDSTXes(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx);
     421             :     void UpdateDSTXConfirmedHeight(const CTransactionRef& tx, std::optional<int> nHeight)
     422             :         EXCLUSIVE_LOCKS_REQUIRED(cs_mapdstx);
     423             : };
     424             : 
     425             : bool ATMPIfSaneFee(ChainstateManager& chainman, const CTransactionRef& tx, bool test_accept = false)
     426             :     EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
     427             : 
     428             : #endif // BITCOIN_COINJOIN_COINJOIN_H

Generated by: LCOV version 1.16