Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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