diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index 81fefe7853f9..f93b20eaffda 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) { @@ -30,7 +32,7 @@ bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidati return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-payload"); } - if (cbTx.nVersion == 0 || cbTx.nVersion > CCbTx::CURRENT_VERSION) { + if (cbTx.nVersion == 0 || cbTx.nVersion > CCbTx::CB_V20_VERSION) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version"); } @@ -40,7 +42,12 @@ bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidati if (pindexPrev) { bool fDIP0008Active = pindexPrev->nHeight >= Params().GetConsensus().DIP0008Height; - if (fDIP0008Active && cbTx.nVersion < 2) { + if (fDIP0008Active && cbTx.nVersion < CCbTx::CB_V19_VERSION) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version"); + } + + bool isV20 = llmq::utils::IsV20Active(pindexPrev); + if ((isV20 && cbTx.nVersion < CCbTx::CB_V20_VERSION) || (!isV20 && cbTx.nVersion >= CCbTx::CB_V20_VERSION)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version"); } } @@ -314,8 +321,158 @@ bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPre return true; } +bool CheckCbTxBestChainlock(const CBlock& block, const CBlockIndex* pindex, const llmq::CChainLocksHandler& chainlock_handler, BlockValidationState& state) +{ + if (block.vtx[0]->nType != TRANSACTION_COINBASE) { + return true; + } + + CCbTx cbTx; + if (!GetTxPayload(*block.vtx[0], cbTx)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-payload"); + } + + if (cbTx.nVersion < CCbTx::CB_V20_VERSION) { + return true; + } + + auto best_clsig = chainlock_handler.GetBestChainLock(); + if (best_clsig.getHeight() == pindex->nHeight - 1 && cbTx.bestCLHeightDiff == 0 && cbTx.bestCLSignature == best_clsig.getSig()) { + // matches our best clsig which still hold values for the previous block + return true; + } + + auto prevBlockCoinbaseChainlock = GetNonNullCoinbaseChainlock(pindex); + // If std::optional prevBlockCoinbaseChainlock is empty, then up to the previous block, coinbase Chainlock is null. + if (prevBlockCoinbaseChainlock.has_value()) { + // Previous block Coinbase has a non-null Chainlock: current block's Chainlock must be non-null and at least as new as the previous one + if (!cbTx.bestCLSignature.IsValid()) { + // IsNull() doesn't exist for CBLSSignature: we assume that a non valid BLS sig is null + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-null-clsig"); + } + int prevBlockCoinbaseCLHeight = pindex->nHeight - static_cast(prevBlockCoinbaseChainlock.value().second) - 1; + int curBlockCoinbaseCLHeight = pindex->nHeight - static_cast(cbTx.bestCLHeightDiff) - 1; + if (curBlockCoinbaseCLHeight < prevBlockCoinbaseCLHeight) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-older-clsig"); + } + } + + // IsNull() doesn't exist for CBLSSignature: we assume that a valid BLS sig is non-null + if (cbTx.bestCLSignature.IsValid()) { + int curBlockCoinbaseCLHeight = pindex->nHeight - static_cast(cbTx.bestCLHeightDiff) - 1; + if (best_clsig.getHeight() == curBlockCoinbaseCLHeight && best_clsig.getSig() == cbTx.bestCLSignature) { + // matches our best (but outdated) clsig, no need to verify it again + return true; + } + uint256 curBlockCoinbaseCLBlockHash = pindex->GetAncestor(curBlockCoinbaseCLHeight)->GetBlockHash(); + if (!chainlock_handler.VerifyChainLock(llmq::CChainLockSig(curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, cbTx.bestCLSignature))) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig"); + } + } else if (cbTx.bestCLHeightDiff != 0) { + // Null bestCLSignature is allowed only with bestCLHeightDiff = 0 + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-cldiff"); + } + + return true; +} + +bool CalcCbTxBestChainlock(const llmq::CChainLocksHandler& chainlock_handler, const CBlockIndex* pindexPrev, uint32_t& bestCLHeightDiff, CBLSSignature& bestCLSignature) +{ + auto best_clsig = chainlock_handler.GetBestChainLock(); + if (best_clsig.getHeight() == pindexPrev->nHeight) { + // Our best CL is the newest one possible + bestCLHeightDiff = 0; + bestCLSignature = best_clsig.getSig(); + return true; + } + + auto prevBlockCoinbaseChainlock = GetNonNullCoinbaseChainlock(pindexPrev); + if (prevBlockCoinbaseChainlock.has_value()) { + // Previous block Coinbase contains a non-null CL: We must insert the same sig or a better (newest) one + if (best_clsig.IsNull()) { + // We don't know any CL, therefore inserting the CL of the previous block + bestCLHeightDiff = prevBlockCoinbaseChainlock->second + 1; + bestCLSignature = prevBlockCoinbaseChainlock->first; + return true; + } + + // We check if our best CL is newer than the one from previous block Coinbase + auto curCLHeight = best_clsig.getHeight(); + auto prevCLHeight = static_cast(pindexPrev->nHeight) - prevBlockCoinbaseChainlock->second - 1; + if (curCLHeight < prevCLHeight) { + // Our best CL isn't newer: inserting CL from previous block + bestCLHeightDiff = prevBlockCoinbaseChainlock->second + 1; + bestCLSignature = prevBlockCoinbaseChainlock->first; + } + else { + // Our best CL is newer + bestCLHeightDiff = static_cast(pindexPrev->nHeight) - best_clsig.getHeight(); + bestCLSignature = best_clsig.getSig(); + } + + return true; + } + else { + // Previous block Coinbase has no CL. We can either insert null or any valid CL + if (best_clsig.IsNull()) { + // We don't know any CL, therefore inserting a null CL + bestCLHeightDiff = 0; + bestCLSignature.Reset(); + return false; + } + + // Inserting our best CL + bestCLHeightDiff = static_cast(pindexPrev->nHeight) - best_clsig.getHeight(); + bestCLSignature = chainlock_handler.GetBestChainLock().getSig(); + + return true; + } +} + + std::string CCbTx::ToString() const { - return strprintf("CCbTx(nVersion=%d, nHeight=%d, merkleRootMNList=%s, merkleRootQuorums=%s)", - nVersion, nHeight, merkleRootMNList.ToString(), merkleRootQuorums.ToString()); + return strprintf("CCbTx(nVersion=%d, nHeight=%d, merkleRootMNList=%s, merkleRootQuorums=%s, bestCLHeightDiff=%d, bestCLSig=%s)", + nVersion, nHeight, merkleRootMNList.ToString(), merkleRootQuorums.ToString(), bestCLHeightDiff, bestCLSignature.ToString()); +} + +std::optional GetCoinbaseTx(const CBlockIndex* pindex) +{ + if (pindex == nullptr) { + return std::nullopt; + } + + CBlock block; + if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { + return std::nullopt; + } + + CTransactionRef cbTx = block.vtx[0]; + CCbTx cbTxPayload; + if (!GetTxPayload(*cbTx, cbTxPayload)) { + return std::nullopt; + } + + return cbTxPayload; +} + +std::optional> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex) +{ + auto opt_cbtx = GetCoinbaseTx(pindex); + + if (!opt_cbtx.has_value()) { + return std::nullopt; + } + + CCbTx& cbtx = opt_cbtx.value(); + + if (cbtx.nVersion < CCbTx::CB_V20_VERSION) { + return std::nullopt; + } + + if (!cbtx.bestCLSignature.IsValid()) { + return std::nullopt; + } + + return std::make_pair(cbtx.bestCLSignature, cbtx.bestCLHeightDiff); } diff --git a/src/evo/cbtx.h b/src/evo/cbtx.h index 1b64ef2036a5..f3141464f5fc 100644 --- a/src/evo/cbtx.h +++ b/src/evo/cbtx.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_EVO_CBTX_H #define BITCOIN_EVO_CBTX_H +#include #include #include @@ -16,6 +17,7 @@ class TxValidationState; namespace llmq { class CQuorumBlockProcessor; +class CChainLocksHandler; }// namespace llmq // coinbase transaction @@ -23,19 +25,26 @@ class CCbTx { public: static constexpr auto SPECIALTX_TYPE = TRANSACTION_COINBASE; - static constexpr uint16_t CURRENT_VERSION = 2; + static constexpr uint16_t CB_V19_VERSION = 2; + static constexpr uint16_t CB_V20_VERSION = 3; - uint16_t nVersion{CURRENT_VERSION}; + uint16_t nVersion{CB_V19_VERSION}; int32_t nHeight{0}; uint256 merkleRootMNList; uint256 merkleRootQuorums; + uint32_t bestCLHeightDiff; + CBLSSignature bestCLSignature; SERIALIZE_METHODS(CCbTx, obj) { READWRITE(obj.nVersion, obj.nHeight, obj.merkleRootMNList); - if (obj.nVersion >= 2) { + if (obj.nVersion >= CB_V19_VERSION) { READWRITE(obj.merkleRootQuorums); + if (obj.nVersion >= CB_V20_VERSION) { + READWRITE(COMPACTSIZE(obj.bestCLHeightDiff)); + READWRITE(obj.bestCLSignature); + } } } @@ -48,8 +57,12 @@ class CCbTx obj.pushKV("version", (int)nVersion); obj.pushKV("height", nHeight); obj.pushKV("merkleRootMNList", merkleRootMNList.ToString()); - if (nVersion >= 2) { + if (nVersion >= CB_V19_VERSION) { obj.pushKV("merkleRootQuorums", merkleRootQuorums.ToString()); + if (nVersion >= CB_V20_VERSION) { + obj.pushKV("bestCLHeightDiff", static_cast(bestCLHeightDiff)); + obj.pushKV("bestCLSignature", bestCLSignature.ToString()); + } } } }; @@ -60,4 +73,9 @@ bool CheckCbTxMerkleRoots(const CBlock& block, const CBlockIndex* pindex, const bool CalcCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, BlockValidationState& state, const CCoinsViewCache& view); bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev, const llmq::CQuorumBlockProcessor& quorum_block_processor, uint256& merkleRootRet, BlockValidationState& state); +bool CheckCbTxBestChainlock(const CBlock& block, const CBlockIndex* pindexPrev, const llmq::CChainLocksHandler& chainlock_handler, BlockValidationState& state); +bool CalcCbTxBestChainlock(const llmq::CChainLocksHandler& chainlock_handler, const CBlockIndex* pindexPrev, uint32_t& bestCLHeightDiff, CBLSSignature& bestCLSignature); + +std::optional GetCoinbaseTx(const CBlockIndex* pindex); +std::optional> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex); #endif // BITCOIN_EVO_CBTX_H diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index e3837c50f0fe..429e9db2d506 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -98,7 +98,7 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) return false; } -bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, +bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler, BlockValidationState& state, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots) { AssertLockHeld(cs_main); @@ -108,6 +108,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll static int64_t nTimeQuorum = 0; static int64_t nTimeDMN = 0; static int64_t nTimeMerkle = 0; + static int64_t nTimeCbTxCL = 0; int64_t nTime1 = GetTimeMicros(); @@ -157,6 +158,15 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll int64_t nTime5 = GetTimeMicros(); nTimeMerkle += nTime5 - nTime4; LogPrint(BCLog::BENCHMARK, " - CheckCbTxMerkleRoots: %.2fms [%.2fs]\n", 0.001 * (nTime5 - nTime4), nTimeMerkle * 0.000001); + + if (fCheckCbTxMerleRoots && !CheckCbTxBestChainlock(block, pindex, chainlock_handler, state)) { + // pass the state returned by the function above + return false; + } + + int64_t nTime6 = GetTimeMicros(); + nTimeCbTxCL += nTime6 - nTime5; + LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5), nTimeCbTxCL * 0.000001); } catch (const std::exception& e) { LogPrintf("%s -- failed: %s\n", __func__, e.what()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-procspectxsinblock"); diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index ed8d4b5c3140..011bc4c5c7f8 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -16,12 +16,13 @@ class CCoinsViewCache; class TxValidationState; namespace llmq { class CQuorumBlockProcessor; +class CChainLocksHandler; } // namespace llmq extern CCriticalSection cs_main; bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state, const CCoinsViewCache& view, bool check_sigs) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, +bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler, BlockValidationState& state, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots) EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/llmq/chainlocks.cpp b/src/llmq/chainlocks.cpp index 7a73939daf0b..370a1ffb36bf 100644 --- a/src/llmq/chainlocks.cpp +++ b/src/llmq/chainlocks.cpp @@ -24,12 +24,13 @@ namespace llmq { std::unique_ptr chainLocksHandler; -CChainLocksHandler::CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, const std::unique_ptr& mn_sync) : +CChainLocksHandler::CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, CQuorumManager& _qman, const std::unique_ptr& mn_sync) : connman(_connman), mempool(_mempool), spork_manager(sporkManager), sigman(_sigman), shareman(_shareman), + qman(_qman), m_mn_sync(mn_sync), scheduler(std::make_unique()), scheduler_thread(std::make_unique([&] { TraceThread("cl-schdlr", [&] { scheduler->serviceQueue(); }); })) @@ -121,8 +122,7 @@ void CChainLocksHandler::ProcessNewChainLock(const NodeId from, const llmq::CCha } } - const uint256 requestId = ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, clsig.getHeight())); - if (!llmq::CSigningManager::VerifyRecoveredSig(Params().GetConsensus().llmqTypeChainLocks, *llmq::quorumManager, clsig.getHeight(), requestId, clsig.getBlockHash(), clsig.getSig())) { + if (!VerifyChainLock(clsig)) { LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- invalid CLSIG (%s), peer=%d\n", __func__, clsig.ToString(), from); if (from != -1) { Misbehaving(from, 10); @@ -557,6 +557,13 @@ bool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash) con return InternalHasChainLock(nHeight, blockHash); } +bool CChainLocksHandler::VerifyChainLock(const CChainLockSig& clsig) const +{ + const auto llmqType = Params().GetConsensus().llmqTypeChainLocks; + const uint256 nRequestId = ::SerializeHash(std::make_pair(llmq::CLSIG_REQUESTID_PREFIX, clsig.getHeight())); + return llmq::CSigningManager::VerifyRecoveredSig(llmqType, qman, clsig.getHeight(), nRequestId, clsig.getBlockHash(), clsig.getSig()); +} + bool CChainLocksHandler::InternalHasChainLock(int nHeight, const uint256& blockHash) const { AssertLockHeld(cs); diff --git a/src/llmq/chainlocks.h b/src/llmq/chainlocks.h index 61d4c92ad9b1..27687da2f51e 100644 --- a/src/llmq/chainlocks.h +++ b/src/llmq/chainlocks.h @@ -46,6 +46,7 @@ class CChainLocksHandler : public CRecoveredSigsListener CSporkManager& spork_manager; CSigningManager& sigman; CSigSharesManager& shareman; + CQuorumManager& qman; const std::unique_ptr& m_mn_sync; std::unique_ptr scheduler; std::unique_ptr scheduler_thread; @@ -79,7 +80,7 @@ class CChainLocksHandler : public CRecoveredSigsListener int64_t lastCleanupTime GUARDED_BY(cs) {0}; public: - explicit CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, const std::unique_ptr& mn_sync); + explicit CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, CQuorumManager& _qman, const std::unique_ptr& mn_sync); ~CChainLocksHandler(); void Start(); @@ -103,6 +104,7 @@ class CChainLocksHandler : public CRecoveredSigsListener bool HasChainLock(int nHeight, const uint256& blockHash) const LOCKS_EXCLUDED(cs); bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const LOCKS_EXCLUDED(cs); + bool VerifyChainLock(const CChainLockSig& clsig) const; bool IsTxSafeForMining(const CInstantSendManager& isman, const uint256& txid) const LOCKS_EXCLUDED(cs); diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 27f92e43fae5..b188aac29470 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -47,7 +47,7 @@ void LLMQContext::Create(CEvoDB& evoDb, CTxMemPool& mempool, CConnman& connman, llmq::quorumManager = std::make_unique(evoDb, connman, *bls_worker, *llmq::quorumBlockProcessor, *qdkgsman, ::masternodeSync); sigman = std::make_unique(connman, *llmq::quorumManager, unitTests, fWipe); shareman = std::make_unique(connman, *llmq::quorumManager, *sigman); - llmq::chainLocksHandler = std::make_unique(mempool, connman, sporkManager, *sigman, *shareman, ::masternodeSync); + llmq::chainLocksHandler = std::make_unique(mempool, connman, sporkManager, *sigman, *shareman, *llmq::quorumManager, ::masternodeSync); llmq::quorumInstantSendManager = std::make_unique(mempool, connman, sporkManager, *llmq::quorumManager, *sigman, *shareman, *llmq::chainLocksHandler, ::masternodeSync, unitTests, fWipe); // NOTE: we use this only to wipe the old db, do NOT use it for anything else diff --git a/src/miner.cpp b/src/miner.cpp index 9673c8a2bc26..c5b5e34c23f0 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -197,8 +197,10 @@ std::unique_ptr BlockAssembler::CreateNewBlock(CChainState& chai CCbTx cbTx; - if (fDIP0008Active_context) { - cbTx.nVersion = 2; + if (llmq::utils::IsV20Active(pindexPrev)) { + cbTx.nVersion = CCbTx::CB_V20_VERSION; + } else if (fDIP0008Active_context) { + cbTx.nVersion = CCbTx::CB_V19_VERSION; } else { cbTx.nVersion = 1; } @@ -213,6 +215,15 @@ std::unique_ptr BlockAssembler::CreateNewBlock(CChainState& chai if (!CalcCbTxMerkleRootQuorums(*pblock, pindexPrev, quorum_block_processor, cbTx.merkleRootQuorums, state)) { throw std::runtime_error(strprintf("%s: CalcCbTxMerkleRootQuorums failed: %s", __func__, FormatStateMessage(state))); } + if (llmq::utils::IsV20Active(pindexPrev)) { + if (CalcCbTxBestChainlock(m_clhandler, pindexPrev, cbTx.bestCLHeightDiff, cbTx.bestCLSignature)) { + LogPrintf("CreateNewBlock() h[%d] CbTx bestCLHeightDiff[%d] CLSig[%s]\n", nHeight, cbTx.bestCLHeightDiff, cbTx.bestCLSignature.ToString()); + } + else { + // not an error + LogPrintf("CreateNewBlock() h[%d] CbTx failed to find best CL. Inserting null CL\n", nHeight); + } + } } SetTxPayload(coinbaseTx, cbTx); diff --git a/src/rpc/quorums.cpp b/src/rpc/quorums.cpp index 28bd9efa8716..c4ea9ed76902 100644 --- a/src/rpc/quorums.cpp +++ b/src/rpc/quorums.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -873,8 +874,8 @@ static UniValue verifychainlock(const JSONRPCRequest& request) const uint256 nBlockHash(ParseHashV(request.params[0], "blockHash")); - CBLSSignature chainLockSig; - if (!chainLockSig.SetHexStr(request.params[1].get_str())) { + CBLSSignature sig; + if (!sig.SetHexStr(request.params[1].get_str())) { throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format"); } @@ -891,9 +892,7 @@ static UniValue verifychainlock(const JSONRPCRequest& request) LLMQContext& llmq_ctx = EnsureLLMQContext(request.context); - const auto llmqType = Params().GetConsensus().llmqTypeChainLocks; - const uint256 nRequestId = ::SerializeHash(std::make_pair(llmq::CLSIG_REQUESTID_PREFIX, nBlockHeight)); - return llmq::CSigningManager::VerifyRecoveredSig(llmqType, *llmq_ctx.qman, nBlockHeight, nRequestId, nBlockHash, chainLockSig); + return llmq_ctx.clhandler->VerifyChainLock(llmq::CChainLockSig(nBlockHeight, nBlockHash, sig)); } static void verifyislock_help(const JSONRPCRequest& request) diff --git a/src/validation.cpp b/src/validation.cpp index f48f81cb9c2b..7b0c7e410988 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2258,7 +2258,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, bool fDIP0001Active_context = pindex->nHeight >= Params().GetConsensus().DIP0001Height; // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing - if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, state, view, fJustCheck, fScriptChecks)) { + if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, *m_clhandler, state, view, fJustCheck, fScriptChecks)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), FormatStateMessage(state)); } @@ -4888,7 +4888,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing BlockValidationState state; - if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, state, inputs, false /*fJustCheck*/, false /*fScriptChecks*/)) { + if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, *m_clhandler, state, inputs, false /*fJustCheck*/, false /*fScriptChecks*/)) { return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), FormatStateMessage(state)); } diff --git a/test/functional/feature_llmq_chainlocks.py b/test/functional/feature_llmq_chainlocks.py index b3b1fc822a72..e6428da555b5 100755 --- a/test/functional/feature_llmq_chainlocks.py +++ b/test/functional/feature_llmq_chainlocks.py @@ -13,7 +13,7 @@ import time from test_framework.test_framework import DashTestFramework -from test_framework.util import force_finish_mnsync +from test_framework.util import force_finish_mnsync, assert_equal class LLMQChainLocksTest(DashTestFramework): @@ -31,6 +31,14 @@ def run_test(self): self.activate_dip8() + self.test_coinbase_best_cl(self.nodes[0], expected_cl_in_cb=False) + + self.activate_v20(expected_activation_height=904) + self.log.info("Activated v20 at height:" + str(self.nodes[0].getblockcount())) + + # no quorums, no CLs - null CL in CbTx + self.test_coinbase_best_cl(self.nodes[0], expected_cl_in_cb=True, expected_null_cl=True) + self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", 0) self.wait_for_sporks_same() @@ -41,11 +49,13 @@ def run_test(self): self.log.info("Mine single block, wait for chainlock") self.nodes[0].generate(1) self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + self.test_coinbase_best_cl(self.nodes[0]) self.log.info("Mine many blocks, wait for chainlock") self.nodes[0].generate(20) # We need more time here due to 20 blocks being generated at once self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash(), timeout=30) + self.test_coinbase_best_cl(self.nodes[0]) self.log.info("Assert that all blocks up until the tip are chainlocked") for h in range(1, self.nodes[0].getblockcount()): @@ -58,10 +68,12 @@ def run_test(self): node0_tip = self.nodes[0].getbestblockhash() self.nodes[1].generatetoaddress(5, node0_mining_addr) self.wait_for_chainlocked_block(self.nodes[1], self.nodes[1].getbestblockhash()) + self.test_coinbase_best_cl(self.nodes[0]) assert self.nodes[0].getbestblockhash() == node0_tip self.reconnect_isolated_node(0, 1) self.nodes[1].generatetoaddress(1, node0_mining_addr) self.wait_for_chainlocked_block_all_nodes(self.nodes[1].getbestblockhash()) + self.test_coinbase_best_cl(self.nodes[0]) self.log.info("Isolate node, mine on both parts of the network, and reconnect") self.isolate_node(0) @@ -73,6 +85,7 @@ def run_test(self): self.reconnect_isolated_node(0, 1) self.nodes[1].generatetoaddress(1, node0_mining_addr) self.wait_for_chainlocked_block_all_nodes(self.nodes[1].getbestblockhash()) + self.test_coinbase_best_cl(self.nodes[0]) assert self.nodes[0].getblock(self.nodes[0].getbestblockhash())["previousblockhash"] == good_tip assert self.nodes[1].getblock(self.nodes[1].getbestblockhash())["previousblockhash"] == good_tip @@ -107,6 +120,7 @@ def run_test(self): good_fork = good_tip good_tip = self.nodes[1].generatetoaddress(1, node0_mining_addr)[-1] # this should mark bad_tip as conflicting self.wait_for_chainlocked_block_all_nodes(good_tip) + self.test_coinbase_best_cl(self.nodes[0]) assert self.nodes[0].getbestblockhash() == good_tip found = False for tip in self.nodes[0].getchaintips(2): @@ -173,6 +187,29 @@ def create_chained_txs(self, node, amount): return [txid, rawtxid] + def test_coinbase_best_cl(self, node, expected_cl_in_cb=True, expected_null_cl=False): + block_hash = node.getbestblockhash() + block = node.getblock(block_hash, 2) + cbtx = block["cbTx"] + assert_equal(int(cbtx["version"]) > 2, expected_cl_in_cb) + if expected_cl_in_cb: + cb_height = int(cbtx["height"]) + best_cl_height_diff = int(cbtx["bestCLHeightDiff"]) + best_cl_signature = cbtx["bestCLSignature"] + assert_equal(expected_null_cl, int(best_cl_signature, 16) == 0) + if expected_null_cl: + # Null bestCLSignature is allowed. + # bestCLHeightDiff must be 0 if bestCLSignature is null + assert_equal(best_cl_height_diff, 0) + # Returning as no more tests can be conducted + return + best_cl_height = cb_height - best_cl_height_diff - 1 + target_block_hash = node.getblockhash(best_cl_height) + # Verify CL signature + assert node.verifychainlock(target_block_hash, best_cl_signature, best_cl_height) + else: + assert "bestCLHeightDiff" not in cbtx and "bestCLSignature" not in cbtx + if __name__ == '__main__': LLMQChainLocksTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 055b9c44e181..fbe32a8b6607 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1046,9 +1046,9 @@ def __repr__(self): class CCbTx: - __slots__ = ("version", "height", "merkleRootMNList", "merkleRootQuorums") + __slots__ = ("version", "height", "merkleRootMNList", "merkleRootQuorums", "bestCLHeightDiff", "bestCLSignature") - def __init__(self, version=None, height=None, merkleRootMNList=None, merkleRootQuorums=None): + def __init__(self, version=None, height=None, merkleRootMNList=None, merkleRootQuorums=None, bestCLHeightDiff=None, bestCLSignature=None): self.set_null() if version is not None: self.version = version @@ -1058,11 +1058,17 @@ def __init__(self, version=None, height=None, merkleRootMNList=None, merkleRootQ self.merkleRootMNList = merkleRootMNList if merkleRootQuorums is not None: self.merkleRootQuorums = merkleRootQuorums + if bestCLHeightDiff is not None: + self.bestCLHeightDiff = bestCLHeightDiff + if bestCLSignature is not None: + self.bestCLSignature = bestCLSignature def set_null(self): self.version = 0 self.height = 0 self.merkleRootMNList = None + self.bestCLHeightDiff = 0 + self.bestCLSignature = b'\x00' * 96 def deserialize(self, f): self.version = struct.unpack("= 2: self.merkleRootQuorums = deser_uint256(f) + if self.version >= 3: + self.bestCLHeightDiff = deser_compact_size(f) + self.bestCLSignature = f.read(96) + def serialize(self): r = b"" @@ -1078,6 +1088,9 @@ def serialize(self): r += ser_uint256(self.merkleRootMNList) if self.version >= 2: r += ser_uint256(self.merkleRootQuorums) + if self.version >= 3: + r += ser_compact_size(self.bestCLHeightDiff) + r += self.bestCLSignature return r diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 8741b59f0050..ab8a8d3d14e2 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1072,6 +1072,9 @@ def activate_dip0024(self, expected_activation_height=None): def activate_v19(self, expected_activation_height=None): self.activate_by_name('v19', expected_activation_height) + def activate_v20(self, expected_activation_height=None): + self.activate_by_name('v20', expected_activation_height) + def set_dash_llmq_test_params(self, llmq_size, llmq_threshold): self.llmq_size = llmq_size self.llmq_threshold = llmq_threshold