From 9eac7ce2598f1ce6739e67ab8f0cd956cdbb5e8b Mon Sep 17 00:00:00 2001 From: Navid Rahimi Date: Tue, 25 Aug 2026 20:09:05 +0000 Subject: [PATCH] LLMQ: bound locked transaction requests --- src/llmq/quorums_instantsend.cpp | 18 +++- src/llmq/quorums_instantsend.h | 2 + src/test/CMakeLists.txt | 1 + .../quorums_instantsend_request_tests.cpp | 93 +++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/test/quorums_instantsend_request_tests.cpp diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 43383681f9..03e884962c 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -1393,12 +1393,26 @@ bool CInstantSendManager::RemoveISLockByTxId(const uint256& txid) void CInstantSendManager::AskNodesForLockedTx(const uint256& txid) { std::vector nodesToAskFor; - g_connman->ForEachNode([&](CNode* pnode) { - LOCK(pnode->cs_filter); + nodesToAskFor.reserve(4); + + auto maybe_add_to_nodesToAskFor = [&nodesToAskFor, &txid](CNode* pnode) { + if (nodesToAskFor.size() >= 4) { + return; + } + LOCK(pnode->cs_inventory); if (pnode->filterInventoryKnown.contains(txid)) { pnode->AddRef(); nodesToAskFor.emplace_back(pnode); } + }; + + g_connman->ForEachNode([&](CNode* pnode) { + // Check masternodes first + if (pnode->fZnode) maybe_add_to_nodesToAskFor(pnode); + }); + g_connman->ForEachNode([&](CNode* pnode) { + // Check non-masternodes next + if (!pnode->fZnode) maybe_add_to_nodesToAskFor(pnode); }); { LOCK(cs_main); diff --git a/src/llmq/quorums_instantsend.h b/src/llmq/quorums_instantsend.h index 2cc0722c7b..d989e392f1 100644 --- a/src/llmq/quorums_instantsend.h +++ b/src/llmq/quorums_instantsend.h @@ -74,6 +74,8 @@ class CInstantSendDb class CInstantSendManager : public CRecoveredSigsListener { + friend struct CInstantSendRequestTestAccess; + private: CCriticalSection cs; CInstantSendDb db; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 6408280c81..c3b908ebad 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -67,6 +67,7 @@ add_executable(test_firo ${CMAKE_CURRENT_SOURCE_DIR}/random_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/reverselock_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rpc_tests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/quorums_instantsend_request_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sanity_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/scheduler_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/scriptnum10.h diff --git a/src/test/quorums_instantsend_request_tests.cpp b/src/test/quorums_instantsend_request_tests.cpp new file mode 100644 index 0000000000..f08874c448 --- /dev/null +++ b/src/test/quorums_instantsend_request_tests.cpp @@ -0,0 +1,93 @@ +// Copyright (c) 2026 The Firo developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "dbwrapper.h" +#include "llmq/quorums_instantsend.h" +#include "net.h" +#include "test/test_bitcoin.h" + +#include + +#include +#include + +namespace llmq +{ + +struct CInstantSendRequestTestAccess { + static void AskNodesForLockedTx(CInstantSendManager& manager, const uint256& txid) + { + manager.AskNodesForLockedTx(txid); + } +}; + +} // namespace llmq + +namespace +{ + +struct InstantSendRequestSetup : BasicTestingSetup { + CDBWrapper db; + llmq::CInstantSendManager manager; + std::vector > nodes; + + InstantSendRequestSetup() : db(boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(), 1 << 20, true, false), + manager(db) + { + g_connman = std::make_unique(0x1337, 0x1337); + } + + ~InstantSendRequestSetup() + { + LOCK(g_connman->cs_vNodes); + g_connman->vNodes.clear(); + } + + void AddNode(bool masternode, bool knowsTx, const uint256& txid) + { + auto node = std::make_unique(nodes.size(), NODE_NETWORK, 0, INVALID_SOCKET, CAddress(), 0, 0); + node->fZnode = masternode; + node->fSuccessfullyConnected = true; + if (knowsTx) { + LOCK(node->cs_inventory); + node->filterInventoryKnown.insert(txid); + } + { + LOCK(g_connman->cs_vNodes); + g_connman->vNodes.emplace_back(node.get()); + } + nodes.emplace_back(std::move(node)); + } +}; + +} // namespace + +BOOST_FIXTURE_TEST_SUITE(quorums_instantsend_request_tests, InstantSendRequestSetup) + +BOOST_AUTO_TEST_CASE(asks_at_most_four_peers_and_prefers_masternodes) +{ + uint256 txid; + txid.SetHex("01"); + + AddNode(false, true, txid); + AddNode(false, true, txid); + AddNode(false, true, txid); + AddNode(true, true, txid); + AddNode(true, false, txid); + AddNode(true, true, txid); + AddNode(true, true, txid); + AddNode(false, true, txid); + + llmq::CInstantSendRequestTestAccess::AskNodesForLockedTx(manager, txid); + + const std::vector expected{true, false, false, true, false, true, true, false}; + for (size_t i = 0; i < nodes.size(); ++i) { + LOCK(nodes[i]->cs_inventory); + BOOST_CHECK_EQUAL(nodes[i]->setAskFor.count(txid), expected[i] ? 1 : 0); + BOOST_CHECK_EQUAL(nodes[i]->GetRefCount(), 0); + } + g_connman->RemoveAskFor(txid); +} + +BOOST_AUTO_TEST_SUITE_END()