Skip to content

Commit 702d147

Browse files
committed
feat: new single node quorum and functional test feature_llmq_singlenode.py
1 parent 67eb2e4 commit 702d147

File tree

10 files changed

+352
-19
lines changed

10 files changed

+352
-19
lines changed

src/llmq/commitment.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ bool CFinalCommitment::Verify(CDeterministicMNManager& dmnman, CQuorumSnapshotMa
7070
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid quorumPublicKey\n", quorumHash.ToString());
7171
return false;
7272
}
73-
if (quorumVvecHash.IsNull()) {
73+
if (llmq_params.size != 1 && quorumVvecHash.IsNull()) {
7474
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid quorumVvecHash\n", quorumHash.ToString());
7575
return false;
7676
}
@@ -115,19 +115,27 @@ bool CFinalCommitment::Verify(CDeterministicMNManager& dmnman, CQuorumSnapshotMa
115115
LogPrint(BCLog::LLMQ, "CFinalCommitment::%s members[%s] quorumPublicKey[%s] commitmentHash[%s]\n",
116116
__func__, ss3.str(), quorumPublicKey.ToString(), commitmentHash.ToString());
117117
}
118-
std::vector<CBLSPublicKey> memberPubKeys;
119-
for (const auto i : irange::range(members.size())) {
120-
if (!signers[i]) {
121-
continue;
118+
if (llmq_params.size == 1) {
119+
LogPrintf("pubkey operator: %s\n", members[0]->pdmnState->pubKeyOperator.Get().ToString());
120+
if (!membersSig.VerifyInsecure(members[0]->pdmnState->pubKeyOperator.Get(), commitmentHash)) {
121+
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid member signature\n", quorumHash.ToString());
122+
return false;
123+
}
124+
} else {
125+
std::vector<CBLSPublicKey> memberPubKeys;
126+
for (const auto i : irange::range(members.size())) {
127+
if (!signers[i]) {
128+
continue;
129+
}
130+
memberPubKeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
122131
}
123-
memberPubKeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
124-
}
125132

126-
if (!membersSig.VerifySecureAggregated(memberPubKeys, commitmentHash)) {
127-
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid aggregated members signature\n", quorumHash.ToString());
128-
return false;
133+
if (!membersSig.VerifySecureAggregated(memberPubKeys, commitmentHash)) {
134+
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid aggregated members signature\n",
135+
quorumHash.ToString());
136+
return false;
137+
}
129138
}
130-
131139
if (!quorumSig.VerifyInsecure(quorumPublicKey, commitmentHash)) {
132140
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid quorum signature\n", quorumHash.ToString());
133141
return false;
@@ -161,7 +169,8 @@ bool CFinalCommitment::VerifySizes(const Consensus::LLMQParams& params) const
161169
return false;
162170
}
163171
if (validMembers.size() != size_t(params.size)) {
164-
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid signers.size=%d\n", quorumHash.ToString(), signers.size());
172+
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid validMembers.size=%d\n", quorumHash.ToString(),
173+
validMembers.size());
165174
return false;
166175
}
167176
return true;

src/llmq/dkgsession.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ void CDKGSession::Contribute(CDKGPendingMessages& pendingMessages, PeerManager&
166166
return;
167167
}
168168

169+
assert(params.threshold > 1); // we should not get there with single-node-quorums
170+
169171
cxxtimer::Timer t1(true);
170172
logger.Batch("generating contributions");
171173
if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, m_sk_contributions)) {
@@ -1236,6 +1238,7 @@ std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments()
12361238
fqc.quorumVvecHash = first.quorumVvecHash;
12371239

12381240
const bool isQuorumRotationEnabled{IsQuorumRotationEnabled(params, m_quorum_base_block_index)};
1241+
// TODO: always put `true` here: so far as v19 is activated, we always write BASIC now
12391242
fqc.nVersion = CFinalCommitment::GetVersion(isQuorumRotationEnabled, DeploymentActiveAfter(m_quorum_base_block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_V19));
12401243
fqc.quorumIndex = isQuorumRotationEnabled ? quorumIndex : 0;
12411244

@@ -1293,6 +1296,64 @@ std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments()
12931296
return finalCommitments;
12941297
}
12951298

