From f8d3c094e2541181b0d81d815ce4315c60bef378 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 4 Aug 2026 15:10:53 -0500 Subject: [PATCH 1/2] fix(llmq): treat unregistered types as inactive in IsQuorumActive IsQuorumActive previously asserted that Params().GetLLMQ(llmqType) was present. Call sites are currently gated, but an invalid wire-derived type that reaches this helper would abort the node. Return false instead and cover the path in llmq_invalid_type_tests. --- src/llmq/signing.cpp | 6 +++++- src/test/llmq_invalid_type_tests.cpp | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index efdcd13cd747..bdcaa8755d72 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -649,7 +649,11 @@ bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, co // we allow one more active quorum as specified in consensus, as otherwise there is a small window where things could // fail while we are on the brink of a new quorum const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt.has_value()); + // Unregistered / unknown types are never active. Callers may pass wire-derived + // llmqType values, so return false instead of asserting. + if (!llmq_params_opt.has_value()) { + return false; + } auto quorums = qman.ScanQuorums(llmqType, llmq_params_opt->keepOldConnections); return std::ranges::any_of(quorums, [&quorumHash](const auto& q) { return q->qc->quorumHash == quorumHash; }); } diff --git a/src/test/llmq_invalid_type_tests.cpp b/src/test/llmq_invalid_type_tests.cpp index 943299dbf2d2..382c58968002 100644 --- a/src/test/llmq_invalid_type_tests.cpp +++ b/src/test/llmq_invalid_type_tests.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -83,4 +84,19 @@ BOOST_FIXTURE_TEST_CASE(get_quorum_unknown_llmq_type_is_safe, RegTestingSetup) BOOST_CHECK(qman.GetQuorum(UNREGISTERED_LLMQ_TYPE, tip_hash) == nullptr); } +// IsQuorumActive used to assert(GetLLMQ(...).has_value()). Wire-derived types can +// reach it if a caller reorders checks or omits the net-layer type gate; treat +// unregistered types as inactive instead of aborting. +BOOST_FIXTURE_TEST_CASE(is_quorum_active_unknown_llmq_type_is_safe, RegTestingSetup) +{ + const auto& qman = *Assert(Assert(m_node.llmq_ctx)->qman); + const uint256 tip_hash = WITH_LOCK(::cs_main, return Assert(m_node.chainman->ActiveTip())->GetBlockHash()); + + BOOST_REQUIRE(!Params().GetLLMQ(UNKNOWN_LLMQ_TYPE).has_value()); + BOOST_REQUIRE(!Params().GetLLMQ(UNREGISTERED_LLMQ_TYPE).has_value()); + + BOOST_CHECK(!llmq::IsQuorumActive(UNKNOWN_LLMQ_TYPE, qman, tip_hash)); + BOOST_CHECK(!llmq::IsQuorumActive(UNREGISTERED_LLMQ_TYPE, qman, tip_hash)); +} + BOOST_AUTO_TEST_SUITE_END() From e42e66634b5a312c1a16c286b9cb4c3b918eae12 Mon Sep 17 00:00:00 2001 From: pasta Date: Tue, 4 Aug 2026 17:17:54 -0500 Subject: [PATCH 2/2] chore: drop low-value IsQuorumActive comments and test The unknown-type early-return is self-explanatory. A dedicated unknown-type-only unit test only restates that guard and is not worth keeping without a full active-quorum fixture. --- src/llmq/signing.cpp | 2 -- src/test/llmq_invalid_type_tests.cpp | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index bdcaa8755d72..5e67a16820b9 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -649,8 +649,6 @@ bool IsQuorumActive(Consensus::LLMQType llmqType, const CQuorumManager& qman, co // we allow one more active quorum as specified in consensus, as otherwise there is a small window where things could // fail while we are on the brink of a new quorum const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - // Unregistered / unknown types are never active. Callers may pass wire-derived - // llmqType values, so return false instead of asserting. if (!llmq_params_opt.has_value()) { return false; } diff --git a/src/test/llmq_invalid_type_tests.cpp b/src/test/llmq_invalid_type_tests.cpp index 382c58968002..943299dbf2d2 100644 --- a/src/test/llmq_invalid_type_tests.cpp +++ b/src/test/llmq_invalid_type_tests.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -84,19 +83,4 @@ BOOST_FIXTURE_TEST_CASE(get_quorum_unknown_llmq_type_is_safe, RegTestingSetup) BOOST_CHECK(qman.GetQuorum(UNREGISTERED_LLMQ_TYPE, tip_hash) == nullptr); } -// IsQuorumActive used to assert(GetLLMQ(...).has_value()). Wire-derived types can -// reach it if a caller reorders checks or omits the net-layer type gate; treat -// unregistered types as inactive instead of aborting. -BOOST_FIXTURE_TEST_CASE(is_quorum_active_unknown_llmq_type_is_safe, RegTestingSetup) -{ - const auto& qman = *Assert(Assert(m_node.llmq_ctx)->qman); - const uint256 tip_hash = WITH_LOCK(::cs_main, return Assert(m_node.chainman->ActiveTip())->GetBlockHash()); - - BOOST_REQUIRE(!Params().GetLLMQ(UNKNOWN_LLMQ_TYPE).has_value()); - BOOST_REQUIRE(!Params().GetLLMQ(UNREGISTERED_LLMQ_TYPE).has_value()); - - BOOST_CHECK(!llmq::IsQuorumActive(UNKNOWN_LLMQ_TYPE, qman, tip_hash)); - BOOST_CHECK(!llmq::IsQuorumActive(UNREGISTERED_LLMQ_TYPE, qman, tip_hash)); -} - BOOST_AUTO_TEST_SUITE_END()