diff --git a/src/Makefile.am b/src/Makefile.am index 23ecb98e4786..da020fc9a23b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -230,6 +230,10 @@ BITCOIN_CORE_H = \ interfaces/ipc.h \ interfaces/node.h \ interfaces/wallet.h \ + instantsend/db.h \ + instantsend/instantsend.h \ + instantsend/lock.h \ + instantsend/signing.h \ key.h \ key_io.h \ limitedmap.h \ @@ -243,7 +247,6 @@ BITCOIN_CORE_H = \ llmq/dkgsessionhandler.h \ llmq/dkgsessionmgr.h \ llmq/ehf_signals.h \ - llmq/instantsend.h \ llmq/options.h \ llmq/params.h \ llmq/quorums.h \ @@ -488,6 +491,10 @@ libbitcoin_node_a_SOURCES = \ index/coinstatsindex.cpp \ index/txindex.cpp \ init.cpp \ + instantsend/db.cpp \ + instantsend/instantsend.cpp \ + instantsend/lock.cpp \ + instantsend/signing.cpp \ llmq/blockprocessor.cpp \ llmq/chainlocks.cpp \ llmq/clsig.cpp \ @@ -498,7 +505,6 @@ libbitcoin_node_a_SOURCES = \ llmq/dkgsessionhandler.cpp \ llmq/dkgsessionmgr.cpp \ llmq/ehf_signals.cpp \ - llmq/instantsend.cpp \ llmq/options.cpp \ llmq/quorums.cpp \ llmq/signing.cpp \ diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 72e9f8bbbfb2..33fc11065d89 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -6,9 +6,9 @@ #include #include +#include #include #include -#include #include #include #include diff --git a/src/coinjoin/coinjoin.cpp b/src/coinjoin/coinjoin.cpp index fafdf4528755..698d53e71859 100644 --- a/src/coinjoin/coinjoin.cpp +++ b/src/coinjoin/coinjoin.cpp @@ -9,8 +9,8 @@ #include #include #include +#include #include -#include #include #include #include diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index 7c8dfcfa771d..04c0a742b635 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -16,12 +16,11 @@ #include #include - +#include #include #include #include #include -#include #include CDSNotificationInterface::CDSNotificationInterface(CConnman& connman, diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index c58c942b535c..4014bb79335c 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -6,8 +6,9 @@ #include #include +#include +#include #include -#include #include CChainstateHelper::CChainstateHelper(CCreditPoolManager& cpoolman, CDeterministicMNManager& dmnman, diff --git a/src/init.cpp b/src/init.cpp index 19f89b47c8db..5dab028704b1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -87,11 +87,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include diff --git a/src/instantsend/db.cpp b/src/instantsend/db.cpp new file mode 100644 index 000000000000..dbd3596ee757 --- /dev/null +++ b/src/instantsend/db.cpp @@ -0,0 +1,395 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include + +static const std::string_view DB_ARCHIVED_BY_HASH = "is_a2"; +static const std::string_view DB_ARCHIVED_BY_HEIGHT_AND_HASH = "is_a1"; +static const std::string_view DB_HASH_BY_OUTPOINT = "is_in"; +static const std::string_view DB_HASH_BY_TXID = "is_tx"; +static const std::string_view DB_ISLOCK_BY_HASH = "is_i"; +static const std::string_view DB_MINED_BY_HEIGHT_AND_HASH = "is_m"; +static const std::string_view DB_VERSION = "is_v"; + +namespace instantsend { +namespace { +static std::tuple BuildInversedISLockKey(std::string_view k, int nHeight, const uint256& islockHash) +{ + return std::make_tuple(std::string{k}, htobe32_internal(std::numeric_limits::max() - nHeight), islockHash); +} +} // anonymous namespace + +CInstantSendDb::CInstantSendDb(bool unitTests, bool fWipe) : + db(std::make_unique(unitTests ? "" : (gArgs.GetDataDirNet() / "llmq/isdb"), 32 << 20, unitTests, fWipe)) +{ + Upgrade(unitTests); +} + +CInstantSendDb::~CInstantSendDb() = default; + +void CInstantSendDb::Upgrade(bool unitTests) +{ + LOCK(cs_db); + int v{0}; + if (!db->Read(DB_VERSION, v) || v < CInstantSendDb::CURRENT_VERSION) { + // Wipe db + db.reset(); + db = std::make_unique(unitTests ? "" : (gArgs.GetDataDirNet() / "llmq/isdb"), 32 << 20, unitTests, + /*fWipe=*/true); + CDBBatch batch(*db); + batch.Write(DB_VERSION, CInstantSendDb::CURRENT_VERSION); + // Sync DB changes to disk + db->WriteBatch(batch, /*fSync=*/true); + batch.Clear(); + } +} + +void CInstantSendDb::WriteNewInstantSendLock(const uint256& hash, const InstantSendLock& islock) +{ + LOCK(cs_db); + CDBBatch batch(*db); + batch.Write(std::make_tuple(DB_ISLOCK_BY_HASH, hash), islock); + batch.Write(std::make_tuple(DB_HASH_BY_TXID, islock.txid), hash); + for (const auto& in : islock.inputs) { + batch.Write(std::make_tuple(DB_HASH_BY_OUTPOINT, in), hash); + } + db->WriteBatch(batch); + + islockCache.insert(hash, std::make_shared(islock)); + txidCache.insert(islock.txid, hash); + for (const auto& in : islock.inputs) { + outpointCache.insert(in, hash); + } +} + +void CInstantSendDb::RemoveInstantSendLock(CDBBatch& batch, const uint256& hash, InstantSendLockPtr islock, bool keep_cache) +{ + AssertLockHeld(cs_db); + if (!islock) { + islock = GetInstantSendLockByHashInternal(hash, false); + if (!islock) { + return; + } + } + + batch.Erase(std::make_tuple(DB_ISLOCK_BY_HASH, hash)); + batch.Erase(std::make_tuple(DB_HASH_BY_TXID, islock->txid)); + for (auto& in : islock->inputs) { + batch.Erase(std::make_tuple(DB_HASH_BY_OUTPOINT, in)); + } + + if (!keep_cache) { + islockCache.erase(hash); + txidCache.erase(islock->txid); + for (const auto& in : islock->inputs) { + outpointCache.erase(in); + } + } +} + +void CInstantSendDb::WriteInstantSendLockMined(const uint256& hash, int nHeight) +{ + LOCK(cs_db); + CDBBatch batch(*db); + WriteInstantSendLockMined(batch, hash, nHeight); + db->WriteBatch(batch); +} + +void CInstantSendDb::WriteInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) +{ + AssertLockHeld(cs_db); + batch.Write(BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nHeight, hash), true); +} + +void CInstantSendDb::RemoveInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) +{ + AssertLockHeld(cs_db); + batch.Erase(BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nHeight, hash)); +} + +void CInstantSendDb::WriteInstantSendLockArchived(CDBBatch& batch, const uint256& hash, int nHeight) +{ + AssertLockHeld(cs_db); + batch.Write(BuildInversedISLockKey(DB_ARCHIVED_BY_HEIGHT_AND_HASH, nHeight, hash), true); + batch.Write(std::make_tuple(DB_ARCHIVED_BY_HASH, hash), true); +} + +std::unordered_map CInstantSendDb::RemoveConfirmedInstantSendLocks(int nUntilHeight) +{ + LOCK(cs_db); + if (nUntilHeight <= best_confirmed_height) { + LogPrint(BCLog::ALL, "CInstantSendDb::%s -- Attempting to confirm height %d, however we've already confirmed height %d. This should never happen.\n", __func__, + nUntilHeight, best_confirmed_height); + return {}; + } + best_confirmed_height = nUntilHeight; + + auto it = std::unique_ptr(db->NewIterator()); + + auto firstKey = BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nUntilHeight, uint256()); + + it->Seek(firstKey); + + CDBBatch batch(*db); + std::unordered_map ret; + while (it->Valid()) { + decltype(firstKey) curKey; + if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_MINED_BY_HEIGHT_AND_HASH) { + break; + } + uint32_t nHeight = std::numeric_limits::max() - be32toh_internal(std::get<1>(curKey)); + if (nHeight > uint32_t(nUntilHeight)) { + break; + } + + auto& islockHash = std::get<2>(curKey); + + if (auto islock = GetInstantSendLockByHashInternal(islockHash, false)) { + RemoveInstantSendLock(batch, islockHash, islock); + ret.try_emplace(islockHash, std::move(islock)); + } + + // archive the islock hash, so that we're still able to check if we've seen the islock in the past + WriteInstantSendLockArchived(batch, islockHash, nHeight); + + batch.Erase(curKey); + + it->Next(); + } + + db->WriteBatch(batch); + + return ret; +} + +void CInstantSendDb::RemoveArchivedInstantSendLocks(int nUntilHeight) +{ + LOCK(cs_db); + if (nUntilHeight <= 0) { + return; + } + + auto it = std::unique_ptr(db->NewIterator()); + + auto firstKey = BuildInversedISLockKey(DB_ARCHIVED_BY_HEIGHT_AND_HASH, nUntilHeight, uint256()); + + it->Seek(firstKey); + + CDBBatch batch(*db); + while (it->Valid()) { + decltype(firstKey) curKey; + if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_ARCHIVED_BY_HEIGHT_AND_HASH) { + break; + } + uint32_t nHeight = std::numeric_limits::max() - be32toh_internal(std::get<1>(curKey)); + if (nHeight > uint32_t(nUntilHeight)) { + break; + } + + auto& islockHash = std::get<2>(curKey); + batch.Erase(std::make_tuple(DB_ARCHIVED_BY_HASH, islockHash)); + batch.Erase(curKey); + + it->Next(); + } + + db->WriteBatch(batch); +} + +void CInstantSendDb::WriteBlockInstantSendLocks(const gsl::not_null>& pblock, + gsl::not_null pindexConnected) +{ + LOCK(cs_db); + CDBBatch batch(*db); + for (const auto& tx : pblock->vtx) { + if (tx->IsCoinBase() || tx->vin.empty()) { + // coinbase and TXs with no inputs can't be locked + continue; + } + uint256 islockHash = GetInstantSendLockHashByTxidInternal(tx->GetHash()); + // update DB about when an IS lock was mined + if (!islockHash.IsNull()) { + WriteInstantSendLockMined(batch, islockHash, pindexConnected->nHeight); + } + } + db->WriteBatch(batch); +} + +void CInstantSendDb::RemoveBlockInstantSendLocks(const gsl::not_null>& pblock, gsl::not_null pindexDisconnected) +{ + LOCK(cs_db); + CDBBatch batch(*db); + for (const auto& tx : pblock->vtx) { + if (tx->IsCoinBase() || tx->vin.empty()) { + // coinbase and TXs with no inputs can't be locked + continue; + } + uint256 islockHash = GetInstantSendLockHashByTxidInternal(tx->GetHash()); + if (!islockHash.IsNull()) { + RemoveInstantSendLockMined(batch, islockHash, pindexDisconnected->nHeight); + } + } + db->WriteBatch(batch); +} + +bool CInstantSendDb::KnownInstantSendLock(const uint256& islockHash) const +{ + LOCK(cs_db); + return GetInstantSendLockByHashInternal(islockHash) != nullptr || db->Exists(std::make_tuple(DB_ARCHIVED_BY_HASH, islockHash)); +} + +size_t CInstantSendDb::GetInstantSendLockCount() const +{ + LOCK(cs_db); + auto it = std::unique_ptr(db->NewIterator()); + auto firstKey = std::make_tuple(std::string{DB_ISLOCK_BY_HASH}, uint256()); + + it->Seek(firstKey); + + size_t cnt = 0; + while (it->Valid()) { + decltype(firstKey) curKey; + if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_ISLOCK_BY_HASH) { + break; + } + + cnt++; + + it->Next(); + } + + return cnt; +} + +InstantSendLockPtr CInstantSendDb::GetInstantSendLockByHashInternal(const uint256& hash, bool use_cache) const +{ + AssertLockHeld(cs_db); + if (hash.IsNull()) { + return nullptr; + } + + InstantSendLockPtr ret; + if (use_cache && islockCache.get(hash, ret)) { + return ret; + } + + ret = std::make_shared(); + bool exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret); + if (!exists || (::SerializeHash(*ret) != hash)) { + ret = std::make_shared(); + exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret); + if (!exists || (::SerializeHash(*ret) != hash)) { + ret = nullptr; + } + } + islockCache.insert(hash, ret); + return ret; +} + +uint256 CInstantSendDb::GetInstantSendLockHashByTxidInternal(const uint256& txid) const +{ + AssertLockHeld(cs_db); + uint256 islockHash; + if (!txidCache.get(txid, islockHash)) { + if (!db->Read(std::make_tuple(DB_HASH_BY_TXID, txid), islockHash)) { + return {}; + } + txidCache.insert(txid, islockHash); + } + return islockHash; +} + +InstantSendLockPtr CInstantSendDb::GetInstantSendLockByTxid(const uint256& txid) const +{ + LOCK(cs_db); + return GetInstantSendLockByHashInternal(GetInstantSendLockHashByTxidInternal(txid)); +} + +InstantSendLockPtr CInstantSendDb::GetInstantSendLockByInput(const COutPoint& outpoint) const +{ + LOCK(cs_db); + uint256 islockHash; + if (!outpointCache.get(outpoint, islockHash)) { + if (!db->Read(std::make_tuple(DB_HASH_BY_OUTPOINT, outpoint), islockHash)) { + return nullptr; + } + outpointCache.insert(outpoint, islockHash); + } + return GetInstantSendLockByHashInternal(islockHash); +} + +std::vector CInstantSendDb::GetInstantSendLocksByParent(const uint256& parent) const +{ + AssertLockHeld(cs_db); + auto it = std::unique_ptr(db->NewIterator()); + auto firstKey = std::make_tuple(std::string{DB_HASH_BY_OUTPOINT}, COutPoint(parent, 0)); + it->Seek(firstKey); + + std::vector result; + + while (it->Valid()) { + decltype(firstKey) curKey; + if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_HASH_BY_OUTPOINT) { + break; + } + const auto& outpoint = std::get<1>(curKey); + if (outpoint.hash != parent) { + break; + } + + uint256 islockHash; + if (!it->GetValue(islockHash)) { + break; + } + result.emplace_back(islockHash); + it->Next(); + } + + return result; +} + +std::vector CInstantSendDb::RemoveChainedInstantSendLocks(const uint256& islockHash, const uint256& txid, int nHeight) +{ + LOCK(cs_db); + std::vector result; + + std::vector stack; + std::unordered_set added; + stack.emplace_back(txid); + + CDBBatch batch(*db); + while (!stack.empty()) { + auto children = GetInstantSendLocksByParent(stack.back()); + stack.pop_back(); + + for (auto& childIslockHash : children) { + auto childIsLock = GetInstantSendLockByHashInternal(childIslockHash, false); + if (!childIsLock) { + continue; + } + + RemoveInstantSendLock(batch, childIslockHash, childIsLock, false); + WriteInstantSendLockArchived(batch, childIslockHash, nHeight); + result.emplace_back(childIslockHash); + + if (added.emplace(childIsLock->txid).second) { + stack.emplace_back(childIsLock->txid); + } + } + } + + RemoveInstantSendLock(batch, islockHash, nullptr, false); + WriteInstantSendLockArchived(batch, islockHash, nHeight); + result.emplace_back(islockHash); + + db->WriteBatch(batch); + + return result; +} +} // namespace instantsend diff --git a/src/instantsend/db.h b/src/instantsend/db.h new file mode 100644 index 000000000000..178171d7ea2d --- /dev/null +++ b/src/instantsend/db.h @@ -0,0 +1,161 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INSTANTSEND_DB_H +#define BITCOIN_INSTANTSEND_DB_H + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +class CBlock; +class CBlockIndex; +class CDBBatch; +class CDBWrapper; +class COutPoint; + +namespace instantsend { +class CInstantSendDb +{ +private: + mutable Mutex cs_db; + + static constexpr int CURRENT_VERSION{1}; + + int best_confirmed_height GUARDED_BY(cs_db) {0}; + + std::unique_ptr db GUARDED_BY(cs_db) {nullptr}; + mutable unordered_lru_cache islockCache GUARDED_BY(cs_db); + mutable unordered_lru_cache txidCache GUARDED_BY(cs_db); + + mutable unordered_lru_cache outpointCache GUARDED_BY(cs_db); + void WriteInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_db); + + void RemoveInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_db); + + /** + * This method removes a InstantSend Lock from the database and is called when a tx with an IS lock is confirmed and Chainlocked + * @param batch Object used to batch many calls together + * @param hash The hash of the InstantSend Lock + * @param islock The InstantSend Lock object itself + * @param keep_cache Should we still keep corresponding entries in the cache or not + */ + void RemoveInstantSendLock(CDBBatch& batch, const uint256& hash, InstantSendLockPtr islock, bool keep_cache = true) EXCLUSIVE_LOCKS_REQUIRED(cs_db); + /** + * Marks an InstantSend Lock as archived. + * @param batch Object used to batch many calls together + * @param hash The hash of the InstantSend Lock + * @param nHeight The height that the transaction was included at + */ + void WriteInstantSendLockArchived(CDBBatch& batch, const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_db); + /** + * Gets a vector of IS Lock hashes of the IS Locks which rely on or are children of the parent IS Lock + * @param parent The hash of the parent IS Lock + * @return Returns a vector of IS Lock hashes + */ + std::vector GetInstantSendLocksByParent(const uint256& parent) const EXCLUSIVE_LOCKS_REQUIRED(cs_db); + + /** + * See GetInstantSendLockByHash + */ + InstantSendLockPtr GetInstantSendLockByHashInternal(const uint256& hash, bool use_cache = true) const EXCLUSIVE_LOCKS_REQUIRED(cs_db); + + /** + * See GetInstantSendLockHashByTxid + */ + uint256 GetInstantSendLockHashByTxidInternal(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_db); + + + void Upgrade(bool unitTests) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + +public: + explicit CInstantSendDb(bool unitTests, bool fWipe); + ~CInstantSendDb(); + + /** + * This method is called when an InstantSend Lock is processed and adds the lock to the database + * @param hash The hash of the InstantSend Lock + * @param islock The InstantSend Lock object itself + */ + void WriteNewInstantSendLock(const uint256& hash, const InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * This method updates a DB entry for an InstantSend Lock from being not included in a block to being included in a block + * @param hash The hash of the InstantSend Lock + * @param nHeight The height that the transaction was included at + */ + void WriteInstantSendLockMined(const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * Archives and deletes all IS Locks which were mined into a block before nUntilHeight + * @param nUntilHeight Removes all IS Locks confirmed up until nUntilHeight + * @return returns an unordered_map of the hash of the IS Locks and a pointer object to the IS Locks for all IS Locks which were removed + */ + std::unordered_map RemoveConfirmedInstantSendLocks(int nUntilHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * Removes IS Locks from the archive if the tx was confirmed 100 blocks before nUntilHeight + * @param nUntilHeight the height from which to base the remove of archive IS Locks + */ + void RemoveArchivedInstantSendLocks(int nUntilHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + void WriteBlockInstantSendLocks(const gsl::not_null>& pblock, gsl::not_null pindexConnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + void RemoveBlockInstantSendLocks(const gsl::not_null>& pblock, gsl::not_null pindexDisconnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + bool KnownInstantSendLock(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * Gets the number of IS Locks which have not been confirmed by a block + * @return size_t value of the number of IS Locks not confirmed by a block + */ + size_t GetInstantSendLockCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * Gets a pointer to the IS Lock based on the hash + * @param hash The hash of the IS Lock + * @param use_cache Should we try using the cache first or not + * @return A Pointer object to the IS Lock, returns nullptr if it doesn't exist + */ + InstantSendLockPtr GetInstantSendLockByHash(const uint256& hash, bool use_cache = true) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db) + { + LOCK(cs_db); + return GetInstantSendLockByHashInternal(hash, use_cache); + }; + /** + * Gets an IS Lock hash based on the txid the IS Lock is for + * @param txid The txid which is being searched for + * @return Returns the hash the IS Lock of the specified txid, returns uint256() if it doesn't exist + */ + uint256 GetInstantSendLockHashByTxid(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db) + { + LOCK(cs_db); + return GetInstantSendLockHashByTxidInternal(txid); + }; + /** + * Gets an IS Lock pointer from the txid given + * @param txid The txid for which the IS Lock Pointer is being returned + * @return Returns the IS Lock Pointer associated with the txid, returns nullptr if it doesn't exist + */ + InstantSendLockPtr GetInstantSendLockByTxid(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * Gets an IS Lock pointer from an input given + * @param outpoint Since all inputs are really just outpoints that are being spent + * @return IS Lock Pointer associated with that input. + */ + InstantSendLockPtr GetInstantSendLockByInput(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + /** + * Called when a ChainLock invalidated a IS Lock, removes any chained/children IS Locks and the invalidated IS Lock + * @param islockHash IS Lock hash which has been invalidated + * @param txid Transaction id associated with the islockHash + * @param nHeight height of the block which received a chainlock and invalidated the IS Lock + * @return A vector of IS Lock hashes of all IS Locks removed + */ + std::vector RemoveChainedInstantSendLocks(const uint256& islockHash, const uint256& txid, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); +}; +} // namespace instantsend + +#endif // BITCOIN_INSTANTSEND_DB_H diff --git a/src/llmq/instantsend.cpp b/src/instantsend/instantsend.cpp similarity index 53% rename from src/llmq/instantsend.cpp rename to src/instantsend/instantsend.cpp index ff4ed81f2d1f..b46b6ceee3d4 100644 --- a/src/llmq/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -2,29 +2,25 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include +#include -#include -#include -#include -#include - -#include #include #include -#include -#include -#include #include #include -#include #include #include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include + #include using node::fImporting; @@ -38,407 +34,44 @@ CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMe } // namespace node using node::GetTransaction; -namespace llmq -{ - +namespace llmq { static const std::string_view INPUTLOCK_REQUESTID_PREFIX = "inlock"; -static const std::string_view ISLOCK_REQUESTID_PREFIX = "islock"; - -static const std::string_view DB_ISLOCK_BY_HASH = "is_i"; -static const std::string_view DB_HASH_BY_TXID = "is_tx"; -static const std::string_view DB_HASH_BY_OUTPOINT = "is_in"; -static const std::string_view DB_MINED_BY_HEIGHT_AND_HASH = "is_m"; -static const std::string_view DB_ARCHIVED_BY_HEIGHT_AND_HASH = "is_a1"; -static const std::string_view DB_ARCHIVED_BY_HASH = "is_a2"; - -static const std::string_view DB_VERSION = "is_v"; - -uint256 CInstantSendLock::GetRequestId() const -{ - CHashWriter hw(SER_GETHASH, 0); - hw << ISLOCK_REQUESTID_PREFIX; - hw << inputs; - return hw.GetHash(); -} - -//////////////// - -CInstantSendDb::CInstantSendDb(bool unitTests, bool fWipe) : - db(std::make_unique(unitTests ? "" : (gArgs.GetDataDirNet() / "llmq/isdb"), 32 << 20, unitTests, fWipe)) +namespace { +template + requires std::same_as || std::same_as +std::unordered_set GetIdsFromLockable(const std::vector& vec) { - Upgrade(unitTests); -} - -CInstantSendDb::~CInstantSendDb() = default; - -void CInstantSendDb::Upgrade(bool unitTests) -{ - LOCK(cs_db); - int v{0}; - if (!db->Read(DB_VERSION, v) || v < CInstantSendDb::CURRENT_VERSION) { - // Wipe db - db.reset(); - db = std::make_unique(unitTests ? "" : (gArgs.GetDataDirNet() / "llmq/isdb"), 32 << 20, unitTests, - /*fWipe=*/true); - CDBBatch batch(*db); - batch.Write(DB_VERSION, CInstantSendDb::CURRENT_VERSION); - // Sync DB changes to disk - db->WriteBatch(batch, /*fSync=*/true); - batch.Clear(); - } -} - -void CInstantSendDb::WriteNewInstantSendLock(const uint256& hash, const CInstantSendLock& islock) -{ - LOCK(cs_db); - CDBBatch batch(*db); - batch.Write(std::make_tuple(DB_ISLOCK_BY_HASH, hash), islock); - batch.Write(std::make_tuple(DB_HASH_BY_TXID, islock.txid), hash); - for (const auto& in : islock.inputs) { - batch.Write(std::make_tuple(DB_HASH_BY_OUTPOINT, in), hash); - } - db->WriteBatch(batch); - - islockCache.insert(hash, std::make_shared(islock)); - txidCache.insert(islock.txid, hash); - for (const auto& in : islock.inputs) { - outpointCache.insert(in, hash); - } -} - -void CInstantSendDb::RemoveInstantSendLock(CDBBatch& batch, const uint256& hash, CInstantSendLockPtr islock, bool keep_cache) -{ - AssertLockHeld(cs_db); - if (!islock) { - islock = GetInstantSendLockByHashInternal(hash, false); - if (!islock) { - return; - } - } - - batch.Erase(std::make_tuple(DB_ISLOCK_BY_HASH, hash)); - batch.Erase(std::make_tuple(DB_HASH_BY_TXID, islock->txid)); - for (auto& in : islock->inputs) { - batch.Erase(std::make_tuple(DB_HASH_BY_OUTPOINT, in)); - } - - if (!keep_cache) { - islockCache.erase(hash); - txidCache.erase(islock->txid); - for (const auto& in : islock->inputs) { - outpointCache.erase(in); - } + std::unordered_set ret{}; + if (vec.empty()) return ret; + ret.reserve(vec.size()); + for (const auto& in : vec) { + ret.emplace(::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in))); } -} - -static std::tuple BuildInversedISLockKey(std::string_view k, int nHeight, const uint256& islockHash) -{ - return std::make_tuple(std::string{k}, htobe32_internal(std::numeric_limits::max() - nHeight), islockHash); -} - -void CInstantSendDb::WriteInstantSendLockMined(const uint256& hash, int nHeight) -{ - LOCK(cs_db); - CDBBatch batch(*db); - WriteInstantSendLockMined(batch, hash, nHeight); - db->WriteBatch(batch); -} - -void CInstantSendDb::WriteInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) -{ - AssertLockHeld(cs_db); - batch.Write(BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nHeight, hash), true); -} - -void CInstantSendDb::RemoveInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) -{ - AssertLockHeld(cs_db); - batch.Erase(BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nHeight, hash)); -} - -void CInstantSendDb::WriteInstantSendLockArchived(CDBBatch& batch, const uint256& hash, int nHeight) -{ - AssertLockHeld(cs_db); - batch.Write(BuildInversedISLockKey(DB_ARCHIVED_BY_HEIGHT_AND_HASH, nHeight, hash), true); - batch.Write(std::make_tuple(DB_ARCHIVED_BY_HASH, hash), true); -} - -std::unordered_map CInstantSendDb::RemoveConfirmedInstantSendLocks(int nUntilHeight) -{ - LOCK(cs_db); - if (nUntilHeight <= best_confirmed_height) { - LogPrint(BCLog::ALL, "CInstantSendDb::%s -- Attempting to confirm height %d, however we've already confirmed height %d. This should never happen.\n", __func__, - nUntilHeight, best_confirmed_height); - return {}; - } - best_confirmed_height = nUntilHeight; - - auto it = std::unique_ptr(db->NewIterator()); - - auto firstKey = BuildInversedISLockKey(DB_MINED_BY_HEIGHT_AND_HASH, nUntilHeight, uint256()); - - it->Seek(firstKey); - - CDBBatch batch(*db); - std::unordered_map ret; - while (it->Valid()) { - decltype(firstKey) curKey; - if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_MINED_BY_HEIGHT_AND_HASH) { - break; - } - uint32_t nHeight = std::numeric_limits::max() - be32toh_internal(std::get<1>(curKey)); - if (nHeight > uint32_t(nUntilHeight)) { - break; - } - - auto& islockHash = std::get<2>(curKey); - - if (auto islock = GetInstantSendLockByHashInternal(islockHash, false)) { - RemoveInstantSendLock(batch, islockHash, islock); - ret.try_emplace(islockHash, std::move(islock)); - } - - // archive the islock hash, so that we're still able to check if we've seen the islock in the past - WriteInstantSendLockArchived(batch, islockHash, nHeight); - - batch.Erase(curKey); - - it->Next(); - } - - db->WriteBatch(batch); - return ret; } +} // anonymous namespace -void CInstantSendDb::RemoveArchivedInstantSendLocks(int nUntilHeight) +CInstantSendManager::CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CQuorumManager& _qman, + CSigningManager& _sigman, CSigSharesManager& _shareman, + CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync, + bool is_masternode, bool unitTests, bool fWipe) : + db{unitTests, fWipe}, + clhandler{_clhandler}, + m_chainstate{chainstate}, + qman{_qman}, + sigman{_sigman}, + spork_manager{sporkman}, + mempool{_mempool}, + m_mn_sync{mn_sync}, + m_signer{is_masternode ? std::make_unique(chainstate, _clhandler, *this, _sigman, + _shareman, _qman, sporkman, _mempool, mn_sync) + : nullptr} { - LOCK(cs_db); - if (nUntilHeight <= 0) { - return; - } - - auto it = std::unique_ptr(db->NewIterator()); - - auto firstKey = BuildInversedISLockKey(DB_ARCHIVED_BY_HEIGHT_AND_HASH, nUntilHeight, uint256()); - - it->Seek(firstKey); - - CDBBatch batch(*db); - while (it->Valid()) { - decltype(firstKey) curKey; - if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_ARCHIVED_BY_HEIGHT_AND_HASH) { - break; - } - uint32_t nHeight = std::numeric_limits::max() - be32toh_internal(std::get<1>(curKey)); - if (nHeight > uint32_t(nUntilHeight)) { - break; - } - - auto& islockHash = std::get<2>(curKey); - batch.Erase(std::make_tuple(DB_ARCHIVED_BY_HASH, islockHash)); - batch.Erase(curKey); - - it->Next(); - } - - db->WriteBatch(batch); + workInterrupt.reset(); } -void CInstantSendDb::WriteBlockInstantSendLocks(const gsl::not_null>& pblock, - gsl::not_null pindexConnected) -{ - LOCK(cs_db); - CDBBatch batch(*db); - for (const auto& tx : pblock->vtx) { - if (tx->IsCoinBase() || tx->vin.empty()) { - // coinbase and TXs with no inputs can't be locked - continue; - } - uint256 islockHash = GetInstantSendLockHashByTxidInternal(tx->GetHash()); - // update DB about when an IS lock was mined - if (!islockHash.IsNull()) { - WriteInstantSendLockMined(batch, islockHash, pindexConnected->nHeight); - } - } - db->WriteBatch(batch); -} - -void CInstantSendDb::RemoveBlockInstantSendLocks(const gsl::not_null>& pblock, gsl::not_null pindexDisconnected) -{ - LOCK(cs_db); - CDBBatch batch(*db); - for (const auto& tx : pblock->vtx) { - if (tx->IsCoinBase() || tx->vin.empty()) { - // coinbase and TXs with no inputs can't be locked - continue; - } - uint256 islockHash = GetInstantSendLockHashByTxidInternal(tx->GetHash()); - if (!islockHash.IsNull()) { - RemoveInstantSendLockMined(batch, islockHash, pindexDisconnected->nHeight); - } - } - db->WriteBatch(batch); -} - -bool CInstantSendDb::KnownInstantSendLock(const uint256& islockHash) const -{ - LOCK(cs_db); - return GetInstantSendLockByHashInternal(islockHash) != nullptr || db->Exists(std::make_tuple(DB_ARCHIVED_BY_HASH, islockHash)); -} - -size_t CInstantSendDb::GetInstantSendLockCount() const -{ - LOCK(cs_db); - auto it = std::unique_ptr(db->NewIterator()); - auto firstKey = std::make_tuple(std::string{DB_ISLOCK_BY_HASH}, uint256()); - - it->Seek(firstKey); - - size_t cnt = 0; - while (it->Valid()) { - decltype(firstKey) curKey; - if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_ISLOCK_BY_HASH) { - break; - } - - cnt++; - - it->Next(); - } - - return cnt; -} - -CInstantSendLockPtr CInstantSendDb::GetInstantSendLockByHashInternal(const uint256& hash, bool use_cache) const -{ - AssertLockHeld(cs_db); - if (hash.IsNull()) { - return nullptr; - } - - CInstantSendLockPtr ret; - if (use_cache && islockCache.get(hash, ret)) { - return ret; - } - - ret = std::make_shared(); - bool exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret); - if (!exists || (::SerializeHash(*ret) != hash)) { - ret = std::make_shared(); - exists = db->Read(std::make_tuple(DB_ISLOCK_BY_HASH, hash), *ret); - if (!exists || (::SerializeHash(*ret) != hash)) { - ret = nullptr; - } - } - islockCache.insert(hash, ret); - return ret; -} - -uint256 CInstantSendDb::GetInstantSendLockHashByTxidInternal(const uint256& txid) const -{ - AssertLockHeld(cs_db); - uint256 islockHash; - if (!txidCache.get(txid, islockHash)) { - if (!db->Read(std::make_tuple(DB_HASH_BY_TXID, txid), islockHash)) { - return {}; - } - txidCache.insert(txid, islockHash); - } - return islockHash; -} - -CInstantSendLockPtr CInstantSendDb::GetInstantSendLockByTxid(const uint256& txid) const -{ - LOCK(cs_db); - return GetInstantSendLockByHashInternal(GetInstantSendLockHashByTxidInternal(txid)); -} - -CInstantSendLockPtr CInstantSendDb::GetInstantSendLockByInput(const COutPoint& outpoint) const -{ - LOCK(cs_db); - uint256 islockHash; - if (!outpointCache.get(outpoint, islockHash)) { - if (!db->Read(std::make_tuple(DB_HASH_BY_OUTPOINT, outpoint), islockHash)) { - return nullptr; - } - outpointCache.insert(outpoint, islockHash); - } - return GetInstantSendLockByHashInternal(islockHash); -} - -std::vector CInstantSendDb::GetInstantSendLocksByParent(const uint256& parent) const -{ - AssertLockHeld(cs_db); - auto it = std::unique_ptr(db->NewIterator()); - auto firstKey = std::make_tuple(std::string{DB_HASH_BY_OUTPOINT}, COutPoint(parent, 0)); - it->Seek(firstKey); - - std::vector result; - - while (it->Valid()) { - decltype(firstKey) curKey; - if (!it->GetKey(curKey) || std::get<0>(curKey) != DB_HASH_BY_OUTPOINT) { - break; - } - const auto& outpoint = std::get<1>(curKey); - if (outpoint.hash != parent) { - break; - } - - uint256 islockHash; - if (!it->GetValue(islockHash)) { - break; - } - result.emplace_back(islockHash); - it->Next(); - } - - return result; -} - -std::vector CInstantSendDb::RemoveChainedInstantSendLocks(const uint256& islockHash, const uint256& txid, int nHeight) -{ - LOCK(cs_db); - std::vector result; - - std::vector stack; - std::unordered_set added; - stack.emplace_back(txid); - - CDBBatch batch(*db); - while (!stack.empty()) { - auto children = GetInstantSendLocksByParent(stack.back()); - stack.pop_back(); - - for (auto& childIslockHash : children) { - auto childIsLock = GetInstantSendLockByHashInternal(childIslockHash, false); - if (!childIsLock) { - continue; - } - - RemoveInstantSendLock(batch, childIslockHash, childIsLock, false); - WriteInstantSendLockArchived(batch, childIslockHash, nHeight); - result.emplace_back(childIslockHash); - - if (added.emplace(childIsLock->txid).second) { - stack.emplace_back(childIsLock->txid); - } - } - } - - RemoveInstantSendLock(batch, islockHash, nullptr, false); - WriteInstantSendLockArchived(batch, islockHash, nHeight); - result.emplace_back(islockHash); - - db->WriteBatch(batch); - - return result; -} - -//////////////// - +CInstantSendManager::~CInstantSendManager() = default; void CInstantSendManager::Start(PeerManager& peerman) { @@ -449,12 +82,16 @@ void CInstantSendManager::Start(PeerManager& peerman) workThread = std::thread(&util::TraceThread, "isman", [this, &peerman] { WorkThreadMain(peerman); }); - sigman.RegisterRecoveredSigsListener(this); + if (m_signer) { + m_signer->Start(); + } } void CInstantSendManager::Stop() { - sigman.UnregisterRecoveredSigsListener(this); + if (m_signer) { + m_signer->Stop(); + } // make sure to call InterruptWorkerThread() first if (!workInterrupt) { @@ -466,293 +103,11 @@ void CInstantSendManager::Stop() } } -void CInstantSendManager::ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params) -{ - if (!m_is_masternode || !IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced()) { - return; - } - - if (params.llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) { - return; - } - - if (!CheckCanLock(tx, true, params)) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: CheckCanLock returned false\n", __func__, - tx.GetHash().ToString()); - return; - } - - auto conflictingLock = GetConflictingLock(tx); - if (conflictingLock != nullptr) { - auto conflictingLockHash = ::SerializeHash(*conflictingLock); - LogPrintf("CInstantSendManager::%s -- txid=%s: conflicts with islock %s, txid=%s\n", __func__, - tx.GetHash().ToString(), conflictingLockHash.ToString(), conflictingLock->txid.ToString()); - return; - } - - // Only sign for inlocks or islocks if mempool IS signing is enabled. - // However, if we are processing a tx because it was included in a block we should - // sign even if mempool IS signing is disabled. This allows a ChainLock to happen on this - // block after we retroactively locked all transactions. - if (!IsInstantSendMempoolSigningEnabled() && !fRetroactive) return; - - if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeDIP0024InstantSend, params)) { - return; - } - - // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the - // islock now instead of waiting for the input locks. - TrySignInstantSendLock(tx); -} - -bool CInstantSendManager::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType, const Consensus::Params& params) -{ - std::vector ids; - ids.reserve(tx.vin.size()); - - size_t alreadyVotedCount = 0; - for (const auto& in : tx.vin) { - auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); - ids.emplace_back(id); - - uint256 otherTxHash; - if (sigman.GetVoteForId(params.llmqTypeDIP0024InstantSend, id, otherTxHash)) { - if (otherTxHash != tx.GetHash()) { - LogPrintf("CInstantSendManager::%s -- txid=%s: input %s is conflicting with previous vote for tx %s\n", __func__, - tx.GetHash().ToString(), in.prevout.ToStringShort(), otherTxHash.ToString()); - return false; - } - alreadyVotedCount++; - } - - // don't even try the actual signing if any input is conflicting - if (sigman.IsConflicting(params.llmqTypeDIP0024InstantSend, id, tx.GetHash())) { - LogPrintf("CInstantSendManager::%s -- txid=%s: sigman.IsConflicting returned true. id=%s\n", __func__, - tx.GetHash().ToString(), id.ToString()); - return false; - } - } - if (!fRetroactive && alreadyVotedCount == ids.size()) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: already voted on all inputs, bailing out\n", __func__, - tx.GetHash().ToString()); - return true; - } - - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: trying to vote on %d inputs\n", __func__, - tx.GetHash().ToString(), tx.vin.size()); - - for (const auto i : irange::range(tx.vin.size())) { - const auto& in = tx.vin[i]; - auto& id = ids[i]; - WITH_LOCK(cs_inputReqests, inputRequestIds.emplace(id)); - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: trying to vote on input %s with id %s. fRetroactive=%d\n", __func__, - tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString(), fRetroactive); - if (sigman.AsyncSignIfMember(llmqType, shareman, id, tx.GetHash(), {}, fRetroactive)) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: voted on input %s with id %s\n", __func__, - tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString()); - } - } - - return true; -} - -bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const -{ - if (tx.vin.empty()) { - // can't lock TXs without inputs (e.g. quorum commitments) - return false; - } - - return ranges::all_of(tx.vin, - [&](const auto& in) { return CheckCanLock(in.prevout, printDebug, tx.GetHash(), params); }); -} - -bool CInstantSendManager::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash, const Consensus::Params& params) const -{ - int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; - - if (IsLocked(outpoint.hash)) { - // if prevout was ix locked, allow locking of descendants (no matter if prevout is in mempool or already mined) - return true; - } - - auto mempoolTx = mempool.get(outpoint.hash); - if (mempoolTx) { - if (printDebug) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: parent mempool TX %s is not locked\n", __func__, - txHash.ToString(), outpoint.hash.ToString()); - } - return false; - } - - uint256 hashBlock{}; - const auto tx = GetTransaction(nullptr, &mempool, outpoint.hash, params, hashBlock); - // this relies on enabled txindex and won't work if we ever try to remove the requirement for txindex for masternodes - if (!tx) { - if (printDebug) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: failed to find parent TX %s\n", __func__, - txHash.ToString(), outpoint.hash.ToString()); - } - return false; - } - - const CBlockIndex* pindexMined; - int nTxAge; - { - LOCK(::cs_main); - pindexMined = m_chainstate.m_blockman.LookupBlockIndex(hashBlock); - nTxAge = m_chainstate.m_chain.Height() - pindexMined->nHeight + 1; - } - - if (nTxAge < nInstantSendConfirmationsRequired && !clhandler.HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) { - if (printDebug) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__, - txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired); - } - return false; - } - - return true; -} - -MessageProcessingResult CInstantSendManager::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) -{ - if (!IsInstantSendEnabled()) { - return {}; - } - - if (Params().GetConsensus().llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) { - return {}; - } - - uint256 txid; - if (LOCK(cs_inputReqests); inputRequestIds.count(recoveredSig.getId())) { - txid = recoveredSig.getMsgHash(); - } - if (!txid.IsNull()) { - HandleNewInputLockRecoveredSig(recoveredSig, txid); - } else if (/*isInstantSendLock=*/ WITH_LOCK(cs_creating, return creatingInstantSendLocks.count(recoveredSig.getId()))) { - HandleNewInstantSendLockRecoveredSig(recoveredSig); - } - return {}; -} - -void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid) -{ - if (g_txindex) { - g_txindex->BlockUntilSyncedToCurrentChain(); - } - - - uint256 hashBlock{}; - const auto tx = GetTransaction(nullptr, &mempool, txid, Params().GetConsensus(), hashBlock); - if (!tx) { - return; - } - - if (LogAcceptDebug(BCLog::INSTANTSEND)) { - for (const auto& in : tx->vin) { - auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); - if (id == recoveredSig.getId()) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: got recovered sig for input %s\n", __func__, - txid.ToString(), in.prevout.ToStringShort()); - break; - } - } - } - - TrySignInstantSendLock(*tx); -} - -void CInstantSendManager::TrySignInstantSendLock(const CTransaction& tx) -{ - const auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; - - for (const auto& in : tx.vin) { - auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); - if (!sigman.HasRecoveredSig(llmqType, id, tx.GetHash())) { - return; - } - } - - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: got all recovered sigs, creating CInstantSendLock\n", __func__, - tx.GetHash().ToString()); - - CInstantSendLock islock; - islock.txid = tx.GetHash(); - for (const auto& in : tx.vin) { - islock.inputs.emplace_back(in.prevout); - } - - auto id = islock.GetRequestId(); - - if (sigman.HasRecoveredSigForId(llmqType, id)) { - return; - } - - const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt); - const auto quorum = SelectQuorumForSigning(llmq_params_opt.value(), m_chainstate.m_chain, qman, id); - - if (!quorum) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- failed to select quorum. islock id=%s, txid=%s\n", - __func__, id.ToString(), tx.GetHash().ToString()); - return; - } - - const int cycle_height = quorum->m_quorum_base_block_index->nHeight - - quorum->m_quorum_base_block_index->nHeight % llmq_params_opt->dkgInterval; - islock.cycleHash = quorum->m_quorum_base_block_index->GetAncestor(cycle_height)->GetBlockHash(); - - { - LOCK(cs_creating); - auto e = creatingInstantSendLocks.emplace(id, std::move(islock)); - if (!e.second) { - return; - } - txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second); - } - - sigman.AsyncSignIfMember(llmqType, shareman, id, tx.GetHash(), quorum->m_quorum_base_block_index->GetBlockHash()); -} - -void CInstantSendManager::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig) -{ - CInstantSendLockPtr islock; - - { - LOCK(cs_creating); - auto it = creatingInstantSendLocks.find(recoveredSig.getId()); - if (it == creatingInstantSendLocks.end()) { - return; - } - - islock = std::make_shared(std::move(it->second)); - creatingInstantSendLocks.erase(it); - txToCreatingInstantSendLocks.erase(islock->txid); - } - - if (islock->txid != recoveredSig.getMsgHash()) { - LogPrintf("CInstantSendManager::%s -- txid=%s: islock conflicts with %s, dropping own version\n", __func__, - islock->txid.ToString(), recoveredSig.getMsgHash().ToString()); - return; - } - - islock->sig = recoveredSig.sig; - auto hash = ::SerializeHash(*islock); - - if (WITH_LOCK(cs_pendingLocks, return pendingInstantSendLocks.count(hash)) || db.KnownInstantSendLock(hash)) { - return; - } - LOCK(cs_pendingLocks); - pendingInstantSendLocks.emplace(hash, std::make_pair(-1, islock)); -} - PeerMsgRet CInstantSendManager::ProcessMessage(const CNode& pfrom, PeerManager& peerman, std::string_view msg_type, CDataStream& vRecv) { if (IsInstantSendEnabled() && msg_type == NetMsgType::ISDLOCK) { - const auto islock = std::make_shared(); + const auto islock = std::make_shared(); vRecv >> *islock; return ProcessMessageInstantSendLock(pfrom, peerman, islock); } @@ -764,7 +119,7 @@ bool ShouldReportISLockTiming() { } PeerMsgRet CInstantSendManager::ProcessMessageInstantSendLock(const CNode& pfrom, PeerManager& peerman, - const llmq::CInstantSendLockPtr& islock) + const instantsend::InstantSendLockPtr& islock) { auto hash = ::SerializeHash(*islock); @@ -818,27 +173,6 @@ PeerMsgRet CInstantSendManager::ProcessMessageInstantSendLock(const CNode& pfrom return {}; } -/** - * Handles trivial ISLock verification - * @return returns false if verification failed, otherwise true - */ -bool CInstantSendLock::TriviallyValid() const -{ - if (txid.IsNull() || inputs.empty()) { - return false; - } - - // Check that each input is unique - std::set dups; - for (const auto& o : inputs) { - if (!dups.emplace(o).second) { - return false; - } - } - - return true; -} - bool CInstantSendManager::ProcessPendingInstantSendLocks(PeerManager& peerman) { decltype(pendingInstantSendLocks) pend; @@ -905,7 +239,7 @@ bool CInstantSendManager::ProcessPendingInstantSendLocks(PeerManager& peerman) std::unordered_set CInstantSendManager::ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, PeerManager& peerman, int signOffset, - const std::unordered_map, StaticSaltedHasher>& pend, bool ban) + const std::unordered_map, StaticSaltedHasher>& pend, bool ban) { CBLSBatchVerifier batchVerifier(false, true, 8); std::unordered_map recSigs; @@ -1014,14 +348,12 @@ std::unordered_set CInstantSendManager::ProcessPend } void CInstantSendManager::ProcessInstantSendLock(NodeId from, PeerManager& peerman, const uint256& hash, - const CInstantSendLockPtr& islock) + const instantsend::InstantSendLockPtr& islock) { LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), from); - { - LOCK(cs_creating); - creatingInstantSendLocks.erase(islock->GetRequestId()); - txToCreatingInstantSendLocks.erase(islock->txid); + if (m_signer) { + m_signer->ClearLockFromQueue(islock); } if (db.KnownInstantSendLock(hash)) { return; @@ -1092,7 +424,7 @@ void CInstantSendManager::ProcessInstantSendLock(NodeId from, PeerManager& peerm // bump mempool counter to make sure newly locked txes are picked up by getblocktemplate mempool.AddTransactionsUpdated(1); } else { - peerman.AskPeersForTransaction(islock->txid, m_is_masternode); + peerman.AskPeersForTransaction(islock->txid, /*is_masternode=*/m_signer != nullptr); } } @@ -1102,7 +434,7 @@ void CInstantSendManager::TransactionAddedToMempool(PeerManager& peerman, const return; } - CInstantSendLockPtr islock{nullptr}; + instantsend::InstantSendLockPtr islock{nullptr}; { LOCK(cs_pendingLocks); auto it = pendingNoTxInstantSendLocks.begin(); @@ -1121,7 +453,9 @@ void CInstantSendManager::TransactionAddedToMempool(PeerManager& peerman, const } if (islock == nullptr) { - ProcessTx(*tx, false, Params().GetConsensus()); + if (m_signer) { + m_signer->ProcessTx(*tx, false, Params().GetConsensus()); + } // TX is not locked, so make sure it is tracked AddNonLockedTx(tx, nullptr); } else { @@ -1135,7 +469,7 @@ void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& t return; } - CInstantSendLockPtr islock = db.GetInstantSendLockByTxid(tx->GetHash()); + instantsend::InstantSendLockPtr islock = db.GetInstantSendLockByTxid(tx->GetHash()); if (islock == nullptr) { return; @@ -1160,7 +494,9 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr& pb } if (!IsLocked(tx->GetHash()) && !has_chainlock) { - ProcessTx(*tx, true, Params().GetConsensus()); + if (m_signer) { + m_signer->ProcessTx(*tx, true, Params().GetConsensus()); + } // TX is not locked, so make sure it is tracked AddNonLockedTx(tx, pindex); } else { @@ -1263,20 +599,28 @@ void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChild void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx) { RemoveNonLockedTx(tx.GetHash(), false); + if (m_signer) { + m_signer->ClearInputsFromQueue(GetIdsFromLockable(tx.vin)); + } +} - LOCK(cs_inputReqests); - for (const auto& in : tx.vin) { - auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in)); - inputRequestIds.erase(inputRequestId); +void CInstantSendManager::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock) +{ + auto ids = GetIdsFromLockable(islock.inputs); + if (m_signer) { + m_signer->ClearInputsFromQueue(ids); + } + for (const auto& id : ids) { + sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id); } } -void CInstantSendManager::TruncateRecoveredSigsForInputs(const llmq::CInstantSendLock& islock) +void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) { - for (const auto& in : islock.inputs) { - auto inputRequestId = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in)); - WITH_LOCK(cs_inputReqests, inputRequestIds.erase(inputRequestId)); - sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, inputRequestId); + if (db.KnownInstantSendLock(hash)) return; + LOCK(cs_pendingLocks); + if (!pendingInstantSendLocks.count(hash)) { + pendingInstantSendLocks.emplace(hash, std::make_pair(id, islock)); } } @@ -1345,7 +689,7 @@ void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) } void CInstantSendManager::RemoveMempoolConflictsForLock(PeerManager& peerman, const uint256& hash, - const CInstantSendLock& islock) + const instantsend::InstantSendLock& islock) { std::unordered_map toDelete; @@ -1374,11 +718,11 @@ void CInstantSendManager::RemoveMempoolConflictsForLock(PeerManager& peerman, co for (const auto& p : toDelete) { RemoveConflictedTx(*p.second); } - peerman.AskPeersForTransaction(islock.txid, m_is_masternode); + peerman.AskPeersForTransaction(islock.txid, /*is_masternode=*/m_signer != nullptr); } } -void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const llmq::CInstantSendLock& islock) +void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) { // Lets first collect all non-locked TXs which conflict with the given ISLOCK std::unordered_map> conflicts; @@ -1466,7 +810,7 @@ void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const } } -void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const llmq::CInstantSendLock& islock) +void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock) { LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: Removing ISLOCK and its chained children\n", __func__, islock.txid.ToString(), islockHash.ToString()); @@ -1479,67 +823,6 @@ void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const } } -void CInstantSendManager::ProcessPendingRetryLockTxs() -{ - const auto retryTxs = WITH_LOCK(cs_pendingRetry, return pendingRetryTxs); - - if (retryTxs.empty()) { - return; - } - - if (!IsInstantSendEnabled()) { - return; - } - - int retryCount = 0; - for (const auto& txid : retryTxs) { - CTransactionRef tx; - { - { - LOCK(cs_nonLocked); - auto it = nonLockedTxs.find(txid); - if (it == nonLockedTxs.end()) { - continue; - } - tx = it->second.tx; - } - if (!tx) { - continue; - } - - if (LOCK(cs_creating); txToCreatingInstantSendLocks.count(tx->GetHash())) { - // we're already in the middle of locking this one - continue; - } - if (IsLocked(tx->GetHash())) { - continue; - } - if (GetConflictingLock(*tx) != nullptr) { - // should not really happen as we have already filtered these out - continue; - } - } - - // CheckCanLock is already called by ProcessTx, so we should avoid calling it twice. But we also shouldn't spam - // the logs when retrying TXs that are not ready yet. - if (LogAcceptDebug(BCLog::INSTANTSEND)) { - if (!CheckCanLock(*tx, false, Params().GetConsensus())) { - continue; - } - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s: retrying to lock\n", __func__, - tx->GetHash().ToString()); - } - - ProcessTx(*tx, false, Params().GetConsensus()); - retryCount++; - } - - if (retryCount != 0) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- retried %d TXs. nonLockedTxs.size=%d\n", __func__, - retryCount, WITH_LOCK(cs_nonLocked, return nonLockedTxs.size())); - } -} - bool CInstantSendManager::AlreadyHave(const CInv& inv) const { if (!IsInstantSendEnabled()) { @@ -1550,7 +833,7 @@ bool CInstantSendManager::AlreadyHave(const CInv& inv) const || db.KnownInstantSendLock(inv.hash); } -bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, llmq::CInstantSendLock& ret) const +bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const { if (!IsInstantSendEnabled()) { return false; @@ -1575,7 +858,7 @@ bool CInstantSendManager::GetInstantSendLockByHash(const uint256& hash, llmq::CI return true; } -CInstantSendLockPtr CInstantSendManager::GetInstantSendLockByTxid(const uint256& txid) const +instantsend::InstantSendLockPtr CInstantSendManager::GetInstantSendLockByTxid(const uint256& txid) const { if (!IsInstantSendEnabled()) { return nullptr; @@ -1612,7 +895,7 @@ bool CInstantSendManager::IsWaitingForTx(const uint256& txHash) const return false; } -CInstantSendLockPtr CInstantSendManager::GetConflictingLock(const CTransaction& tx) const +instantsend::InstantSendLockPtr CInstantSendManager::GetConflictingLock(const CTransaction& tx) const { if (!IsInstantSendEnabled()) { return nullptr; @@ -1639,8 +922,29 @@ size_t CInstantSendManager::GetInstantSendLockCount() const void CInstantSendManager::WorkThreadMain(PeerManager& peerman) { while (!workInterrupt) { - bool fMoreWork = ProcessPendingInstantSendLocks(peerman); - ProcessPendingRetryLockTxs(); + bool fMoreWork = [&]() -> bool { + if (!IsInstantSendEnabled()) return false; + const bool more_work{ProcessPendingInstantSendLocks(peerman)}; + if (!m_signer) return more_work; + // Construct set of non-locked transactions that are pending to retry + std::vector txns{}; + { + LOCK2(cs_nonLocked, cs_pendingRetry); + if (pendingRetryTxs.empty()) return more_work; + txns.reserve(pendingRetryTxs.size()); + for (const auto& txid : pendingRetryTxs) { + if (auto it = nonLockedTxs.find(txid); it != nonLockedTxs.end()) { + const auto& [_, tx_info] = *it; + if (tx_info.tx) { + txns.push_back(tx_info.tx); + } + } + } + } + // Retry processing them + m_signer->ProcessPendingRetryLockTxs(txns); + return more_work; + }(); if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { return; @@ -1653,11 +957,6 @@ bool CInstantSendManager::IsInstantSendEnabled() const return !fReindex && !fImporting && spork_manager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED); } -bool CInstantSendManager::IsInstantSendMempoolSigningEnabled() const -{ - return !fReindex && !fImporting && spork_manager.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED) == 0; -} - bool CInstantSendManager::RejectConflictingBlocks() const { if (!m_mn_sync.IsBlockchainSynced()) { diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h new file mode 100644 index 000000000000..1e4f3c4e764f --- /dev/null +++ b/src/instantsend/instantsend.h @@ -0,0 +1,170 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INSTANTSEND_INSTANTSEND_H +#define BITCOIN_INSTANTSEND_INSTANTSEND_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +class CBlockIndex; +class CChainState; +class CDataStream; +class CMasternodeSync; +class CNode; +class CSporkManager; +class CTxMemPool; +class PeerManager; +namespace Consensus { +struct LLMQParams; +} // namespace Consensus +namespace instantsend { +class InstantSendSigner; +} // namespace instantsend + +using NodeId = int64_t; + +namespace llmq { +class CChainLocksHandler; +class CQuorumManager; +class CSigningManager; +class CSigSharesManager; + +class CInstantSendManager +{ +private: + instantsend::CInstantSendDb db; + + CChainLocksHandler& clhandler; + CChainState& m_chainstate; + CQuorumManager& qman; + CSigningManager& sigman; + CSporkManager& spork_manager; + CTxMemPool& mempool; + const CMasternodeSync& m_mn_sync; + + std::unique_ptr m_signer{nullptr}; + + std::thread workThread; + CThreadInterrupt workInterrupt; + + mutable Mutex cs_pendingLocks; + // Incoming and not verified yet + std::unordered_map, StaticSaltedHasher> pendingInstantSendLocks GUARDED_BY(cs_pendingLocks); + // Tried to verify but there is no tx yet + std::unordered_map, StaticSaltedHasher> pendingNoTxInstantSendLocks GUARDED_BY(cs_pendingLocks); + + // TXs which are neither IS locked nor ChainLocked. We use this to determine for which TXs we need to retry IS locking + // of child TXs + struct NonLockedTxInfo { + const CBlockIndex* pindexMined; + CTransactionRef tx; + std::unordered_set children; + }; + + mutable Mutex cs_nonLocked; + std::unordered_map nonLockedTxs GUARDED_BY(cs_nonLocked); + std::unordered_map nonLockedTxsByOutpoints GUARDED_BY(cs_nonLocked); + + mutable Mutex cs_pendingRetry; + std::unordered_set pendingRetryTxs GUARDED_BY(cs_pendingRetry); + + mutable Mutex cs_timingsTxSeen; + std::unordered_map timingsTxSeen GUARDED_BY(cs_timingsTxSeen); + +public: + explicit CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CQuorumManager& _qman, + CSigningManager& _sigman, CSigSharesManager& _shareman, CSporkManager& sporkman, + CTxMemPool& _mempool, const CMasternodeSync& mn_sync, bool is_masternode, + bool unitTests, bool fWipe); + ~CInstantSendManager(); + + void Start(PeerManager& peerman); + void Stop(); + void InterruptWorkerThread() { workInterrupt(); }; + +private: + PeerMsgRet ProcessMessageInstantSendLock(const CNode& pfrom, PeerManager& peerman, const instantsend::InstantSendLockPtr& islock); + bool ProcessPendingInstantSendLocks(PeerManager& peerman) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + + std::unordered_set ProcessPendingInstantSendLocks( + const Consensus::LLMQParams& llmq_params, PeerManager& peerman, int signOffset, + const std::unordered_map, StaticSaltedHasher>& pend, bool ban) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + void ProcessInstantSendLock(NodeId from, PeerManager& peerman, const uint256& hash, const instantsend::InstantSendLockPtr& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + + void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks); + void RemoveNonLockedTx(const uint256& txid, bool retryChildren) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + void RemoveConflictedTx(const CTransaction& tx) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); + + void RemoveMempoolConflictsForLock(PeerManager& peerman, const uint256& hash, const instantsend::InstantSendLock& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + + void WorkThreadMain(PeerManager& peerman) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + + void HandleFullyConfirmedBlock(const CBlockIndex* pindex) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + +public: + bool IsLocked(const uint256& txHash) const; + bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const; + + PeerMsgRet ProcessMessage(const CNode& pfrom, PeerManager& peerman, std::string_view msg_type, CDataStream& vRecv); + + void TransactionAddedToMempool(PeerManager& peerman, const CTransactionRef& tx) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + void TransactionRemovedFromMempool(const CTransactionRef& tx); + void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); + void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected); + + bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + bool GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + instantsend::InstantSendLockPtr GetInstantSendLockByTxid(const uint256& txid) const; + + void NotifyChainLock(const CBlockIndex* pindexChainLock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + void UpdatedBlockTip(const CBlockIndex* pindexNew) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + + void RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock); + void TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + + size_t GetInstantSendLockCount() const; + + bool IsInstantSendEnabled() const; + /** + * If true, MN should sign all transactions, if false, MN should not sign + * transactions in mempool, but should sign txes included in a block. This + * allows ChainLocks to continue even while this spork is disabled. + */ + bool RejectConflictingBlocks() const; +}; +} // namespace llmq + +#endif // BITCOIN_INSTANTSEND_INSTANTSEND_H diff --git a/src/instantsend/lock.cpp b/src/instantsend/lock.cpp new file mode 100644 index 000000000000..3d093db77806 --- /dev/null +++ b/src/instantsend/lock.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +#include +#include + +static const std::string_view ISLOCK_REQUESTID_PREFIX = "islock"; + +namespace instantsend { +uint256 InstantSendLock::GetRequestId() const +{ + CHashWriter hw(SER_GETHASH, 0); + hw << ISLOCK_REQUESTID_PREFIX; + hw << inputs; + return hw.GetHash(); +} + +/** + * Handles trivial ISLock verification + * @return returns false if verification failed, otherwise true + */ +bool InstantSendLock::TriviallyValid() const +{ + if (txid.IsNull() || inputs.empty()) { + return false; + } + + // Check that each input is unique + std::set dups; + for (const auto& o : inputs) { + if (!dups.emplace(o).second) { + return false; + } + } + + return true; +} +} // namespace instantsend diff --git a/src/instantsend/lock.h b/src/instantsend/lock.h new file mode 100644 index 000000000000..5223ce5f9a76 --- /dev/null +++ b/src/instantsend/lock.h @@ -0,0 +1,46 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INSTANTSEND_LOCK_H +#define BITCOIN_INSTANTSEND_LOCK_H + +#include +#include +#include + +#include +#include +#include + +class COutPoint; + +namespace instantsend { +struct InstantSendLock { + static constexpr uint8_t CURRENT_VERSION{1}; + + uint8_t nVersion{CURRENT_VERSION}; + std::vector inputs; + uint256 txid; + uint256 cycleHash; + CBLSLazySignature sig; + + InstantSendLock() = default; + + SERIALIZE_METHODS(InstantSendLock, obj) + { + READWRITE(obj.nVersion); + READWRITE(obj.inputs); + READWRITE(obj.txid); + READWRITE(obj.cycleHash); + READWRITE(obj.sig); + } + + uint256 GetRequestId() const; + bool TriviallyValid() const; +}; + +using InstantSendLockPtr = std::shared_ptr; +} // namespace instantsend + +#endif // BITCOIN_INSTANTSEND_LOCK_H diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp new file mode 100644 index 000000000000..63bbcd13677a --- /dev/null +++ b/src/instantsend/signing.cpp @@ -0,0 +1,396 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// Forward declaration to break dependency over node/transaction.h +namespace node { +CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, + const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock); +} // namespace node + +using node::fImporting; +using node::fReindex; +using node::GetTransaction; + +namespace instantsend { +static const std::string_view INPUTLOCK_REQUESTID_PREFIX = "inlock"; + +InstantSendSigner::InstantSendSigner(CChainState& chainstate, llmq::CChainLocksHandler& clhandler, + llmq::CInstantSendManager& isman, llmq::CSigningManager& sigman, + llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman, + CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : + m_chainstate{chainstate}, + m_clhandler{clhandler}, + m_isman{isman}, + m_sigman{sigman}, + m_shareman{shareman}, + m_qman{qman}, + m_sporkman{sporkman}, + m_mempool{mempool}, + m_mn_sync{mn_sync} +{ +} + +InstantSendSigner::~InstantSendSigner() = default; + +void InstantSendSigner::Start() +{ + m_sigman.RegisterRecoveredSigsListener(this); +} + +void InstantSendSigner::Stop() +{ + m_sigman.UnregisterRecoveredSigsListener(this); +} + +void InstantSendSigner::ClearInputsFromQueue(const std::unordered_set& ids) +{ + LOCK(cs_inputReqests); + for (const auto& id : ids) { + inputRequestIds.erase(id); + } +} + +void InstantSendSigner::ClearLockFromQueue(const InstantSendLockPtr& islock) +{ + LOCK(cs_creating); + creatingInstantSendLocks.erase(islock->GetRequestId()); + txToCreatingInstantSendLocks.erase(islock->txid); +} + +MessageProcessingResult InstantSendSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) +{ + if (!m_isman.IsInstantSendEnabled()) { + return {}; + } + + if (Params().GetConsensus().llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) { + return {}; + } + + uint256 txid; + if (LOCK(cs_inputReqests); inputRequestIds.count(recoveredSig.getId())) { + txid = recoveredSig.getMsgHash(); + } + if (!txid.IsNull()) { + HandleNewInputLockRecoveredSig(recoveredSig, txid); + } else if (/*isInstantSendLock=*/ WITH_LOCK(cs_creating, return creatingInstantSendLocks.count(recoveredSig.getId()))) { + HandleNewInstantSendLockRecoveredSig(recoveredSig); + } + return {}; +} + +bool InstantSendSigner::IsInstantSendMempoolSigningEnabled() const +{ + return !fReindex && !fImporting && m_sporkman.GetSporkValue(SPORK_2_INSTANTSEND_ENABLED) == 0; +} + +void InstantSendSigner::HandleNewInputLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const uint256& txid) +{ + if (g_txindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } + + uint256 hashBlock{}; + const auto tx = GetTransaction(nullptr, &m_mempool, txid, Params().GetConsensus(), hashBlock); + if (!tx) { + return; + } + + if (LogAcceptDebug(BCLog::INSTANTSEND)) { + for (const auto& in : tx->vin) { + auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); + if (id == recoveredSig.getId()) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got recovered sig for input %s\n", __func__, + txid.ToString(), in.prevout.ToStringShort()); + break; + } + } + } + + TrySignInstantSendLock(*tx); +} + +void InstantSendSigner::ProcessPendingRetryLockTxs(const std::vector& retryTxs) +{ + if (!m_isman.IsInstantSendEnabled()) { + return; + } + + int retryCount = 0; + for (const auto& tx : retryTxs) { + { + if (LOCK(cs_creating); txToCreatingInstantSendLocks.count(tx->GetHash())) { + // we're already in the middle of locking this one + continue; + } + if (m_isman.IsLocked(tx->GetHash())) { + continue; + } + if (m_isman.GetConflictingLock(*tx) != nullptr) { + // should not really happen as we have already filtered these out + continue; + } + } + + // CheckCanLock is already called by ProcessTx, so we should avoid calling it twice. But we also shouldn't spam + // the logs when retrying TXs that are not ready yet. + if (LogAcceptDebug(BCLog::INSTANTSEND)) { + if (!CheckCanLock(*tx, false, Params().GetConsensus())) { + continue; + } + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: retrying to lock\n", __func__, + tx->GetHash().ToString()); + } + + ProcessTx(*tx, false, Params().GetConsensus()); + retryCount++; + } + + if (retryCount != 0) { + LogPrint(BCLog::INSTANTSEND, "%s -- retried %d TXs.\n", __func__, retryCount); + } +} + +bool InstantSendSigner::CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const +{ + if (tx.vin.empty()) { + // can't lock TXs without inputs (e.g. quorum commitments) + return false; + } + + return ranges::all_of(tx.vin, + [&](const auto& in) { return CheckCanLock(in.prevout, printDebug, tx.GetHash(), params); }); +} + +bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash, const Consensus::Params& params) const +{ + int nInstantSendConfirmationsRequired = params.nInstantSendConfirmationsRequired; + + if (m_isman.IsLocked(outpoint.hash)) { + // if prevout was ix locked, allow locking of descendants (no matter if prevout is in mempool or already mined) + return true; + } + + auto mempoolTx = m_mempool.get(outpoint.hash); + if (mempoolTx) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: parent mempool TX %s is not locked\n", __func__, + txHash.ToString(), outpoint.hash.ToString()); + } + return false; + } + + uint256 hashBlock{}; + const auto tx = GetTransaction(nullptr, &m_mempool, outpoint.hash, params, hashBlock); + // this relies on enabled txindex and won't work if we ever try to remove the requirement for txindex for masternodes + if (!tx) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to find parent TX %s\n", __func__, + txHash.ToString(), outpoint.hash.ToString()); + } + return false; + } + + const CBlockIndex* pindexMined; + int nTxAge; + { + LOCK(::cs_main); + pindexMined = m_chainstate.m_blockman.LookupBlockIndex(hashBlock); + nTxAge = m_chainstate.m_chain.Height() - pindexMined->nHeight + 1; + } + + if (nTxAge < nInstantSendConfirmationsRequired && !m_clhandler.HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__, + txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired); + } + return false; + } + + return true; +} + +void InstantSendSigner::HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig) +{ + InstantSendLockPtr islock; + + { + LOCK(cs_creating); + auto it = creatingInstantSendLocks.find(recoveredSig.getId()); + if (it == creatingInstantSendLocks.end()) { + return; + } + + islock = std::make_shared(std::move(it->second)); + creatingInstantSendLocks.erase(it); + txToCreatingInstantSendLocks.erase(islock->txid); + } + + if (islock->txid != recoveredSig.getMsgHash()) { + LogPrintf("%s -- txid=%s: islock conflicts with %s, dropping own version\n", __func__, + islock->txid.ToString(), recoveredSig.getMsgHash().ToString()); + return; + } + + islock->sig = recoveredSig.sig; + m_isman.TryEmplacePendingLock(/*hash=*/::SerializeHash(*islock), /*id=*/-1, islock); +} + +void InstantSendSigner::ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params) +{ + if (!m_isman.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced()) { + return; + } + + if (params.llmqTypeDIP0024InstantSend == Consensus::LLMQType::LLMQ_NONE) { + return; + } + + if (!CheckCanLock(tx, true, params)) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: CheckCanLock returned false\n", __func__, + tx.GetHash().ToString()); + return; + } + + auto conflictingLock = m_isman.GetConflictingLock(tx); + if (conflictingLock != nullptr) { + auto conflictingLockHash = ::SerializeHash(*conflictingLock); + LogPrintf("%s -- txid=%s: conflicts with islock %s, txid=%s\n", __func__, + tx.GetHash().ToString(), conflictingLockHash.ToString(), conflictingLock->txid.ToString()); + return; + } + + // Only sign for inlocks or islocks if mempool IS signing is enabled. + // However, if we are processing a tx because it was included in a block we should + // sign even if mempool IS signing is disabled. This allows a ChainLock to happen on this + // block after we retroactively locked all transactions. + if (!IsInstantSendMempoolSigningEnabled() && !fRetroactive) return; + + if (!TrySignInputLocks(tx, fRetroactive, params.llmqTypeDIP0024InstantSend, params)) { + return; + } + + // We might have received all input locks before we got the corresponding TX. In this case, we have to sign the + // islock now instead of waiting for the input locks. + TrySignInstantSendLock(tx); +} + +bool InstantSendSigner::TrySignInputLocks(const CTransaction& tx, bool fRetroactive, Consensus::LLMQType llmqType, const Consensus::Params& params) +{ + std::vector ids; + ids.reserve(tx.vin.size()); + + size_t alreadyVotedCount = 0; + for (const auto& in : tx.vin) { + auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); + ids.emplace_back(id); + + uint256 otherTxHash; + if (m_sigman.GetVoteForId(params.llmqTypeDIP0024InstantSend, id, otherTxHash)) { + if (otherTxHash != tx.GetHash()) { + LogPrintf("%s -- txid=%s: input %s is conflicting with previous vote for tx %s\n", __func__, + tx.GetHash().ToString(), in.prevout.ToStringShort(), otherTxHash.ToString()); + return false; + } + alreadyVotedCount++; + } + + // don't even try the actual signing if any input is conflicting + if (m_sigman.IsConflicting(params.llmqTypeDIP0024InstantSend, id, tx.GetHash())) { + LogPrintf("%s -- txid=%s: m_sigman.IsConflicting returned true. id=%s\n", __func__, + tx.GetHash().ToString(), id.ToString()); + return false; + } + } + if (!fRetroactive && alreadyVotedCount == ids.size()) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: already voted on all inputs, bailing out\n", __func__, + tx.GetHash().ToString()); + return true; + } + + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on %d inputs\n", __func__, + tx.GetHash().ToString(), tx.vin.size()); + + for (const auto i : irange::range(tx.vin.size())) { + const auto& in = tx.vin[i]; + auto& id = ids[i]; + WITH_LOCK(cs_inputReqests, inputRequestIds.emplace(id)); + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: trying to vote on input %s with id %s. fRetroactive=%d\n", __func__, + tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString(), fRetroactive); + if (m_sigman.AsyncSignIfMember(llmqType, m_shareman, id, tx.GetHash(), {}, fRetroactive)) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: voted on input %s with id %s\n", __func__, + tx.GetHash().ToString(), in.prevout.ToStringShort(), id.ToString()); + } + } + + return true; +} + +void InstantSendSigner::TrySignInstantSendLock(const CTransaction& tx) +{ + const auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; + + for (const auto& in : tx.vin) { + auto id = ::SerializeHash(std::make_pair(INPUTLOCK_REQUESTID_PREFIX, in.prevout)); + if (!m_sigman.HasRecoveredSig(llmqType, id, tx.GetHash())) { + return; + } + } + + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: got all recovered sigs, creating InstantSendLock\n", __func__, + tx.GetHash().ToString()); + + InstantSendLock islock; + islock.txid = tx.GetHash(); + for (const auto& in : tx.vin) { + islock.inputs.emplace_back(in.prevout); + } + + auto id = islock.GetRequestId(); + + if (m_sigman.HasRecoveredSigForId(llmqType, id)) { + return; + } + + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + assert(llmq_params_opt); + const auto quorum = llmq::SelectQuorumForSigning(llmq_params_opt.value(), m_chainstate.m_chain, m_qman, id); + + if (!quorum) { + LogPrint(BCLog::INSTANTSEND, "%s -- failed to select quorum. islock id=%s, txid=%s\n", + __func__, id.ToString(), tx.GetHash().ToString()); + return; + } + + const int cycle_height = quorum->m_quorum_base_block_index->nHeight - + quorum->m_quorum_base_block_index->nHeight % llmq_params_opt->dkgInterval; + islock.cycleHash = quorum->m_quorum_base_block_index->GetAncestor(cycle_height)->GetBlockHash(); + + { + LOCK(cs_creating); + auto e = creatingInstantSendLocks.emplace(id, std::move(islock)); + if (!e.second) { + return; + } + txToCreatingInstantSendLocks.emplace(tx.GetHash(), &e.first->second); + } + + m_sigman.AsyncSignIfMember(llmqType, m_shareman, id, tx.GetHash(), quorum->m_quorum_base_block_index->GetBlockHash()); +} +} // namespace instantsend diff --git a/src/instantsend/signing.h b/src/instantsend/signing.h new file mode 100644 index 000000000000..1256fcdefe08 --- /dev/null +++ b/src/instantsend/signing.h @@ -0,0 +1,104 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INSTANTSEND_SIGNING_H +#define BITCOIN_INSTANTSEND_SIGNING_H + +#include +#include + +class CMasternodeSync; +class CSporkManager; +class CTxMemPool; +struct MessageProcessingResult; + +namespace Consensus { +struct Params; +} // namespace Consensus +namespace llmq { +class CChainLocksHandler; +class CInstantSendManager; +class CSigningManager; +class CSigSharesManager; +class CQuorumManager; +} // namespace llmq + +namespace instantsend { +class InstantSendSigner : public llmq::CRecoveredSigsListener +{ +private: + CChainState& m_chainstate; + llmq::CChainLocksHandler& m_clhandler; + llmq::CInstantSendManager& m_isman; + llmq::CSigningManager& m_sigman; + llmq::CSigSharesManager& m_shareman; + llmq::CQuorumManager& m_qman; + CSporkManager& m_sporkman; + CTxMemPool& m_mempool; + const CMasternodeSync& m_mn_sync; + +private: + mutable Mutex cs_inputReqests; + mutable Mutex cs_creating; + + /** + * Request ids of inputs that we signed. Used to determine if a recovered signature belongs to an + * in-progress input lock. + */ + std::unordered_set inputRequestIds GUARDED_BY(cs_inputReqests); + + /** + * These are the islocks that are currently in the middle of being created. Entries are created when we observed + * recovered signatures for all inputs of a TX. At the same time, we initiate signing of our sigshare for the islock. + * When the recovered sig for the islock later arrives, we can finish the islock and propagate it. + */ + std::unordered_map creatingInstantSendLocks GUARDED_BY(cs_creating); + // maps from txid to the in-progress islock + std::unordered_map txToCreatingInstantSendLocks GUARDED_BY(cs_creating); + +public: + explicit InstantSendSigner(CChainState& chainstate, llmq::CChainLocksHandler& clhandler, + llmq::CInstantSendManager& isman, llmq::CSigningManager& sigman, + llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman, CSporkManager& sporkman, + CTxMemPool& mempool, const CMasternodeSync& mn_sync); + ~InstantSendSigner(); + + void Start(); + void Stop(); + + void ClearInputsFromQueue(const std::unordered_set& ids) + EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests); + + void ClearLockFromQueue(const InstantSendLockPtr& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating); + + [[nodiscard]] MessageProcessingResult HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) override + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests); + + void ProcessPendingRetryLockTxs(const std::vector& retryTxs) + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests); + void ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params) + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests); + +private: + [[nodiscard]] bool CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const; + [[nodiscard]] bool CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash, + const Consensus::Params& params) const; + + void HandleNewInputLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig, const uint256& txid) + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating); + void HandleNewInstantSendLockRecoveredSig(const llmq::CRecoveredSig& recoveredSig) + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating); + + [[nodiscard]] bool IsInstantSendMempoolSigningEnabled() const; + + [[nodiscard]] bool TrySignInputLocks(const CTransaction& tx, bool allowResigning, Consensus::LLMQType llmqType, + const Consensus::Params& params) + EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests); + void TrySignInstantSendLock(const CTransaction& tx) + EXCLUSIVE_LOCKS_REQUIRED(!cs_creating); +}; +} // namespace instantsend + +#endif // BITCOIN_INSTANTSEND_SIGNING_H diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 753b158055ae..5dc214fa54a7 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -31,9 +31,11 @@ enum class MemPoolRemovalReason; struct bilingual_str; struct CBlockLocator; struct FeeCalculation; +namespace instantsend { +struct InstantSendLock; +} // namespace instantsend namespace llmq { class CChainLockSig; -struct CInstantSendLock; } // namespace llmq namespace node { struct NodeContext; @@ -266,7 +268,7 @@ class Chain virtual void updatedBlockTip() {} virtual void chainStateFlushed(const CBlockLocator& locator) {} virtual void notifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr& clsig) {} - virtual void notifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) {} + virtual void notifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) {} }; //! Register handler for notifications. diff --git a/src/llmq/chainlocks.cpp b/src/llmq/chainlocks.cpp index 7bef167fc638..43836b68378b 100644 --- a/src/llmq/chainlocks.cpp +++ b/src/llmq/chainlocks.cpp @@ -3,9 +3,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include -#include -#include -#include #include #include @@ -23,6 +20,10 @@ #include #include +#include +#include +#include + using node::ReadBlockFromDisk; // Forward declaration to break dependency over node/transaction.h diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 303194291e6c..7b3d31a1fc77 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -7,13 +7,13 @@ #include #include +#include #include #include #include #include #include #include -#include #include #include #include diff --git a/src/llmq/context.h b/src/llmq/context.h index 1f6dba27916e..b6586bb01186 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -69,7 +69,7 @@ struct LLMQContext { const std::unique_ptr sigman; const std::unique_ptr shareman; const std::unique_ptr clhandler; - const std::unique_ptr isman; // TODO: split CInstantSendManager and CInstantSendLock to 2 files + const std::unique_ptr isman; const std::unique_ptr ehfSignalsHandler; }; diff --git a/src/llmq/instantsend.h b/src/llmq/instantsend.h deleted file mode 100644 index 6d2038087215..000000000000 --- a/src/llmq/instantsend.h +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright (c) 2019-2025 The Dash Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_LLMQ_INSTANTSEND_H -#define BITCOIN_LLMQ_INSTANTSEND_H - -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -class CBlockIndex; -class CChainState; -class CConnman; -class CDBWrapper; -class CMasternodeSync; -class CSporkManager; -class PeerManager; - -namespace llmq -{ -class CChainLocksHandler; -class CQuorumManager; -class CSigningManager; -class CSigSharesManager; - -struct CInstantSendLock -{ - static constexpr uint8_t CURRENT_VERSION{1}; - - uint8_t nVersion{CURRENT_VERSION}; - std::vector inputs; - uint256 txid; - uint256 cycleHash; - CBLSLazySignature sig; - - CInstantSendLock() = default; - - SERIALIZE_METHODS(CInstantSendLock, obj) - { - READWRITE(obj.nVersion); - READWRITE(obj.inputs); - READWRITE(obj.txid); - READWRITE(obj.cycleHash); - READWRITE(obj.sig); - } - - uint256 GetRequestId() const; - bool TriviallyValid() const; -}; - -using CInstantSendLockPtr = std::shared_ptr; - -class CInstantSendDb -{ -private: - mutable Mutex cs_db; - - static constexpr int CURRENT_VERSION{1}; - - int best_confirmed_height GUARDED_BY(cs_db) {0}; - - std::unique_ptr db GUARDED_BY(cs_db) {nullptr}; - mutable unordered_lru_cache islockCache GUARDED_BY(cs_db); - mutable unordered_lru_cache txidCache GUARDED_BY(cs_db); - - mutable unordered_lru_cache outpointCache GUARDED_BY(cs_db); - void WriteInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_db); - - void RemoveInstantSendLockMined(CDBBatch& batch, const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_db); - - /** - * This method removes a InstantSend Lock from the database and is called when a tx with an IS lock is confirmed and Chainlocked - * @param batch Object used to batch many calls together - * @param hash The hash of the InstantSend Lock - * @param islock The InstantSend Lock object itself - * @param keep_cache Should we still keep corresponding entries in the cache or not - */ - void RemoveInstantSendLock(CDBBatch& batch, const uint256& hash, CInstantSendLockPtr islock, bool keep_cache = true) EXCLUSIVE_LOCKS_REQUIRED(cs_db); - /** - * Marks an InstantSend Lock as archived. - * @param batch Object used to batch many calls together - * @param hash The hash of the InstantSend Lock - * @param nHeight The height that the transaction was included at - */ - void WriteInstantSendLockArchived(CDBBatch& batch, const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_db); - /** - * Gets a vector of IS Lock hashes of the IS Locks which rely on or are children of the parent IS Lock - * @param parent The hash of the parent IS Lock - * @return Returns a vector of IS Lock hashes - */ - std::vector GetInstantSendLocksByParent(const uint256& parent) const EXCLUSIVE_LOCKS_REQUIRED(cs_db); - - /** - * See GetInstantSendLockByHash - */ - CInstantSendLockPtr GetInstantSendLockByHashInternal(const uint256& hash, bool use_cache = true) const EXCLUSIVE_LOCKS_REQUIRED(cs_db); - - /** - * See GetInstantSendLockHashByTxid - */ - uint256 GetInstantSendLockHashByTxidInternal(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_db); - - - void Upgrade(bool unitTests) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - -public: - explicit CInstantSendDb(bool unitTests, bool fWipe); - ~CInstantSendDb(); - - /** - * This method is called when an InstantSend Lock is processed and adds the lock to the database - * @param hash The hash of the InstantSend Lock - * @param islock The InstantSend Lock object itself - */ - void WriteNewInstantSendLock(const uint256& hash, const CInstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * This method updates a DB entry for an InstantSend Lock from being not included in a block to being included in a block - * @param hash The hash of the InstantSend Lock - * @param nHeight The height that the transaction was included at - */ - void WriteInstantSendLockMined(const uint256& hash, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * Archives and deletes all IS Locks which were mined into a block before nUntilHeight - * @param nUntilHeight Removes all IS Locks confirmed up until nUntilHeight - * @return returns an unordered_map of the hash of the IS Locks and a pointer object to the IS Locks for all IS Locks which were removed - */ - std::unordered_map RemoveConfirmedInstantSendLocks(int nUntilHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * Removes IS Locks from the archive if the tx was confirmed 100 blocks before nUntilHeight - * @param nUntilHeight the height from which to base the remove of archive IS Locks - */ - void RemoveArchivedInstantSendLocks(int nUntilHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - void WriteBlockInstantSendLocks(const gsl::not_null>& pblock, gsl::not_null pindexConnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - void RemoveBlockInstantSendLocks(const gsl::not_null>& pblock, gsl::not_null pindexDisconnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - bool KnownInstantSendLock(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * Gets the number of IS Locks which have not been confirmed by a block - * @return size_t value of the number of IS Locks not confirmed by a block - */ - size_t GetInstantSendLockCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * Gets a pointer to the IS Lock based on the hash - * @param hash The hash of the IS Lock - * @param use_cache Should we try using the cache first or not - * @return A Pointer object to the IS Lock, returns nullptr if it doesn't exist - */ - CInstantSendLockPtr GetInstantSendLockByHash(const uint256& hash, bool use_cache = true) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db) - { - LOCK(cs_db); - return GetInstantSendLockByHashInternal(hash, use_cache); - }; - /** - * Gets an IS Lock hash based on the txid the IS Lock is for - * @param txid The txid which is being searched for - * @return Returns the hash the IS Lock of the specified txid, returns uint256() if it doesn't exist - */ - uint256 GetInstantSendLockHashByTxid(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db) - { - LOCK(cs_db); - return GetInstantSendLockHashByTxidInternal(txid); - }; - /** - * Gets an IS Lock pointer from the txid given - * @param txid The txid for which the IS Lock Pointer is being returned - * @return Returns the IS Lock Pointer associated with the txid, returns nullptr if it doesn't exist - */ - CInstantSendLockPtr GetInstantSendLockByTxid(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * Gets an IS Lock pointer from an input given - * @param outpoint Since all inputs are really just outpoints that are being spent - * @return IS Lock Pointer associated with that input. - */ - CInstantSendLockPtr GetInstantSendLockByInput(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); - /** - * Called when a ChainLock invalidated a IS Lock, removes any chained/children IS Locks and the invalidated IS Lock - * @param islockHash IS Lock hash which has been invalidated - * @param txid Transaction id associated with the islockHash - * @param nHeight height of the block which received a chainlock and invalidated the IS Lock - * @return A vector of IS Lock hashes of all IS Locks removed - */ - std::vector RemoveChainedInstantSendLocks(const uint256& islockHash, const uint256& txid, int nHeight) EXCLUSIVE_LOCKS_REQUIRED(!cs_db); -}; - -class CInstantSendManager : public CRecoveredSigsListener -{ -private: - CInstantSendDb db; - - CChainLocksHandler& clhandler; - CChainState& m_chainstate; - CQuorumManager& qman; - CSigningManager& sigman; - CSigSharesManager& shareman; - CSporkManager& spork_manager; - CTxMemPool& mempool; - const CMasternodeSync& m_mn_sync; - - const bool m_is_masternode; - - std::thread workThread; - CThreadInterrupt workInterrupt; - - mutable Mutex cs_inputReqests; - - /** - * Request ids of inputs that we signed. Used to determine if a recovered signature belongs to an - * in-progress input lock. - */ - std::unordered_set inputRequestIds GUARDED_BY(cs_inputReqests); - - mutable Mutex cs_creating; - - /** - * These are the islocks that are currently in the middle of being created. Entries are created when we observed - * recovered signatures for all inputs of a TX. At the same time, we initiate signing of our sigshare for the islock. - * When the recovered sig for the islock later arrives, we can finish the islock and propagate it. - */ - std::unordered_map creatingInstantSendLocks GUARDED_BY(cs_creating); - // maps from txid to the in-progress islock - std::unordered_map txToCreatingInstantSendLocks GUARDED_BY(cs_creating); - - mutable Mutex cs_pendingLocks; - // Incoming and not verified yet - std::unordered_map, StaticSaltedHasher> pendingInstantSendLocks GUARDED_BY(cs_pendingLocks); - // Tried to verify but there is no tx yet - std::unordered_map, StaticSaltedHasher> pendingNoTxInstantSendLocks GUARDED_BY(cs_pendingLocks); - - // TXs which are neither IS locked nor ChainLocked. We use this to determine for which TXs we need to retry IS locking - // of child TXs - struct NonLockedTxInfo { - const CBlockIndex* pindexMined; - CTransactionRef tx; - std::unordered_set children; - }; - - mutable Mutex cs_nonLocked; - std::unordered_map nonLockedTxs GUARDED_BY(cs_nonLocked); - std::unordered_map nonLockedTxsByOutpoints GUARDED_BY(cs_nonLocked); - - mutable Mutex cs_pendingRetry; - std::unordered_set pendingRetryTxs GUARDED_BY(cs_pendingRetry); - - mutable Mutex cs_timingsTxSeen; - std::unordered_map timingsTxSeen GUARDED_BY(cs_timingsTxSeen); - -public: - explicit CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CQuorumManager& _qman, - CSigningManager& _sigman, CSigSharesManager& _shareman, CSporkManager& sporkman, - CTxMemPool& _mempool, const CMasternodeSync& mn_sync, bool is_masternode, - bool unitTests, bool fWipe) : - db(unitTests, fWipe), - clhandler(_clhandler), - m_chainstate(chainstate), - qman(_qman), - sigman(_sigman), - shareman(_shareman), - spork_manager(sporkman), - mempool(_mempool), - m_mn_sync(mn_sync), - m_is_masternode{is_masternode} - { - workInterrupt.reset(); - } - ~CInstantSendManager() = default; - - void Start(PeerManager& peerman); - void Stop(); - void InterruptWorkerThread() { workInterrupt(); }; - -private: - void ProcessTx(const CTransaction& tx, bool fRetroactive, const Consensus::Params& params) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests); - bool CheckCanLock(const CTransaction& tx, bool printDebug, const Consensus::Params& params) const; - bool CheckCanLock(const COutPoint& outpoint, bool printDebug, const uint256& txHash, - const Consensus::Params& params) const; - - void HandleNewInputLockRecoveredSig(const CRecoveredSig& recoveredSig, const uint256& txid) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating); - void HandleNewInstantSendLockRecoveredSig(const CRecoveredSig& recoveredSig) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_pendingLocks); - - bool TrySignInputLocks(const CTransaction& tx, bool allowResigning, Consensus::LLMQType llmqType, - const Consensus::Params& params) EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests); - void TrySignInstantSendLock(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_creating); - - PeerMsgRet ProcessMessageInstantSendLock(const CNode& pfrom, PeerManager& peerman, const CInstantSendLockPtr& islock); - bool ProcessPendingInstantSendLocks(PeerManager& peerman) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - - std::unordered_set ProcessPendingInstantSendLocks( - const Consensus::LLMQParams& llmq_params, PeerManager& peerman, int signOffset, - const std::unordered_map, StaticSaltedHasher>& pend, bool ban) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void ProcessInstantSendLock(NodeId from, PeerManager& peerman, const uint256& hash, const CInstantSendLockPtr& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - - void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks); - void RemoveNonLockedTx(const uint256& txid, bool retryChildren) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - void RemoveConflictedTx(const CTransaction& tx) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests, !cs_nonLocked, !cs_pendingRetry); - void TruncateRecoveredSigsForInputs(const CInstantSendLock& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests); - - void RemoveMempoolConflictsForLock(PeerManager& peerman, const uint256& hash, const CInstantSendLock& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests, !cs_nonLocked, !cs_pendingRetry); - void ResolveBlockConflicts(const uint256& islockHash, const CInstantSendLock& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void ProcessPendingRetryLockTxs() - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingRetry); - - void WorkThreadMain(PeerManager& peerman) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - - void HandleFullyConfirmedBlock(const CBlockIndex* pindex) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests, !cs_nonLocked, !cs_pendingRetry); - -public: - bool IsLocked(const uint256& txHash) const; - bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); - CInstantSendLockPtr GetConflictingLock(const CTransaction& tx) const; - - [[nodiscard]] MessageProcessingResult HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) override - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_pendingLocks); - - PeerMsgRet ProcessMessage(const CNode& pfrom, PeerManager& peerman, std::string_view msg_type, CDataStream& vRecv); - - void TransactionAddedToMempool(PeerManager& peerman, const CTransactionRef& tx) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void TransactionRemovedFromMempool(const CTransactionRef& tx); - void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) - EXCLUSIVE_LOCKS_REQUIRED(!cs_creating, !cs_inputReqests, !cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected); - - bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); - bool GetInstantSendLockByHash(const uint256& hash, CInstantSendLock& ret) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); - CInstantSendLockPtr GetInstantSendLockByTxid(const uint256& txid) const; - - void NotifyChainLock(const CBlockIndex* pindexChainLock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests, !cs_nonLocked, !cs_pendingRetry); - void UpdatedBlockTip(const CBlockIndex* pindexNew) - EXCLUSIVE_LOCKS_REQUIRED(!cs_inputReqests, !cs_nonLocked, !cs_pendingRetry); - - void RemoveConflictingLock(const uint256& islockHash, const CInstantSendLock& islock); - - size_t GetInstantSendLockCount() const; - - bool IsInstantSendEnabled() const; - /** - * If true, MN should sign all transactions, if false, MN should not sign - * transactions in mempool, but should sign txes included in a block. This - * allows ChainLocks to continue even while this spork is disabled. - */ - bool IsInstantSendMempoolSigningEnabled() const; - bool RejectConflictingBlocks() const; -}; -} // namespace llmq - -#endif // BITCOIN_LLMQ_INSTANTSEND_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 03a6d83a2117..37d2fabf3bb4 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -62,13 +62,14 @@ #include #include #include +#include +#include #include #include #include #include #include #include -#include #include #include #include @@ -2865,7 +2866,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic } if (!push && inv.type == MSG_ISDLOCK) { - llmq::CInstantSendLock o; + instantsend::InstantSendLock o; if (m_llmq_ctx->isman->GetInstantSendLockByHash(inv.hash, o)) { m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::ISDLOCK, o)); push = true; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index df80facbf11f..b9b96e18082c 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -20,9 +20,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -657,7 +657,7 @@ class NotificationsProxy : public CValidationInterface { m_notifications->notifyChainLock(pindexChainLock, clsig); } - void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) override + void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) override { m_notifications->notifyTransactionLock(tx, islock); } diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 0d35e2cbd638..58c7693130e1 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -33,10 +33,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include diff --git a/src/rest.cpp b/src/rest.cpp index 39be8e6e3eb1..790b55af47ac 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -11,9 +11,9 @@ #include #include #include +#include #include #include -#include #include #include #include diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c33dc4b42b38..dd08f585103b 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -54,9 +54,8 @@ #include #include #include - +#include #include -#include #include diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index c090221f0540..1e3d67c3ea70 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -16,8 +16,8 @@ #include #include +#include #include -#include using node::NodeContext; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 30bbfa543f00..5f498d654cb0 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 3f6ec00219e2..0ad984c98faa 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -50,10 +50,10 @@ #include #include - +#include +#include #include #include -#include #include #include @@ -456,7 +456,7 @@ static RPCHelpMan getislocks() for (const auto idx : irange::range(txids.size())) { const uint256 txid(ParseHashV(txids[idx], "txid")); - if (const llmq::CInstantSendLockPtr islock = llmq_ctx.isman->GetInstantSendLockByTxid(txid); islock != nullptr) { + if (const instantsend::InstantSendLockPtr islock = llmq_ctx.isman->GetInstantSendLockByTxid(txid); islock != nullptr) { UniValue objIS(UniValue::VOBJ); objIS.pushKV("txid", islock->txid.ToString()); UniValue inputs(UniValue::VARR); diff --git a/src/test/cachemap_tests.cpp b/src/test/cachemap_tests.cpp index d8c4a8bc2e9a..5ee98bcd76a6 100644 --- a/src/test/cachemap_tests.cpp +++ b/src/test/cachemap_tests.cpp @@ -1,4 +1,6 @@ // Copyright (c) 2014-2023 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include diff --git a/src/test/cachemultimap_tests.cpp b/src/test/cachemultimap_tests.cpp index 61f78204ffed..e0a4ae6fff8b 100644 --- a/src/test/cachemultimap_tests.cpp +++ b/src/test/cachemultimap_tests.cpp @@ -1,4 +1,6 @@ // Copyright (c) 2014-2023 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index d9c6f6df3bbd..01dbf27cf33c 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -23,8 +23,8 @@ #include #include #include +#include #include -#include #include diff --git a/src/test/evo_islock_tests.cpp b/src/test/evo_islock_tests.cpp index 6cfa8ff25e44..1e35775c235f 100644 --- a/src/test/evo_islock_tests.cpp +++ b/src/test/evo_islock_tests.cpp @@ -1,14 +1,20 @@ -#include +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include #include -#include +#include +#include #include #include -#include #include - -// For constructing dummy outpoints using uint256S. #include +#include + +#include + BOOST_AUTO_TEST_SUITE(evo_islock_tests) uint256 CalculateRequestId(const std::vector& inputs) @@ -22,10 +28,10 @@ uint256 CalculateRequestId(const std::vector& inputs) BOOST_AUTO_TEST_CASE(getrequestid) { // Create an empty InstantSendLock - llmq::CInstantSendLock islock; + instantsend::InstantSendLock islock; // Compute expected hash for an empty inputs vector. - // Note: CInstantSendLock::GetRequestId() serializes the prefix "islock" + // Note: InstantSendLock::GetRequestId() serializes the prefix "islock" // followed by the 'inputs' vector. { const uint256 expected = CalculateRequestId(islock.inputs); @@ -70,7 +76,7 @@ BOOST_AUTO_TEST_CASE(deserialize_instantlock_from_realdata2) // Convert hex string to a byte vector and deserialize. std::vector islockData = ParseHex(islockHex); CDataStream ss(islockData, SER_NETWORK, PROTOCOL_VERSION); - llmq::CInstantSendLock islock; + instantsend::InstantSendLock islock; ss >> islock; // Verify the calculated signHash diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 7830b2998218..5b458ba0a541 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/src/test/governance_validators_tests.cpp b/src/test/governance_validators_tests.cpp index f3bf4199f8b2..deb74d40dfea 100644 --- a/src/test/governance_validators_tests.cpp +++ b/src/test/governance_validators_tests.cpp @@ -1,4 +1,6 @@ // Copyright (c) 2014-2023 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 8adf72e04514..8447a5e927da 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/src/test/ratecheck_tests.cpp b/src/test/ratecheck_tests.cpp index 5c248e842264..9a97b9be22c1 100644 --- a/src/test/ratecheck_tests.cpp +++ b/src/test/ratecheck_tests.cpp @@ -1,4 +1,6 @@ // Copyright (c) 2014-2022 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. #include diff --git a/src/txmempool.cpp b/src/txmempool.cpp index b5e270ca60ec..e90128f7df5a 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 1758621965b9..ffdcafaa64d0 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -269,7 +269,7 @@ void CMainSignals::NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDo m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.NotifyHeaderTip(pindexNew, fInitialDownload); }); } -void CMainSignals::NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) { +void CMainSignals::NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) { auto event = [tx, islock, this] { m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.NotifyTransactionLock(tx, islock); }); }; diff --git a/src/validationinterface.h b/src/validationinterface.h index 281fad978bfa..5cb6bb96edcb 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -25,16 +25,15 @@ class CDeterministicMNListDiff; class uint256; class CScheduler; enum class MemPoolRemovalReason; - -namespace Governance -{ - class Object; -} - +namespace Governance { +class Object; +} // namespace Governance +namespace instantsend { +struct InstantSendLock; +} // namespace instantsend namespace llmq { - class CChainLockSig; - struct CInstantSendLock; - class CRecoveredSig; +class CChainLockSig; +class CRecoveredSig; } // namespace llmq /** Register subscriber */ @@ -164,7 +163,7 @@ class CValidationInterface { * Called on a background thread. */ virtual void BlockDisconnected(const std::shared_ptr &block, const CBlockIndex *pindex) {} - virtual void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) {} + virtual void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) {} virtual void NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig) {} virtual void NotifyGovernanceVote(const std::shared_ptr& tip_mn_list, const std::shared_ptr& vote) {} virtual void NotifyGovernanceObject(const std::shared_ptr& object) {} @@ -232,7 +231,7 @@ class CMainSignals { void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason, uint64_t mempool_sequence); void BlockConnected(const std::shared_ptr &, const CBlockIndex *pindex); void BlockDisconnected(const std::shared_ptr &, const CBlockIndex* pindex); - void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock); + void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock); void NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig, const std::string& id); void NotifyGovernanceVote(const std::shared_ptr& tip_mn_list, const std::shared_ptr& vote, const std::string& id); void NotifyGovernanceObject(const std::shared_ptr& object, const std::string& id); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c28e23fabe07..8883f3a0f41b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3409,7 +3409,7 @@ bool CWallet::AutoBackupWallet(const fs::path& wallet_path, bilingual_str& error } #endif // USE_BDB -void CWallet::notifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) +void CWallet::notifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) { LOCK(cs_wallet); // Only notify UI if this transaction is in this wallet diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f59cc4de6da6..adf0329c650c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -921,7 +921,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */ bool CanGetAddresses(bool internal = false) const; - void notifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) override; + void notifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) override; void notifyChainLock(const CBlockIndex* pindexChainLock, const std::shared_ptr& clsig) override; /** Load a CGovernanceObject into m_gobjects. */ diff --git a/src/zmq/zmqabstractnotifier.cpp b/src/zmq/zmqabstractnotifier.cpp index aa3feb605f70..f65aae46724a 100644 --- a/src/zmq/zmqabstractnotifier.cpp +++ b/src/zmq/zmqabstractnotifier.cpp @@ -48,7 +48,7 @@ bool CZMQAbstractNotifier::NotifyTransactionRemoval(const CTransaction &/*transa return true; } -bool CZMQAbstractNotifier::NotifyTransactionLock(const CTransactionRef &/*transaction*/, const std::shared_ptr& /*islock*/) +bool CZMQAbstractNotifier::NotifyTransactionLock(const CTransactionRef &/*transaction*/, const std::shared_ptr& /*islock*/) { return true; } diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h index c1a7dc67f882..c292eedd9cfc 100644 --- a/src/zmq/zmqabstractnotifier.h +++ b/src/zmq/zmqabstractnotifier.h @@ -14,20 +14,18 @@ class CDeterministicMNList; class CGovernanceVote; class CTransaction; class CZMQAbstractNotifier; - -typedef std::shared_ptr CTransactionRef; - -namespace Governance -{ - class Object; -} //namespace Governance - +namespace Governance { +class Object; +} // namespace Governance +namespace instantsend { +struct InstantSendLock; +} // namespace instantsend namespace llmq { - class CChainLockSig; - struct CInstantSendLock; - class CRecoveredSig; +class CChainLockSig; +class CRecoveredSig; } // namespace llmq +using CTransactionRef = std::shared_ptr; using CZMQNotifierFactory = std::unique_ptr (*)(); class CZMQAbstractNotifier @@ -71,7 +69,7 @@ class CZMQAbstractNotifier // Notifies of transactions added to mempool or appearing in blocks virtual bool NotifyTransaction(const CTransaction &transaction); virtual bool NotifyChainLock(const CBlockIndex *pindex, const std::shared_ptr& clsig); - virtual bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock); + virtual bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock); virtual bool NotifyGovernanceVote(const std::shared_ptr& tip_mn_list, const std::shared_ptr& vote); virtual bool NotifyGovernanceObject(const std::shared_ptr& object); virtual bool NotifyInstantSendDoubleSpendAttempt(const CTransactionRef& currentTx, const CTransactionRef& previousTx); diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index 82b683154001..dd1f5f8bbc8f 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -213,7 +213,7 @@ void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr& islock) +void CZMQNotificationInterface::NotifyTransactionLock(const CTransactionRef& tx, const std::shared_ptr& islock) { TryForEachAndRemoveFailed(notifiers, [&tx, &islock](CZMQAbstractNotifier* notifier) { return notifier->NotifyTransactionLock(tx, islock); diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index 70510fee07fa..e42479df66be 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -32,7 +32,7 @@ class CZMQNotificationInterface final : public CValidationInterface void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; void NotifyChainLock(const CBlockIndex *pindex, const std::shared_ptr& clsig) override; - void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) override; + void NotifyTransactionLock(const CTransactionRef &tx, const std::shared_ptr& islock) override; void NotifyGovernanceVote(const std::shared_ptr& tip_mn_list, const std::shared_ptr& vote) override; void NotifyGovernanceObject(const std::shared_ptr& object) override; void NotifyInstantSendDoubleSpendAttempt(const CTransactionRef& currentTx, const CTransactionRef& previousTx) override; diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index c08c64f58e2b..017afcb30086 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -9,16 +9,15 @@ #include #include #include -#include #include #include - +#include #include -#include #include #include +#include #include #include @@ -254,7 +253,7 @@ bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &t return SendZmqMessage(MSG_HASHTX, data, 32); } -bool CZMQPublishHashTransactionLockNotifier::NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) +bool CZMQPublishHashTransactionLockNotifier::NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) { uint256 hash = transaction->GetHash(); LogPrint(BCLog::ZMQ, "Publish hashtxlock %s to %s\n", hash.GetHex(), this->address); @@ -416,7 +415,7 @@ bool CZMQPublishSequenceNotifier::NotifyTransactionRemoval(const CTransaction &t return SendSequenceMsg(*this, hash, /* Mempool (R)emoval */ 'R', mempool_sequence); } -bool CZMQPublishRawTransactionLockNotifier::NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) +bool CZMQPublishRawTransactionLockNotifier::NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) { uint256 hash = transaction->GetHash(); LogPrint(BCLog::ZMQ, "Publish rawtxlock %s to %s\n", hash.GetHex(), this->address); @@ -425,7 +424,7 @@ bool CZMQPublishRawTransactionLockNotifier::NotifyTransactionLock(const CTransac return SendZmqMessage(MSG_RAWTXLOCK, &(*ss.begin()), ss.size()); } -bool CZMQPublishRawTransactionLockSigNotifier::NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) +bool CZMQPublishRawTransactionLockSigNotifier::NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) { uint256 hash = transaction->GetHash(); LogPrint(BCLog::ZMQ, "Publish rawtxlocksig %s to %s\n", hash.GetHex(), this->address); diff --git a/src/zmq/zmqpublishnotifier.h b/src/zmq/zmqpublishnotifier.h index 2bcb3c068446..507db059291b 100644 --- a/src/zmq/zmqpublishnotifier.h +++ b/src/zmq/zmqpublishnotifier.h @@ -57,7 +57,7 @@ class CZMQPublishHashTransactionNotifier : public CZMQAbstractPublishNotifier class CZMQPublishHashTransactionLockNotifier : public CZMQAbstractPublishNotifier { public: - bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) override; + bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) override; }; class CZMQPublishHashGovernanceVoteNotifier : public CZMQAbstractPublishNotifier @@ -120,13 +120,13 @@ class CZMQPublishSequenceNotifier : public CZMQAbstractPublishNotifier class CZMQPublishRawTransactionLockNotifier : public CZMQAbstractPublishNotifier { public: - bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) override; + bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) override; }; class CZMQPublishRawTransactionLockSigNotifier : public CZMQAbstractPublishNotifier { public: - bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) override; + bool NotifyTransactionLock(const CTransactionRef& transaction, const std::shared_ptr& islock) override; }; class CZMQPublishRawGovernanceVoteNotifier : public CZMQAbstractPublishNotifier diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 698d013bb068..e9c0efc438e5 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -44,16 +44,18 @@ "governance/governance -> governance/object -> governance/governance", "governance/governance -> masternode/sync -> governance/governance", "governance/governance -> net_processing -> governance/governance", + "instantsend/instantsend -> instantsend/signing -> instantsend/instantsend", + "instantsend/instantsend -> llmq/chainlocks -> instantsend/instantsend", + "instantsend/instantsend -> net_processing -> instantsend/instantsend", + "instantsend/instantsend -> net_processing -> llmq/context -> instantsend/instantsend", + "instantsend/instantsend -> txmempool -> instantsend/instantsend", "llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> llmq/blockprocessor", - "llmq/chainlocks -> llmq/instantsend -> llmq/chainlocks", - "llmq/chainlocks -> llmq/instantsend -> net_processing -> llmq/chainlocks", "llmq/chainlocks -> validation -> llmq/chainlocks", "llmq/commitment -> llmq/utils -> llmq/snapshot -> llmq/commitment", - "llmq/context -> llmq/instantsend -> net_processing -> llmq/context", + "llmq/chainlocks -> llmq/signing -> net_processing -> llmq/chainlocks", + "llmq/context -> llmq/signing -> net_processing -> llmq/context", "llmq/dkgsession -> llmq/dkgsessionmgr -> llmq/dkgsessionhandler -> llmq/dkgsession", "llmq/dkgsessionhandler -> net_processing -> llmq/dkgsessionmgr -> llmq/dkgsessionhandler", - "llmq/instantsend -> net_processing -> llmq/instantsend", - "llmq/instantsend -> txmempool -> llmq/instantsend", "llmq/signing -> llmq/signing_shares -> llmq/signing", "llmq/signing -> net_processing -> llmq/signing", "llmq/signing_shares -> net_processing -> llmq/signing_shares", diff --git a/test/util/data/non-backported.txt b/test/util/data/non-backported.txt index d7567f67035d..05fc3f1cd774 100644 --- a/test/util/data/non-backported.txt +++ b/test/util/data/non-backported.txt @@ -14,6 +14,8 @@ src/evo/*.cpp src/evo/*.h src/governance/*.cpp src/governance/*.h +src/instantsend/*.cpp +src/instantsend/*.h src/llmq/*.cpp src/llmq/*.h src/masternode/*.cpp