1299+
CFinalCommitment CDKGSession::FinalizeSingleCommitment()
1300+
{
1301+
if (!AreWeMember()) {
1302+
return {};
1303+
}
1304+
1305+
CDKGLogger logger(*this, __func__, __LINE__);
1306+
1307+
std::vector<CBLSId> signerIds;
1308+
std::vector<CBLSSignature> thresholdSigs;
1309+
1310+
CFinalCommitment fqc(params, m_quorum_base_block_index->GetBlockHash());
1311+
1312+
1313+
fqc.signers = {true};
1314+
fqc.validMembers = {true};
1315+
1316+
CBLSSecretKey sk1;
1317+
sk1.MakeNewKey();
1318+
1319+
fqc.quorumPublicKey = sk1.GetPublicKey();
1320+
fqc.quorumVvecHash = {};
1321+
1322+
// use just MN's operator public key as quorum pubkey.
1323+
// TODO: use sk1 here instead and use recovery mechanism from shares, but that's not trivial to do
1324+
const bool workaround_qpublic_key = true;
1325+
if (workaround_qpublic_key) {
1326+
fqc.quorumPublicKey = m_mn_activeman->GetPubKey();
1327+
}
1328+
const bool isQuorumRotationEnabled{false};
1329+
fqc.nVersion = CFinalCommitment::GetVersion(isQuorumRotationEnabled,
1330+
DeploymentActiveAfter(m_quorum_base_block_index, Params().GetConsensus(),
1331+
Consensus::DEPLOYMENT_V19));
1332+
fqc.quorumIndex = 0;
1333+
1334+
uint256 commitmentHash = BuildCommitmentHash(fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey,
1335+
fqc.quorumVvecHash);
1336+
fqc.quorumSig = sk1.Sign(commitmentHash, m_use_legacy_bls);
1337+
1338+
fqc.membersSig = m_mn_activeman->Sign(commitmentHash, m_use_legacy_bls);
1339+
1340+
if (workaround_qpublic_key) {
1341+
fqc.quorumSig = fqc.membersSig;
1342+
}
1343+
1344+
if (!fqc.Verify(m_dmnman, m_qsnapman, m_quorum_base_block_index, true)) {
1345+
logger.Batch("failed to verify final commitment");
1346+
assert(false);
1347+
}
1348+
1349+
logger.Batch("final commitment: validMembers=%d, signers=%d, quorumPublicKey=%s", fqc.CountValidMembers(),
1350+
fqc.CountSigners(), fqc.quorumPublicKey.ToString());
1351+
1352+
logger.Flush();
1353+
1354+
return fqc;
1355+
}
1356+
12961357
CDKGMember* CDKGSession::GetMember(const uint256& proTxHash) const
12971358
{
12981359
auto it = membersMap.find(proTxHash);

src/llmq/dkgsession.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,9 @@ class CDKGSession
382382
// Phase 5: aggregate/finalize
383383
std::vector<CFinalCommitment> FinalizeCommitments();
384384

385+
// All Phases 5-in-1 for single-node-quorum
386+
CFinalCommitment FinalizeSingleCommitment();
387+
385388
[[nodiscard]] bool AreWeMember() const { return !myProTxHash.IsNull(); }
386389
void MarkBadMember(size_t idx);
387390

src/llmq/dkgsessionhandler.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,17 @@ void CDKGSessionHandler::HandleDKGRound(CConnman& connman, PeerManager& peerman)
549549
return changed;
550550
});
551551

