Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ BITCOIN_CORE_H = \
llmq/ehf_signals.h \
llmq/options.h \
llmq/params.h \
llmq/quorumproofdata.h \
llmq/quorumproofs.h \
llmq/quorums.h \
llmq/quorumsman.h \
llmq/signhash.h \
Expand Down Expand Up @@ -557,6 +559,7 @@ libbitcoin_node_a_SOURCES = \
llmq/ehf_signals.cpp \
llmq/net_signing.cpp \
llmq/options.cpp \
llmq/quorumproofs.cpp \
llmq/quorums.cpp \
llmq/quorumsman.cpp \
llmq/signhash.cpp \
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ BITCOIN_TESTS =\
test/llmq_snapshot_tests.cpp \
test/llmq_utils_tests.cpp \
test/logging_tests.cpp \
test/quorum_proofs_tests.cpp \
test/quorum_proofs_regression_tests.cpp \
test/dbwrapper_tests.cpp \
test/validation_tests.cpp \
test/mempool_tests.cpp \
Expand Down
5 changes: 3 additions & 2 deletions src/evo/chainhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ CChainstateHelper::CChainstateHelper(CCreditPoolManager& cpoolman, CDeterministi
llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman,
const ChainstateManager& chainman, const Consensus::Params& consensus_params,
const CMasternodeSync& mn_sync, const CSporkManager& sporkman,
const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman) :
const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman,
llmq::CQuorumProofManager& quorum_proof_manager) :
isman{isman},
clhandler{clhandler},
mn_payments{std::make_unique<CMNPaymentsProcessor>(dmnman, govman, chainman, consensus_params, mn_sync, sporkman)},
special_tx{std::make_unique<CSpecialTxProcessor>(cpoolman, dmnman, mnhfman, qblockman, qsnapman, chainman,
consensus_params, clhandler, qman)}
consensus_params, clhandler, qman, quorum_proof_manager)}
{}

CChainstateHelper::~CChainstateHelper() = default;
Expand Down
4 changes: 3 additions & 1 deletion src/evo/chainhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class CChainLocksHandler;
class CInstantSendManager;
class CQuorumBlockProcessor;
class CQuorumManager;
class CQuorumProofManager;
class CQuorumSnapshotManager;
}

Expand All @@ -44,7 +45,8 @@ class CChainstateHelper
llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman,
const ChainstateManager& chainman, const Consensus::Params& consensus_params,
const CMasternodeSync& mn_sync, const CSporkManager& sporkman,
const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman);
const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman,
llmq::CQuorumProofManager& quorum_proof_manager);
~CChainstateHelper();

/** Passthrough functions to CChainLocksHandler */
Expand Down
26 changes: 26 additions & 0 deletions src/evo/specialtxman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <evo/simplifiedmns.h>
#include <llmq/blockprocessor.h>
#include <llmq/commitment.h>
#include <llmq/quorumproofs.h>
#include <llmq/quorumsman.h>
#include <llmq/utils.h>

Expand Down Expand Up @@ -662,6 +663,23 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB
return false;
}

// Index the chainlock from cbtx for proof generation
// Only index if not just checking AND block is part of the active chain
// This prevents indexing chainlocks from blocks during a reorg
if (!fJustCheck && opt_cbTx->bestCLSignature.IsValid() &&
m_chainman.ActiveChain().Contains(pindex)) {
int chainlockedHeight = pindex->nHeight - static_cast<int>(opt_cbTx->bestCLHeightDiff) - 1;
const CBlockIndex* pChainlockedBlock = pindex->GetAncestor(chainlockedHeight);
if (pChainlockedBlock) {
m_quorum_proof_manager.IndexChainlock(
chainlockedHeight,
pChainlockedBlock->GetBlockHash(),
opt_cbTx->bestCLSignature,
pindex->GetBlockHash(),
pindex->nHeight);
}
}

