// Copyright (c) 2021-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_PARAMS_H #define BITCOIN_LLMQ_PARAMS_H #include #include #include namespace Consensus { enum class LLMQType : uint8_t { LLMQ_NONE = 0xff, LLMQ_50_60 = 1, // 50 members, 30 (60%) threshold, one per hour LLMQ_400_60 = 2, // 400 members, 240 (60%) threshold, one every 12 hours LLMQ_400_85 = 3, // 400 members, 340 (85%) threshold, one every 24 hours LLMQ_100_67 = 4, // 100 members, 67 (67%) threshold, one per hour LLMQ_60_75 = 5, // 60 members, 45 (75%) threshold, one every 12 hours LLMQ_25_67 = 6, // 25 members, 17 (67%) threshold, one per hour // for testing only LLMQ_TEST = 100, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used // for devnets only LLMQ_DEVNET = 101, // 12 members, 6 (50%) threshold, one per hour. Params might differ when -llmqdevnetparams is used LLMQ_DEVNET_PLATFORM = 107, // 12 members, 8 (67%) threshold, one per hour. // for testing activation of new quorums only LLMQ_TEST_V17 = 102, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestparams is used // for testing only LLMQ_TEST_DIP0024 = 103, // 4 members, 3 (75%) threshold, one per hour. LLMQ_TEST_INSTANTSEND = 104, // 3 members, 2 (66%) threshold, one per hour. Params might differ when -llmqtestinstantsendparams is used LLMQ_TEST_PLATFORM = 106, // 3 members, 2 (66%) threshold, one per hour. // for devnets only. rotated version (v2) for devnets LLMQ_DEVNET_DIP0024 = 105, // 8 members, 4 (50%) threshold, one per hour. Params might differ when -llmqdevnetparams is used }; // Configures a LLMQ and its DKG // See https://github.com/dashpay/dips/blob/master/dip-0006.md for more details struct LLMQParams { LLMQType type; // not consensus critical, only used in logging, RPC and UI std::string_view name; // Whether this is a DIP0024 quorum or not bool useRotation; // the size of the quorum, e.g. 50 or 400 int size; // The minimum number of valid members after the DKG. If less members are determined valid, no commitment can be // created. Should be higher then the threshold to allow some room for failing nodes, otherwise quorum might end up // not being able to ever created a recovered signature if more nodes fail after the DKG int minSize; // The threshold required to recover a final signature. Should be at least 50%+1 of the quorum size. This value // also controls the size of the public key verification vector and has a large influence on the performance of // recovery. It also influences the amount of minimum messages that need to be exchanged for a single signing session. // This value has the most influence on the security of the quorum. The number of total malicious masternodes // required to negatively influence signing sessions highly correlates to the threshold percentage. int threshold; // The interval in number blocks for DKGs and the creation of LLMQs. If set to 24 for example, a DKG will start // every 24 blocks, which is approximately once every hour. int dkgInterval; // The number of blocks per phase in a DKG session. There are 6 phases plus the mining phase that need to be processed // per DKG. Set this value to a number of blocks so that each phase has enough time to propagate all required // messages to all members before the next phase starts. If blocks are produced too fast, whole DKG sessions will // fail. int dkgPhaseBlocks; // The starting block inside the DKG interval for when mining of commitments starts. The value is inclusive. // Starting from this block, the inclusion of (possibly null) commitments is enforced until the first non-null // commitment is mined. The chosen value should be at least 5 * dkgPhaseBlocks so that it starts right after the // finalization phase. int dkgMiningWindowStart; // The ending block inside the DKG interval for when mining of commitments ends. The value is inclusive. // Choose a value so that miners have enough time to receive the commitment and mine it. Also take into consideration // that miners might omit real commitments and revert to always including null commitments. The mining window should // be large enough so that other miners have a chance to produce a block containing a non-null commitment. The window // should at the same time not be too large so that not too much space is wasted with null commitments in case a DKG // session failed. int dkgMiningWindowEnd; // In the complaint phase, members will vote on other members being bad (missing valid contribution). If at least // dkgBadVotesThreshold have voted for another member to be bad, it will considered to be bad by all other members // as well. This serves as a protection against late-comers who send their contribution on the bring of // phase-transition, which would otherwise result in inconsistent views of the valid members set int dkgBadVotesThreshold; // Number of quorums to consider "active" for signing sessions int signingActiveQuorumCount; // Used for intra-quorum communication. This is the number of quorums for which we should keep old connections. // For non-rotated quorums it should be at least one more than the active quorums set. // For rotated quorums it should be equal to 2 x active quorums set. int keepOldConnections; // The number of quorums for which we should keep keys. Usually it's equal to signingActiveQuorumCount * 2. // Unlike for other quorum types we want to keep data (secret key shares and vvec) // for Platform quorums for much longer because Platform can be restarted and // it must be able to re-sign stuff. int keepOldKeys; // How many members should we try to send all sigShares to before we give up. int recoveryMembers; public: [[nodiscard]] constexpr int max_cycles(int quorums_count) const { return useRotation ? quorums_count / signingActiveQuorumCount : quorums_count; } // For how many blocks recent DKG info should be kept [[nodiscard]] constexpr int max_store_depth() const { return max_cycles(keepOldKeys) * dkgInterval; } [[nodiscard]] constexpr bool is_single_member() const { return size == 1; } }; //static_assert(std::is_trivial_v, "LLMQParams is not a trivial type"); static_assert(std::is_trivially_copyable_v, "LLMQParams is not trivially copyable"); //static_assert(std::is_trivially_default_constructible_v, "LLMQParams is not trivially default constructible"); static_assert(std::is_trivially_assignable_v, "LLMQParams is not trivially assignable"); static constexpr std::array available_llmqs = { /** * llmq_test * This quorum is only used for testing * */ LLMQParams{ .type = LLMQType::LLMQ_TEST, .name = "llmq_test", .useRotation = false, .size = 3, .minSize = 2, .threshold = 2, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 2, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 3, .keepOldKeys = 4, .recoveryMembers = 3, }, /** * llmq_test_instantsend (same as llmq_test but used for InstantSend exclusively) * This quorum is only used for testing * */ LLMQParams{ .type = LLMQType::LLMQ_TEST_INSTANTSEND, .name = "llmq_test_instantsend", .useRotation = false, .size = 3, .minSize = 2, .threshold = 2, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 2, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 3, .keepOldKeys = 4, .recoveryMembers = 3, }, /** * llmq_test (Dash Core 0.17) aka llmq_test_v17 * This quorum is only used for testing * */ LLMQParams{ .type = LLMQType::LLMQ_TEST_V17, .name = "llmq_test_v17", .useRotation = false, .size = 3, .minSize = 2, .threshold = 2, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 2, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 3, .keepOldKeys = 4, .recoveryMembers = 3, }, /** * llmq_test_dip0024 * This quorum is only used for testing * */ LLMQParams{ .type = LLMQType::LLMQ_TEST_DIP0024, .name = "llmq_test_dip0024", .useRotation = true, .size = 4, .minSize = 4, .threshold = 3, .dkgInterval = 24, // DKG cycle .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 12, // signingActiveQuorumCount + dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 20, .dkgBadVotesThreshold = 2, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 4, .keepOldKeys = 4, .recoveryMembers = 3, }, /** * llmq_test_platform * This quorum is only used for testing * */ LLMQParams{ .type = LLMQType::LLMQ_TEST_PLATFORM, .name = "llmq_test_platform", .useRotation = false, .size = 3, .minSize = 2, .threshold = 2, .dkgInterval = 24, // DKG cycle .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // signingActiveQuorumCount + dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 2, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 4, .keepOldKeys = 24 * 30 * 2, // 2 months of quorums .recoveryMembers = 3, }, /** * llmq_devnet * This quorum is only used for testing on devnets * */ LLMQParams{ .type = LLMQType::LLMQ_DEVNET, .name = "llmq_devnet", .useRotation = false, .size = 12, .minSize = 7, .threshold = 6, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 7, .signingActiveQuorumCount = 4, // just a few ones to allow easier testing .keepOldConnections = 5, .keepOldKeys = 8, .recoveryMembers = 6, }, /** * llmq_devnet_dip0024 * This quorum is only used for testing on devnets * */ LLMQParams{ .type = LLMQType::LLMQ_DEVNET_DIP0024, .name = "llmq_devnet_dip0024", .useRotation = true, .size = 8, .minSize = 6, .threshold = 4, .dkgInterval = 48, // DKG cycle .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 12, // signingActiveQuorumCount + dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 20, .dkgBadVotesThreshold = 7, .signingActiveQuorumCount = 2, // just a few ones to allow easier testing .keepOldConnections = 4, .keepOldKeys = 4, .recoveryMembers = 4, }, /** * llmq_devnet_platform * This quorum is only used for testing on devnets * */ LLMQParams{ .type = LLMQType::LLMQ_DEVNET_PLATFORM, .name = "llmq_devnet_platform", .useRotation = false, .size = 12, .minSize = 9, .threshold = 8, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 7, .signingActiveQuorumCount = 4, // just a few ones to allow easier testing .keepOldConnections = 5, .keepOldKeys = 24 * 30 * 2, // 2 months of quorums .recoveryMembers = 6, }, /** * llmq_50_60 * This quorum is deployed on mainnet and requires * 40 - 50 participants * */ LLMQParams{ .type = LLMQType::LLMQ_50_60, .name = "llmq_50_60", .useRotation = false, .size = 50, .minSize = 40, .threshold = 30, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 40, .signingActiveQuorumCount = 24, // a full day worth of LLMQs .keepOldConnections = 25, .keepOldKeys = 48, .recoveryMembers = 25, }, /** * llmq_60_75 * This quorum is deployed on mainnet and requires * 50 - 60 participants * */ LLMQParams{ .type = LLMQType::LLMQ_60_75, .name = "llmq_60_75", .useRotation = true, .size = 60, .minSize = 50, .threshold = 45, .dkgInterval = 24 * 12, // DKG cycle every 12 hours .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 42, // signingActiveQuorumCount + dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 50, .dkgBadVotesThreshold = 48, .signingActiveQuorumCount = 32, .keepOldConnections = 64, .keepOldKeys = 64, .recoveryMembers = 25, }, /** * llmq_400_60 * This quorum is deployed on mainnet and requires * 300 - 400 participants * */ LLMQParams{ .type = LLMQType::LLMQ_400_60, .name = "llmq_400_60", .useRotation = false, .size = 400, .minSize = 300, .threshold = 240, .dkgInterval = 24 * 12, // one DKG every 12 hours .dkgPhaseBlocks = 4, .dkgMiningWindowStart = 20, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 28, .dkgBadVotesThreshold = 300, .signingActiveQuorumCount = 4, // two days worth of LLMQs .keepOldConnections = 5, .keepOldKeys = 8, .recoveryMembers = 100, }, /** * llmq_400_85 * This quorum is deployed on mainnet and requires * 300 - 400 participants _with_ a supermajority * * Used for deployment and min-proto-version signalling */ LLMQParams{ .type = LLMQType::LLMQ_400_85, .name = "llmq_400_85", .useRotation = false, .size = 400, .minSize = 350, .threshold = 340, .dkgInterval = 24 * 24, // one DKG every 24 hours .dkgPhaseBlocks = 4, .dkgMiningWindowStart = 20, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 48, // give it a larger mining window to make sure it is mined .dkgBadVotesThreshold = 300, .signingActiveQuorumCount = 4, // four days worth of LLMQs .keepOldConnections = 5, .keepOldKeys = 8, .recoveryMembers = 100, }, /** * llmq_100_67 * This quorum is deployed on mainnet and requires * 80 - 100 participants * * Used by Dash Platform */ LLMQParams{ .type = LLMQType::LLMQ_100_67, .name = "llmq_100_67", .useRotation = false, .size = 100, .minSize = 80, .threshold = 67, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 80, .signingActiveQuorumCount = 24, // a full day worth of LLMQs .keepOldConnections = 25, .keepOldKeys = 24 * 30 * 2, // 2 months of quorums .recoveryMembers = 50, }, /** * llmq_25_67 * This quorum is deployed on Testnet and requires * 25 participants * * Used by Dash Platform */ LLMQParams{ .type = LLMQType::LLMQ_25_67, .name = "llmq_25_67", .useRotation = false, .size = 25, .minSize = 22, .threshold = 17, .dkgInterval = 24, // one DKG per hour .dkgPhaseBlocks = 2, .dkgMiningWindowStart = 10, // dkgPhaseBlocks * 5 = after finalization .dkgMiningWindowEnd = 18, .dkgBadVotesThreshold = 22, .signingActiveQuorumCount = 24, // a full day worth of LLMQs .keepOldConnections = 25, .keepOldKeys = 24 * 30 * 2, // 2 months of quorums .recoveryMembers = 12, }, }; // available_llmqs } // namespace Consensus // This must be outside of all namespaces. We must also duplicate the forward declaration of is_serializable_enum to // avoid inclusion of serialize.h here. template struct is_serializable_enum; template<> struct is_serializable_enum : std::true_type {}; #endif // BITCOIN_LLMQ_PARAMS_H