552+
if (params.size == 1) {
553+
auto finalCommitment = curSession->FinalizeSingleCommitment();
554+
if (!finalCommitment.IsNull()) { // it can be null only if we are not member
555+
if (auto inv_opt = quorumBlockProcessor.AddMineableCommitment(finalCommitment); inv_opt.has_value()) {
556+
peerman.RelayInv(inv_opt.value());
557+
}
558+
}
559+
WaitForNextPhase(QuorumPhase::Initialized, QuorumPhase::Contribute, curQuorumHash);
560+
return;
561+
}
562+
552563
const auto tip_mn_list = m_dmnman.GetListAtChainTip();
553564
utils::EnsureQuorumConnections(params, connman, m_dmnman, m_sporkman, m_qsnapman, tip_mn_list, pQuorumBaseBlockIndex,
554565
curSession->myProTxHash, /* is_masternode = */ m_mn_activeman != nullptr);

src/llmq/params.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ enum class LLMQType : uint8_t {
3737
LLMQ_TEST_PLATFORM = 106, // 3 members, 2 (66%) threshold, one per hour.
3838

3939
// for devnets only. rotated version (v2) for devnets
40-
LLMQ_DEVNET_DIP0024 = 105 // 8 members, 4 (50%) threshold, one per hour. Params might differ when -llmqdevnetparams is used
40+
LLMQ_DEVNET_DIP0024 = 105, // 8 members, 4 (50%) threshold, one per hour. Params might differ when -llmqdevnetparams is used
4141
};
4242

4343
// Configures a LLMQ and its DKG

src/llmq/quorums.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,12 @@ CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType l
414414

415415
quorum->Init(std::move(qc), pQuorumBaseBlockIndex, minedBlockHash, members);
416416

417+
if (populate_cache && llmq_params_opt->size == 1) {
418+
WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum));
419+
420+
return quorum;
421+
}
422+
417423
bool hasValidVvec = false;
418424
if (WITH_LOCK(cs_db, return quorum->ReadContributions(*db))) {
419425
hasValidVvec = true;

src/llmq/signing_shares.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,25 @@ void CSigSharesManager::TryRecoverSig(PeerManager& peerman, const CQuorumCPtr& q
778778
return;
779779
}
780780

781+
if (quorum->params.size == 1) {
782+
if (sigSharesForSignHash->empty()) {
783+
LogPrint(BCLog::LLMQ_SIGS, /* Continued */
784+
"CSigSharesManager::%s -- impossible to recover single-node signature - no shares yet. id=%s, "
785+
"msgHash=%s\n",
786+
__func__, id.ToString(), msgHash.ToString());
787+
return;
788+
}
789+
const auto& sigShare = sigSharesForSignHash->begin()->second;
790+
CBLSSignature recoveredSig = sigShare.sigShare.Get();
791+
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- recover single-node signature. id=%s, msgHash=%s\n",
792+
__func__, id.ToString(), msgHash.ToString());
793+
794+
auto rs = std::make_shared<CRecoveredSig>(quorum->params.type, quorum->qc->quorumHash, id, msgHash,
795+
recoveredSig);
796+
sigman.ProcessRecoveredSig(rs, peerman);
797+
return; // end of single-quorum processing
798+
}
799+
781800
sigSharesForRecovery.reserve((size_t) quorum->params.threshold);
782801
idsForRecovery.reserve((size_t) quorum->params.threshold);
783802
for (auto it = sigSharesForSignHash->begin(); it != sigSharesForSignHash->end() && sigSharesForRecovery.size() < size_t(quorum->params.threshold); ++it) {
@@ -1524,6 +1543,37 @@ std::optional<CSigShare> CSigSharesManager::CreateSigShare(const CQuorumCPtr& qu
15241543
return std::nullopt;
15251544
}
15261545

1546+
if (quorum->params.size == 1) {
1547+
int memberIdx = quorum->GetMemberIndex(activeMasterNodeProTxHash);
1548+
if (memberIdx == -1) {
1549+
// this should really not happen (IsValidMember gave true)
1550+
return std::nullopt;
1551+
}
1552+
1553+
CSigShare sigShare(quorum->params.type, quorum->qc->quorumHash, id, msgHash, uint16_t(memberIdx), {});
1554+
uint256 signHash = sigShare.buildSignHash();
1555+
1556+
// TODO: This one should be SIGN by QUORUM key, not by OPERATOR key
1557+
// see TODO in CDKGSession::FinalizeSingleCommitment for details
1558+
sigShare.sigShare.Set(m_mn_activeman->Sign(signHash, bls::bls_legacy_scheme.load()), bls::bls_legacy_scheme.load());
1559+
1560+
if (!sigShare.sigShare.Get().IsValid()) {
1561+
LogPrintf("CSigSharesManager::%s -- failed to sign sigShare. signHash=%s, id=%s, msgHash=%s, time=%s\n",
1562+
__func__, signHash.ToString(), sigShare.getId().ToString(), sigShare.getMsgHash().ToString(),
1563+
t.count());
1564+
return std::nullopt;
1565+
}
1566+
1567+
sigShare.UpdateKey();
1568+
1569+
LogPrint(BCLog::LLMQ_SIGS, /* Continued */
1570+
"CSigSharesManager::%s -- created sigShare. signHash=%s, id=%s, msgHash=%s, llmqType=%d, quorum=%s, "
1571+
"time=%s\n",
1572+
__func__, signHash.ToString(), sigShare.getId().ToString(), sigShare.getMsgHash().ToString(),
1573+
ToUnderlying(quorum->params.type), quorum->qc->quorumHash.ToString(), t.count());
1574+
1575+
return sigShare;
1576+
}
15271577
const CBLSSecretKey& skShare = quorum->GetSkShare();
15281578
if (!skShare.IsValid()) {
15291579
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- we don't have our skShare for quorum %s\n", __func__, quorum->qc->quorumHash.ToString());

src/rpc/quorums.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ static RPCHelpMan quorum_list_extended()
175175
};
176176
}
177177