int64_t nTime6_3 = GetTimeMicros();
nTimeCbTxCL += nTime6_3 - nTime6_2;
LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n",
Expand Down Expand Up @@ -719,6 +737,14 @@ bool CSpecialTxProcessor::UndoSpecialTxsInBlock(const CBlock& block, const CBloc
if (!m_qblockman.UndoBlock(block, pindex)) {
return false;
}

// Remove chainlock index for this block's cbtx
if (block.vtx.size() > 0 && block.vtx[0]->nType == TRANSACTION_COINBASE) {
if (const auto opt_cbTx = GetTxPayload<CCbTx>(*block.vtx[0]); opt_cbTx && opt_cbTx->bestCLSignature.IsValid()) {
int chainlockedHeight = pindex->nHeight - static_cast<int>(opt_cbTx->bestCLHeightDiff) - 1;
m_quorum_proof_manager.RemoveChainlockIndex(chainlockedHeight);
}
}
} catch (const std::exception& e) {
bls::bls_legacy_scheme.store(bls_legacy_scheme);
LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
Expand Down
8 changes: 6 additions & 2 deletions src/evo/specialtxman.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace llmq {
class CChainLocksHandler;
class CQuorumBlockProcessor;
class CQuorumManager;
class CQuorumProofManager;
class CQuorumSnapshotManager;
} // namespace llmq

Expand All @@ -47,12 +48,14 @@ class CSpecialTxProcessor
const Consensus::Params& m_consensus_params;
const llmq::CChainLocksHandler& m_clhandler;
const llmq::CQuorumManager& m_qman;
llmq::CQuorumProofManager& m_quorum_proof_manager;

public:
explicit CSpecialTxProcessor(CCreditPoolManager& cpoolman, CDeterministicMNManager& dmnman, CMNHFManager& mnhfman,
llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman,
const ChainstateManager& chainman, const Consensus::Params& consensus_params,
const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman) :
const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman,
llmq::CQuorumProofManager& quorum_proof_manager) :
m_cpoolman(cpoolman),
m_dmnman{dmnman},
m_mnhfman{mnhfman},
Expand All @@ -61,7 +64,8 @@ class CSpecialTxProcessor
m_chainman(chainman),
m_consensus_params{consensus_params},
m_clhandler{clhandler},
m_qman{qman}
m_qman{qman},
m_quorum_proof_manager{quorum_proof_manager}
{
}

Expand Down
10 changes: 10 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
#include <llmq/net_signing.h>
#include <llmq/options.h>
#include <llmq/observer/context.h>
#include <llmq/quorumproofs.h>
#include <masternode/meta.h>
#include <masternode/sync.h>
#include <masternode/utils.h>
Expand Down Expand Up @@ -2171,6 +2172,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

ChainstateManager& chainman = *Assert(node.chainman);

// Migrate chainlock index for quorum proof generation (one-time on first run after upgrade)
if (node.llmq_ctx && node.llmq_ctx->quorum_proof_manager) {
LOCK(cs_main);
node.llmq_ctx->quorum_proof_manager->MigrateChainlockIndex(chainman.ActiveChain(), chainparams);
// Migrate quorum proof data index for fast proof chain generation
node.llmq_ctx->quorum_proof_manager->MigrateQuorumProofIndex(chainman.ActiveChain(), chainparams,
chainman.m_blockman);
}

assert(!node.dstxman);
node.dstxman = std::make_unique<CDSTXManager>();

Expand Down
178 changes: 178 additions & 0 deletions src/llmq/blockprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <llmq/blockprocessor.h>
#include <llmq/commitment.h>
#include <llmq/options.h>
#include <llmq/quorumproofdata.h>
#include <llmq/utils.h>

#include <evo/evodb.h>
Expand All @@ -16,6 +17,7 @@
#include <consensus/params.h>
#include <consensus/validation.h>
#include <deploymentstatus.h>
#include <hash.h>
#include <net.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
Expand Down Expand Up @@ -162,6 +164,52 @@ MessageProcessingResult CQuorumBlockProcessor::ProcessMessage(const CNode& peer,
return ret;
}

