From f5e834c0f9011e523725684c543b055a7ea86621 Mon Sep 17 00:00:00 2001 From: Pasta Date: Tue, 14 Jul 2026 01:17:20 -0500 Subject: [PATCH 1/2] backport: adapt Dash time users to NodeClock Convert nearby CoinJoin, governance, spork, LLMQ, RPC, and test code to chrono time points and durations. Preserve serialized, signed, hashed, and RPC-facing timestamps as integer Unix seconds with explicit conversions at those boundaries. --- src/coinjoin/coinjoin.cpp | 13 +++-- src/coinjoin/coinjoin.h | 13 +++-- src/governance/governance.cpp | 55 +++++++++--------- src/governance/governance.h | 26 +++++++-- src/governance/object.cpp | 22 ++++--- src/governance/object.h | 21 +++---- src/governance/vote.cpp | 7 ++- src/governance/vote.h | 6 ++ src/llmq/debug.cpp | 11 ++-- src/llmq/debug.h | 3 +- src/rpc/governance.cpp | 3 +- src/spork.cpp | 5 +- src/spork.h | 7 ++- src/test/coinjoin_inouts_tests.cpp | 15 +++-- src/test/coinjoin_queue_tests.cpp | 76 ++++++++++--------------- src/test/governance_vote_wire_tests.cpp | 41 ++++++++++++- 16 files changed, 192 insertions(+), 132 deletions(-) diff --git a/src/coinjoin/coinjoin.cpp b/src/coinjoin/coinjoin.cpp index 947dc2ea40c7..45b9cde5b4fa 100644 --- a/src/coinjoin/coinjoin.cpp +++ b/src/coinjoin/coinjoin.cpp @@ -55,11 +55,16 @@ bool CCoinJoinQueue::CheckSignature(const CBLSPublicKey& blsPubKey) const return true; } -bool CCoinJoinQueue::IsTimeOutOfBounds(int64_t current_time) const +bool CCoinJoinQueue::IsTimeOutOfBounds(NodeSeconds current_time) const { - if (current_time < 0 || nTime < 0) return true; - return current_time - nTime > COINJOIN_QUEUE_TIMEOUT || - nTime - current_time > COINJOIN_QUEUE_TIMEOUT; + const auto queue_time{Time()}; + if (current_time < NodeSeconds{} || queue_time < NodeSeconds{}) return true; + return std::chrono::abs(current_time - queue_time) > std::chrono::seconds{COINJOIN_QUEUE_TIMEOUT}; +} + +bool CCoinJoinQueue::IsTimeOutOfBounds() const +{ + return IsTimeOutOfBounds(std::chrono::time_point_cast(GetAdjustedTime())); } [[nodiscard]] std::string CCoinJoinQueue::ToString() const diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index e39387c2edb8..5ccfaaf19148 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -193,15 +193,17 @@ class CCoinJoinQueue CCoinJoinQueue() = default; - CCoinJoinQueue(int nDenom, const COutPoint& outpoint, const uint256& proTxHash, int64_t nTime, bool fReady) : + CCoinJoinQueue(int nDenom, const COutPoint& outpoint, const uint256& proTxHash, NodeClock::time_point time, bool fReady) : nDenom(nDenom), masternodeOutpoint(outpoint), m_protxHash(proTxHash), - nTime(nTime), + nTime(TicksSinceEpoch(time)), fReady(fReady) { } + NodeSeconds Time() const { return NodeSeconds{std::chrono::seconds{nTime}}; } + SERIALIZE_METHODS(CCoinJoinQueue, obj) { READWRITE(obj.nDenom, obj.m_protxHash, obj.nTime, obj.fReady); @@ -217,7 +219,8 @@ class CCoinJoinQueue [[nodiscard]] bool CheckSignature(const CBLSPublicKey& blsPubKey) const; /// Check if a queue is too old or too far into the future - [[nodiscard]] bool IsTimeOutOfBounds(int64_t current_time = GetAdjustedTime()) const; + [[nodiscard]] bool IsTimeOutOfBounds(NodeSeconds current_time) const; + [[nodiscard]] bool IsTimeOutOfBounds() const; [[nodiscard]] std::string ToString() const; @@ -247,11 +250,11 @@ class CCoinJoinBroadcastTx { } - CCoinJoinBroadcastTx(CTransactionRef _tx, const COutPoint& _outpoint, const uint256& proTxHash, int64_t _sigTime) : + CCoinJoinBroadcastTx(CTransactionRef _tx, const COutPoint& _outpoint, const uint256& proTxHash, NodeClock::time_point time) : tx(std::move(_tx)), masternodeOutpoint(_outpoint), m_protxHash(proTxHash), - sigTime(_sigTime) + sigTime(TicksSinceEpoch(time)) { } diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index ede7ac1dfaef..5edb84a66bd6 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -278,25 +278,25 @@ void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj) AssertLockNotHeld(cs_relay); uint256 nHash = govobj.GetHash(); - std::vector vecVotePairs; - cmmapOrphanVotes.GetAll(nHash, vecVotePairs); + std::vector orphan_votes; + cmmapOrphanVotes.GetAll(nHash, orphan_votes); ScopedLockBool guard(cs_store, fRateChecksEnabled, false); - int64_t nNow = GetAdjustedTime(); + const auto now{GetAdjustedTime()}; const auto tip_mn_list = m_dmnman.GetListAtChainTip(); - for (const auto& pairVote : vecVotePairs) { - const auto& [vote, time] = pairVote; + for (const auto& orphan_vote : orphan_votes) { + const auto& vote = orphan_vote.vote; bool fRemove = false; CGovernanceException e; - if (time < nNow) { + if (orphan_vote.expiration < now) { fRemove = true; } else if (govobj.ProcessVote(m_mn_metaman, fRateChecksEnabled, tip_mn_list, vote, e)) { RelayVote(vote); fRemove = true; } if (fRemove) { - cmmapOrphanVotes.Erase(nHash, pairVote); + cmmapOrphanVotes.Erase(nHash, orphan_vote); } } } @@ -733,21 +733,21 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo } const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint(); - int64_t nTimestamp = govobj.GetCreationTime(); - int64_t nNow = GetAdjustedTime(); - int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; + const auto timestamp{govobj.CreationTime()}; + const auto now{GetAdjustedTime()}; + const auto superblock_cycle{Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().PowTargetSpacing()}; std::string strHash = govobj.GetHash().ToString(); - if (nTimestamp < nNow - 2 * nSuperblockCycleSeconds) { + if (timestamp < now - 2 * superblock_cycle) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too old timestamp, masternode = %s, timestamp = %d, current time = %d\n", - strHash, masternodeOutpoint.ToStringShort(), nTimestamp, nNow); + strHash, masternodeOutpoint.ToStringShort(), govobj.GetCreationTime(), TicksSinceEpoch(now)); return false; } - if (nTimestamp > nNow + count_seconds(MAX_TIME_FUTURE_DEVIATION)) { + if (timestamp > now + MAX_TIME_FUTURE_DEVIATION) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too new (future) timestamp, masternode = %s, timestamp = %d, current time = %d\n", - strHash, masternodeOutpoint.ToStringShort(), nTimestamp, nNow); + strHash, masternodeOutpoint.ToStringShort(), govobj.GetCreationTime(), TicksSinceEpoch(now)); return false; } @@ -760,12 +760,12 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo } // Allow 1 trigger per mn per cycle, with a small fudge factor - double dMaxRate = 2 * 1.1 / double(nSuperblockCycleSeconds); + double dMaxRate = 2 * 1.1 / static_cast(count_seconds(superblock_cycle)); // Temporary copy to check rate after new timestamp is added CRateCheckBuffer buffer = it->second.triggerBuffer; - buffer.AddTimestamp(nTimestamp); + buffer.AddTimestamp(govobj.GetCreationTime()); double dRate = buffer.GetRate(); if (dRate < dMaxRate) { @@ -773,7 +773,7 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo } LogPrint(BCLog::GOBJECT, "CGovernanceManager::MasternodeRateCheck -- Rate too high: object hash = %s, masternode = %s, object timestamp = %d, rate = %f, max rate = %f\n", - strHash, masternodeOutpoint.ToStringShort(), nTimestamp, dRate, dMaxRate); + strHash, masternodeOutpoint.ToStringShort(), govobj.GetCreationTime(), dRate, dMaxRate); if (fUpdateFailStatus) { it->second.fStatusOK = false; @@ -823,8 +823,7 @@ bool CGovernanceManager::ProcessVote(const CGovernanceVote& vote, CGovernanceExc std::string msg{strprintf("CGovernanceManager::%s -- Unknown parent object %s, MN outpoint = %s", __func__, nHashGovobj.ToString(), vote.GetMasternodeOutpoint().ToStringShort())}; exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_WARNING); - if (cmmapOrphanVotes.Insert(nHashGovobj, vote_time_pair_t(vote, count_seconds(GetTime() + - GOVERNANCE_ORPHAN_EXPIRATION_TIME)))) { + if (cmmapOrphanVotes.Insert(nHashGovobj, governance::OrphanVote{vote, Now() + GOVERNANCE_ORPHAN_EXPIRATION_TIME})) { hashToRequest = nHashGovobj; // Caller should request this object } LogPrint(BCLog::GOBJECT, "%s\n", msg); @@ -883,20 +882,19 @@ void CGovernanceManager::CheckPostponedObjects() // Perform additional relays for triggers - int64_t nNow = GetAdjustedTime(); - int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; + const auto now{GetAdjustedTime()}; + const auto superblock_cycle{Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().PowTargetSpacing()}; for (auto it = setAdditionalRelayObjects.begin(); it != setAdditionalRelayObjects.end();) { auto itObject = mapObjects.find(*it); if (itObject != mapObjects.end()) { const auto& govobj = *Assert(itObject->second); - int64_t nTimestamp = govobj.GetCreationTime(); + const auto timestamp{govobj.CreationTime()}; - bool fValid = (nTimestamp <= nNow + count_seconds(MAX_TIME_FUTURE_DEVIATION)) && - (nTimestamp >= nNow - 2 * nSuperblockCycleSeconds); - bool fReady = (nTimestamp <= - nNow + count_seconds(MAX_TIME_FUTURE_DEVIATION) - count_seconds(RELIABLE_PROPAGATION_TIME)); + const bool fValid{timestamp <= now + MAX_TIME_FUTURE_DEVIATION && + timestamp >= now - 2 * superblock_cycle}; + const bool fReady{timestamp <= now + MAX_TIME_FUTURE_DEVIATION - RELIABLE_PROPAGATION_TIME}; if (fValid) { if (fReady) { @@ -1092,15 +1090,14 @@ std::vector CGovernanceManager::GetOrphanVoteObjectHashes() { LOCK(cs_store); - int64_t nNow = GetTime().count(); + const auto now{Now()}; // Clean up expired orphan votes const vote_cmm_t::list_t& items = cmmapOrphanVotes.GetItemList(); for (auto it = items.begin(); it != items.end();) { auto prevIt = it; ++it; - const auto& [_, time] = prevIt->value; - if (time < nNow) { + if (prevIt->value.expiration < now) { cmmapOrphanVotes.Erase(prevIt->key, prevIt->value); } } diff --git a/src/governance/governance.h b/src/governance/governance.h index 87833b8fee80..ccd3f1351cb1 100644 --- a/src/governance/governance.h +++ b/src/governance/governance.h @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include #include #include @@ -29,7 +31,6 @@ template class CFlatDB; class CGovernanceException; class CGovernanceObject; -class CGovernanceVote; class CInv; class CMasternodeMetaMan; class CMasternodeSync; @@ -41,9 +42,26 @@ namespace governance { class SuperblockManager; // How long a requested governance inv hash remains in the request cache. inline constexpr std::chrono::seconds RELIABLE_PROPAGATION_TIME{60}; -} // namespace governance -using vote_time_pair_t = std::pair; +struct OrphanVote { + CGovernanceVote vote; + NodeSeconds expiration; + + OrphanVote() = default; + OrphanVote(const CGovernanceVote& vote, NodeSeconds expiration) : vote(vote), expiration(expiration) {} + + SERIALIZE_METHODS(OrphanVote, obj) + { + // Preserve the historical integer Unix-seconds representation on disk. + READWRITE(obj.vote, Using>(obj.expiration)); + } +}; + +inline bool operator<(const OrphanVote& lhs, const OrphanVote& rhs) +{ + return lhs.vote < rhs.vote; +} +} // namespace governance static constexpr int RATE_BUFFER_SIZE = 5; static constexpr bool DEFAULT_GOVERNANCE_ENABLE{true}; @@ -160,7 +178,7 @@ class GovernanceStore }; using txout_m_t = std::map; - using vote_cmm_t = CacheMultiMap; + using vote_cmm_t = CacheMultiMap; protected: static constexpr int MAX_CACHE_SIZE = 1000000; diff --git a/src/governance/object.cpp b/src/governance/object.cpp index e348444b5096..2ecf7c406c1b 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -144,7 +144,7 @@ bool ValidateStartEndEpoch(const UniValue& objJSON, bool fCheckExpiration, std:: return false; } - if (fCheckExpiration && nEndEpoch <= GetAdjustedTime()) { + if (fCheckExpiration && NodeSeconds{std::chrono::seconds{nEndEpoch}} <= GetAdjustedTime()) { strErrorMessages += "expired;"; return false; } @@ -346,6 +346,12 @@ CGovernanceObject::CGovernanceObject(const uint256& nHashParentIn, int nRevision LoadData(); } +CGovernanceObject::CGovernanceObject(const uint256& nHashParentIn, int nRevisionIn, NodeClock::time_point time, + const uint256& nCollateralHashIn, const std::string& strDataHexIn) : + CGovernanceObject{nHashParentIn, nRevisionIn, TicksSinceEpoch(time), nCollateralHashIn, strDataHexIn} +{ +} + CGovernanceObject::CGovernanceObject(const CGovernanceObject& other) : cs(), m_obj{other.m_obj}, @@ -430,19 +436,19 @@ bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, bool fRateCh LogPrint(BCLog::GOBJECT, "%s\n", msg); } - int64_t nNow = GetAdjustedTime(); - int64_t nVoteTimeUpdate = voteInstanceRef.nTime; + auto vote_time_update{voteInstanceRef.last_update}; if (fRateChecksEnabled) { - int64_t nTimeDelta = nNow - voteInstanceRef.nTime; - if (nTimeDelta < GOVERNANCE_UPDATE_MIN) { + const auto now{GetAdjustedTime()}; + const auto time_delta{now - voteInstanceRef.last_update}; + if (time_delta < GOVERNANCE_UPDATE_MIN) { std::string msg{strprintf("CGovernanceObject::%s -- Masternode voting too often, MN outpoint = %s, " "governance object hash = %s, time delta = %d", - __func__, vote.GetMasternodeOutpoint().ToStringShort(), GetHash().ToString(), nTimeDelta)}; + __func__, vote.GetMasternodeOutpoint().ToStringShort(), GetHash().ToString(), Ticks(time_delta))}; LogPrint(BCLog::GOBJECT, "%s\n", msg); exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_TEMPORARY_ERROR); return false; } - nVoteTimeUpdate = nNow; + vote_time_update = std::chrono::time_point_cast(now); } bool onlyVotingKeyAllowed = m_obj.type == GovernanceObject::PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING; @@ -459,7 +465,7 @@ bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, bool fRateCh mn_metaman.AddGovernanceVote(dmn->proTxHash, vote.GetParentHash()); - voteInstanceRef = vote_instance_t(vote.GetOutcome(), nVoteTimeUpdate, vote.GetTimestamp()); + voteInstanceRef = vote_instance_t(vote.GetOutcome(), vote_time_update, vote.GetTimestamp()); fileVotes.AddVote(vote); fDirtyCache = true; // SEND NOTIFICATION TO SCRIPT/ZMQ diff --git a/src/governance/object.h b/src/governance/object.h index 38b88185d6f8..6478dccb066a 100644 --- a/src/governance/object.h +++ b/src/governance/object.h @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -87,7 +88,7 @@ static constexpr double GOVERNANCE_FILTER_FP_RATE = 0.001; static constexpr CAmount GOVERNANCE_PROPOSAL_FEE_TX = (1 * COIN); static constexpr int64_t GOVERNANCE_FEE_CONFIRMATIONS = 6; static constexpr int64_t GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS = 1; -static constexpr int64_t GOVERNANCE_UPDATE_MIN = 60 * 60; +static constexpr std::chrono::hours GOVERNANCE_UPDATE_MIN{1}; // FOR SEEN MAP ARRAYS - GOVERNANCE OBJECTS AND VOTES enum class SeenObjectStatus { @@ -97,21 +98,14 @@ enum class SeenObjectStatus { Unknown }; -using vote_time_pair_t = std::pair; - -inline bool operator<(const vote_time_pair_t& p1, const vote_time_pair_t& p2) -{ - return (p1.first < p2.first); -} - struct vote_instance_t { vote_outcome_enum_t eOutcome; - int64_t nTime; + NodeSeconds last_update; int64_t nCreationTime; - explicit vote_instance_t(vote_outcome_enum_t eOutcomeIn = VOTE_OUTCOME_NONE, int64_t nTimeIn = 0, int64_t nCreationTimeIn = 0) : + explicit vote_instance_t(vote_outcome_enum_t eOutcomeIn = VOTE_OUTCOME_NONE, NodeSeconds last_update_in = {}, int64_t nCreationTimeIn = 0) : eOutcome(eOutcomeIn), - nTime(nTimeIn), + last_update(last_update_in), nCreationTime(nCreationTimeIn) { } @@ -120,7 +114,8 @@ struct vote_instance_t { { int nOutcome; SER_WRITE(obj, nOutcome = int(obj.eOutcome)); - READWRITE(nOutcome, obj.nTime, obj.nCreationTime); + // Preserve the historical integer Unix-seconds representation on disk. + READWRITE(nOutcome, Using>(obj.last_update), obj.nCreationTime); SER_READ(obj, obj.eOutcome = vote_outcome_enum_t(nOutcome)); } }; @@ -192,6 +187,7 @@ class CGovernanceObject public: CGovernanceObject(); CGovernanceObject(const uint256& nHashParentIn, int nRevisionIn, int64_t nTime, const uint256& nCollateralHashIn, const std::string& strDataHexIn); + CGovernanceObject(const uint256& nHashParentIn, int nRevisionIn, NodeClock::time_point time, const uint256& nCollateralHashIn, const std::string& strDataHexIn); CGovernanceObject(const CGovernanceObject& other); template CGovernanceObject(deserialize_type, Stream& s) { s >> *this; } @@ -207,6 +203,7 @@ class CGovernanceObject return WITH_LOCK(cs, return fExpired); } GovernanceObject GetObjectType() const { return m_obj.type; } + NodeSeconds CreationTime() const { return NodeSeconds{std::chrono::seconds{m_obj.time}}; } int64_t GetCreationTime() const { return m_obj.time; } int64_t GetDeletionTime() const EXCLUSIVE_LOCKS_REQUIRED(!cs) { diff --git a/src/governance/vote.cpp b/src/governance/vote.cpp index 91556dbfd4a5..e4e99528f5a4 100644 --- a/src/governance/vote.cpp +++ b/src/governance/vote.cpp @@ -91,7 +91,7 @@ CGovernanceVote::CGovernanceVote(const COutPoint& outpointMasternodeIn, const ui nParentHash(nParentHashIn), nVoteOutcome(eVoteOutcomeIn), nVoteSignal(eVoteSignalIn), - nTime(GetAdjustedTime()) + nTime(TicksSinceEpoch(GetAdjustedTime())) { UpdateHash(); } @@ -158,8 +158,9 @@ bool CGovernanceVote::CheckSignature(const CBLSPublicKey& pubKey) const bool CGovernanceVote::IsValid(const CDeterministicMNList& tip_mn_list, bool useVotingKey) const { - if (nTime > GetAdjustedTime() + (60 * 60)) { - LogPrint(BCLog::GOBJECT, "CGovernanceVote::IsValid -- vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", GetHash().ToString(), nTime, GetAdjustedTime() + (60 * 60)); + const auto max_time{GetAdjustedTime() + 1h}; + if (Time() > max_time) { + LogPrint(BCLog::GOBJECT, "CGovernanceVote::IsValid -- vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", GetHash().ToString(), nTime, TicksSinceEpoch(max_time)); return false; } diff --git a/src/governance/vote.h b/src/governance/vote.h index 876e3c0067dd..cd33d5434898 100644 --- a/src/governance/vote.h +++ b/src/governance/vote.h @@ -10,6 +10,7 @@ #include #include #include +#include class CBLSPublicKey; class CDeterministicMNList; @@ -85,6 +86,7 @@ class CGovernanceVote CGovernanceVote(const COutPoint& outpointMasternodeIn, const uint256& nParentHashIn, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn); int64_t GetTimestamp() const { return nTime; } + NodeSeconds Time() const { return NodeSeconds{std::chrono::seconds{nTime}}; } vote_signal_enum_t GetSignal() const { return nVoteSignal; } @@ -97,6 +99,10 @@ class CGovernanceVote nTime = nTimeIn; UpdateHash(); } + void SetTime(NodeClock::time_point time) + { + SetTime(TicksSinceEpoch(time)); + } void SetSignature(const std::vector& vchSigIn) { vchSig = vchSigIn; } diff --git a/src/llmq/debug.cpp b/src/llmq/debug.cpp index d3678d61a6df..975fe6b753c9 100644 --- a/src/llmq/debug.cpp +++ b/src/llmq/debug.cpp @@ -128,8 +128,9 @@ UniValue CDKGDebugManager::ToJson(int detailLevel) const LOCK(cs_lockStatus); UniValue ret(UniValue::VOBJ); - ret.pushKV("time", localStatus.nTime); - ret.pushKV("timeStr", FormatISO8601DateTime(localStatus.nTime)); + const int64_t time{TicksSinceEpoch(localStatus.time)}; + ret.pushKV("time", time); + ret.pushKV("timeStr", FormatISO8601DateTime(time)); // TODO Support array of sessions UniValue sessionsArrJson(UniValue::VARR); @@ -160,7 +161,7 @@ void CDKGDebugManager::ResetLocalSessionStatus(Consensus::LLMQType llmqType, int } localStatus.sessions.erase(it); - localStatus.nTime = GetAdjustedTime(); + localStatus.time = GetAdjustedTime(); } void CDKGDebugManager::InitLocalSessionStatus(const Consensus::LLMQParams& llmqParams, int quorumIndex, const uint256& quorumHash, int quorumHeight) @@ -192,7 +193,7 @@ void CDKGDebugManager::UpdateLocalSessionStatus(Consensus::LLMQType llmqType, in } if (func(it->second)) { - localStatus.nTime = GetAdjustedTime(); + localStatus.time = GetAdjustedTime(); } } @@ -206,7 +207,7 @@ void CDKGDebugManager::UpdateLocalMemberStatus(Consensus::LLMQType llmqType, int } if (func(it->second.members.at(memberIdx))) { - localStatus.nTime = GetAdjustedTime(); + localStatus.time = GetAdjustedTime(); } } diff --git a/src/llmq/debug.h b/src/llmq/debug.h index 7ca0e566d313..85c20c7b4c7f 100644 --- a/src/llmq/debug.h +++ b/src/llmq/debug.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -84,7 +85,7 @@ class CDKGDebugSessionStatus }; struct CDKGDebugStatus { - int64_t nTime{0}; + NodeClock::time_point time{}; std::map, CDKGDebugSessionStatus> sessions; }; diff --git a/src/rpc/governance.cpp b/src/rpc/governance.cpp index 23a600fe1370..a4a97f6596cb 100644 --- a/src/rpc/governance.cpp +++ b/src/rpc/governance.cpp @@ -117,10 +117,9 @@ static RPCHelpMan gobject_check() int nRevision = 1; - int64_t nTime = GetAdjustedTime(); std::string strDataHex = request.params[0].get_str(); - CGovernanceObject govobj(hashParent, nRevision, nTime, uint256(), strDataHex); + CGovernanceObject govobj(hashParent, nRevision, GetAdjustedTime(), uint256(), strDataHex); if (govobj.GetObjectType() == GovernanceObject::PROPOSAL) { std::string strValidationError; diff --git a/src/spork.cpp b/src/spork.cpp index 98d9086a4f81..4148abdfdb25 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -125,7 +125,7 @@ void CSporkManager::CheckAndRemove() std::optional CSporkManager::GetValidSporkSigner(const CSporkMessage& spork) const { - if (spork.nTimeSigned > GetAdjustedTime() + 2 * 60 * 60) { + if (spork.TimeSigned() > GetAdjustedTime() + 2h) { LogPrint(BCLog::SPORK, "CSporkManager::%s -- ERROR: too far into the future\n", __func__); return std::nullopt; } @@ -225,7 +225,8 @@ bool CSporkManager::IsSporkActive(SporkId nSporkID) const SporkValue nSporkValue = GetSporkValue(nSporkID); // Get time is somewhat costly it looks like - bool ret = nSporkValue < GetAdjustedTime(); + const NodeSeconds activation_time{std::chrono::seconds{nSporkValue}}; + const bool ret = activation_time < GetAdjustedTime(); // Only cache true values if (ret) { LOCK(cs_cache); diff --git a/src/spork.h b/src/spork.h index 9d23fa68b240..375ed30d163d 100644 --- a/src/spork.h +++ b/src/spork.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -99,14 +100,16 @@ class CSporkMessage SporkValue nValue{0}; int64_t nTimeSigned{0}; - CSporkMessage(SporkId nSporkID, SporkValue nValue, int64_t nTimeSigned) : + CSporkMessage(SporkId nSporkID, SporkValue nValue, NodeClock::time_point time_signed) : nSporkID(nSporkID), nValue(nValue), - nTimeSigned(nTimeSigned) + nTimeSigned(TicksSinceEpoch(time_signed)) {} CSporkMessage() = default; + NodeSeconds TimeSigned() const { return NodeSeconds{std::chrono::seconds{nTimeSigned}}; } + SERIALIZE_METHODS(CSporkMessage, obj) { READWRITE(obj.nSporkID, obj.nValue, obj.nTimeSigned, diff --git a/src/test/coinjoin_inouts_tests.cpp b/src/test/coinjoin_inouts_tests.cpp index 516aa4ae737a..b5edb2913e67 100644 --- a/src/test/coinjoin_inouts_tests.cpp +++ b/src/test/coinjoin_inouts_tests.cpp @@ -157,23 +157,22 @@ BOOST_AUTO_TEST_CASE(entry_addscriptsig_matches_and_rejects) BOOST_AUTO_TEST_CASE(queue_timeout_bounds) { - CCoinJoinQueue dsq; - dsq.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - dsq.m_protxHash = uint256::ONE; - dsq.nTime = GetAdjustedTime(); + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + CCoinJoinQueue dsq{CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()), + COutPoint{}, uint256::ONE, now, /*fReady=*/false}; // current time -> not out of bounds BOOST_CHECK(!dsq.IsTimeOutOfBounds()); // Too old (beyond COINJOIN_QUEUE_TIMEOUT) - SetMockTime(GetTime() + (COINJOIN_QUEUE_TIMEOUT + 1)); + SetMockTime((now + std::chrono::seconds{COINJOIN_QUEUE_TIMEOUT + 1}).time_since_epoch()); BOOST_CHECK(dsq.IsTimeOutOfBounds()); // Too far in the future - SetMockTime(GetTime() - 2 * (COINJOIN_QUEUE_TIMEOUT + 1)); // move back to anchor baseline - dsq.nTime = GetAdjustedTime() + (COINJOIN_QUEUE_TIMEOUT + 1); + SetMockTime((now - std::chrono::seconds{COINJOIN_QUEUE_TIMEOUT + 1}).time_since_epoch()); + dsq.nTime = TicksSinceEpoch(now + std::chrono::seconds{COINJOIN_QUEUE_TIMEOUT + 1}); BOOST_CHECK(dsq.IsTimeOutOfBounds()); // Reset mock time - SetMockTime(0); + SetMockTime(0s); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/coinjoin_queue_tests.cpp b/src/test/coinjoin_queue_tests.cpp index 8e1c3de6f564..b4e89e4c717a 100644 --- a/src/test/coinjoin_queue_tests.cpp +++ b/src/test/coinjoin_queue_tests.cpp @@ -27,17 +27,18 @@ static CBLSSecretKey MakeSecretKey() return sk; } +static CCoinJoinQueue MakeQueue(int denom, NodeClock::time_point time, bool fReady, const COutPoint& outpoint) +{ + return CCoinJoinQueue{denom, outpoint, uint256::ONE, time, fReady}; +} + BOOST_AUTO_TEST_CASE(queue_sign_and_verify) { // Build active MN manager with operator key using node context wiring CActiveMasternodeManager mn_activeman(*Assert(m_node.connman), *Assert(m_node.dmnman), MakeSecretKey()); - CCoinJoinQueue q; - q.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - q.masternodeOutpoint = COutPoint(uint256S("aa"), 1); - q.m_protxHash = uint256::ONE; - q.nTime = GetAdjustedTime(); - q.fReady = false; + auto q{MakeQueue(CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()), + GetAdjustedTime(), /*fReady=*/false, COutPoint{uint256S("aa"), 1})}; // Sign and verify with corresponding pubkey q.vchSig = mn_activeman.SignBasic(q.GetSignatureHash()); @@ -47,12 +48,9 @@ BOOST_AUTO_TEST_CASE(queue_sign_and_verify) BOOST_AUTO_TEST_CASE(queue_hashes_and_equality) { - CCoinJoinQueue a, b; - a.nDenom = b.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - a.masternodeOutpoint = b.masternodeOutpoint = COutPoint(uint256S("bb"), 2); - a.m_protxHash = b.m_protxHash = uint256::ONE; - a.nTime = b.nTime = GetAdjustedTime(); - a.fReady = b.fReady = true; + const auto a{MakeQueue(CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()), + GetAdjustedTime(), /*fReady=*/true, COutPoint{uint256S("bb"), 2})}; + const auto b{a}; BOOST_CHECK(a == b); BOOST_CHECK(a.GetHash() == b.GetHash()); @@ -73,31 +71,28 @@ BOOST_AUTO_TEST_CASE(queue_denomination_validation) BOOST_AUTO_TEST_CASE(queue_timestamp_validation) { - CCoinJoinQueue q; - q.nDenom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - q.masternodeOutpoint = COutPoint(uint256S("cc"), 3); - q.m_protxHash = uint256::ONE; - - int64_t current_time = GetAdjustedTime(); + const int denom{CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination())}; + const COutPoint outpoint{uint256S("cc"), 3}; + const auto current_time{std::chrono::time_point_cast(GetAdjustedTime())}; // Test valid timestamp (current time) - q.nTime = current_time; + auto q{MakeQueue(denom, current_time, /*fReady=*/false, outpoint)}; BOOST_CHECK(!q.IsTimeOutOfBounds(current_time)); // Test timestamp slightly in future (within COINJOIN_QUEUE_TIMEOUT = 30) - q.nTime = current_time + 15; // 15 seconds in future + q = MakeQueue(denom, current_time + 15s, /*fReady=*/false, outpoint); BOOST_CHECK(!q.IsTimeOutOfBounds(current_time)); // Test timestamp slightly in past (within COINJOIN_QUEUE_TIMEOUT = 30) - q.nTime = current_time - 15; // 15 seconds ago + q = MakeQueue(denom, current_time - 15s, /*fReady=*/false, outpoint); BOOST_CHECK(!q.IsTimeOutOfBounds(current_time)); // Test timestamp too far in future (outside COINJOIN_QUEUE_TIMEOUT = 30) - q.nTime = current_time + 60; // 60 seconds in future + q = MakeQueue(denom, current_time + 60s, /*fReady=*/false, outpoint); BOOST_CHECK(q.IsTimeOutOfBounds(current_time)); // Test timestamp too far in past (outside COINJOIN_QUEUE_TIMEOUT = 30) - q.nTime = current_time - 60; // 60 seconds ago + q = MakeQueue(denom, current_time - 60s, /*fReady=*/false, outpoint); BOOST_CHECK(q.IsTimeOutOfBounds(current_time)); } @@ -109,25 +104,25 @@ BOOST_AUTO_TEST_CASE(queue_timestamp_extreme_values) // Negative timestamps are rejected by the guard q.nTime = INT64_MIN; - BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MAX)); + BOOST_CHECK(q.IsTimeOutOfBounds(NodeSeconds{std::chrono::seconds{INT64_MAX}})); q.nTime = INT64_MAX; - BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MIN)); + BOOST_CHECK(q.IsTimeOutOfBounds(NodeSeconds{std::chrono::seconds{INT64_MIN}})); q.nTime = INT64_MIN; - BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MIN)); + BOOST_CHECK(q.IsTimeOutOfBounds(NodeSeconds{std::chrono::seconds{INT64_MIN}})); // Large positive timestamp with same value: zero diff, in bounds q.nTime = INT64_MAX; - BOOST_CHECK(!q.IsTimeOutOfBounds(INT64_MAX)); + BOOST_CHECK(!q.IsTimeOutOfBounds(NodeSeconds{std::chrono::seconds{INT64_MAX}})); // Zero vs extreme positive: huge gap, out of bounds q.nTime = 0; - BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MAX)); + BOOST_CHECK(q.IsTimeOutOfBounds(NodeSeconds{std::chrono::seconds{INT64_MAX}})); // Zero vs negative: rejected by guard q.nTime = 0; - BOOST_CHECK(q.IsTimeOutOfBounds(INT64_MIN)); + BOOST_CHECK(q.IsTimeOutOfBounds(NodeSeconds{std::chrono::seconds{INT64_MIN}})); } static_assert(CoinJoin::CalculateAmountPriority(MAX_MONEY) == -(MAX_MONEY / COIN)); @@ -145,26 +140,15 @@ BOOST_AUTO_TEST_CASE(calculate_amount_priority_guard) BOOST_CHECK_EQUAL(CoinJoin::CalculateAmountPriority(MAX_MONEY + 1), 0); } -static CCoinJoinQueue MakeQueue(int denom, int64_t nTime, bool fReady, const COutPoint& outpoint) -{ - CCoinJoinQueue q; - q.nDenom = denom; - q.masternodeOutpoint = outpoint; - q.m_protxHash = uint256::ONE; - q.nTime = nTime; - q.fReady = fReady; - return q; -} - BOOST_AUTO_TEST_CASE(queuemanager_checkqueue_removes_timeouts) { CoinJoinQueueManager man; const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - const int64_t now = GetAdjustedTime(); + const auto now{GetAdjustedTime()}; // Non-expired man.AddQueue(MakeQueue(denom, now, false, COutPoint(uint256S("11"), 0))); // Expired (too old) - man.AddQueue(MakeQueue(denom, now - COINJOIN_QUEUE_TIMEOUT - 1, false, COutPoint(uint256S("12"), 0))); + man.AddQueue(MakeQueue(denom, now - std::chrono::seconds{COINJOIN_QUEUE_TIMEOUT + 1}, false, COutPoint(uint256S("12"), 0))); BOOST_CHECK_EQUAL(man.GetQueueSize(), 2); man.CheckQueue(); @@ -176,7 +160,7 @@ BOOST_AUTO_TEST_CASE(queuemanager_getqueueitem_marks_tried_once) { CoinJoinQueueManager man; const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - const int64_t now = GetAdjustedTime(); + const auto now{GetAdjustedTime()}; CCoinJoinQueue dsq = MakeQueue(denom, now, false, COutPoint(uint256S("21"), 0)); man.AddQueue(dsq); @@ -192,7 +176,7 @@ BOOST_AUTO_TEST_CASE(queuemanager_has_queue_from_masternode_readiness) { CoinJoinQueueManager man; const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - const int64_t now = GetAdjustedTime(); + const auto now{GetAdjustedTime()}; const COutPoint mn(uint256S("31"), 0); // Single queue from `mn` with fReady=false. man.AddQueue(MakeQueue(denom, now, /*fReady=*/false, mn)); @@ -220,7 +204,7 @@ BOOST_AUTO_TEST_CASE(queuemanager_try_check_duplicate) { CoinJoinQueueManager man; const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - const int64_t now = GetAdjustedTime(); + const auto now{GetAdjustedTime()}; const COutPoint mn(uint256S("41"), 0); const COutPoint other_mn(uint256S("42"), 0); @@ -236,7 +220,7 @@ BOOST_AUTO_TEST_CASE(queuemanager_try_check_duplicate) // Exact duplicate. BOOST_CHECK(is_dup(seed)); // Same masternode + same readiness, different nTime: the "too many dsqs" guard still flags it. - BOOST_CHECK(is_dup(MakeQueue(denom, now + 1, /*fReady=*/false, mn))); + BOOST_CHECK(is_dup(MakeQueue(denom, now + 1s, /*fReady=*/false, mn))); // Same masternode but different readiness: allowed. BOOST_CHECK(!is_dup(MakeQueue(denom, now, /*fReady=*/true, mn))); // Different masternode: allowed. diff --git a/src/test/governance_vote_wire_tests.cpp b/src/test/governance_vote_wire_tests.cpp index d2a164f67025..e4d1b28b57e4 100644 --- a/src/test/governance_vote_wire_tests.cpp +++ b/src/test/governance_vote_wire_tests.cpp @@ -2,7 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include +#include +#include #include #include #include @@ -93,4 +94,42 @@ BOOST_AUTO_TEST_CASE(ser_disk_deserialization_unaffected) BOOST_CHECK_EQUAL(ss.size(), 0U); } +BOOST_AUTO_TEST_CASE(chrono_cache_times_preserve_disk_encoding) +{ + CDataStream vote_stream = MakeVoteWire(CGovernanceVote::COMPACT_SIG_SIZE); + CGovernanceVote vote; + vote_stream >> vote; + + constexpr int64_t expiration{1'700'000'600}; + const governance::OrphanVote orphan_vote{vote, NodeSeconds{std::chrono::seconds{expiration}}}; + + CDataStream chrono_encoding{SER_DISK, PROTOCOL_VERSION}; + chrono_encoding << orphan_vote; + CDataStream integer_encoding{SER_DISK, PROTOCOL_VERSION}; + integer_encoding << vote << expiration; + BOOST_CHECK_EQUAL_COLLECTIONS( + chrono_encoding.begin(), chrono_encoding.end(), integer_encoding.begin(), integer_encoding.end()); + + governance::OrphanVote decoded; + chrono_encoding >> decoded; + BOOST_CHECK(decoded.vote.GetHash() == vote.GetHash()); + BOOST_CHECK_EQUAL(decoded.expiration.time_since_epoch().count(), expiration); + + constexpr int64_t creation_time{1'700'000'000}; + const vote_instance_t vote_instance{ + VOTE_OUTCOME_YES, NodeSeconds{std::chrono::seconds{expiration}}, creation_time}; + CDataStream chrono_vote_instance{SER_DISK, PROTOCOL_VERSION}; + chrono_vote_instance << vote_instance; + CDataStream integer_vote_instance{SER_DISK, PROTOCOL_VERSION}; + integer_vote_instance << int{VOTE_OUTCOME_YES} << expiration << creation_time; + BOOST_CHECK_EQUAL_COLLECTIONS( + chrono_vote_instance.begin(), chrono_vote_instance.end(), integer_vote_instance.begin(), integer_vote_instance.end()); + + vote_instance_t decoded_vote_instance; + chrono_vote_instance >> decoded_vote_instance; + BOOST_CHECK_EQUAL(decoded_vote_instance.eOutcome, VOTE_OUTCOME_YES); + BOOST_CHECK_EQUAL(decoded_vote_instance.last_update.time_since_epoch().count(), expiration); + BOOST_CHECK_EQUAL(decoded_vote_instance.nCreationTime, creation_time); +} + BOOST_AUTO_TEST_SUITE_END() From 0f409bfba1874af7e2342e060d0b1c743f21f36d Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Tue, 14 Jul 2026 11:37:05 -0500 Subject: [PATCH 2/2] fix: compare serialized timestamps in seconds --- src/Makefile.test.include | 1 + src/governance/governance.cpp | 6 +- src/governance/object.cpp | 24 ++++--- src/governance/vote.cpp | 3 +- src/spork.cpp | 6 +- src/test/chrono_overflow_tests.cpp | 103 +++++++++++++++++++++++++++++ 6 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 src/test/chrono_overflow_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e98111bed99c..72f3013a0908 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -98,6 +98,7 @@ BITCOIN_TESTS =\ test/checkqueue_tests.cpp \ test/cachemap_tests.cpp \ test/cachemultimap_tests.cpp \ + test/chrono_overflow_tests.cpp \ test/coins_tests.cpp \ test/coinstatsindex_tests.cpp \ test/compilerbug_tests.cpp \ diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index 5edb84a66bd6..2f353eefaa1d 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -283,7 +283,7 @@ void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj) ScopedLockBool guard(cs_store, fRateChecksEnabled, false); - const auto now{GetAdjustedTime()}; + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; const auto tip_mn_list = m_dmnman.GetListAtChainTip(); for (const auto& orphan_vote : orphan_votes) { const auto& vote = orphan_vote.vote; @@ -734,7 +734,7 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint(); const auto timestamp{govobj.CreationTime()}; - const auto now{GetAdjustedTime()}; + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; const auto superblock_cycle{Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().PowTargetSpacing()}; std::string strHash = govobj.GetHash().ToString(); @@ -882,7 +882,7 @@ void CGovernanceManager::CheckPostponedObjects() // Perform additional relays for triggers - const auto now{GetAdjustedTime()}; + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; const auto superblock_cycle{Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().PowTargetSpacing()}; for (auto it = setAdditionalRelayObjects.begin(); it != setAdditionalRelayObjects.end();) { diff --git a/src/governance/object.cpp b/src/governance/object.cpp index 2ecf7c406c1b..b4f9ccdc5707 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -144,9 +144,12 @@ bool ValidateStartEndEpoch(const UniValue& objJSON, bool fCheckExpiration, std:: return false; } - if (fCheckExpiration && NodeSeconds{std::chrono::seconds{nEndEpoch}} <= GetAdjustedTime()) { - strErrorMessages += "expired;"; - return false; + if (fCheckExpiration) { + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + if (NodeSeconds{std::chrono::seconds{nEndEpoch}} <= now) { + strErrorMessages += "expired;"; + return false; + } } return true; @@ -438,17 +441,20 @@ bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, bool fRateCh auto vote_time_update{voteInstanceRef.last_update}; if (fRateChecksEnabled) { - const auto now{GetAdjustedTime()}; - const auto time_delta{now - voteInstanceRef.last_update}; - if (time_delta < GOVERNANCE_UPDATE_MIN) { + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + const bool within_update_window{now < NodeSeconds::min() + GOVERNANCE_UPDATE_MIN || + voteInstanceRef.last_update > now - GOVERNANCE_UPDATE_MIN}; + if (within_update_window) { std::string msg{strprintf("CGovernanceObject::%s -- Masternode voting too often, MN outpoint = %s, " - "governance object hash = %s, time delta = %d", - __func__, vote.GetMasternodeOutpoint().ToStringShort(), GetHash().ToString(), Ticks(time_delta))}; + "governance object hash = %s, last update = %d, current time = %d", + __func__, vote.GetMasternodeOutpoint().ToStringShort(), GetHash().ToString(), + TicksSinceEpoch(voteInstanceRef.last_update), + TicksSinceEpoch(now))}; LogPrint(BCLog::GOBJECT, "%s\n", msg); exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_TEMPORARY_ERROR); return false; } - vote_time_update = std::chrono::time_point_cast(now); + vote_time_update = now; } bool onlyVotingKeyAllowed = m_obj.type == GovernanceObject::PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING; diff --git a/src/governance/vote.cpp b/src/governance/vote.cpp index e4e99528f5a4..ccb7a2dd2eae 100644 --- a/src/governance/vote.cpp +++ b/src/governance/vote.cpp @@ -158,7 +158,8 @@ bool CGovernanceVote::CheckSignature(const CBLSPublicKey& pubKey) const bool CGovernanceVote::IsValid(const CDeterministicMNList& tip_mn_list, bool useVotingKey) const { - const auto max_time{GetAdjustedTime() + 1h}; + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + const auto max_time{now + 1h}; if (Time() > max_time) { LogPrint(BCLog::GOBJECT, "CGovernanceVote::IsValid -- vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", GetHash().ToString(), nTime, TicksSinceEpoch(max_time)); return false; diff --git a/src/spork.cpp b/src/spork.cpp index 4148abdfdb25..612fa0ea88f9 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -125,7 +125,8 @@ void CSporkManager::CheckAndRemove() std::optional CSporkManager::GetValidSporkSigner(const CSporkMessage& spork) const { - if (spork.TimeSigned() > GetAdjustedTime() + 2h) { + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + if (spork.TimeSigned() > now + 2h) { LogPrint(BCLog::SPORK, "CSporkManager::%s -- ERROR: too far into the future\n", __func__); return std::nullopt; } @@ -226,7 +227,8 @@ bool CSporkManager::IsSporkActive(SporkId nSporkID) const SporkValue nSporkValue = GetSporkValue(nSporkID); // Get time is somewhat costly it looks like const NodeSeconds activation_time{std::chrono::seconds{nSporkValue}}; - const bool ret = activation_time < GetAdjustedTime(); + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + const bool ret = activation_time < now; // Only cache true values if (ret) { LOCK(cs_cache); diff --git a/src/test/chrono_overflow_tests.cpp b/src/test/chrono_overflow_tests.cpp new file mode 100644 index 000000000000..6b63ba0c4b12 --- /dev/null +++ b/src/test/chrono_overflow_tests.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2026 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +struct ChronoOverflowSetup : BasicTestingSetup { + ChronoOverflowSetup() : + BasicTestingSetup{CBaseChainParams::REGTEST} + { + } +}; + +BOOST_FIXTURE_TEST_SUITE(chrono_overflow_tests, ChronoOverflowSetup) + +BOOST_AUTO_TEST_CASE(spork_signed_time_extremes) +{ + auto& sporkman{*Assert(m_node.sporkman)}; + const CKey key{GenerateRandomKey()}; + const CKeyID signer_id{key.GetPubKey().GetID()}; + BOOST_REQUIRE(sporkman.SetSporkAddress(EncodeDestination(PKHash{key.GetPubKey()}))); + BOOST_REQUIRE(sporkman.SetMinSporkKeys(1)); + + CSporkMessage future_spork; + future_spork.nSporkID = SPORK_2_INSTANTSEND_ENABLED; + future_spork.nTimeSigned = std::numeric_limits::max(); + BOOST_REQUIRE(future_spork.Sign(key)); + BOOST_REQUIRE(future_spork.CheckSignature(signer_id)); + BOOST_CHECK(!sporkman.GetValidSporkSigner(future_spork)); + + CSporkMessage past_spork; + past_spork.nSporkID = SPORK_2_INSTANTSEND_ENABLED; + past_spork.nValue = std::numeric_limits::max(); + past_spork.nTimeSigned = std::numeric_limits::min(); + BOOST_REQUIRE(past_spork.Sign(key)); + const auto past_signer{sporkman.GetValidSporkSigner(past_spork)}; + BOOST_REQUIRE(past_signer); + BOOST_CHECK(*past_signer == signer_id); + BOOST_REQUIRE(sporkman.ProcessSpork(past_spork, *past_signer)); + BOOST_CHECK(!sporkman.IsSporkActive(past_spork.nSporkID)); + + CSporkMessage active_spork; + active_spork.nSporkID = SPORK_17_QUORUM_DKG_ENABLED; + active_spork.nValue = std::numeric_limits::min(); + active_spork.nTimeSigned = std::numeric_limits::min(); + BOOST_REQUIRE(active_spork.Sign(key)); + const auto active_signer{sporkman.GetValidSporkSigner(active_spork)}; + BOOST_REQUIRE(active_signer); + BOOST_REQUIRE(sporkman.ProcessSpork(active_spork, *active_signer)); + BOOST_CHECK(sporkman.IsSporkActive(active_spork.nSporkID)); +} + +BOOST_AUTO_TEST_CASE(governance_vote_future_time_extreme) +{ + const CKey voting_key{GenerateRandomKey()}; + const CKeyID voting_id{voting_key.GetPubKey().GetID()}; + const COutPoint collateral{uint256::ONE, 0}; + + auto dmn_state{std::make_shared()}; + dmn_state->keyIDOwner = voting_id; + dmn_state->keyIDVoting = voting_id; + dmn_state->netInfo = NetInfoInterface::MakeNetInfo(dmn_state->nVersion); + + auto dmn{std::make_shared(0)}; + dmn->proTxHash = uint256S("02"); + dmn->collateralOutpoint = collateral; + dmn->pdmnState = dmn_state; + + CDeterministicMNList mn_list{uint256{}, 0, 0}; + mn_list.AddMN(dmn); + + const auto sign_vote{[&](CGovernanceVote& vote) { + std::vector signature; + if (!CMessageSigner::SignMessage(vote.GetSignatureString(), signature, voting_key)) return false; + vote.SetSignature(signature); + return vote.CheckSignature(voting_id); + }}; + + CGovernanceVote current_vote{collateral, uint256::ONE, VOTE_SIGNAL_FUNDING, VOTE_OUTCOME_YES}; + current_vote.SetTime(GetAdjustedTime()); + BOOST_REQUIRE(sign_vote(current_vote)); + BOOST_CHECK(current_vote.IsValid(mn_list, /*useVotingKey=*/true)); + + CGovernanceVote future_vote{collateral, uint256::ONE, VOTE_SIGNAL_FUNDING, VOTE_OUTCOME_YES}; + future_vote.SetTime(std::numeric_limits::max()); + BOOST_REQUIRE(sign_vote(future_vote)); + BOOST_CHECK(!future_vote.IsValid(mn_list, /*useVotingKey=*/true)); +} + +BOOST_AUTO_TEST_SUITE_END()