Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,7 @@ class CRegTestParams : public CChainParams {

UpdateLLMQTestParametersFromArgs(args, Consensus::LLMQType::LLMQ_TEST);
UpdateLLMQTestParametersFromArgs(args, Consensus::LLMQType::LLMQ_TEST_INSTANTSEND);
UpdateLLMQTestParametersFromArgs(args, Consensus::LLMQType::LLMQ_TEST_PLATFORM);
UpdateLLMQInstantSendDIP0024FromArgs(args);
}

Expand Down Expand Up @@ -1144,13 +1145,17 @@ void CRegTestParams::UpdateBudgetParametersFromArgs(const ArgsManager& args)

void CRegTestParams::UpdateLLMQTestParametersFromArgs(const ArgsManager& args, const Consensus::LLMQType llmqType)
{
assert(llmqType == Consensus::LLMQType::LLMQ_TEST || llmqType == Consensus::LLMQType::LLMQ_TEST_INSTANTSEND);
assert(llmqType == Consensus::LLMQType::LLMQ_TEST || llmqType == Consensus::LLMQType::LLMQ_TEST_INSTANTSEND || llmqType == Consensus::LLMQType::LLMQ_TEST_PLATFORM);

std::string cmd_param{"-llmqtestparams"}, llmq_name{"LLMQ_TEST"};
if (llmqType == Consensus::LLMQType::LLMQ_TEST_INSTANTSEND) {
cmd_param = "-llmqtestinstantsendparams";
llmq_name = "LLMQ_TEST_INSTANTSEND";
}
if (llmqType == Consensus::LLMQType::LLMQ_TEST_PLATFORM) {
cmd_param = "-llmqtestplatformparams";
llmq_name = "LLMQ_TEST_PLATFORM";
}

if (!args.IsArgSet(cmd_param)) return;

Expand Down Expand Up @@ -1379,6 +1384,7 @@ void SetupChainParamsOptions(ArgsManager& argsman)
argsman.AddArg("-llmqtestinstantsenddip0024=<quorum name>", "Override the default LLMQ type used for InstantSendDIP0024. Used mainly to test Platform. (default: llmq_test_dip0024, regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-llmqtestinstantsendparams=<size>:<threshold>", "Override the default LLMQ size for the LLMQ_TEST_INSTANTSEND quorums (default: 3:2, regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-llmqtestparams=<size>:<threshold>", "Override the default LLMQ size for the LLMQ_TEST quorum (default: 3:2, regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-llmqtestplatformparams=<size>:<threshold>", "Override the default LLMQ size for the LLMQ_TEST_PLATFORM quorum (default: 3:2, regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-minimumdifficultyblocks=<n>", "The number of blocks that can be mined with the minimum difficulty at the start of a chain (default: 0, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-powtargetspacing=<n>", "Override the default PowTargetSpacing value in seconds (default: 2.5 minutes, devnet-only)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (bip147, bip34, dersig, cltv, csv, brr, dip0001, dip0008, dip0024, v19, v20, mn_rr). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
Expand Down
33 changes: 21 additions & 12 deletions src/llmq/commitment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ bool CFinalCommitment::Verify(CDeterministicMNManager& dmnman, CQuorumSnapshotMa
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid quorumPublicKey\n", quorumHash.ToString());
return false;
}
if (quorumVvecHash.IsNull()) {
if (llmq_params.size != 1 && quorumVvecHash.IsNull()) {
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid quorumVvecHash\n", quorumHash.ToString());
return false;
}
Expand Down Expand Up @@ -115,19 +115,27 @@ bool CFinalCommitment::Verify(CDeterministicMNManager& dmnman, CQuorumSnapshotMa
LogPrint(BCLog::LLMQ, "CFinalCommitment::%s members[%s] quorumPublicKey[%s] commitmentHash[%s]\n",
__func__, ss3.str(), quorumPublicKey.ToString(), commitmentHash.ToString());
}
std::vector<CBLSPublicKey> memberPubKeys;
for (const auto i : irange::range(members.size())) {
if (!signers[i]) {
continue;
if (llmq_params.size == 1) {
LogPrintf("pubkey operator: %s\n", members[0]->pdmnState->pubKeyOperator.Get().ToString());
if (!membersSig.VerifyInsecure(members[0]->pdmnState->pubKeyOperator.Get(), commitmentHash)) {
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid member signature\n", quorumHash.ToString());
return false;
}
} else {
std::vector<CBLSPublicKey> memberPubKeys;
for (const auto i : irange::range(members.size())) {
if (!signers[i]) {
continue;
}
memberPubKeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
}
memberPubKeys.emplace_back(members[i]->pdmnState->pubKeyOperator.Get());
}

if (!membersSig.VerifySecureAggregated(memberPubKeys, commitmentHash)) {
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid aggregated members signature\n", quorumHash.ToString());
return false;
if (!membersSig.VerifySecureAggregated(memberPubKeys, commitmentHash)) {
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid aggregated members signature\n",
quorumHash.ToString());
return false;
}
}

if (!quorumSig.VerifyInsecure(quorumPublicKey, commitmentHash)) {
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid quorum signature\n", quorumHash.ToString());
return false;
Expand Down Expand Up @@ -161,7 +169,8 @@ bool CFinalCommitment::VerifySizes(const Consensus::LLMQParams& params) const
return false;
}
if (validMembers.size() != size_t(params.size)) {
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid signers.size=%d\n", quorumHash.ToString(), signers.size());
LogPrint(BCLog::LLMQ, "CFinalCommitment -- q[%s] invalid validMembers.size=%d\n", quorumHash.ToString(),
validMembers.size());
return false;
}
return true;
Expand Down
62 changes: 62 additions & 0 deletions src/llmq/dkgsession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ void CDKGSession::Contribute(CDKGPendingMessages& pendingMessages, PeerManager&
return;
}

assert(params.threshold > 1); // we should not get there with single-node-quorums

cxxtimer::Timer t1(true);
logger.Batch("generating contributions");
if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, m_sk_contributions)) {
Expand Down Expand Up @@ -278,6 +280,7 @@ bool CDKGSession::PreVerifyMessage(const CDKGContribution& qc, bool& retBan) con
return true;
}

// TODO: remove duplicated code between all ReceiveMessage: CDKGContribution, CDKGComplaint, CDKGJustification, CDKGPrematureCommitment
std::optional<CInv> CDKGSession::ReceiveMessage(const CDKGContribution& qc)
{
CDKGLogger logger(*this, __func__, __LINE__);
Expand Down Expand Up @@ -1235,6 +1238,7 @@ std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments()
fqc.quorumVvecHash = first.quorumVvecHash;

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

Expand Down Expand Up @@ -1292,6 +1296,64 @@ std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments()
return finalCommitments;
}

CFinalCommitment CDKGSession::FinalizeSingleCommitment()
{
if (!AreWeMember()) {
return {};
}

CDKGLogger logger(*this, __func__, __LINE__);

std::vector<CBLSId> signerIds;
std::vector<CBLSSignature> thresholdSigs;

CFinalCommitment fqc(params, m_quorum_base_block_index->GetBlockHash());


fqc.signers = {true};
fqc.validMembers = {true};

CBLSSecretKey sk1;
sk1.MakeNewKey();

fqc.quorumPublicKey = sk1.GetPublicKey();
fqc.quorumVvecHash = {};

// use just MN's operator public key as quorum pubkey.
// TODO: use sk1 here instead and use recovery mechanism from shares, but that's not trivial to do
const bool workaround_qpublic_key = true;
if (workaround_qpublic_key) {
fqc.quorumPublicKey = m_mn_activeman->GetPubKey();
}
const bool isQuorumRotationEnabled{false};
fqc.nVersion = CFinalCommitment::GetVersion(isQuorumRotationEnabled,
DeploymentActiveAfter(m_quorum_base_block_index, Params().GetConsensus(),
Consensus::DEPLOYMENT_V19));
fqc.quorumIndex = 0;

uint256 commitmentHash = BuildCommitmentHash(fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey,
fqc.quorumVvecHash);
fqc.quorumSig = sk1.Sign(commitmentHash, m_use_legacy_bls);

fqc.membersSig = m_mn_activeman->Sign(commitmentHash, m_use_legacy_bls);

if (workaround_qpublic_key) {
fqc.quorumSig = fqc.membersSig;
}

if (!fqc.Verify(m_dmnman, m_qsnapman, m_quorum_base_block_index, true)) {
logger.Batch("failed to verify final commitment");
assert(false);
}

logger.Batch("final commitment: validMembers=%d, signers=%d, quorumPublicKey=%s", fqc.CountValidMembers(),
fqc.CountSigners(), fqc.quorumPublicKey.ToString());

logger.Flush();

return fqc;
}

CDKGMember* CDKGSession::GetMember(const uint256& proTxHash) const
{
auto it = membersMap.find(proTxHash);
Expand Down
3 changes: 3 additions & 0 deletions src/llmq/dkgsession.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ class CDKGSession
// Phase 5: aggregate/finalize
std::vector<CFinalCommitment> FinalizeCommitments();

// All Phases 5-in-1 for single-node-quorum
CFinalCommitment FinalizeSingleCommitment();

[[nodiscard]] bool AreWeMember() const { return !myProTxHash.IsNull(); }
void MarkBadMember(size_t idx);

Expand Down
11 changes: 11 additions & 0 deletions src/llmq/dkgsessionhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,17 @@ void CDKGSessionHandler::HandleDKGRound(CConnman& connman, PeerManager& peerman)
return changed;
});

if (params.size == 1) {
auto finalCommitment = curSession->FinalizeSingleCommitment();
if (!finalCommitment.IsNull()) { // it can be null only if we are not member
if (auto inv_opt = quorumBlockProcessor.AddMineableCommitment(finalCommitment); inv_opt.has_value()) {
peerman.RelayInv(inv_opt.value());
}
}
WaitForNextPhase(QuorumPhase::Initialized, QuorumPhase::Contribute, curQuorumHash);
return;
}

const auto tip_mn_list = m_dmnman.GetListAtChainTip();
utils::EnsureQuorumConnections(params, connman, m_dmnman, m_sporkman, m_qsnapman, tip_mn_list, pQuorumBaseBlockIndex,
curSession->myProTxHash, /* is_masternode = */ m_mn_activeman != nullptr);
Expand Down
1 change: 1 addition & 0 deletions src/llmq/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ bool IsQuorumTypeEnabledInternal(Consensus::LLMQType llmqType, gsl::not_null<con
case Consensus::LLMQType::LLMQ_TEST_DIP0024: {
return fDIP0024IsActive;
}
// TODO: remove it in case of testnet reset
case Consensus::LLMQType::LLMQ_25_67:
return pindexPrev->nHeight >= TESTNET_LLMQ_25_67_ACTIVATION_HEIGHT;

Expand Down
2 changes: 1 addition & 1 deletion src/llmq/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ enum class LLMQType : uint8_t {
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
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
Expand Down
6 changes: 6 additions & 0 deletions src/llmq/quorums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType l

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

if (populate_cache && llmq_params_opt->size == 1) {
WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum));

return quorum;
}

Comment on lines 417 to 422

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation before caching single-node quorum

The code should validate the quorum before caching it for single-node quorums.

     if (populate_cache && llmq_params_opt->size == 1) {
+        // Validate quorum before caching
+        if (!quorum->qc->IsValidMember(quorum->members[0]->proTxHash)) {
+            LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid member in single-node quorum\n", __func__);
+            return nullptr;
+        }
+        if (!quorum->qc->quorumPublicKey.IsValid()) {
+            LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid public key in single-node quorum\n", __func__);
+            return nullptr;
+        }
         WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum));
 
         return quorum;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (populate_cache && llmq_params_opt->size == 1) {
WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum));
return quorum;
}
if (populate_cache && llmq_params_opt->size == 1) {
// Validate quorum before caching
if (!quorum->qc->IsValidMember(quorum->members[0]->proTxHash)) {
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid member in single-node quorum\n", __func__);
return nullptr;
}
if (!quorum->qc->quorumPublicKey.IsValid()) {
LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid public key in single-node quorum\n", __func__);
return nullptr;
}
WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum));
return quorum;
}

