diff --git a/src/chainparams.cpp b/src/chainparams.cpp index dc0d985239fd..2ea2979bfc8e 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -1186,6 +1186,10 @@ void CRegTestParams::UpdateLLMQTestParametersFromArgs(const ArgsManager& args, c if (!ParseInt32(vParams[1], &threshold)) { throw std::runtime_error(strprintf("Invalid %s threshold (%s)", llmq_name, vParams[1])); } + if (size > Consensus::MAX_LLMQ_SIZE) { + throw std::runtime_error(strprintf("Invalid %s size (%d), must not exceed the maximum supported quorum size (%d)", + llmq_name, size, Consensus::MAX_LLMQ_SIZE)); + } LogPrintf("Setting %s parameters to size=%ld, threshold=%ld\n", llmq_name, size, threshold); UpdateLLMQTestParameters(size, threshold, llmqType); } @@ -1352,6 +1356,10 @@ void CDevNetParams::UpdateLLMQDevnetParametersFromArgs(const ArgsManager& args) if (!ParseInt32(vParams[1], &threshold)) { throw std::runtime_error(strprintf("Invalid LLMQ_DEVNET threshold (%s)", vParams[1])); } + if (size > Consensus::MAX_LLMQ_SIZE) { + throw std::runtime_error(strprintf("Invalid LLMQ_DEVNET size (%d), must not exceed the maximum supported quorum size (%d)", + size, Consensus::MAX_LLMQ_SIZE)); + } LogPrintf("Setting LLMQ_DEVNET parameters to size=%ld, threshold=%ld\n", size, threshold); UpdateLLMQDevnetParameters(size, threshold); } diff --git a/src/llmq/params.h b/src/llmq/params.h index 99574930075d..eaa2c05ad09d 100644 --- a/src/llmq/params.h +++ b/src/llmq/params.h @@ -11,6 +11,11 @@ namespace Consensus { +//! The largest quorum size the signing protocol supports. The signing session inventory +//! and batched sig-share messages are bounded against this value, so quorum sizes above it +//! (only reachable through the devnet/regtest size overrides) cannot sign end-to-end. +constexpr int MAX_LLMQ_SIZE{400}; + enum class LLMQType : uint8_t { LLMQ_NONE = 0xff, diff --git a/src/llmq/signing_shares.h b/src/llmq/signing_shares.h index 332f553fc895..3fdfe8e80033 100644 --- a/src/llmq/signing_shares.h +++ b/src/llmq/signing_shares.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -42,8 +43,8 @@ using SigShareKey = std::pair; constexpr uint32_t UNINITIALIZED_SESSION_ID{std::numeric_limits::max()}; -// 400 is the maximum quorum size, so this is also the maximum number of sigs we need to support -constexpr size_t MAX_MSGS_TOTAL_BATCHED_SIGS{400}; +// The maximum quorum size is also the maximum number of sigs we need to support +constexpr size_t MAX_MSGS_TOTAL_BATCHED_SIGS{Consensus::MAX_LLMQ_SIZE}; class CSigShare : virtual public CSigBase { diff --git a/src/test/llmq_params_tests.cpp b/src/test/llmq_params_tests.cpp index 058776f5f2c2..69b803f9c09e 100644 --- a/src/test/llmq_params_tests.cpp +++ b/src/test/llmq_params_tests.cpp @@ -5,12 +5,18 @@ #include #include +#include +#include #include #include +#include #include +#include #include +#include +#include using namespace llmq; using namespace llmq::testutils; @@ -206,4 +212,60 @@ BOOST_AUTO_TEST_CASE(llmq_params_calculations_overflow_test) BOOST_CHECK_EQUAL(cycles, 0); // 1000 / max_int = 0 } +//! Build chain params for `chain` with a single LLMQ size/threshold override applied. +static std::unique_ptr ChainParamsWithLLMQOverride(const std::string& chain, + const std::string& arg, + int size, int threshold) +{ + ArgsManager args; + args.ForceSetArg(arg, strprintf("%d:%d", size, threshold)); + return CreateChainParams(args, chain); +} + +static int LLMQSize(const CChainParams& params, LLMQType type) +{ + const auto& llmqs = params.GetConsensus().llmqs; + const auto it = std::ranges::find_if(llmqs, [type](const auto& llmq) { return llmq.type == type; }); + BOOST_REQUIRE(it != llmqs.end()); + return it->size; +} + +// Quorum sizes above MAX_LLMQ_SIZE cannot sign end-to-end: signing session inventories and +// batched sig-share messages are bounded by that same maximum, so a node running such a quorum +// would drop its peers' sig shares. Reject the override at chain-parameter construction instead. +BOOST_AUTO_TEST_CASE(llmq_params_size_override_bounds_test) +{ + constexpr int kMax = Consensus::MAX_LLMQ_SIZE; + + const std::pair regtest_overrides[]{ + {"-llmqtestparams", LLMQType::LLMQ_TEST}, + {"-llmqtestinstantsendparams", LLMQType::LLMQ_TEST_INSTANTSEND}, + {"-llmqtestplatformparams", LLMQType::LLMQ_TEST_PLATFORM}, + }; + + for (const auto& [arg, type] : regtest_overrides) { + // Normal custom values keep working + const auto small = ChainParamsWithLLMQOverride(CBaseChainParams::REGTEST, arg, 5, 3); + BOOST_CHECK_EQUAL(LLMQSize(*small, type), 5); + + // Exactly the maximum is accepted + const auto at_max = ChainParamsWithLLMQOverride(CBaseChainParams::REGTEST, arg, kMax, kMax * 2 / 3); + BOOST_CHECK_EQUAL(LLMQSize(*at_max, type), kMax); + + // One above the maximum is rejected + BOOST_CHECK_THROW(ChainParamsWithLLMQOverride(CBaseChainParams::REGTEST, arg, kMax + 1, kMax * 2 / 3), + std::runtime_error); + } + + // Same bounds for the devnet override + const auto devnet_small = ChainParamsWithLLMQOverride(CBaseChainParams::DEVNET, "-llmqdevnetparams", 12, 6); + BOOST_CHECK_EQUAL(LLMQSize(*devnet_small, LLMQType::LLMQ_DEVNET), 12); + + const auto devnet_at_max = ChainParamsWithLLMQOverride(CBaseChainParams::DEVNET, "-llmqdevnetparams", kMax, kMax / 2); + BOOST_CHECK_EQUAL(LLMQSize(*devnet_at_max, LLMQType::LLMQ_DEVNET), kMax); + + BOOST_CHECK_THROW(ChainParamsWithLLMQOverride(CBaseChainParams::DEVNET, "-llmqdevnetparams", kMax + 1, kMax / 2), + std::runtime_error); +} + BOOST_AUTO_TEST_SUITE_END()