Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/Makefile.bench.include
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bench_bench_dash_SOURCES = \
bench/crypto_hash.cpp \
bench/data.cpp \
bench/data.h \
bench/dkg_deserialize.cpp \
bench/duplicate_inputs.cpp \
bench/ecdsa.cpp \
bench/ellswift.cpp \
Expand Down
179 changes: 179 additions & 0 deletions src/bench/dkg_deserialize.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) 2026 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <bench/bench.h>

#include <bls/bls.h>
#include <bls/bls_ies.h>
#include <llmq/dkgmessages.h>
#include <llmq/params.h>
#include <random.h>
#include <streams.h>
#include <uint256.h>
#include <version.h>

#include <memory>
#include <vector>

namespace {

// Build a well-formed serialized QCONTRIB payload for the given quorum size /
// threshold. The ciphertext blobs are opaque bytes on the wire (only the
// enclosing IES header is a BLS pubkey), so we can synthesise them without
// running IES encryption. Vvec entries and the outer signature are real BLS
// points so the deserializer performs the same G1/G2 decompression as it would
// in production.
CDataStream BuildSerializedContribution(int quorumSize, int threshold)
{
llmq::CDKGContribution qc;
qc.llmqType = Consensus::LLMQType::LLMQ_50_60;
qc.quorumHash = GetRandHash();
qc.proTxHash = GetRandHash();

auto vvec = std::make_shared<std::vector<CBLSPublicKey>>();
vvec->reserve(threshold);
for (int i = 0; i < threshold; ++i) {
CBLSSecretKey sk;
sk.MakeNewKey();
vvec->emplace_back(sk.GetPublicKey());
}
qc.vvec = std::move(vvec);

auto contributions = std::make_shared<CBLSIESMultiRecipientObjects<CBLSSecretKey>>();
{
CBLSSecretKey sk;
sk.MakeNewKey();
contributions->ephemeralPubKey = sk.GetPublicKey();
}
contributions->ivSeed = GetRandHash();
contributions->blobs.resize(quorumSize);
// Each IES blob wraps a CBLSSecretKey (32 bytes) plus AEAD framing overhead
// (~16 bytes tag). Sixty-four bytes matches what the real DKG produces on
// the wire closely enough for deserialization cost. Blob contents are
// opaque bytes on the wire (no BLS decoding); a deterministic fill keeps
// the benchmark reproducible.
FastRandomContext rng{true};
for (auto& blob : contributions->blobs) {
blob = rng.randbytes(64);
}
qc.contributions = std::move(contributions);

{
CBLSSecretKey sk;
sk.MakeNewKey();
qc.sig = sk.Sign(GetRandHash(), false);
}

CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
ds << qc;
return ds;
}

CDataStream BuildSerializedPrematureCommitment(int quorumSize)
{
llmq::CDKGPrematureCommitment qc;
qc.llmqType = Consensus::LLMQType::LLMQ_50_60;
qc.quorumHash = GetRandHash();
qc.proTxHash = GetRandHash();
qc.validMembers.assign(quorumSize, true);
{
CBLSSecretKey sk;
sk.MakeNewKey();
qc.quorumPublicKey = sk.GetPublicKey();
}
qc.quorumVvecHash = GetRandHash();
{
CBLSSecretKey sk1, sk2;
sk1.MakeNewKey();
sk2.MakeNewKey();
qc.quorumSig = sk1.Sign(GetRandHash(), false);
qc.sig = sk2.Sign(GetRandHash(), false);
}
CDataStream ds(SER_NETWORK, PROTOCOL_VERSION);
ds << qc;
return ds;
}

} // namespace

// Simulates the current intake path: deserialize a copy on the network thread
// for structural pre-validation, then deserialize again on the DKG worker to
// obtain the typed object.
static void DKG_QCONTRIB_DoubleDeserialize(benchmark::Bench& bench)
{
const CDataStream wire = BuildSerializedContribution(50, 30);

bench.minEpochIterations(200).run([&] {
// Structural check on a copy (mirrors CheckDKGMessageStructure).
{
CDataStream s(wire);
llmq::CDKGContribution qc;
s >> qc;
ankerl::nanobench::doNotOptimizeAway(qc.vvec->size());
}
// Worker deserialize (mirrors PopAndDeserializeMessages, which built
// the typed object via std::make_shared<Message>()).
{
CDataStream s(wire);
auto qc = std::make_shared<llmq::CDKGContribution>();
s >> *qc;
ankerl::nanobench::doNotOptimizeAway(qc->contributions->blobs.size());
}
});
}