/**
* Helper function to build merkle proof with path tracking.
* Returns the merkle path (sibling hashes) and side indicators.
*/
static std::pair<std::vector<uint256>, std::vector<bool>> BuildMerkleProofPath(
const std::vector<uint256>& hashes, size_t targetIndex)
{
std::vector<uint256> merklePath;
std::vector<bool> merklePathSide;

if (hashes.empty()) {
return {merklePath, merklePathSide};
}

std::vector<uint256> current = hashes;
size_t index = targetIndex;

while (current.size() > 1) {
std::vector<uint256> next;
size_t nextIndex = 0;

for (size_t i = 0; i < current.size(); i += 2) {
size_t left = i;
size_t right = (i + 1 < current.size()) ? i + 1 : i;

if (index == left || index == right) {
if (index == left) {
merklePath.push_back(current[right]);
merklePathSide.push_back(true);
} else {
merklePath.push_back(current[left]);
merklePathSide.push_back(false);
}
nextIndex = next.size();
}

next.push_back(Hash(current[left], current[right]));
}

index = nextIndex;
current = std::move(next);
}

return {merklePath, merklePathSide};
}

bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, BlockValidationState& state, bool fJustCheck, bool fBLSChecks)
{
AssertLockHeld(::cs_main);
Expand Down Expand Up @@ -230,6 +278,78 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, gsl::not_null<cons
}
}

// Store quorum proof data for fast proof chain generation (only for actual block processing, not just checking)
if (!fJustCheck) {
// Get active commitments up to this block's previous block (matching CalcCbTxMerkleRootQuorums logic)
auto commitmentsMap = GetMinedAndActiveCommitmentsUntilBlock(pindex->pprev);

// Collect all commitment hashes for merkle root calculation
std::vector<uint256> allCommitmentHashes;
for (const auto& [type, blockIndexes] : commitmentsMap) {
for (const auto* blockIndex : blockIndexes) {
uint256 commitmentHash = GetMinedCommitmentTxHash(type, blockIndex->GetBlockHash());
if (commitmentHash != uint256::ZERO) {
allCommitmentHashes.push_back(commitmentHash);
}
}
}

// Add commitments from current block
for (size_t i = 1; i < block.vtx.size(); ++i) {
const auto& tx = block.vtx[i];
if (tx->IsSpecialTxVersion() && tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
const auto opt_qc = GetTxPayload<CFinalCommitmentTxPayload>(*tx);
if (opt_qc && !opt_qc->commitment.IsNull()) {
allCommitmentHashes.push_back(::SerializeHash(opt_qc->commitment));
}
}
}

// Sort to match CalcCbTxMerkleRootQuorums
std::sort(allCommitmentHashes.begin(), allCommitmentHashes.end());

// Build coinbase merkle proof (same for all commitments in this block)
std::vector<uint256> txHashes;
txHashes.reserve(block.vtx.size());
for (const auto& tx : block.vtx) {
txHashes.push_back(tx->GetHash());
}
auto [cbPath, cbSide] = BuildMerkleProofPath(txHashes, 0);

// Store proof data for each non-null commitment in this block
for (const auto& [type, qc] : qcs) {
if (qc.IsNull()) continue;

// Find commitment hash in sorted list
uint256 targetHash = ::SerializeHash(qc);
auto it = std::find(allCommitmentHashes.begin(), allCommitmentHashes.end(), targetHash);
if (it == allCommitmentHashes.end()) {
LogPrint(BCLog::LLMQ, "[ProcessBlock] Could not find commitment hash for %s in active set\n",
qc.quorumHash.ToString());
continue;
}
size_t targetIndex = std::distance(allCommitmentHashes.begin(), it);

// Build quorum merkle proof
auto [qPath, qSide] = BuildMerkleProofPath(allCommitmentHashes, targetIndex);

// Store proof data
QuorumProofData proofData;
proofData.quorumMerkleProof.merklePath = std::move(qPath);
proofData.quorumMerkleProof.merklePathSide = std::move(qSide);
proofData.coinbaseTx = block.vtx[0];
proofData.coinbaseMerklePath = cbPath; // Copy since reused
proofData.coinbaseMerklePathSide = cbSide;
proofData.header = block.GetBlockHeader();

auto proofKey = std::make_pair(DB_QUORUM_PROOF_DATA, std::make_pair(qc.llmqType, qc.quorumHash));
m_evoDb.Write(proofKey, proofData);

LogPrint(BCLog::LLMQ, "[ProcessBlock] Stored proof data for quorum %s type=%d\n",
qc.quorumHash.ToString(), ToUnderlying(qc.llmqType));
}
}

