Skip to content

feat: implement automatic detection of clsigs in blocks and process them #6236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
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
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ BITCOIN_TESTS =\
test/lcg.h \
test/limitedmap_tests.cpp \
test/llmq_dkg_tests.cpp \
test/llmq_chainlock_tests.cpp \
test/logging_tests.cpp \
test/dbwrapper_tests.cpp \
test/validation_tests.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test_util.include
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ TEST_UTIL_H = \
test/util/chainstate.h \
test/util/json.h \
test/util/index.h \
test/util/llmq_tests.h \
test/util/logging.h \
test/util/mining.h \
test/util/net.h \
Expand Down
31 changes: 27 additions & 4 deletions src/evo/cbtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,12 @@ std::string CCbTx::ToString() const
creditPoolBalance / COIN, creditPoolBalance % COIN);
}

std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex)
std::string CCoinbaseChainlock::ToString() const
{
return strprintf("CCoinbaseChainlock(signature=%s, heightDiff=%d)", signature.ToString(), heightDiff);
}

std::optional<CCoinbaseChainlock> GetCoinbaseChainlock(const CBlock& block, const CBlockIndex* pindex)
{
if (pindex == nullptr) {
return std::nullopt;
Expand All @@ -234,8 +239,7 @@ std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(co
return std::nullopt;
}

CBlock block;
if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
if (block.vtx.empty()) {
return std::nullopt;
}

Expand All @@ -250,5 +254,24 @@ std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(co
return std::nullopt;
}

return std::make_pair(opt_cbtx->bestCLSignature, opt_cbtx->bestCLHeightDiff);
return CCoinbaseChainlock(opt_cbtx->bestCLSignature, opt_cbtx->bestCLHeightDiff);
}

std::optional<CCoinbaseChainlock> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex)
{
if (pindex == nullptr) {
return std::nullopt;
}

// There's no CL in CbTx before v20 activation
if (!DeploymentActiveAt(*pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) {
return std::nullopt;
}

CBlock block;
if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
return std::nullopt;
}

return GetCoinbaseChainlock(block, pindex);
}
21 changes: 20 additions & 1 deletion src/evo/cbtx.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPre
const llmq::CQuorumBlockProcessor& quorum_block_processor, uint256& merkleRootRet,
BlockValidationState& state);

std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
class CCoinbaseChainlock
{
public:
CBLSSignature signature;
uint32_t heightDiff{0};

CCoinbaseChainlock() = default;
CCoinbaseChainlock(const CBLSSignature& sig, uint32_t diff) : signature(sig), heightDiff(diff) {}

[[nodiscard]] bool IsNull() const { return !signature.IsValid(); }
[[nodiscard]] std::string ToString() const;

SERIALIZE_METHODS(CCoinbaseChainlock, obj)
{
READWRITE(obj.signature, obj.heightDiff);
}
};

std::optional<CCoinbaseChainlock> GetCoinbaseChainlock(const CBlock& block, const CBlockIndex* pindex);
std::optional<CCoinbaseChainlock> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);

#endif // BITCOIN_EVO_CBTX_H
2 changes: 1 addition & 1 deletion src/evo/simplifiedmns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager&
const auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex);
CBLSSignature sig;
if (cbcl.has_value()) {
sig = cbcl.value().first;
sig = cbcl.value().signature;
}
// Get the range of indexes (values) for the current key and merge them into a single std::set
const auto [it_begin, it_end] = workBaseBlockIndexMap.equal_range(it->first);
Expand Down
12 changes: 6 additions & 6 deletions src/evo/specialtxman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex,

static Mutex cached_mutex;
static const CBlockIndex* cached_pindex GUARDED_BY(cached_mutex){nullptr};
static std::optional<std::pair<CBLSSignature, uint32_t>> cached_chainlock GUARDED_BY(cached_mutex){std::nullopt};
static std::optional<CCoinbaseChainlock> cached_chainlock GUARDED_BY(cached_mutex){std::nullopt};

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
LOCK(cached_mutex);
cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
cached_chainlock = CCoinbaseChainlock(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
cached_pindex = pindex;
return true;
}