// Simulates the proposed intake path: deserialize once at intake, retain the
// typed object, no worker-side deserialize.
static void DKG_QCONTRIB_SingleDeserialize(benchmark::Bench& bench)
{
const CDataStream wire = BuildSerializedContribution(50, 30);

bench.minEpochIterations(200).run([&] {
CDataStream s(wire);
auto qc = std::make_shared<llmq::CDKGContribution>();
s >> *qc;
ankerl::nanobench::doNotOptimizeAway(qc->vvec->size());
ankerl::nanobench::doNotOptimizeAway(qc->contributions->blobs.size());
});
}

static void DKG_QPCOMMITMENT_DoubleDeserialize(benchmark::Bench& bench)
{
const CDataStream wire = BuildSerializedPrematureCommitment(50);

bench.minEpochIterations(2000).run([&] {
// Structural check on a copy (mirrors CheckDKGMessageStructure).
{
CDataStream s(wire);
llmq::CDKGPrematureCommitment qc;
s >> qc;
ankerl::nanobench::doNotOptimizeAway(qc.validMembers.size());
}
// Worker deserialize (mirrors PopAndDeserializeMessages, which built
// the typed object via std::make_shared<Message>()).
{
CDataStream s(wire);
auto qc = std::make_shared<llmq::CDKGPrematureCommitment>();
s >> *qc;
ankerl::nanobench::doNotOptimizeAway(qc->validMembers.size());
}
});
}

static void DKG_QPCOMMITMENT_SingleDeserialize(benchmark::Bench& bench)
{
const CDataStream wire = BuildSerializedPrematureCommitment(50);

bench.minEpochIterations(2000).run([&] {
CDataStream s(wire);
auto qc = std::make_shared<llmq::CDKGPrematureCommitment>();
s >> *qc;
ankerl::nanobench::doNotOptimizeAway(qc->validMembers.size());
});
}

BENCHMARK(DKG_QCONTRIB_DoubleDeserialize, benchmark::PriorityLevel::HIGH)
BENCHMARK(DKG_QCONTRIB_SingleDeserialize, benchmark::PriorityLevel::HIGH)
BENCHMARK(DKG_QPCOMMITMENT_DoubleDeserialize, benchmark::PriorityLevel::HIGH)
BENCHMARK(DKG_QPCOMMITMENT_SingleDeserialize, benchmark::PriorityLevel::HIGH)
49 changes: 0 additions & 49 deletions src/llmq/dkgsessionhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

#include <llmq/dkgsessionhandler.h>

#include <logging.h>
#include <uint256.h>

#include <stdexcept>

namespace llmq {
Expand All @@ -25,52 +22,6 @@ CDKGSessionHandler::CDKGSessionHandler(const Consensus::LLMQParams& _params) :

CDKGSessionHandler::~CDKGSessionHandler() = default;

void CDKGPendingMessages::PushPendingMessage(NodeId from, std::shared_ptr<CDataStream> pm, const uint256& hash)
{
LOCK(cs_messages);

if (messagesPerNode[from] >= maxMessagesPerNode) {
// TODO ban?
LogPrint(BCLog::LLMQ_DKG, "CDKGPendingMessages::%s -- too many messages, peer=%d\n", __func__, from);
return;
}
messagesPerNode[from]++;

if (!seenMessages.emplace(hash).second) {
LogPrint(BCLog::LLMQ_DKG, "CDKGPendingMessages::%s -- already seen %s, peer=%d\n", __func__, hash.ToString(), from);
return;
}

pendingMessages.emplace_back(std::make_pair(from, std::move(pm)));
}

std::list<CDKGPendingMessages::BinaryMessage> CDKGPendingMessages::PopPendingMessages(size_t maxCount)
{
LOCK(cs_messages);

std::list<BinaryMessage> ret;
while (!pendingMessages.empty() && ret.size() < maxCount) {
ret.emplace_back(std::move(pendingMessages.front()));
pendingMessages.pop_front();
}

return ret;
}

bool CDKGPendingMessages::HasSeen(const uint256& hash) const
{
LOCK(cs_messages);
return seenMessages.count(hash) != 0;
}

void CDKGPendingMessages::Clear()
{
LOCK(cs_messages);
pendingMessages.clear();
messagesPerNode.clear();
seenMessages.clear();
}

void CDKGSessionHandler::ClearPendingMessages()
{
pendingContributions.Clear();
Expand Down
Loading
Loading