bool hasValidVvec = false;
if (WITH_LOCK(cs_db, return quorum->ReadContributions(*db))) {
hasValidVvec = true;
Expand Down
50 changes: 50 additions & 0 deletions src/llmq/signing_shares.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,25 @@ void CSigSharesManager::TryRecoverSig(PeerManager& peerman, const CQuorumCPtr& q
return;
}

if (quorum->params.size == 1) {
if (sigSharesForSignHash->empty()) {
LogPrint(BCLog::LLMQ_SIGS, /* Continued */
"CSigSharesManager::%s -- impossible to recover single-node signature - no shares yet. id=%s, "
"msgHash=%s\n",
__func__, id.ToString(), msgHash.ToString());
return;
}
const auto& sigShare = sigSharesForSignHash->begin()->second;
CBLSSignature recoveredSig = sigShare.sigShare.Get();
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- recover single-node signature. id=%s, msgHash=%s\n",
__func__, id.ToString(), msgHash.ToString());

auto rs = std::make_shared<CRecoveredSig>(quorum->params.type, quorum->qc->quorumHash, id, msgHash,
recoveredSig);
sigman.ProcessRecoveredSig(rs, peerman);
return; // end of single-quorum processing
}

sigSharesForRecovery.reserve((size_t) quorum->params.threshold);
idsForRecovery.reserve((size_t) quorum->params.threshold);
for (auto it = sigSharesForSignHash->begin(); it != sigSharesForSignHash->end() && sigSharesForRecovery.size() < size_t(quorum->params.threshold); ++it) {
Expand Down Expand Up @@ -1524,6 +1543,37 @@ std::optional<CSigShare> CSigSharesManager::CreateSigShare(const CQuorumCPtr& qu
return std::nullopt;
}

if (quorum->params.size == 1) {
int memberIdx = quorum->GetMemberIndex(activeMasterNodeProTxHash);
if (memberIdx == -1) {
// this should really not happen (IsValidMember gave true)
return std::nullopt;
}

CSigShare sigShare(quorum->params.type, quorum->qc->quorumHash, id, msgHash, uint16_t(memberIdx), {});
uint256 signHash = sigShare.buildSignHash();

// TODO: This one should be SIGN by QUORUM key, not by OPERATOR key
// see TODO in CDKGSession::FinalizeSingleCommitment for details
sigShare.sigShare.Set(m_mn_activeman->Sign(signHash, bls::bls_legacy_scheme.load()), bls::bls_legacy_scheme.load());

if (!sigShare.sigShare.Get().IsValid()) {
LogPrintf("CSigSharesManager::%s -- failed to sign sigShare. signHash=%s, id=%s, msgHash=%s, time=%s\n",
__func__, signHash.ToString(), sigShare.getId().ToString(), sigShare.getMsgHash().ToString(),
t.count());
return std::nullopt;
}

sigShare.UpdateKey();

LogPrint(BCLog::LLMQ_SIGS, /* Continued */
"CSigSharesManager::%s -- created sigShare. signHash=%s, id=%s, msgHash=%s, llmqType=%d, quorum=%s, "
"time=%s\n",
__func__, signHash.ToString(), sigShare.getId().ToString(), sigShare.getMsgHash().ToString(),
ToUnderlying(quorum->params.type), quorum->qc->quorumHash.ToString(), t.count());

return sigShare;
}
const CBLSSecretKey& skShare = quorum->GetSkShare();
if (!skShare.IsValid()) {
LogPrint(BCLog::LLMQ_SIGS, "CSigSharesManager::%s -- we don't have our skShare for quorum %s\n", __func__, quorum->qc->quorumHash.ToString());
Expand Down
13 changes: 9 additions & 4 deletions src/rpc/quorums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ static RPCHelpMan quorum_list_extended()
};
}

