Skip to content
Draft
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ BITCOIN_TESTS =\
test/dynamic_activation_thresholds_tests.cpp \
test/evo_assetlocks_tests.cpp \
test/evo_cbtx_tests.cpp \
test/evo_db_tests.cpp \
test/evo_deterministicmns_tests.cpp \
test/evo_islock_tests.cpp \
test/evo_mnhf_tests.cpp \
Expand Down
13 changes: 13 additions & 0 deletions src/active/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <llmq/quorums.h>
#include <llmq/quorumsman.h>
#include <llmq/signing_shares.h>
#include <logging.h>
#include <masternode/sync.h>
#include <util/check.h>
#include <validation.h>
Expand All @@ -35,6 +36,7 @@ ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman
const CBLSSecretKey& operator_sk, const util::DbWrapperParams& db_params, bool quorums_watch) :
llmq::QuorumRole{qman},
m_bls_worker{bls_worker},
m_chainman{chainman},
m_quorums_watch{quorums_watch},
nodeman{std::make_unique<CActiveMasternodeManager>(connman, dmnman, operator_sk)},
dkgdbgman{std::make_unique<llmq::CDKGDebugManager>(dmnman, qsnapman, chainman)},
Expand Down Expand Up @@ -94,6 +96,17 @@ void ActiveContext::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIn
return;

nodeman->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);

if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
if (!m_snapshot_duty_blocked.exchange(true)) {
LogPrintf("Masternode DKG participation and quorum signing are disabled until snapshot background validation completes\n");
}
return;
}
if (m_snapshot_duty_blocked.exchange(false)) {
LogPrintf("Snapshot background validation completed; masternode DKG participation and quorum signing are enabled\n");
}

ehf_sighandler->UpdatedBlockTip(pindexNew);
gov_signer->UpdatedBlockTip(pindexNew);
qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload);
Expand Down
3 changes: 3 additions & 0 deletions src/active/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <gsl/pointers.h>
#include <span.h>

#include <atomic>
#include <memory>

