From ccb8bcebfca6c59488d3074828181530a906c455 Mon Sep 17 00:00:00 2001 From: MacroFake Date: Tue, 14 Jul 2026 01:03:18 -0500 Subject: [PATCH 1/2] Merge bitcoin/bitcoin#25786: refactor: Make adjusted time type safe Upstream commits: fa3be799fe951a7ea9b4de78d5a907c6db71eeb8 Add time helpers eeee5ada23f2a71d245671556b6ecfdaabfeddf4 Make adjusted time type safe --- src/bitcoin-chainstate.cpp | 2 +- src/chain.h | 6 ++++++ src/consensus/params.h | 5 +++++ src/kernel/chainstatemanager_opts.h | 4 +++- src/net_processing.cpp | 8 ++++---- src/node/miner.cpp | 4 ++-- src/primitives/block.h | 6 ++++++ src/timedata.cpp | 4 ++-- src/timedata.h | 2 +- src/validation.cpp | 9 +++++---- src/validation.h | 4 ++-- 11 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp index 07373cc00cbd..82e9b58fc5e3 100644 --- a/src/bitcoin-chainstate.cpp +++ b/src/bitcoin-chainstate.cpp @@ -89,7 +89,7 @@ int main(int argc, char* argv[]) // SETUP: Chainstate const ChainstateManager::Options chainman_opts{ .chainparams = chainparams, - .adjusted_time_callback = static_cast(GetTime), + .adjusted_time_callback = NodeClock::now, }; ChainstateManager chainman{chainman_opts}; diff --git a/src/chain.h b/src/chain.h index 4236e5a7adff..9dfb5e86aba6 100644 --- a/src/chain.h +++ b/src/chain.h @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -255,6 +256,11 @@ class CBlockIndex */ bool HaveTxsDownloaded() const { return nChainTx != 0; } + NodeSeconds Time() const + { + return NodeSeconds{std::chrono::seconds{nTime}}; + } + int64_t GetBlockTime() const { return (int64_t)nTime; diff --git a/src/consensus/params.h b/src/consensus/params.h index 0c1f99825c01..a7f8feb96b17 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -176,6 +177,10 @@ struct Params { int64_t nPowTargetTimespan; int nPowKGWHeight; int nPowDGWHeight; + std::chrono::seconds PowTargetSpacing() const + { + return std::chrono::seconds{nPowTargetSpacing}; + } int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; } /** The best chain should have at least this much work */ uint256 nMinimumChainWork; diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 510a1f9edcfa..520d0e8e7525 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H +#include + #include #include @@ -19,7 +21,7 @@ namespace kernel { */ struct ChainstateManagerOpts { const CChainParams& chainparams; - const std::function adjusted_time_callback{nullptr}; + const std::function adjusted_time_callback{nullptr}; }; } // namespace kernel diff --git a/src/net_processing.cpp b/src/net_processing.cpp index b8d059ae1abd..eece8ea0e594 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1408,7 +1408,7 @@ bool PeerManagerImpl::TipMayBeStale() bool PeerManagerImpl::CanDirectFetch() { - return m_chainman.ActiveChain().Tip()->GetBlockTime() > GetAdjustedTime() - m_chainparams.GetConsensus().nPowTargetSpacing * 20; + return m_chainman.ActiveChain().Tip()->Time() > GetAdjustedTime() - m_chainparams.GetConsensus().PowTargetSpacing() * 20; } static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -6218,7 +6218,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (!state.fSyncStarted && CanServeBlocks(*peer) && !fImporting && !fReindex && pto->CanRelay()) { // Only actively request headers from a single peer, unless we're close to end of initial download. - if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - nMaxTipAge) { + if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->Time() > GetAdjustedTime() - std::chrono::seconds{nMaxTipAge}) { const CBlockIndex* pindexStart = m_chainman.m_best_header; /* If possible, start at the block preceding the currently best known header. This ensures that we always get a @@ -6239,7 +6239,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling // to maintain precision std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} * - (GetAdjustedTime() - m_chainman.m_best_header->GetBlockTime()) / consensusParams.nPowTargetSpacing + Ticks(GetAdjustedTime() - m_chainman.m_best_header->Time()) / consensusParams.nPowTargetSpacing ); nSyncStarted++; } @@ -6620,7 +6620,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) // Check for headers sync timeouts if (state.fSyncStarted && state.m_headers_sync_timeout < std::chrono::microseconds::max()) { // Detect whether this is a stalling initial-headers-sync peer - if (m_chainman.m_best_header->GetBlockTime() <= GetAdjustedTime() - nMaxTipAge) { + if (m_chainman.m_best_header->Time() <= GetAdjustedTime() - std::chrono::seconds{nMaxTipAge}) { if (current_time > state.m_headers_sync_timeout && nSyncStarted == 1 && (m_num_preferred_download_peers - state.fPreferredDownload >= 1)) { // Disconnect a peer (without NetPermissionFlags::NoBan permission) if it is our only sync peer, // and we have others we could be using instead. diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 656e21d35fd8..9cbb56e12782 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -49,7 +49,7 @@ namespace node { int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { int64_t nOldTime = pblock->nTime; - int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast() + 1, GetAdjustedTime()); + int64_t nNewTime{std::max(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch(GetAdjustedTime()))}; if (nOldTime < nNewTime) { pblock->nTime = nNewTime; @@ -218,7 +218,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc pblock->nVersion = gArgs.GetIntArg("-blockversion", pblock->nVersion); } - pblock->nTime = GetAdjustedTime(); + pblock->nTime = TicksSinceEpoch(GetAdjustedTime()); m_lock_time_cutoff = pindexPrev->GetMedianTimePast(); if (fDIP0003Active_context) { diff --git a/src/primitives/block.h b/src/primitives/block.h index 92061e5d82bb..0d91fc041cd8 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -12,6 +12,7 @@ #include #include #include +#include /** Nodes collect new transactions into a block, hash them into a hash tree, * and scan through nonce values to make the block's hash satisfy proof-of-work @@ -55,6 +56,11 @@ class CBlockHeader uint256 GetHash() const; + NodeSeconds Time() const + { + return NodeSeconds{std::chrono::seconds{nTime}}; + } + int64_t GetBlockTime() const { return (int64_t)nTime; diff --git a/src/timedata.cpp b/src/timedata.cpp index ceee08e68c4e..fe9a5fbed7fc 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -32,9 +32,9 @@ int64_t GetTimeOffset() return nTimeOffset; } -int64_t GetAdjustedTime() +NodeClock::time_point GetAdjustedTime() { - return GetTime() + GetTimeOffset(); + return NodeClock::now() + std::chrono::seconds{GetTimeOffset()}; } #define BITCOIN_TIMEDATA_MAX_SAMPLES 200 diff --git a/src/timedata.h b/src/timedata.h index 3779486c1f88..fae4d60b3b9a 100644 --- a/src/timedata.h +++ b/src/timedata.h @@ -75,7 +75,7 @@ class CMedianFilter /** Functions to keep track of adjusted P2P time */ int64_t GetTimeOffset(); -int64_t GetAdjustedTime(); +NodeClock::time_point GetAdjustedTime(); void AddTimeData(const CNetAddr& ip, int64_t nTime); /** diff --git a/src/validation.cpp b/src/validation.cpp index 8fbf243f64fa..b6780c188969 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3916,7 +3916,7 @@ bool IsBlockMutated(const CBlock& block) * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const ChainstateManager& chainman, const CBlockIndex* pindexPrev, NodeClock::time_point now) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { AssertLockHeld(::cs_main); assert(pindexPrev != nullptr); @@ -3956,8 +3956,9 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", strprintf("block's timestamp is too early %d %d", block.GetBlockTime(), pindexPrev->GetMedianTimePast())); // Check timestamp - if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) - return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", strprintf("block timestamp too far in the future %d %d", block.GetBlockTime(), nAdjustedTime + 2 * 60 * 60)); + if (block.Time() > now + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME}) { + return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE, "time-too-new", strprintf("block timestamp too far in the future %d %d", block.GetBlockTime(), TicksSinceEpoch(now) + 2 * 60 * 60)); + } // Reject blocks with outdated version if ((block.nVersion < 2 && DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), Consensus::DEPLOYMENT_HEIGHTINCB)) || @@ -4361,7 +4362,7 @@ bool TestBlockValidity(BlockValidationState& state, Chainstate& chainstate, const CBlock& block, CBlockIndex* pindexPrev, - const std::function& adjusted_time_callback, + const std::function& adjusted_time_callback, bool fCheckPOW, bool fCheckMerkleRoot) { diff --git a/src/validation.h b/src/validation.h index 78ebdf366c04..66c1ac09dd1b 100644 --- a/src/validation.h +++ b/src/validation.h @@ -382,7 +382,7 @@ bool TestBlockValidity(BlockValidationState& state, Chainstate& chainstate, const CBlock& block, CBlockIndex* pindexPrev, - const std::function& adjusted_time_callback, + const std::function& adjusted_time_callback, bool fCheckPOW = true, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -904,7 +904,7 @@ class ChainstateManager const CChainParams m_chainparams; - const std::function m_adjusted_time_callback; + const std::function m_adjusted_time_callback; //! Internal helper for ActivateSnapshot(). [[nodiscard]] bool PopulateAndValidateSnapshot( From 40e554c929cd6a4aac8a50de84afd0e2e8f10f06 Mon Sep 17 00:00:00 2001 From: Pasta Date: Tue, 14 Jul 2026 01:17:20 -0500 Subject: [PATCH 2/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/Makefile.test.include | 1 + src/coinjoin/coinjoin.cpp | 13 ++-- src/coinjoin/coinjoin.h | 13 ++-- src/governance/governance.cpp | 55 ++++++++--------- src/governance/governance.h | 26 ++++++-- src/governance/object.cpp | 25 +++++--- 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 | 7 ++- src/spork.h | 7 ++- src/test/coinjoin_inouts_tests.cpp | 15 +++-- src/test/coinjoin_queue_tests.cpp | 76 ++++++++++-------------- src/test/governance_validators_tests.cpp | 18 ++++++ src/test/governance_vote_wire_tests.cpp | 50 +++++++++++++++- src/test/spork_tests.cpp | 41 +++++++++++++ test/util/data/non-backported.txt | 1 + 20 files changed, 266 insertions(+), 133 deletions(-) create mode 100644 src/test/spork_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index e98111bed99c..c789b8c35bd8 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -186,6 +186,7 @@ BITCOIN_TESTS =\ test/sigopcount_tests.cpp \ test/skiplist_tests.cpp \ test/sock_tests.cpp \ + test/spork_tests.cpp \ test/streams_tests.cpp \ test/subsidy_tests.cpp \ test/sync_tests.cpp \ 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..2f353eefaa1d 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{std::chrono::time_point_cast(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{std::chrono::time_point_cast(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{std::chrono::time_point_cast(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..9f66f39f7a48 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -144,7 +144,8 @@ bool ValidateStartEndEpoch(const UniValue& objJSON, bool fCheckExpiration, std:: return false; } - if (fCheckExpiration && nEndEpoch <= GetAdjustedTime()) { + const auto now{std::chrono::time_point_cast(GetAdjustedTime())}; + if (fCheckExpiration && NodeSeconds{std::chrono::seconds{nEndEpoch}} <= now) { strErrorMessages += "expired;"; return false; } @@ -346,6 +347,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 +437,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{std::chrono::time_point_cast(GetAdjustedTime())}; + if (voteInstanceRef.last_update > now - 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)}; + "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; } - 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 +466,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..b1ba269872be 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{std::chrono::time_point_cast(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..0c0a69168585 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.nTimeSigned > GetAdjustedTime() + 2 * 60 * 60) { + const auto max_time{std::chrono::time_point_cast(GetAdjustedTime() + 2h)}; + if (spork.TimeSigned() > max_time) { LogPrint(BCLog::SPORK, "CSporkManager::%s -- ERROR: too far into the future\n", __func__); return std::nullopt; } @@ -225,7 +226,9 @@ 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 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/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_validators_tests.cpp b/src/test/governance_validators_tests.cpp index c79dcb915a7a..3ee0a2126bac 100644 --- a/src/test/governance_validators_tests.cpp +++ b/src/test/governance_validators_tests.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -91,4 +92,21 @@ BOOST_AUTO_TEST_CASE(invalid_proposals_test) } } +BOOST_AUTO_TEST_CASE(extreme_end_epoch_is_not_expired) +{ + UniValue proposal{UniValue::VOBJ}; + proposal.pushKV("end_epoch", std::numeric_limits::max()); + proposal.pushKV("name", "extreme-end-epoch"); + proposal.pushKV("payment_address", "XpG61qAVhdyN7AqVZQsHfJL7AEk4dPVinc"); + proposal.pushKV("payment_amount", 1.0); + proposal.pushKV("start_epoch", std::numeric_limits::max() - 1); + proposal.pushKV("type", 1); + proposal.pushKV("url", "https://example.com"); + + std::string error; + BOOST_CHECK_MESSAGE(governance::ValidateProposal(HexStr(proposal.write()), error, + /*fCheckExpiration=*/true, /*fAllowScript=*/false), + error); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/governance_vote_wire_tests.cpp b/src/test/governance_vote_wire_tests.cpp index d2a164f67025..71bd113fe8e2 100644 --- a/src/test/governance_vote_wire_tests.cpp +++ b/src/test/governance_vote_wire_tests.cpp @@ -2,7 +2,9 @@ // 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 @@ -93,4 +95,50 @@ BOOST_AUTO_TEST_CASE(ser_disk_deserialization_unaffected) BOOST_CHECK_EQUAL(ss.size(), 0U); } +BOOST_AUTO_TEST_CASE(extreme_timestamp_is_rejected_without_overflow) +{ + CGovernanceVote vote; + vote.SetTime(std::numeric_limits::max()); + + BOOST_CHECK(!vote.IsValid(CDeterministicMNList{}, /*useVotingKey=*/false)); +} + +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() diff --git a/src/test/spork_tests.cpp b/src/test/spork_tests.cpp new file mode 100644 index 000000000000..ab526f6abed3 --- /dev/null +++ b/src/test/spork_tests.cpp @@ -0,0 +1,41 @@ +// 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