std::optional<std::pair<CBLSSignature, uint32_t>> prevBlockCoinbaseChainlock{std::nullopt};
std::optional<CCoinbaseChainlock> prevBlockCoinbaseChainlock{std::nullopt};
if (LOCK(cached_mutex); cached_pindex == pindex->pprev) {
prevBlockCoinbaseChainlock = cached_chainlock;
}
Expand All @@ -61,7 +61,7 @@ static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex,
// 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");
}
if (cbTx.bestCLHeightDiff > prevBlockCoinbaseChainlock.value().second + 1) {
if (cbTx.bestCLHeightDiff > prevBlockCoinbaseChainlock.value().heightDiff + 1) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-older-clsig");
}
}
Expand All @@ -72,7 +72,7 @@ static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex,
if (best_clsig.getHeight() == curBlockCoinbaseCLHeight && best_clsig.getSig() == cbTx.bestCLSignature) {
// matches our best (but outdated) clsig, no need to verify it again
LOCK(cached_mutex);
cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
cached_chainlock = CCoinbaseChainlock(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
cached_pindex = pindex;
return true;
}
Expand All @@ -83,7 +83,7 @@ static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex,
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig");
}
LOCK(cached_mutex);
cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
cached_chainlock = CCoinbaseChainlock(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
cached_pindex = pindex;
} else if (cbTx.bestCLHeightDiff != 0) {
// Null bestCLSignature is allowed only with bestCLHeightDiff = 0
Expand Down
62 changes: 48 additions & 14 deletions src/llmq/chainlocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <chain.h>
#include <chainparams.h>
#include <consensus/validation.h>
#include <evo/cbtx.h>
#include <masternode/sync.h>
#include <node/blockstorage.h>
#include <node/interface_ui.h>
Expand Down Expand Up @@ -363,27 +364,60 @@ void CChainLocksHandler::BlockConnected(const std::shared_ptr<const CBlock>& pbl
// We need this information later when we try to sign a new tip, so that we can determine if all included TXs are
// safe.

LOCK(cs);
{
LOCK(cs);

auto it = blockTxs.find(pindex->GetBlockHash());
if (it == blockTxs.end()) {
// we must create this entry even if there are no lockable transactions in the block, so that TrySignChainTip later knows about this block
it = blockTxs
.emplace(pindex->GetBlockHash(), std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>())
.first;
}
auto& txids = *it->second;

int64_t curTime = GetTime<std::chrono::seconds>().count();

for (const auto& tx : pblock->vtx) {
if (tx->IsCoinBase() || tx->vin.empty()) {
continue;
}

auto it = blockTxs.find(pindex->GetBlockHash());
if (it == blockTxs.end()) {
// we must create this entry even if there are no lockable transactions in the block, so that TrySignChainTip
// later knows about this block
it = blockTxs.emplace(pindex->GetBlockHash(), std::make_shared<std::unordered_set<uint256, StaticSaltedHasher>>()).first;
txids.emplace(tx->GetHash());
txFirstSeenTime.emplace(tx->GetHash(), curTime);
}
}
auto& txids = *it->second;

int64_t curTime = GetTime<std::chrono::seconds>().count();
// Check if coinbase transaction contains a chainlock signature
auto opt_chainlock = GetCoinbaseChainlock(*pblock, pindex);
if (opt_chainlock.has_value()) {
const auto& coinbase_cl = *opt_chainlock;
int32_t clsig_height = pindex->nHeight - coinbase_cl.heightDiff;

for (const auto& tx : pblock->vtx) {
if (tx->IsCoinBase() || tx->vin.empty()) {
continue;
// Validate chainlock height is reasonable
if (clsig_height < 0 || clsig_height > pindex->nHeight) {
LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- Invalid chainlock height %d from coinbase (block height %d, height diff %d)\n",
__func__, clsig_height, pindex->nHeight, coinbase_cl.heightDiff);
return;
}

txids.emplace(tx->GetHash());
txFirstSeenTime.emplace(tx->GetHash(), curTime);
}
if (clsig_height > WITH_LOCK(cs, return bestChainLock.getHeight())) {
// Get the ancestor block for the chainlock
const CBlockIndex* pindexAncestor = pindex->GetAncestor(uint32_t(clsig_height));
if (!pindexAncestor) {
LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- Cannot find ancestor block at height %d for chainlock\n",
__func__, clsig_height);
return;
}

auto clsig = CChainLockSig(uint32_t(clsig_height), pindexAncestor->GetBlockHash(), coinbase_cl.signature);
auto result = ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig));
if (result.m_error.has_value()) {
LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- Failed to process chainlock from coinbase: %s\n",
__func__, result.m_error->message);
}
}
}
}

