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
165 changes: 161 additions & 4 deletions src/evo/cbtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <evo/cbtx.h>
#include <evo/deterministicmns.h>
#include <llmq/blockprocessor.h>
#include <llmq/chainlocks.h>
#include <llmq/commitment.h>
#include <llmq/utils.h>
#include <evo/simplifiedmns.h>
Expand All @@ -14,6 +15,7 @@
#include <chain.h>
#include <chainparams.h>
#include <consensus/merkle.h>
#include <validation.h>

bool CheckCbTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state)
{
Expand All @@ -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) {
Comment thread
ogabrielides marked this conversation as resolved.
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
}

Expand All @@ -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");
}
}
Expand Down Expand Up @@ -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;
}
Comment thread
ogabrielides marked this conversation as resolved.

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<int>(prevBlockCoinbaseChainlock.value().second) - 1;
int curBlockCoinbaseCLHeight = pindex->nHeight - static_cast<int>(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<int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<CCbTx> 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<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex)
{
auto opt_cbtx = GetCoinbaseTx(pindex);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we should probably storecbtx.bestCLSignature, cbtx.bestCLHeightDiff in state somewhere so we don't have to load blocks from disk

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Do you mean to cache {cbtx.bestCLSignature, cbtx.bestCLHeightDiff} per block ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Implemented in #5347 by the way


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);
}
26 changes: 22 additions & 4 deletions src/evo/cbtx.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef BITCOIN_EVO_CBTX_H
#define BITCOIN_EVO_CBTX_H

#include <bls/bls.h>
#include <primitives/transaction.h>
#include <univalue.h>

Expand All @@ -16,26 +17,34 @@ class TxValidationState;

namespace llmq {
class CQuorumBlockProcessor;
class CChainLocksHandler;
}// namespace llmq

// coinbase transaction
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);
}
}
}

Expand All @@ -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<int>(bestCLHeightDiff));
obj.pushKV("bestCLSignature", bestCLSignature.ToString());
}
}
}
};
Expand All @@ -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<CCbTx> GetCoinbaseTx(const CBlockIndex* pindex);
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
#endif // BITCOIN_EVO_CBTX_H
12 changes: 11 additions & 1 deletion src/evo/specialtxman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();

Expand Down Expand Up @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion src/evo/specialtxman.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
13 changes: 10 additions & 3 deletions src/llmq/chainlocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ namespace llmq
{
std::unique_ptr<CChainLocksHandler> chainLocksHandler;

CChainLocksHandler::CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, const std::unique_ptr<CMasternodeSync>& mn_sync) :
CChainLocksHandler::CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, CQuorumManager& _qman, const std::unique_ptr<CMasternodeSync>& mn_sync) :
connman(_connman),
mempool(_mempool),
spork_manager(sporkManager),
sigman(_sigman),
shareman(_shareman),
qman(_qman),
m_mn_sync(mn_sync),
scheduler(std::make_unique<CScheduler>()),
scheduler_thread(std::make_unique<std::thread>([&] { TraceThread("cl-schdlr", [&] { scheduler->serviceQueue(); }); }))
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/llmq/chainlocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class CChainLocksHandler : public CRecoveredSigsListener
CSporkManager& spork_manager;
CSigningManager& sigman;
CSigSharesManager& shareman;
CQuorumManager& qman;
const std::unique_ptr<CMasternodeSync>& m_mn_sync;
std::unique_ptr<CScheduler> scheduler;
std::unique_ptr<std::thread> scheduler_thread;
Expand Down Expand Up @@ -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<CMasternodeSync>& mn_sync);
explicit CChainLocksHandler(CTxMemPool& _mempool, CConnman& _connman, CSporkManager& sporkManager, CSigningManager& _sigman, CSigSharesManager& _shareman, CQuorumManager& _qman, const std::unique_ptr<CMasternodeSync>& mn_sync);
~CChainLocksHandler();

void Start();
Expand All @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/llmq/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void LLMQContext::Create(CEvoDB& evoDb, CTxMemPool& mempool, CConnman& connman,
llmq::quorumManager = std::make_unique<llmq::CQuorumManager>(evoDb, connman, *bls_worker, *llmq::quorumBlockProcessor, *qdkgsman, ::masternodeSync);
sigman = std::make_unique<llmq::CSigningManager>(connman, *llmq::quorumManager, unitTests, fWipe);
shareman = std::make_unique<llmq::CSigSharesManager>(connman, *llmq::quorumManager, *sigman);
llmq::chainLocksHandler = std::make_unique<llmq::CChainLocksHandler>(mempool, connman, sporkManager, *sigman, *shareman, ::masternodeSync);
llmq::chainLocksHandler = std::make_unique<llmq::CChainLocksHandler>(mempool, connman, sporkManager, *sigman, *shareman, *llmq::quorumManager, ::masternodeSync);
llmq::quorumInstantSendManager = std::make_unique<llmq::CInstantSendManager>(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
Expand Down
Loading