static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CQuorumCPtr& quorum, bool includeMembers, bool includeSkShare)
static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_processor,
const llmq::CQuorumCPtr& quorum, bool includeMembers, bool includeSkShare)
{
UniValue ret(UniValue::VOBJ);

Expand Down Expand Up @@ -206,9 +207,13 @@ static UniValue BuildQuorumInfo(const llmq::CQuorumBlockProcessor& quorum_block_
mo.pushKV("pubKeyOperator", dmn->pdmnState->pubKeyOperator.ToString());
mo.pushKV("valid", quorum->qc->validMembers[i]);
if (quorum->qc->validMembers[i]) {
CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
if (pubKey.IsValid()) {
mo.pushKV("pubKeyShare", pubKey.ToString());
if (quorum->params.size == 1) {
mo.pushKV("pubKeyShare", dmn->pdmnState->pubKeyOperator.ToString());
} else {
CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
if (pubKey.IsValid()) {
mo.pushKV("pubKeyShare", pubKey.ToString());
}
}
}
membersArr.push_back(mo);
Expand Down
15 changes: 3 additions & 12 deletions test/functional/feature_dip4_coinbasemerkleroots.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,17 +253,6 @@ def test_getmnlistdiff_base(self, baseBlockHash, blockHash):

return d

def activate_dip8(self, slow_mode=False):
# NOTE: set slow_mode=True if you are activating dip8 after a huge reorg
# or nodes might fail to catch up otherwise due to a large
# (MAX_BLOCKS_IN_TRANSIT_PER_PEER = 16 blocks) reorg error.
self.log.info("Wait for dip0008 activation")
while self.nodes[0].getblockcount() < DIP0008_HEIGHT:
self.bump_mocktime(10)
self.generate(self.nodes[0], 10, sync_fun=self.no_op)
if slow_mode:
self.sync_blocks()
self.sync_blocks()

def test_dip8_quorum_merkle_root_activation(self, with_initial_quorum, slow_mode=False):
if with_initial_quorum:
Expand All @@ -279,7 +268,9 @@ def test_dip8_quorum_merkle_root_activation(self, with_initial_quorum, slow_mode
cbtx = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 2)["tx"][0]
assert cbtx["cbTx"]["version"] == 1

self.activate_dip8(slow_mode)
self.activate_by_name('dip0008', expected_activation_height=DIP0008_HEIGHT)
self.log.info("Mine one more block with new rules of dip0008")
self.generate(self.nodes[0], 1)

# Assert that merkleRootQuorums is present and 0 (we have no quorums yet)
cbtx = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 2)["tx"][0]
Expand Down
Loading