void CChainLocksHandler::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, gsl::not_null<const CBlockIndex*> pindexDisconnected)
Expand Down
6 changes: 3 additions & 3 deletions src/llmq/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <chainparams.h>
#include <deploymentstatus.h>
#include <evo/cbtx.h>
#include <evo/deterministicmns.h>
#include <evo/evodb.h>
#include <masternode/meta.h>
Expand All @@ -32,7 +33,6 @@ using CQuorumCPtr = std::shared_ptr<const CQuorum>;
/**
* Forward declarations
*/
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);

static bool IsV19Active(gsl::not_null<const CBlockIndex*> pindexPrev)
{
Expand Down Expand Up @@ -92,8 +92,8 @@ static uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, gsl::not
auto cbcl = GetNonNullCoinbaseChainlock(pWorkBlockIndex);
if (cbcl.has_value()) {
// We have a non-null CL signature: calculate modifier using this CL signature
auto& [bestCLSignature, bestCLHeightDiff] = cbcl.value();
return ::SerializeHash(std::make_tuple(llmqParams.type, pWorkBlockIndex->nHeight, bestCLSignature));
const auto& coinbase_cl = cbcl.value();
return ::SerializeHash(std::make_tuple(llmqParams.type, pWorkBlockIndex->nHeight, coinbase_cl.signature));
}
// No non-null CL signature found in coinbase: calculate modifier using block hash only
return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
Expand Down
10 changes: 5 additions & 5 deletions src/node/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,18 @@ static bool CalcCbTxBestChainlock(const llmq::CChainLocksHandler& chainlock_hand
// 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;
bestCLHeightDiff = prevBlockCoinbaseChainlock->heightDiff + 1;
bestCLSignature = prevBlockCoinbaseChainlock->signature;
return true;
}

// We check if our best CL is newer than the one from previous block Coinbase
int curCLHeight = best_clsig.getHeight();
int prevCLHeight = pindexPrev->nHeight - static_cast<int>(prevBlockCoinbaseChainlock->second) - 1;
int prevCLHeight = pindexPrev->nHeight - static_cast<int>(prevBlockCoinbaseChainlock->heightDiff) - 1;
if (curCLHeight < prevCLHeight) {
// Our best CL isn't newer: inserting CL from previous block
bestCLHeightDiff = prevBlockCoinbaseChainlock->second + 1;
bestCLSignature = prevBlockCoinbaseChainlock->first;
bestCLHeightDiff = prevBlockCoinbaseChainlock->heightDiff + 1;
bestCLSignature = prevBlockCoinbaseChainlock->signature;
}
else {
// Our best CL is newer
Expand Down
2 changes: 1 addition & 1 deletion src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ static RPCHelpMan getassetunlockstatuses()
}
// If no CL info is available, try to use CbTx CL information
if (const auto cbtx_best_cl = GetNonNullCoinbaseChainlock(pTipBlockIndex)) {
return pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->second - 1);
return pTipBlockIndex->GetAncestor(pTipBlockIndex->nHeight - cbtx_best_cl->heightDiff - 1);
}
// no CL info, no CbTx CL
return nullptr;
Expand Down
Loading
Loading