178-
static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CQuorumCPtr& quorum, bool includeMembers, bool includeSkShare)
178+
static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CQuorumCPtr& quorum,
179+
bool includeMembers, bool includeSkShare, bool single_node_quorum = false)
179180
{
180181
UniValue ret(UniValue::VOBJ);
181182

@@ -206,9 +207,13 @@ static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_
206207
mo.pushKV("pubKeyOperator", dmn->pdmnState->pubKeyOperator.ToString());
207208
mo.pushKV("valid", quorum->qc->validMembers[i]);
208209
if (quorum->qc->validMembers[i]) {
209-
CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
210-
if (pubKey.IsValid()) {
211-
mo.pushKV("pubKeyShare", pubKey.ToString());
210+
if (single_node_quorum) {
211+
mo.pushKV("pubKeyShare", dmn->pdmnState->pubKeyOperator.ToString());
212+
} else {
213+
CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
214+
if (pubKey.IsValid()) {
215+
mo.pushKV("pubKeyShare", pubKey.ToString());
216+
}
212217
}
213218
}
214219
membersArr.push_back(mo);
@@ -241,7 +246,8 @@ static RPCHelpMan quorum_info()
241246
const LLMQContext& llmq_ctx = EnsureLLMQContext(node);
242247

243248
const Consensus::LLMQType llmqType{static_cast<Consensus::LLMQType>(ParseInt32V(request.params[0], "llmqType"))};
244-
if (!Params().GetLLMQ(llmqType).has_value()) {
249+
auto llmq_opt = Params().GetLLMQ(llmqType);
250+
if (!llmq_opt.has_value()) {
245251
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
246252
}
247253

@@ -256,7 +262,7 @@ static RPCHelpMan quorum_info()
256262
throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
257263
}
258264

259-
return BuildQuorumInfo(*llmq_ctx.quorum_block_processor, quorum, true, includeSkShare);
265+
return BuildQuorumInfo(*llmq_ctx.quorum_block_processor, quorum, true, includeSkShare, llmq_opt->size == 1);
260266
},
261267
};
262268
}

0 commit comments

Comments
 (0)