m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, blockHash);

return true;
Expand Down Expand Up @@ -399,6 +519,9 @@ bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, gsl::not_null<const C

m_evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash)));

// Also erase the cached proof data
m_evoDb.Erase(std::make_pair(DB_QUORUM_PROOF_DATA, std::make_pair(qc.llmqType, qc.quorumHash)));

const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
assert(llmq_params_opt.has_value());

Expand Down Expand Up @@ -525,6 +648,61 @@ std::pair<CFinalCommitment, uint256> CQuorumBlockProcessor::GetMinedCommitment(C
return ret;
}

uint256 CQuorumBlockProcessor::GetMinedCommitmentTxHash(Consensus::LLMQType llmqType, const uint256& quorumHash) const
{
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash));
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey << key;

// Fast path: try to read raw data from disk to avoid deserializing BLS keys
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
if (m_evoDb.GetRawDB().ReadDataStream(ssKey, ssValue)) {
// The data in DB is std::pair<CFinalCommitment, uint256>
// It's serialized as: [CFinalCommitment serialized data][uint256 serialized data]
// uint256 is exactly 32 bytes
if (ssValue.size() > 32) {
// We just want the hash of the CFinalCommitment part
// SerializeHash uses SER_GETHASH, but we have SER_DISK bytes.
// CFinalCommitment serialization is identical for both (as long as nVersion matches).
// We trust the data in DB is consistent.
return Hash(MakeByteSpan(ssValue).first(ssValue.size() - 32));
}
}

// Fallback: use slow path (read from memory/cache or if disk read failed)
// This will deserialize the full object
auto [commitment, _] = GetMinedCommitment(llmqType, quorumHash);
if (commitment.IsNull()) {
return uint256::ZERO;
}
return ::SerializeHash(commitment);
}

uint256 CQuorumBlockProcessor::GetMinedCommitmentBlockHash(Consensus::LLMQType llmqType, const uint256& quorumHash) const
{
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash));
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey << key;

// Fast path: try to read raw data from disk
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
if (m_evoDb.GetRawDB().ReadDataStream(ssKey, ssValue)) {
// The data in DB is std::pair<CFinalCommitment, uint256>
// It's serialized as: [CFinalCommitment serialized data][uint256 serialized data]
// uint256 is exactly 32 bytes and it is at the end
if (ssValue.size() >= 32) {
uint256 blockHash;
// Read last 32 bytes
std::memcpy(blockHash.begin(), ssValue.data() + ssValue.size() - 32, 32);
return blockHash;
}
}

// Fallback: use slow path
auto [_, blockHash] = GetMinedCommitment(llmqType, quorumHash);
return blockHash;
}

// The returned quorums are in reversed order, so the most recent one is at index 0
std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null<const CBlockIndex*> pindex, size_t maxCount) const
{
Expand Down
2 changes: 2 additions & 0 deletions src/llmq/blockprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class CQuorumBlockProcessor
bool HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const
EXCLUSIVE_LOCKS_REQUIRED(!minableCommitmentsCs);
std::pair<CFinalCommitment, uint256> GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const;
uint256 GetMinedCommitmentTxHash(Consensus::LLMQType llmqType, const uint256& quorumHash) const;
uint256 GetMinedCommitmentBlockHash(Consensus::LLMQType llmqType, const uint256& quorumHash) const;

std::vector<const CBlockIndex*> GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null<const CBlockIndex*> pindex, size_t maxCount) const;
std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> GetMinedAndActiveCommitmentsUntilBlock(gsl::not_null<const CBlockIndex*> pindex) const;
Expand Down
Loading
Loading