class CActiveMasternodeManager;
Expand Down Expand Up @@ -49,7 +50,9 @@ struct DbWrapperParams;
struct ActiveContext final : public llmq::QuorumRole, public CValidationInterface {
private:
CBLSWorker& m_bls_worker;
ChainstateManager& m_chainman;
const bool m_quorums_watch{false};
std::atomic_bool m_snapshot_duty_blocked{false};

public:
ActiveContext() = delete;
Expand Down
23 changes: 23 additions & 0 deletions src/active/dkgsessionhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <deploymentstatus.h>
#include <logging.h>
#include <util/time.h>
#include <validation.h>

namespace llmq {
ActiveDKGSessionHandler::ActiveDKGSessionHandler(
Expand Down Expand Up @@ -41,6 +42,8 @@ ActiveDKGSessionHandler::~ActiveDKGSessionHandler() = default;

void ActiveDKGSessionHandler::UpdatedBlockTip(const CBlockIndex* pindexNew)
{
if (m_chainman.IsSnapshotActiveAndUnvalidated()) return;

//AssertLockNotHeld(cs_main);
//Indexed quorums (greater than 0) are enabled with Quorum Rotation
if (quorumIndex > 0 && !IsQuorumRotationEnabled(params, pindexNew)) {
Expand Down Expand Up @@ -76,6 +79,10 @@ std::pair<QuorumPhase, uint256> ActiveDKGSessionHandler::GetPhaseAndQuorumHash()

bool ActiveDKGSessionHandler::InitNewQuorum(gsl::not_null<const CBlockIndex*> pQuorumBaseBlockIndex)
{
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
LogPrint(BCLog::LLMQ_DKG, "%s -- refusing DKG participation while snapshot background validation is incomplete\n", __func__);
return false;
}
if (!DeploymentDIP0003Enforced(pQuorumBaseBlockIndex->nHeight, Params().GetConsensus())) {
return false;
}
Expand All @@ -100,6 +107,10 @@ void ActiveDKGSessionHandler::WaitForNextPhase(std::optional<QuorumPhase> curPha
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, curPhase.has_value() ? std23::to_underlying(*curPhase) : -1, std23::to_underlying(nextPhase));

while (true) {
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
}
if (stopRequested) {
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
Expand Down Expand Up @@ -139,6 +150,10 @@ void ActiveDKGSessionHandler::WaitForNewQuorum(const uint256& oldQuorumHash) con
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d]- starting\n", __func__, params.name, quorumIndex);

while (true) {
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
}
if (stopRequested) {
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
Expand Down Expand Up @@ -186,6 +201,10 @@ void ActiveDKGSessionHandler::SleepBeforePhase(QuorumPhase curPhase, const uint2
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting sleep for %d ms, curPhase=%d\n", __func__, params.name, quorumIndex, sleepTime, std23::to_underlying(curPhase));

while (SteadyClock::now() < endTime) {
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
}
if (stopRequested) {
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
Expand Down Expand Up @@ -220,6 +239,10 @@ void ActiveDKGSessionHandler::HandlePhase(QuorumPhase curPhase, QuorumPhase next
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, std23::to_underlying(curPhase), std23::to_underlying(nextPhase));

SleepBeforePhase(curPhase, expectedQuorumHash, randomSleepFactor, runWhileWaiting);
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
LogPrint(BCLog::LLMQ_DKG, "%s -- refusing DKG participation while snapshot background validation is incomplete\n", __func__);
throw AbortPhaseException();
}
startPhaseFunc();
WaitForNextPhase(curPhase, nextPhase, expectedQuorumHash, runWhileWaiting);

Expand Down
16 changes: 16 additions & 0 deletions src/dbwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,22 @@ class CDBTransaction {
return parent.Read(ssKey, value);
}

/** Read a value only if it is present in this transaction's write set. */
template <typename K, typename V>
bool ReadPending(const K& key, V& value) {
const CDataStream ssKey = KeyToDataStream(key);
auto it = writes.find(ssKey);
if (it == writes.end()) {
return false;
}
auto* impl = dynamic_cast<ValueHolderImpl<V>*>(it->second.get());
if (!impl) {
throw std::runtime_error("ReadPending called with V != previously written type");
}
value = impl->value;
return true;
}

template <typename K>
bool Exists(const K& key) {
return Exists(KeyToDataStream(key));
Expand Down
70 changes: 59 additions & 11 deletions src/evo/assetlocktx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ std::string CAssetLockPayload::ToString() const

const std::string ASSETUNLOCK_REQUESTID_PREFIX = "plwdtx";

bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint256& msgHash, gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state) const
template <typename ScanQuorums, typename GetQuorum>
static bool VerifyAssetUnlockSig(const CAssetUnlockPayload& payload, ScanQuorums&& scan_quorums,
GetQuorum&& get_quorum, const uint256& msgHash,
gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state)
{
// That quourm hash must be active at `requestHeight`,
// and at the quorumHash must be active in either the current or previous quorum cycle
Expand All @@ -110,36 +113,60 @@ bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint

// We check all active quorums + 1 the latest inactive
const int quorums_to_scan = llmq_params_opt->signingActiveQuorumCount + 1;
const auto quorums = qman.ScanQuorums(llmqType, pindexTip, quorums_to_scan);
const auto quorums = scan_quorums(llmqType, pindexTip, quorums_to_scan);

if (bool isActive = std::any_of(quorums.begin(), quorums.end(), [&](const auto &q) { return q->qc->quorumHash == quorumHash; }); !isActive) {
if (bool isActive = std::any_of(quorums.begin(), quorums.end(), [&](const auto &q) { return q->qc->quorumHash == payload.getQuorumHash(); }); !isActive) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-too-old-quorum");
}

if (static_cast<uint32_t>(pindexTip->nHeight) < requestedHeight || pindexTip->nHeight >= getHeightToExpiry()) {
if (static_cast<uint32_t>(pindexTip->nHeight) < payload.getRequestedHeight() || pindexTip->nHeight >= payload.getHeightToExpiry()) {
LogPrint(BCLog::CREDITPOOL, "Asset unlock tx %d with requested height %d could not be accepted on height: %d\n",
index, requestedHeight, pindexTip->nHeight);
payload.getIndex(), payload.getRequestedHeight(), pindexTip->nHeight);
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-too-late");
}

const auto quorum = qman.GetQuorum(llmqType, quorumHash);
const auto quorum = get_quorum(llmqType, payload.getQuorumHash());
// quorum must be valid at this point. Let's check and throw error just in case
if (!quorum) {
LogPrintf("%s: ERROR! No quorum for credit pool found for hash=%s\n", __func__, quorumHash.ToString());
LogPrintf("%s: ERROR! No quorum for credit pool found for hash=%s\n", __func__, payload.getQuorumHash().ToString());
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-quorum-internal-error");
}

const uint256 requestId = ::SerializeHash(std::make_pair(ASSETUNLOCK_REQUESTID_PREFIX, index));
const uint256 requestId = ::SerializeHash(std::make_pair(ASSETUNLOCK_REQUESTID_PREFIX, payload.getIndex()));

if (const llmq::SignHash signHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
payload.getQuorumSig().VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
return true;
}

return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-not-verified");
}

bool CheckAssetUnlockTx(const BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, const std::optional<CRangesSet>& indexes, TxValidationState& state)
bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint256& msgHash,
gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state) const
{
return VerifyAssetUnlockSig(*this, [&](Consensus::LLMQType llmq_type, const CBlockIndex* pindex, size_t count) {
return qman.ScanQuorums(llmq_type, pindex, count);
}, [&](Consensus::LLMQType llmq_type, const uint256& quorum_hash) {
return qman.GetQuorum(llmq_type, quorum_hash);
}, msgHash, pindexTip, state);
}

bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const CChain& chain, const uint256& msgHash,
gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state) const
{
AssertLockHeld(::cs_main);
return VerifyAssetUnlockSig(*this, [&](Consensus::LLMQType llmq_type, const CBlockIndex* pindex, size_t count) NO_THREAD_SAFETY_ANALYSIS {
return qman.ScanQuorums(llmq_type, pindex, count, chain);
}, [&](Consensus::LLMQType llmq_type, const uint256& quorum_hash) NO_THREAD_SAFETY_ANALYSIS {
return qman.GetQuorum(llmq_type, quorum_hash, chain);
}, msgHash, pindexTip, state);
}

template <typename VerifySig>
static bool CheckAssetUnlockTxImpl(const BlockManager& blockman, VerifySig&& verify_sig, const CTransaction& tx,
gsl::not_null<const CBlockIndex*> pindexPrev,
const std::optional<CRangesSet>& indexes, TxValidationState& state)
{
// Some checks depends from blockchain status also, such as `known indexes` and `withdrawal limits`
// They are omitted here and done by CCreditPool
Expand Down Expand Up @@ -180,7 +207,28 @@ bool CheckAssetUnlockTx(const BlockManager& blockman, const llmq::CQuorumManager

uint256 msgHash = tx_copy.GetHash();

return assetUnlockTx.VerifySig(qman, msgHash, pindexPrev, state);
return verify_sig(assetUnlockTx, msgHash, pindexPrev, state);
}

bool CheckAssetUnlockTx(const BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx,
gsl::not_null<const CBlockIndex*> pindexPrev, const std::optional<CRangesSet>& indexes,
TxValidationState& state)
{
return CheckAssetUnlockTxImpl(blockman, [&](const CAssetUnlockPayload& payload, const uint256& msg_hash,
const CBlockIndex* pindex, TxValidationState& tx_state) {
return payload.VerifySig(qman, msg_hash, pindex, tx_state);
}, tx, pindexPrev, indexes, state);
}

bool CheckAssetUnlockTx(const BlockManager& blockman, const llmq::CQuorumManager& qman, const CChain& chain,
const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
const std::optional<CRangesSet>& indexes, TxValidationState& state)
{
AssertLockHeld(::cs_main);
return CheckAssetUnlockTxImpl(blockman, [&](const CAssetUnlockPayload& payload, const uint256& msg_hash,
const CBlockIndex* pindex, TxValidationState& tx_state) NO_THREAD_SAFETY_ANALYSIS {
return payload.VerifySig(qman, chain, msg_hash, pindex, tx_state);
}, tx, pindexPrev, indexes, state);
}

bool GetAssetUnlockFee(const CTransaction& tx, CAmount& txfee, TxValidationState& state)
Expand Down
11 changes: 11 additions & 0 deletions src/evo/assetlocktx.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
#include <gsl/pointers.h>
#include <primitives/transaction.h>
#include <serialize.h>
#include <sync.h>
#include <threadsafety.h>
#include <univalue.h>

#include <optional>

class CBlockIndex;
class CChain;
class CRangesSet;
class TxValidationState;
extern RecursiveMutex cs_main; // NOLINT(readability-redundant-declaration)
struct RPCResult;
namespace llmq {
class CQuorumManager;
Expand Down Expand Up @@ -114,6 +118,9 @@ class CAssetUnlockPayload
[[nodiscard]] UniValue ToJson() const;

bool VerifySig(const llmq::CQuorumManager& qman, const uint256& msgHash, gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state) const;
bool VerifySig(const llmq::CQuorumManager& qman, const CChain& chain, const uint256& msgHash,
gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state) const
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

// getters
uint8_t getVersion() const
Expand Down Expand Up @@ -156,6 +163,10 @@ class CAssetUnlockPayload

bool CheckAssetLockTx(const CTransaction& tx, TxValidationState& state);
bool CheckAssetUnlockTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, const std::optional<CRangesSet>& indexes, TxValidationState& state);
bool CheckAssetUnlockTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CChain& chain,
const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
const std::optional<CRangesSet>& indexes, TxValidationState& state)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool GetAssetUnlockFee(const CTransaction& tx, CAmount& txfee, TxValidationState& state);

#endif // BITCOIN_EVO_ASSETLOCKTX_H
2 changes: 1 addition & 1 deletion src/evo/chainhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ bool CChainstateHelper::RemoveConflictingISLockByTx(const CTransaction& tx)
return true;
}

std::unordered_map<uint8_t, int> CChainstateHelper::GetSignalsStage(const CBlockIndex* const pindexPrev)
std::map<uint8_t, int> CChainstateHelper::GetSignalsStage(const CBlockIndex* const pindexPrev)
{
return ehf_manager->GetSignalsStage(pindexPrev);
}
4 changes: 2 additions & 2 deletions src/evo/chainhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#define BITCOIN_EVO_CHAINHELPER_H

#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <unordered_map>

class CBlockIndex;
class CCreditPoolManager;
Expand Down Expand Up @@ -77,7 +77,7 @@ class CChainstateHelper
bool IsInstantSendWaitingForTx(const uint256& hash) const;
bool RemoveConflictingISLockByTx(const CTransaction& tx);

std::unordered_map<uint8_t, int> GetSignalsStage(const CBlockIndex* const pindexPrev);
std::map<uint8_t, int> GetSignalsStage(const CBlockIndex* const pindexPrev);
};

#endif // BITCOIN_EVO_CHAINHELPER_H
10 changes: 7 additions & 3 deletions src/evo/creditpool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,17 @@ std::optional<CCreditPool> CCreditPoolManager::GetFromCache(const CBlockIndex& b

void CCreditPoolManager::AddToCache(const uint256& block_hash, int height, const CCreditPool &pool)
{
if (height % DISK_SNAPSHOT_PERIOD == 0) {
if (!evoDb.WriteDerived(std::make_pair(DB_CREDITPOOL_SNAPSHOT, block_hash), pool)) {
LogPrintf("ERROR: CCreditPoolManager::%s -- EvoDB credit pool mismatch for block %s\n",
__func__, block_hash.ToString());
throw std::runtime_error("EvoDB credit pool payload mismatch");
}
}
{
LOCK(cache_mutex);
creditPoolCache.insert(block_hash, pool);
}
if (height % DISK_SNAPSHOT_PERIOD == 0) {
evoDb.Write(std::make_pair(DB_CREDITPOOL_SNAPSHOT, block_hash), pool);
}
}

CCreditPool CCreditPoolManager::ConstructCreditPool(const gsl::not_null<const CBlockIndex*> block_index, CCreditPool prev)
Expand Down
Loading