diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 7cbb557c43..3e5adbe386 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -42,7 +42,12 @@ #include // For NDEBUG compile time check #include #include +#include #include +#include +#include +#include +#include #include #include @@ -176,6 +181,13 @@ static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND}; /** The compactblocks version we support. See BIP 152. */ static constexpr uint64_t CMPCTBLOCKS_VERSION{2}; +struct COrphanBlock { + uint256 hashBlock; + uint256 hashPrev; + std::pair stake; + std::vector vchBlock; +}; + // Internal stuff namespace { /** Blocks that are in flight, and that are in the queue to be downloaded. */ @@ -476,6 +488,93 @@ struct CNodeState { CNodeState(bool is_inbound) : m_is_inbound(is_inbound) {} }; +class CNodeHeaders +{ +public: + CNodeHeaders(): + maxSize(0), + maxAvg(0) + { + maxSize = gArgs.GetIntArg("-headerspamfiltermaxsize", GefaultHeaderSpamFilterMaxSize()); + maxAvg = gArgs.GetIntArg("-headerspamfiltermaxavg", DEFAULT_HEADER_SPAM_FILTER_MAX_AVG); + } + + bool addHeaders(const CBlockIndex *pindexFirst, const CBlockIndex *pindexLast) + { + if(pindexFirst && pindexLast && maxSize && maxAvg) + { + // Get the begin block index + int nBegin = pindexFirst->nHeight; + + // Get the end block index + int nEnd = pindexLast->nHeight; + + for(int point = nBegin; point<= nEnd; point++) + { + addPoint(point); + } + + return true; + } + + return false; + } + + bool updateState(BlockValidationState& state, bool ret) + { + // No headers + size_t size = points.size(); + if(size == 0) + return ret; + + // Compute the number of the received headers + size_t nHeaders = 0; + for(auto point : points) + { + nHeaders += point.second; + } + + // Compute the average value per height + double nAvgValue = (double)nHeaders / size; + + // Ban the node if try to spam + bool banNode = (nAvgValue >= 1.5 * maxAvg && size >= maxAvg) || + (nAvgValue >= maxAvg && nHeaders >= maxSize) || + (nHeaders >= maxSize * 4.1); + if(banNode) + { + // Clear the points and ban the node + points.clear(); + return state.Invalid(BlockValidationResult::BLOCK_HEADER_SPAM, "header-spam", "ban node for sending spam"); + } + + return ret; + } + +private: + void addPoint(int height) + { + // Erace the last element in the list + if(points.size() == maxSize) + { + points.erase(points.begin()); + } + + // Add the point to the list + int occurrence = 0; + auto mi = points.find(height); + if (mi != points.end()) + occurrence = (*mi).second; + occurrence++; + points[height] = occurrence; + } + +private: + std::map points; + size_t maxSize; + size_t maxAvg; +}; + class PeerManagerImpl final : public PeerManager { public: @@ -602,8 +701,8 @@ class PeerManagerImpl final : public PeerManager bool via_compact_block) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex, g_msgproc_mutex); /** Various helpers for headers processing, invoked by ProcessHeadersMessage() */ - /** Return true if headers are continuous and have valid proof-of-work (DoS points assigned on failure) */ - bool CheckHeadersPoW(const std::vector& headers, const Consensus::Params& consensusParams, Peer& peer); + /** Return true if headers are continuous and have valid proof-of-work and basic PoS checks (DoS points assigned on failure) */ + bool CheckHeadersSanity(const std::vector& headers, const Consensus::Params& consensusParams, Peer& peer); /** Calculate an anti-DoS work threshold for headers chains */ arith_uint256 GetAntiDoSWorkThreshold(); /** Deal with state tracking and headers sync for peers that send the @@ -612,6 +711,10 @@ class PeerManagerImpl final : public PeerManager void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector& headers) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); /** Return true if the headers connect to each other, false otherwise */ bool CheckHeadersAreContinuous(const std::vector& headers) const; + /** Return true if the PoS headers connect to each other, false otherwise */ + bool CheckPoSHeadersAreContinuous(const std::vector& headers, const CBlockIndex* chain_start_header) const; + /** Return true if the headers timestamp and signature size is correct for PoS */ + bool CheckHeadersTimestampAndSignatureSize(const std::vector& headers) const; /** Try to continue a low-work headers sync that has already begun. * Assumes the caller has already verified the headers connect, and has * checked that each header satisfies the proof-of-work target included in @@ -699,6 +802,29 @@ class PeerManagerImpl final : public PeerManager /** Send `feefilter` message. */ void MaybeSendFeefilter(CNode& node, Peer& peer, std::chrono::microseconds current_time) EXCLUSIVE_LOCKS_REQUIRED(g_msgproc_mutex); + /** Process net block. */ + void PushGetBlocks(CNode& node, const CBlockIndex* pindexBegin, const uint256& hashEnd); + uint256 GetOrphanRoot(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void PruneOrphanBlocks() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool ProcessNetBlockHeaders(CNode& node, const std::vector& block, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex=nullptr); + bool ProcessNetBlock(const std::shared_ptr pblock, bool force_processing, bool min_pow_checked, bool* new_block, CNode& node); + + /** Clean block index. */ + bool RemoveStateBlockIndex(CBlockIndex *pindex); + bool RemoveNetBlockIndex(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool NeedToEraseBlockIndex(const CBlockIndex *pindex, const CBlockIndex *pindexCheck) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool RemoveBlockIndex(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void CleanBlockIndex(); + CNodeHeaders& ServiceHeaders(const CService& address) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + void CleanAddressHeaders(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + std::map mapOrphanBlocks GUARDED_BY(cs_main); + std::multimap mapOrphanBlocksByPrev GUARDED_BY(cs_main); + std::set> setStakeSeenOrphan GUARDED_BY(cs_main); + size_t nOrphanBlocksSize = 0; + std::thread threadCleanBlockIndex; + std::atomic m_stop_thread_clean_block_index = false; + FastRandomContext m_rng GUARDED_BY(NetEventsInterface::g_msgproc_mutex); FeeFilterRounder m_fee_filter_rounder GUARDED_BY(NetEventsInterface::g_msgproc_mutex); @@ -740,6 +866,7 @@ class PeerManagerImpl final : public PeerManager /** Map maintaining per-node state. */ std::map m_node_states GUARDED_BY(cs_main); + std::map m_service_headers GUARDED_BY(cs_main); /** Get a pointer to a const CNodeState, used when not mutating the CNodeState object. */ const CNodeState* State(NodeId pnode) const EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -1136,6 +1263,26 @@ static bool CanServeWitnesses(const Peer& peer) return peer.m_their_services & NODE_WITNESS; } +CNodeHeaders& PeerManagerImpl::ServiceHeaders(const CService& address) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + unsigned short port = + gArgs.GetBoolArg("-headerspamfilterignoreport", DEFAULT_HEADER_SPAM_FILTER_IGNORE_PORT) ? 0 : address.GetPort(); + CService addr(address, port); + return m_service_headers[addr]; +} + +void PeerManagerImpl::CleanAddressHeaders(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + CSubNet subNet(addr); + for (std::map::iterator it=m_service_headers.begin(); it!=m_service_headers.end();){ + if(subNet.Match(it->first)) + { + it = m_service_headers.erase(it); + } + else{ + it++; + } + } +} + std::chrono::microseconds PeerManagerImpl::NextInvToInbounds(std::chrono::microseconds now, std::chrono::seconds average_interval) { @@ -1365,7 +1512,7 @@ void PeerManagerImpl::FindNextBlocksToDownload(const Peer& peer, unsigned int co // Make sure pindexBestKnownBlock is up to date, we'll need it. ProcessBlockAvailability(peer.m_id); - if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) { + if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork <= m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < m_chainman.MinimumChainWork()) { // This peer has nothing interesting. return; } @@ -1502,6 +1649,21 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, const Peer& peer) } } +bool PeerManagerImpl::ProcessNetBlockHeaders(CNode& pfrom, const std::vector& block, bool min_pow_checked, BlockValidationState& state, const CBlockIndex** ppindex) +{ + const CBlockIndex *pindexFirst = nullptr; + bool ret = m_chainman.ProcessNewBlockHeaders(block, min_pow_checked, state, ppindex, &pindexFirst); + if(gArgs.GetBoolArg("-headerspamfilter", DEFAULT_HEADER_SPAM_FILTER)) + { + LOCK(cs_main); + CNodeHeaders& headers = ServiceHeaders(pfrom.GetAddrLocal()); + const CBlockIndex *pindexLast = ppindex == nullptr ? nullptr : *ppindex; + headers.addHeaders(pindexFirst, pindexLast); + return headers.updateState(state, ret); + } + return ret; +} + void PeerManagerImpl::AddTxAnnouncement(const CNode& node, const GenTxid& gtxid, std::chrono::microseconds current_time) { AssertLockHeld(::cs_main); // For m_txrequest @@ -1785,6 +1947,7 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati case BlockValidationResult::BLOCK_INVALID_HEADER: case BlockValidationResult::BLOCK_CHECKPOINT: case BlockValidationResult::BLOCK_INVALID_PREV: + case BlockValidationResult::BLOCK_HEADER_SPAM: if (peer) Misbehaving(*peer, 100, message); return true; // Conflicting (but not necessarily invalid) data or different policy: @@ -1792,8 +1955,13 @@ bool PeerManagerImpl::MaybePunishNodeForBlock(NodeId nodeid, const BlockValidati // TODO: Handle this much more gracefully (10 DoS points is super arbitrary) if (peer) Misbehaving(*peer, 10, message); return true; + case BlockValidationResult::BLOCK_HEADER_SYNC: + case BlockValidationResult::BLOCK_GAS_EXCEEDS_LIMIT: + if (peer) Misbehaving(*peer, 1, message); + return true; case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE: case BlockValidationResult::BLOCK_TIME_FUTURE: + case BlockValidationResult::BLOCK_HEADER_REJECT: break; } if (message != "") { @@ -1812,6 +1980,10 @@ bool PeerManagerImpl::MaybePunishNodeForTx(NodeId nodeid, const TxValidationStat case TxValidationResult::TX_CONSENSUS: if (peer) Misbehaving(*peer, 100, ""); return true; + case TxValidationResult::TX_INVALID_SENDER_SCRIPT: + case TxValidationResult::TX_GAS_EXCEEDS_LIMIT: + if (peer) Misbehaving(*peer, 1, ""); + return true; // Conflicting (but not necessarily invalid) data or different policy: case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE: case TxValidationResult::TX_INPUTS_NOT_STANDARD: @@ -2489,7 +2661,7 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlo m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp)); } -bool PeerManagerImpl::CheckHeadersPoW(const std::vector& headers, const Consensus::Params& consensusParams, Peer& peer) +bool PeerManagerImpl::CheckHeadersSanity(const std::vector& headers, const Consensus::Params& consensusParams, Peer& peer) { // Do these headers have proof-of-work matching what's claimed? if (!HasValidProofOfWork(headers, consensusParams)) { @@ -2502,6 +2674,13 @@ bool PeerManagerImpl::CheckHeadersPoW(const std::vector& headers, Misbehaving(peer, 20, "non-continuous headers sequence"); return false; } + + // Are these headers have valid timestamp and signature size? + if (!CheckHeadersTimestampAndSignatureSize(headers)) { + Misbehaving(peer, 100, "header with invalid proof of stake"); + return false; + } + return true; } @@ -2509,11 +2688,12 @@ arith_uint256 PeerManagerImpl::GetAntiDoSWorkThreshold() { arith_uint256 near_chaintip_work = 0; LOCK(cs_main); - if (m_chainman.ActiveChain().Tip() != nullptr) { - const CBlockIndex *tip = m_chainman.ActiveChain().Tip(); - // Use a 144 block buffer, so that we'll accept headers that fork from + const CBlockIndex *tip = m_chainman.ActiveChain().Tip(); + if (tip != nullptr) { + // Use an auto selected sync checkpoint // near our tip. - near_chaintip_work = tip->nChainWork - std::min(144*GetBlockProof(*tip), tip->nChainWork); + const CBlockIndex* pcheckpoint = m_chainman.m_blockman.AutoSelectSyncCheckpoint(tip); + near_chaintip_work = pcheckpoint->nChainWork; } return std::max(near_chaintip_work, m_chainman.MinimumChainWork()); } @@ -2568,6 +2748,56 @@ bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector& return true; } +bool PeerManagerImpl::CheckPoSHeadersAreContinuous(const std::vector& headers, const CBlockIndex* chain_start_header) const +{ + int64_t nTimePrev = chain_start_header->GetBlockTime(); + int nHeight = chain_start_header->nHeight + 1; + const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); + for (const CBlockHeader& header : headers) + { + //reject proof of work at height consensusParams.nLastPOWBlock + if (header.IsProofOfWork() && nHeight > consensusParams.nLastPOWBlock) + return false; + + // Check coinstake timestamp + if (header.IsProofOfStake() && !CheckCoinStakeTimestamp(header.GetBlockTime(), nHeight, consensusParams)) + return false; + + // Check timestamp against prev + if (header.IsProofOfStake() && (header.GetBlockTime() <= nTimePrev || FutureDrift(header.GetBlockTime(), nHeight, consensusParams) < nTimePrev)) + return false; + + nTimePrev = header.GetBlockTime(); + nHeight++; + } + return true; +} + +bool PeerManagerImpl::CheckHeadersTimestampAndSignatureSize(const std::vector& headers) const +{ + int64_t timeLastBlock = m_chainparams.GenesisBlock().GetBlockTime(); + const Consensus::Params& consensusParams = m_chainparams.GetConsensus(); + for (const CBlockHeader& header : headers) { + if(header.IsProofOfStake()) + { + // Check coinstake timestamp + if (header.GetBlockTime() & consensusParams.MinStakeTimestampMask()) + return false; + + // Check timestamp against prev + if (header.GetBlockTime() <= timeLastBlock) + return false; + } + + // Check signature size + if(!CheckCanonicalBlockSignature(&header)) + return false; + + timeLastBlock = header.GetBlockTime(); + } + return true; +} + bool PeerManagerImpl::IsContinuationOfLowWorkHeadersSync(Peer& peer, CNode& pfrom, std::vector& headers) { if (peer.m_headers_sync) { @@ -2880,8 +3110,8 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, // We'll rely on headers having valid proof-of-work further down, as an // anti-DoS criteria (note: this check is required before passing any // headers into HeadersSyncState). - if (!CheckHeadersPoW(headers, m_chainparams.GetConsensus(), peer)) { - // Misbehaving() calls are handled within CheckHeadersPoW(), so we can + if (!CheckHeadersSanity(headers, m_chainparams.GetConsensus(), peer)) { + // Misbehaving() calls are handled within CheckHeadersSanity(), so we can // just return. (Note that even if a header is announced via compact // block, the header itself should be valid, so this type of error can // always be punished.) @@ -2937,6 +3167,13 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, return; } + // Check that the received PoS headers are continuous + if(!CheckPoSHeadersAreContinuous(headers, chain_start_header)) + { + Misbehaving(peer, 100, "invalid header received"); + return; + } + // If the headers we received are already in memory and an ancestor of // m_best_header or our tip, skip anti-DoS checks. These headers will not // use any more memory (and we are not leaking information that could be @@ -2977,7 +3214,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer, // Now process all the headers. BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders(headers, /*min_pow_checked=*/true, state, &pindexLast)) { + if (!ProcessNetBlockHeaders(pfrom, headers, /*min_pow_checked=*/true, state, &pindexLast)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received"); return; @@ -3255,7 +3492,7 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& node, Peer& peer, CDataStream& void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr& block, bool force_processing, bool min_pow_checked) { bool new_block{false}; - m_chainman.ProcessNewBlock(block, force_processing, min_pow_checked, &new_block); + ProcessNetBlock(block, force_processing, min_pow_checked, &new_block, node); if (new_block) { node.m_last_block_time = GetTime(); // In case this block came from a different peer than we requested @@ -3406,6 +3643,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + if (m_chainman.ActiveChain().Tip()->nHeight >= m_chainparams.GetConsensus().nShanghaiHeight && nVersion < MIN_PEER_PROTO_VERSION_AFTER_EVMSHANGHAI) { + // disconnect from peers older than this proto version + LogPrint(BCLog::NET, "peer=%d using obsolete version after evm Shanghai hardfork %i; disconnecting\n", pfrom.GetId(), nVersion); + pfrom.fDisconnect = true; + return; + } + if (!vRecv.empty()) { // The version message includes information about the sending node which we don't use: // - 8 bytes (service bits) @@ -3592,6 +3836,20 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, return; } + if (pfrom.nVersion < MIN_PEER_PROTO_VERSION) { + // disconnect from peers older than this proto version + LogPrint(BCLog::NET, "peer=%d using obsolete version %i; disconnecting\n", pfrom.GetId(), pfrom.nVersion); + pfrom.fDisconnect = true; + return; + } + + if (pfrom.nVersion < MIN_PEER_PROTO_VERSION_AFTER_EVMSHANGHAI && m_chainman.ActiveChain().Tip()->nHeight >= m_chainparams.GetConsensus().nShanghaiHeight) { + // disconnect from peers older than this proto version + LogPrint(BCLog::NET, "peer=%d using obsolete version after evm Shanghai hardfork %i; disconnecting\n", pfrom.GetId(), pfrom.nVersion); + pfrom.fDisconnect = true; + return; + } + // At this point, the outgoing message serialization version can't change. const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); @@ -4030,7 +4288,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, } // If pruning, don't inv blocks unless we have on disk and are likely to still have // for some reasonable time window (1 hour) that block relay might require. - const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / m_chainparams.GetConsensus().nPowTargetSpacing; + const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / m_chainparams.GetConsensus().TargetSpacing(pindex->nHeight); if (m_chainman.m_blockman.IsPruneMode() && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= m_chainman.ActiveChain().Tip()->nHeight - nPrunedBlocksLikelyToHave)) { LogPrint(BCLog::NET, " getblocks stopping, pruned or too old block at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); break; @@ -4416,7 +4674,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, const CBlockIndex *pindex = nullptr; BlockValidationState state; - if (!m_chainman.ProcessNewBlockHeaders({cmpctblock.header}, /*min_pow_checked=*/true, state, &pindex)) { + if (!ProcessNetBlockHeaders(pfrom, {cmpctblock.header}, /*min_pow_checked=*/true, state, &pindex)) { if (state.IsInvalid()) { MaybePunishNodeForBlock(pfrom.GetId(), state, /*via_compact_block=*/true, "invalid header via cmpctblock"); return; @@ -5019,6 +5277,9 @@ bool PeerManagerImpl::MaybeDiscourageAndDisconnect(CNode& pnode, Peer& peer) LogPrint(BCLog::NET, "Disconnecting and discouraging peer %d!\n", peer.m_id); if (m_banman) m_banman->Discourage(pnode.addr); m_connman.DisconnectNode(pnode.addr); + LOCK(cs_main); + // Remove all data from the header spam filter when the address is banned + CleanAddressHeaders(pnode.addr); return true; } @@ -5602,7 +5863,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} * - Ticks(GetAdjustedTime() - m_chainman.m_best_header->Time()) / consensusParams.nPowTargetSpacing + Ticks(GetAdjustedTime() - m_chainman.m_best_header->Time()) / consensusParams.TargetSpacing(m_chainman.m_best_header->nHeight) ); nSyncStarted++; } @@ -5892,7 +6153,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) if (state.vBlocksInFlight.size() > 0) { QueuedBlock &queuedBlock = state.vBlocksInFlight.front(); int nOtherPeersWithValidatedDownloads = m_peers_downloading_from - 1; - if (current_time > state.m_downloading_since + std::chrono::seconds{consensusParams.nPowTargetSpacing} * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { + if (current_time > state.m_downloading_since + std::chrono::seconds{consensusParams.TargetSpacing(m_chainman.m_best_header->nHeight)} * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) { LogPrintf("Timeout downloading block %s from peer=%d%s, disconnecting\n", queuedBlock.pindex->GetBlockHash().ToString(), pto->GetId(), fLogIPs ? strprintf(" peeraddr=%s", pto->addr.ToStringAddrPort()) : ""); pto->fDisconnect = true; return true; @@ -6005,12 +6266,328 @@ bool PeerManagerImpl::SendMessages(CNode* pto) return true; } +void PeerManagerImpl::PushGetBlocks(CNode& node, const CBlockIndex* pindexBegin, const uint256& hashEnd) +{ + const CNetMsgMaker msgMaker(node.GetCommonVersion()); + m_connman.PushMessage(&node, msgMaker.Make(NetMsgType::GETBLOCKS, GetLocator(pindexBegin), hashEnd)); +} + +uint256 PeerManagerImpl::GetOrphanRoot(const uint256& hash) +{ + AssertLockHeld(cs_main); + std::map::iterator it = mapOrphanBlocks.find(hash); + if (it == mapOrphanBlocks.end()) + return hash; + + // Work back to the first block in the orphan chain + do { + std::map::iterator it2 = mapOrphanBlocks.find(it->second->hashPrev); + if (it2 == mapOrphanBlocks.end()) + return it->first; + it = it2; + } while(true); +} + +// Remove a random orphan block (which does not have any dependent orphans). +void PeerManagerImpl::PruneOrphanBlocks() +{ + AssertLockHeld(cs_main); + size_t nMaxOrphanBlocksSize = gArgs.GetIntArg("-maxorphanblocksmib", DEFAULT_MAX_ORPHAN_BLOCKS) * ((size_t) 1 << 20); + while (nOrphanBlocksSize > nMaxOrphanBlocksSize) + { + // Pick a random orphan block. + uint256 randomhash = GetRandHash(); + std::multimap::iterator it = mapOrphanBlocksByPrev.lower_bound(randomhash); + if (it == mapOrphanBlocksByPrev.end()) + it = mapOrphanBlocksByPrev.begin(); + + // As long as this block has other orphans depending on it, move to one of those successors. + do { + std::multimap::iterator it2 = mapOrphanBlocksByPrev.find(it->second->hashBlock); + if (it2 == mapOrphanBlocksByPrev.end()) + break; + it = it2; + } while(1); + + setStakeSeenOrphan.erase(it->second->stake); + uint256 hash = it->second->hashBlock; + nOrphanBlocksSize -= it->second->vchBlock.size(); + delete it->second; + mapOrphanBlocksByPrev.erase(it); + mapOrphanBlocks.erase(hash); + } +} + +bool PeerManagerImpl::ProcessNetBlock(const std::shared_ptr pblock, bool force_processing, bool min_pow_checked, bool* new_block, CNode& pfrom) +{ + PeerRef peer = GetPeerRef(pfrom.GetId()); + uint256 hash; + { + LOCK(cs_main); + + // Check that the coinstake transaction exist in the received block + if(pblock->IsProofOfStake() && !(pblock->vtx.size() > 1 && pblock->vtx[1]->IsCoinStake())) + { + if (peer) Misbehaving(*peer, 100, "Coinstake transaction does not exist"); + return error("ProcessNetBlock() : coinstake transaction does not exist"); + } + + // Check for duplicate orphan block + // Duplicate stake allowed only when there is orphan child block + // if the block header is already known, allow it (to account for headers being sent before the block itself) + hash = pblock->GetHash(); + if (!m_chainman.m_blockman.LoadingBlocks() && pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !m_chainman.BlockIndex().count(hash) && !mapOrphanBlocksByPrev.count(hash)) + return error("ProcessNetBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString(), pblock->GetProofOfStake().second, hash.ToString()); + } + + // Process the header before processing the block + const CBlockIndex *pindex = nullptr; + BlockValidationState state; + if (!ProcessNetBlockHeaders(pfrom, {*pblock}, min_pow_checked, state, &pindex)) { + if (state.IsInvalid()) { + MaybePunishNodeForBlock(pfrom.GetId(), state, false, strprintf("Peer %d sent us invalid header\n", pfrom.GetId())); + return error("ProcessNetBlock() : invalid header received"); + } + } + + { + LOCK(cs_main); + if (mapOrphanBlocks.count(hash)) + return error("ProcessNetBlock() : already have block (orphan) %s", hash.ToString()); + + // Check for the checkpoint + CBlockIndex* tip = m_chainman.ActiveChain().Tip(); + if (tip && pblock->hashPrevBlock != tip->GetBlockHash()) + { + // Extra checks to prevent "fill up memory by spamming with bogus blocks" + const CBlockIndex* pcheckpoint = m_chainman.m_blockman.AutoSelectSyncCheckpoint(tip); + int64_t deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; + if (deltaTime < 0) + { + if (peer) Misbehaving(*peer, 1, "Block with timestamp before last checkpoint"); + return error("ProcessNetBlock() : block with timestamp before last checkpoint"); + } + } + + // Check for the signiture encoding + if (!CheckCanonicalBlockSignature(pblock.get())) + { + if (peer) Misbehaving(*peer, 100, "Bad block signature encoding"); + return error("ProcessNetBlock(): bad block signature encoding"); + } + + // If we don't already have its previous block, shunt it off to holding area until we get it + if (!m_chainman.BlockIndex().count(pblock->hashPrevBlock)) + { + LogPrintf("ProcessNetBlock: ORPHAN BLOCK %lu, prev=%s\n", (unsigned long)mapOrphanBlocks.size(), pblock->hashPrevBlock.ToString()); + + // Accept orphans as long as there is a node to request its parents from + // ppcoin: check proof-of-stake + if (pblock->IsProofOfStake()) + { + // Limited duplicity on stake: prevents block flood attack + // Duplicate stake allowed only when there is orphan child block + if (setStakeSeenOrphan.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) + return error("ProcessNetBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock->GetProofOfStake().first.ToString(), pblock->GetProofOfStake().second, hash.ToString()); + } + PruneOrphanBlocks(); + COrphanBlock* pblock2 = new COrphanBlock(); + { + CDataStream ss(SER_DISK, CLIENT_VERSION); + ss << *pblock; + Span cs = MakeUCharSpan(ss); + pblock2->vchBlock = std::vector(cs.begin(), cs.end()); + } + pblock2->hashBlock = hash; + pblock2->hashPrev = pblock->hashPrevBlock; + pblock2->stake = pblock->GetProofOfStake(); + nOrphanBlocksSize += pblock2->vchBlock.size(); + mapOrphanBlocks.insert(std::make_pair(hash, pblock2)); + mapOrphanBlocksByPrev.insert(std::make_pair(pblock2->hashPrev, pblock2)); + if (pblock->IsProofOfStake()) + setStakeSeenOrphan.insert(pblock->GetProofOfStake()); + + // Ask this guy to fill in what we're missing + PushGetBlocks(pfrom, m_chainman.m_best_header, GetOrphanRoot(hash)); + return true; + } + } + + if(!m_chainman.ProcessNewBlock(pblock, force_processing, min_pow_checked, new_block)) + return error("%s: ProcessNewBlock FAILED", __func__); + + std::vector vWorkQueue; + vWorkQueue.push_back(pblock->GetHash()); + for (unsigned int i = 0; i < vWorkQueue.size(); i++) + { + uint256 hashPrev = vWorkQueue[i]; + for (std::multimap::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev); + mi != mapOrphanBlocksByPrev.upper_bound(hashPrev); + ++mi) + { + CBlock block; + { + CDataStream ss(mi->second->vchBlock, SER_DISK, CLIENT_VERSION); + ss >> block; + } + block.hashMerkleRoot = BlockMerkleRoot(block); + + bool new_blockOrphan = false; + std::shared_ptr shared_pblock = std::make_shared(block); + if (m_chainman.ProcessNewBlock(shared_pblock, force_processing, min_pow_checked, &new_blockOrphan)) + vWorkQueue.push_back(mi->second->hashBlock); + + LOCK(cs_main); + mapOrphanBlocks.erase(mi->second->hashBlock); + setStakeSeenOrphan.erase(block.GetProofOfStake()); + nOrphanBlocksSize -= mi->second->vchBlock.size(); + delete mi->second; + } + + LOCK(cs_main); + mapOrphanBlocksByPrev.erase(hashPrev); + } + + return true; +} + +bool PeerManagerImpl::RemoveStateBlockIndex(CBlockIndex *pindex) +{ + AssertLockHeld(cs_main); + return m_chainman.ActiveChainstate().RemoveBlockIndex(pindex); +} + +bool PeerManagerImpl::RemoveNetBlockIndex(CBlockIndex *pindex) +{ + AssertLockHeld(cs_main); + // Make sure it's not listed somewhere already. + RemoveBlockRequest(pindex->GetBlockHash(), std::nullopt); + + for (std::map::iterator it=m_node_states.begin(); it!=m_node_states.end(); it++) + { + CNodeState * state = &it->second; + + if(state->pindexBestKnownBlock == pindex) + state->pindexBestKnownBlock = nullptr; + + if(state->pindexLastCommonBlock == pindex) + state->pindexLastCommonBlock = nullptr; + + if(state->pindexBestHeaderSent == pindex) + state->pindexBestHeaderSent = nullptr; + + if(state->m_chain_sync.m_work_header == pindex) + state->m_chain_sync.m_work_header = nullptr; + } + + return true; +} + +bool PeerManagerImpl::NeedToEraseBlockIndex(const CBlockIndex *pindex, const CBlockIndex *pindexCheck) +{ + AssertLockHeld(cs_main); + if(!m_chainman.ActiveChain().Contains(pindex)) + { + if(pindex->nHeight <= pindexCheck->nHeight) return true; + const CBlockIndex *pindexBlock = pindex; + while(pindexBlock) + { + pindexBlock = pindexBlock->pprev; + if(pindexBlock->nHeight == pindexCheck->nHeight) return pindexBlock != pindexCheck; + } + } + return false; +} + +bool PeerManagerImpl::RemoveBlockIndex(CBlockIndex *pindex) +{ + AssertLockHeld(cs_main); + bool ret = RemoveStateBlockIndex(pindex); + ret &= RemoveNetBlockIndex(pindex); + return ret; +} + +void PeerManagerImpl::CleanBlockIndex() +{ + unsigned int cleanTimeout = gArgs.GetIntArg("-cleanblockindextimeout", DEFAULT_CLEANBLOCKINDEXTIMEOUT); + if(cleanTimeout == 0) cleanTimeout = DEFAULT_CLEANBLOCKINDEXTIMEOUT; + + while(!m_stop_thread_clean_block_index) + { + if(!m_chainman.IsInitialBlockDownload()) + { + // Select block indexes to delete + std::vector indexNeedErase; + { + LOCK(cs_main); + int nHeight = m_chainman.ActiveChain().Height(); + int checkpointSpan = Params().GetConsensus().CheckpointSpan(nHeight); + const CBlockIndex *pindexCheck = m_chainman.ActiveChain()[nHeight - checkpointSpan -1]; + if(pindexCheck) + { + for (node::BlockMap::iterator it=m_chainman.BlockIndex().begin(); it!=m_chainman.BlockIndex().end(); it++) + { + CBlockIndex *pindex = &((*it).second); + if(NeedToEraseBlockIndex(pindex, pindexCheck)) + { + indexNeedErase.push_back(pindex->GetBlockHash()); + } + } + } + } + + // Delete selected block indexes + if(indexNeedErase.size() > 0) + { + SyncWithValidationInterfaceQueue(); + + LOCK(cs_main); + std::vector indexEraseDB; + for(uint256 blockHash : indexNeedErase) + { + node::BlockMap::iterator it=m_chainman.BlockIndex().find(blockHash); + if(it!=m_chainman.BlockIndex().end()) + { + CBlockIndex *pindex = &((*it).second); + if(RemoveBlockIndex(pindex)) + { + // The map contain instance of CBlockIndex + // which is deleted when the iterator is deleted + m_chainman.BlockIndex().erase(it); + indexEraseDB.push_back(blockHash); + } + } + } + + if(m_chainman.m_blockman.m_block_tree_db) + { + if(!m_chainman.m_blockman.m_block_tree_db->EraseBlockIndex(indexEraseDB)) + { + LogPrintf("Fail to erase block indexes.\n"); + } + } + } + } + + for(unsigned int i = 0; (i < cleanTimeout) && !m_stop_thread_clean_block_index; i++) + UninterruptibleSleep(std::chrono::seconds{1}); + } +} + void PeerManagerImpl::InitCleanBlockIndex() { + m_stop_thread_clean_block_index = false; + if(gArgs.GetBoolArg("-cleanblockindex", DEFAULT_CLEANBLOCKINDEX)) + threadCleanBlockIndex = std::thread(&util::TraceThread, "cleanindex", [this] { CleanBlockIndex(); }); } void PeerManagerImpl::StopCleanBlockIndex() { + if(!m_stop_thread_clean_block_index) { + m_stop_thread_clean_block_index = true; + if (threadCleanBlockIndex.joinable()) + threadCleanBlockIndex.join(); + } } unsigned int GefaultHeaderSpamFilterMaxSize() diff --git a/src/validation.cpp b/src/validation.cpp index f786187c43..47962b5d6e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -1792,7 +1793,21 @@ bool CheckHeaderPoW(const CBlockHeader& block, const Consensus::Params& consensu bool CheckHeaderPoS(const CBlockHeader& block, const Consensus::Params& consensusParams, Chainstate& chainstate) { - return {}; + LOCK(cs_main); + // Check for proof of stake block header + // Get prev block index + BlockMap::iterator mi = chainstate.m_blockman.m_block_index.find(block.hashPrevBlock); + if (mi == chainstate.m_blockman.m_block_index.end()) + return false; + + // Check the kernel hash + CBlockIndex* pindexPrev = &((*mi).second); + + if(pindexPrev->nHeight >= consensusParams.nEnableHeaderSignatureHeight && !CheckRecoveredPubKeyFromBlockSignature(pindexPrev, block, chainstate.CoinsTip(), chainstate.m_chain)) { + return error("Failed signature check"); + } + + return CheckKernel(pindexPrev, block.nBits, block.StakeTime(), block.prevoutStake, chainstate.CoinsTip(), chainstate.m_chain); } bool CheckHeaderProof(const CBlockHeader& block, const Consensus::Params& consensusParams, Chainstate& chainstate){ @@ -2577,7 +2592,87 @@ bool CheckMinGasPrice(std::vector& etps, const uint64_t& m bool CheckReward(const CBlock& block, BlockValidationState& state, int nHeight, const Consensus::Params& consensusParams, CAmount nFees, CAmount gasRefunds, CAmount nActualStakeReward, const std::vector& vouts, CAmount nValueCoinPrev, bool delegateOutputExist, CChain& chain, node::BlockManager& blockman) { - return {}; + size_t offset = block.IsProofOfStake() ? 1 : 0; + std::vector vTempVouts=block.vtx[offset]->vout; + std::vector::iterator it; + for(size_t i = 0; i < vouts.size(); i++){ + it=std::find(vTempVouts.begin(), vTempVouts.end(), vouts[i]); + if(it==vTempVouts.end()){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-gas-refund-missing", "CheckReward(): Gas refund missing"); + }else{ + vTempVouts.erase(it); + } + } + + // Check block reward + if (block.IsProofOfWork()) + { + // Check proof-of-work reward + CAmount blockReward = nFees + GetBlockSubsidy(nHeight, consensusParams); + if (block.vtx[offset]->GetValueOut() > blockReward) { + LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)\n", block.vtx[offset]->GetValueOut(), blockReward); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-amount"); + } + } + else + { + // Check full reward + CAmount blockReward = nFees + GetBlockSubsidy(nHeight, consensusParams); + if (nActualStakeReward > blockReward) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-amount", strprintf("CheckReward(): coinstake pays too much (actual=%d vs limit=%d)", nActualStakeReward, blockReward)); + + // The first proof-of-stake blocks get full reward, the rest of them are split between recipients + int rewardRecipients = 1; + int nPrevHeight = nHeight -1; + if(nPrevHeight >= consensusParams.nFirstMPoSBlock && nPrevHeight < consensusParams.nLastMPoSBlock) + rewardRecipients = consensusParams.nMPoSRewardRecipients; + + // Check reward recipients number + if(rewardRecipients < 1) + return error("CheckReward(): invalid reward recipients"); + + // Check reward can cover the gas refunds + if(blockReward < gasRefunds){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-gas-greater-than-reward", "CheckReward(): Block Reward is less than total gas refunds"); + } + + CAmount splitReward = (blockReward - gasRefunds) / rewardRecipients; + + // Check that the reward is in the second output for the staker and the third output for the delegate + // Delegation contract data like the fee is checked in CheckProofOfStake + if(block.HasProofOfDelegation()) + { + CAmount nReward = blockReward - gasRefunds - splitReward * (rewardRecipients -1); + CAmount nValueStaker = block.vtx[offset]->vout[1].nValue; + CAmount nValueDelegate = delegateOutputExist ? block.vtx[offset]->vout[2].nValue : 0; + CAmount nMinedReward = nValueStaker + nValueDelegate - nValueCoinPrev; + if(nReward != nMinedReward) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-delegate-reward", "CheckReward(): The block reward is not split correctly between the staker and the delegate"); + } + + //if only 1 then no MPoS logic required + if(rewardRecipients == 1){ + return true; + } + + // Generate the list of mpos outputs including all of their parameters + std::vector mposOutputList; + if(!GetMPoSOutputs(mposOutputList, splitReward, nPrevHeight, consensusParams, chain, blockman)) + return error("CheckReward(): cannot create the list of MPoS outputs"); + + for(size_t i = 0; i < mposOutputList.size(); i++){ + it=std::find(vTempVouts.begin(), vTempVouts.end(), mposOutputList[i]); + if(it==vTempVouts.end()){ + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cs-mpos-missing", "CheckReward(): An MPoS participant was not properly paid"); + }else{ + vTempVouts.erase(it); + } + } + + vTempVouts.clear(); + } + + return true; } valtype GetSenderAddress(const CTransaction& tx, const CCoinsViewCache* coinsView, const std::vector* blockTxs, Chainstate& chainstate, const CTxMemPool* mempool, int nOut = -1){ @@ -2885,6 +2980,36 @@ size_t QtumTxConverter::correctedStackSize(size_t size){ } /////////////////////////////////////////////////////////////////////// +bool CheckDelegationOutput(const CBlock& block, bool& delegateOutputExist, CCoinsViewCache& view, Chainstate& chainstate) +{ + if(block.IsProofOfStake() && block.HasProofOfDelegation()) + { + uint160 staker; + std::vector vchPubKey; + if(GetBlockPublicKey(block, vchPubKey)) + { + staker = uint160(ToByteVector(CPubKey(vchPubKey).GetID())); + uint160 address; + uint8_t fee = 0; + if(GetBlockDelegation(block, staker, address, fee, view, chainstate)) + { + delegateOutputExist = IsDelegateOutputExist(fee); + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + + return true; +} + /** Apply the effects of this block (with given index) on the UTXO set represented by coins. * Validity checks that depend on the UTXO set are also done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ @@ -2929,6 +3054,13 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, } bool delegateOutputExist = false; + if (!CheckDelegationOutput(block, delegateOutputExist, view, *this)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-delegate-output", strprintf("%s : delegation output check failed", __func__)); + } + + if (block.IsProofOfStake() && pindex->nHeight > params.GetConsensus().nEnableHeaderSignatureHeight && !CheckBlockInputPubKeyMatchesOutputPubKey(block, view, delegateOutputExist)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-coinstake-input-output-mismatch"); + } // Check it again in case a previous version let a bad block in // NOTE: We don't currently (re-)invoke ContextualCheckBlock() or @@ -4815,7 +4947,54 @@ bool GetBlockPublicKey(const CBlock& block, std::vector& vchPubKe bool GetBlockDelegation(const CBlock& block, const uint160& staker, uint160& address, uint8_t& fee, CCoinsViewCache& view, Chainstate& chainstate) { - return {}; + // Check block parameters + if (block.IsProofOfWork()) + return false; + + if (block.vchBlockSigDlgt.empty()) + return false; + + if (!block.HasProofOfDelegation()) + return false; + + if(block.vtx.size() < 1) + return false; + + // Get the delegate + std::string strMessage = staker.GetReverseHex(); + CKeyID keyid; + if(!SignStr::GetKeyIdMessage(strMessage, block.GetProofOfDelegation(), keyid)) + return false; + address = uint160(keyid); + + // Get the fee from the delegation contract + uint8_t inFee = 0; + if(!GetDelegationFeeFromContract(address, inFee, chainstate)) + return false; + + bool delegateOutputExist = IsDelegateOutputExist(inFee); + size_t minVoutSize = delegateOutputExist ? 3 : 2; + if(block.vtx[1]->vin.size() < 1 || + block.vtx[1]->vout.size() < minVoutSize) + return false; + + // Get the staker fee + COutPoint prevout = block.vtx[1]->vin[0].prevout; + CAmount nValueCoin = view.AccessCoin(prevout).out.nValue; + if(nValueCoin <= 0) + return false; + + CAmount nValueStaker = block.vtx[1]->vout[1].nValue - nValueCoin; + CAmount nValueDelegate = delegateOutputExist ? block.vtx[1]->vout[2].nValue : 0; + CAmount nReward = nValueStaker + nValueDelegate; + if(nReward <= 0) + return false; + + fee = (nValueStaker * 100 + nReward - 1) / nReward; + if(inFee != fee) + return false; + + return true; } bool CheckBlockSignature(const CBlock& block) @@ -5172,7 +5351,41 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat bool Chainstate::UpdateHashProof(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, CBlockIndex* pindex, CCoinsViewCache& view) { - return {}; + int nHeight = pindex->nHeight; + uint256 hash = block.GetHash(); + + //reject proof of work at height consensusParams.nLastPOWBlock + if (block.IsProofOfWork() && nHeight > consensusParams.nLastPOWBlock) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "reject-pow", strprintf("UpdateHashProof() : reject proof-of-work at height %d", nHeight)); + + // Check coinstake timestamp + if (block.IsProofOfStake() && !CheckCoinStakeTimestamp(block.GetBlockTime(), nHeight, consensusParams)) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "timestamp-invalid", strprintf("UpdateHashProof() : coinstake timestamp violation nTimeBlock=%d", block.GetBlockTime())); + + // Check proof-of-work or proof-of-stake + if (block.nBits != GetNextWorkRequired(pindex->pprev, &block, consensusParams,block.IsProofOfStake())) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", strprintf("UpdateHashProof() : incorrect %s", block.IsProofOfWork() ? "proof-of-work" : "proof-of-stake")); + + uint256 hashProof; + // Verify hash target and signature of coinstake tx + if (block.IsProofOfStake()) + { + uint256 targetProofOfStake; + if (!CheckProofOfStake(pindex->pprev, state, *block.vtx[1], block.nBits, block.nTime, block.GetProofOfDelegation(), block.prevoutStake, hashProof, targetProofOfStake, view, *this)) + { + return error("UpdateHashProof() : check proof-of-stake failed for block %s", hash.ToString()); + } + } + + // PoW is checked in CheckBlock() + if (block.IsProofOfWork()) + { + hashProof = block.GetHash(); + } + + // Record proof hash value + pindex->hashProof = hashProof; + return true; } bool CheckPOS(const CBlockHeader& block, CBlockIndex* pindexPrev, Chainstate& chainstate) @@ -5299,6 +5512,30 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida } } } + + // Reject proof of work at height consensusParams.nLastPOWBlock + int nHeight = pindexPrev->nHeight + 1; + if (block.IsProofOfWork() && nHeight > GetConsensus().nLastPOWBlock) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "reject-pow", strprintf("reject proof-of-work at height %d", nHeight)); + + if(block.IsProofOfStake()) + { + // Reject proof of stake before height coinbaseMaturity + int coinbaseMaturity = GetConsensus().CoinbaseMaturity(nHeight); + if (nHeight < coinbaseMaturity) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "reject-pos", strprintf("reject proof-of-stake at height %d", nHeight)); + + // Check coin stake timestamp + if(!CheckCoinStakeTimestamp(block.nTime, nHeight, GetConsensus())) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "timestamp-invalid", "proof of stake failed due to invalid timestamp"); + } + + // Check block header + // if (!CheckBlockHeader(block, state, GetConsensus(), true, CheckPOS(block, pindexPrev))) + if (!CheckBlockHeader(block, state, GetConsensus(), chainstate)) { + LogPrint(BCLog::VALIDATION, "%s: Consensus::CheckBlockHeader: %s, %s\n", __func__, hash.ToString(), state.ToString()); + return false; + } } if (!min_pow_checked) { LogPrint(BCLog::VALIDATION, "%s: not adding new block header %s, missing anti-dos proof-of-work validation\n", __func__, hash.ToString()); @@ -5437,12 +5674,56 @@ bool ChainstateManager::AcceptBlock(const std::shared_ptr& pblock, if (!accepted_header) return false; + if(block.IsProofOfWork()) { + if (!ActiveChainstate().UpdateHashProof(block, state, GetParams().GetConsensus(), pindex, ActiveChainstate().CoinsTip())) + { + return error("%s: AcceptBlock(): %s", __func__, state.GetRejectReason().c_str()); + } + } + + // Get prev block index + CBlockIndex* pindexPrev = nullptr; + if(pindex->nHeight > 0){ + BlockMap::iterator mi = m_blockman.m_block_index.find(block.hashPrevBlock); + if (mi == m_blockman.m_block_index.end()) + return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, "prev-blk-not-found", strprintf("%s: prev block not found", __func__)); + pindexPrev = &((*mi).second); + } + + // Get block height + int nHeight = pindex->nHeight; + + // Check for the last proof of work block + if (block.IsProofOfWork() && nHeight > GetParams().GetConsensus().nLastPOWBlock) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "reject-pow", strprintf("%s: reject proof-of-work at height %d", __func__, nHeight)); + + // Check that the block satisfies synchronized checkpoint + if (!m_blockman.CheckSync(nHeight, ActiveTip())) + return error("AcceptBlock() : rejected by synchronized checkpoint"); + + // Check timestamp against prev + if (pindexPrev && block.IsProofOfStake() && (block.GetBlockTime() <= pindexPrev->GetBlockTime() || FutureDrift(block.GetBlockTime(), nHeight, GetParams().GetConsensus()) < pindexPrev->GetBlockTime())) + return error("AcceptBlock() : block's timestamp is too early"); + + // Check timestamp + if (block.IsProofOfStake() && block.GetBlockTime() > FutureDrift(GetAdjustedTimeSeconds(), nHeight, GetParams().GetConsensus())) + return error("AcceptBlock() : block timestamp too far in the future"); + + // Enforce rule that the coinbase starts with serialized block height + if (nHeight >= GetParams().GetConsensus().BIP34Height) + { + CScript expect = CScript() << nHeight; + if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || + !std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cb-height", "block height mismatch in coinbase"); + } + // Check all requested blocks that we do not already have for validity and // save them to disk. Skip processing of unrequested blocks as an anti-DoS // measure, unless the blocks have more work than the active chain tip, and // aren't too far ahead of it, so are likely to be attached soon. bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA; - bool fHasMoreOrSameWork = (ActiveTip() ? pindex->nChainWork >= ActiveTip()->nChainWork : true); + bool fHasMoreWork = (ActiveTip() ? pindex->nChainWork > ActiveTip()->nChainWork : true); // Blocks that are too out-of-order needlessly limit the effectiveness of // pruning, because pruning will not delete block files that contain any // blocks which are too close in height to the tip. Apply this test @@ -5461,7 +5742,7 @@ bool ChainstateManager::AcceptBlock(const std::shared_ptr& pblock, if (fAlreadyHave) return true; if (!fRequested) { // If we didn't ask for it: if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned - if (!fHasMoreOrSameWork) return true; // Don't process less-work chains + if (!fHasMoreWork) return true; // Don't process less-work OR equal-work chains if (fTooFarAhead) return true; // Block height is too high // Protect against DoS attacks from low-work chains.