Files
thehub/hub/server/main.cpp

2986 lines
124 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (c) 2009-2010 Satoshi Nakamoto
* Copyright (c) 2009-2015 The Bitcoin Core developers
* Copyright (C) 2017-2024 Tom Zander <tom@flowee.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "main.h"
#include "addrman.h"
#include "blocklocator.h"
#include "chainparams.h"
#include "consensus/consensus.h"
#include "merkleblock.h"
#include "consensus/validation.h"
#include "DoubleSpendProof.h"
#include "DoubleSpendProofStorage.h"
#include "UiInterface.h"
#include "init.h"
#include "serverutil.h"
#include "timedata.h"
#include "txmempool.h"
#include "txorphancache.h"
#include "thinblock.h"
#include "validation/Engine.h"
#include <Application.h>
#include <SettingsDefaults.h>
#include <Logger.h>
#include <util.h>
#include <utils/hash.h>
#include <utiltime.h>
#include <utilstrencodings.h>
#include "validationinterface.h"
#include <primitives/Block.h>
#include <utxo/UnspentOutputDatabase.h>
#include <BlocksDB.h>
#include <BlocksDB_p.h> // for Blocks::BlockFileInfo
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
/**
* Global state
*/
CCriticalSection cs_main;
CChain chainActive;
CBlockIndex *pindexBestHeader = nullptr;
CWaitableCriticalSection csBestBlock;
CConditionVariable cvBlockChange;
bool fIsBareMultisigStd = Settings::DefaultPermitBareMultisig;
bool fRequireStandard = true;
bool fCheckpointsEnabled = Settings::DefaultCheckpointsEnabled;
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying, mining and transaction creation) */
CFeeRate minRelayTxFee = CFeeRate(Settings::DefaultMinRelayTxFee);
CTxMemPool mempool;
const std::string strMessageMagic = "Bitcoin Signed Message:\n";
CCriticalSection cs_LastBlockFile;
std::vector<Blocks::BlockFileInfo> vinfoBlockFile;
int nLastBlockFile = 0;
/** Dirty block file entries. */
std::set<int> setDirtyFileInfo;
// Internal stuff
namespace {
struct CBlockIndexWorkComparator
{
bool operator()(CBlockIndex *pa, CBlockIndex *pb) const {
// First sort by most total work, ...
if (pa->nChainWork > pb->nChainWork) return false;
if (pa->nChainWork < pb->nChainWork) return true;
// ... then by earliest time received, ...
if (pa->nSequenceId < pb->nSequenceId) return false;
if (pa->nSequenceId > pb->nSequenceId) return true;
// Use pointer address as tie breaker (should only happen with blocks
// loaded from disk, as those all have id 0).
if (pa < pb) return false;
if (pa > pb) return true;
// Identical blocks.
return false;
}
};
/** Number of nodes with fSyncStarted. */
int nSyncStarted = 0;
/**
* Every received block is assigned a unique and increasing identifier, so we
* know which one to give priority in case of a fork.
*/
CCriticalSection cs_nBlockSequenceId;
/** Blocks loaded from disk are assigned id 0, so start the counter at 1. */
uint32_t nBlockSequenceId = 1;
/**
* Sources of received blocks, saved to be able to send them reject
* messages or ban them when processing happens afterwards. Protected by
* cs_main.
*/
std::map<uint256, NodeId> mapBlockSource;
/**
* Filter for transactions that were recently rejected by
* AcceptToMemoryPool. These are not rerequested until the chain tip
* changes, at which point the entire filter is reset. Protected by
* cs_main.
*
* Without this filter we'd be re-requesting txs from each of our peers,
* increasing bandwidth consumption considerably. For instance, with 100
* peers, half of which relay a tx we don't accept, that might be a 50x
* bandwidth increase. A flooding attacker attempting to roll-over the
* filter using minimum-sized, 60byte, transactions might manage to send
* 1000/sec if we have fast peers, so we pick 120,000 to give our peers a
* two minute window to send invs to us.
*
* Decreasing the false positive rate is fairly cheap, so we pick one in a
* million to make it highly unlikely for users to have issues with this
* filter.
*
* Memory used: 1.7MB
*/
boost::scoped_ptr<CRollingBloomFilter> recentRejects;
uint256 hashRecentRejectsChainTip;
/** Blocks that are in flight, and that are in the queue to be downloaded. Protected by cs_main. */
struct QueuedBlock {
uint256 hash;
CBlockIndex* pindex; //!< Optional.
bool fValidatedHeaders; //!< Whether this block has validated headers at the time of request.
};
std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> > mapBlocksInFlight;
/** Number of preferable block download peers. */
int nPreferredDownload = 0;
/** Dirty block index entries. The block index instances not yet persisted to (index) DB */
std::set<CBlockIndex*> setDirtyBlockIndex;
/** Number of peers from which we're downloading blocks. */
int nPeersWithValidatedDownloads = 0;
} // anon namespace
//////////////////////////////////////////////////////////////////////////////
//
// Registration of network node signals.
//
namespace {
struct CBlockReject {
unsigned char chRejectCode;
std::string strRejectReason;
uint256 hashBlock;
};
/**
* Maintain validation-specific state about nodes, protected by cs_main, instead
* by CNode's own locks. This simplifies asynchronous operation, where
* processing of incoming data is done after the ProcessMessage call returns,
* and we're no longer holding the node's locks.
*/
struct CNodeState {
//! The peer's address
CService address;
//! Whether we have a fully established connection.
bool fCurrentlyConnected;
//! Accumulated misbehaviour score for this peer.
int nMisbehavior;
//! Whether this peer should be disconnected and banned (unless whitelisted).
bool fShouldBan;
//! List of asynchronously-determined block rejections to notify this peer about.
std::vector<CBlockReject> rejects;
//! The best known block we know this peer has announced.
CBlockIndex *pindexBestKnownBlock;
//! The hash of the last unknown block this peer has announced.
uint256 hashLastUnknownBlock;
//! The last full block we both have.
CBlockIndex *pindexLastCommonBlock;
//! The best header we have sent our peer.
CBlockIndex *pindexBestHeaderSent;
//! Whether we've started headers synchronization with this peer.
bool fSyncStarted;
//! Since when we're stalling block download progress (in microseconds), or 0.
int64_t nStallingSince;
std::list<QueuedBlock> vBlocksInFlight;
//! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty.
int64_t nDownloadingSince;
int nBlocksInFlight;
int nBlocksInFlightValidHeaders;
//! Whether we consider this a preferred download peer.
bool fPreferredDownload;
//! Whether this peer wants invs or headers (when possible) for block announcements.
bool fPreferHeaders;
CNodeState() {
fCurrentlyConnected = false;
nMisbehavior = 0;
fShouldBan = false;
pindexBestKnownBlock = nullptr;
hashLastUnknownBlock.SetNull();
pindexLastCommonBlock = nullptr;
pindexBestHeaderSent = nullptr;
ResetASERTAnchorBlockCache();
fSyncStarted = false;
nStallingSince = 0;
nDownloadingSince = 0;
nBlocksInFlight = 0;
nBlocksInFlightValidHeaders = 0;
fPreferredDownload = false;
fPreferHeaders = false;
}
};
/** Map maintaining per-node state. Requires cs_main. */
std::map<NodeId, CNodeState> mapNodeState;
// Requires cs_main.
CNodeState *State(NodeId pnode) {
std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode);
if (it == mapNodeState.end())
return nullptr;
return &it->second;
}
int GetHeight()
{
LOCK(cs_main);
return chainActive.Height();
}
void UpdatePreferredDownload(CNode* node, CNodeState* state)
{
nPreferredDownload -= state->fPreferredDownload;
// Whether this node should be marked as a preferred download node.
state->fPreferredDownload = (!node->fInbound || node->fWhitelisted) && !node->fOneShot && !node->fClient;
nPreferredDownload += state->fPreferredDownload;
}
void InitializeNode(NodeId nodeid, const CNode *pnode) {
LOCK(cs_main);
CNodeState &state = mapNodeState.insert(std::make_pair(nodeid, CNodeState())).first->second;
state.address = pnode->addr;
}
void FinalizeNode(NodeId nodeid) {
LOCK(cs_main);
CNodeState *state = State(nodeid);
assert(state);
if (state->fSyncStarted)
nSyncStarted--;
if (state->nMisbehavior == 0 && state->fCurrentlyConnected) {
AddressCurrentlyConnected(state->address);
}
for (const QueuedBlock& entry : state->vBlocksInFlight) {
mapBlocksInFlight.erase(entry.hash);
}
nPreferredDownload -= state->fPreferredDownload;
nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0);
assert(nPeersWithValidatedDownloads >= 0);
mapNodeState.erase(nodeid);
if (mapNodeState.empty()) {
// Do a consistency check after the last peer is removed.
assert(mapBlocksInFlight.empty());
assert(nPreferredDownload == 0);
assert(nPeersWithValidatedDownloads == 0);
}
}
}
// Requires cs_main.
// Returns a bool indicating whether we requested this block.
bool MarkBlockAsReceived(const uint256& hash) {
std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash);
if (itInFlight != mapBlocksInFlight.end()) {
CNodeState *state = State(itInFlight->second.first);
state->nBlocksInFlightValidHeaders -= itInFlight->second.second->fValidatedHeaders;
if (state->nBlocksInFlightValidHeaders == 0 && itInFlight->second.second->fValidatedHeaders) {
// Last validated block on the queue was received.
nPeersWithValidatedDownloads--;
}
if (state->vBlocksInFlight.begin() == itInFlight->second.second) {
// First block on the queue was received, update the start download time for the next one
state->nDownloadingSince = std::max(state->nDownloadingSince, GetTimeMicros());
}
state->vBlocksInFlight.erase(itInFlight->second.second);
state->nBlocksInFlight--;
state->nStallingSince = 0;
mapBlocksInFlight.erase(itInFlight);
return true;
}
return false;
}
bool IsBlockInFlight(const uint256 &hash)
{
return mapBlocksInFlight.count(hash) > 0;
}
namespace {
// Requires cs_main.
void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Params&, CBlockIndex *pindex = nullptr) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
// Make sure it's not listed somewhere already.
MarkBlockAsReceived(hash);
QueuedBlock newentry = {hash, pindex, pindex != nullptr};
std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
state->nBlocksInFlight++;
state->nBlocksInFlightValidHeaders += newentry.fValidatedHeaders;
if (state->nBlocksInFlight == 1) {
// We're starting a block download (batch) from this peer.
state->nDownloadingSince = GetTimeMicros();
}
if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) {
nPeersWithValidatedDownloads++;
}
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
}
/** Check whether the last unknown block a peer advertised is not yet known. */
void ProcessBlockAvailability(NodeId nodeid) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
if (!state->hashLastUnknownBlock.IsNull()) {
auto bi = Blocks::Index::get(state->hashLastUnknownBlock);
if (bi && bi->nChainWork > 0) {
if (state->pindexBestKnownBlock == nullptr || bi->nChainWork >= state->pindexBestKnownBlock->nChainWork)
state->pindexBestKnownBlock = bi;
state->hashLastUnknownBlock.SetNull();
}
}
}
/** Update tracking information about which blocks a peer is assumed to have. */
void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
ProcessBlockAvailability(nodeid);
auto bi = Blocks::Index::get(hash);
if (bi && bi->nChainWork > 0) {
// An actually better block was announced.
if (state->pindexBestKnownBlock == nullptr || bi->nChainWork >= state->pindexBestKnownBlock->nChainWork)
state->pindexBestKnownBlock = bi;
} else {
// An unknown block was announced; just assume that the latest one is the best one.
state->hashLastUnknownBlock = hash;
}
}
// Requires cs_main
/// returns true if the Hub has as 'tip' a chain that is close in time to 'now'.
bool CanDirectFetch(const Consensus::Params &consensusParams)
{
return chainActive.Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20;
}
// Requires cs_main
bool PeerHasHeader(CNodeState *state, CBlockIndex *pindex)
{
if (state->pindexBestKnownBlock && pindex == state->pindexBestKnownBlock->GetAncestor(pindex->nHeight))
return true;
if (state->pindexBestHeaderSent && pindex == state->pindexBestHeaderSent->GetAncestor(pindex->nHeight))
return true;
return false;
}
/** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has
* at most count entries. */
void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBlockIndex*>& vBlocks, NodeId& nodeStaller) {
if (count == 0)
return;
vBlocks.reserve(vBlocks.size() + count);
CNodeState *state = State(nodeid);
assert(state != nullptr);
// Make sure pindexBestKnownBlock is up to date, we'll need it.
ProcessBlockAvailability(nodeid);
if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < chainActive.Tip()->nChainWork) {
// This peer has nothing interesting.
return;
}
if (state->pindexLastCommonBlock == nullptr) {
// Bootstrap quickly by guessing a parent of our best tip is the forking point.
// Guessing wrong in either direction is not a problem.
state->pindexLastCommonBlock = chainActive[std::min(state->pindexBestKnownBlock->nHeight, chainActive.Height())];
}
// If the peer reorganized, our previous pindexLastCommonBlock may not be an ancestor
// of its current tip anymore. Go back enough to fix that.
state->pindexLastCommonBlock = Blocks::Index::lastCommonAncestor(state->pindexLastCommonBlock, state->pindexBestKnownBlock);
if (state->pindexLastCommonBlock == state->pindexBestKnownBlock)
return;
std::vector<CBlockIndex*> vToFetch;
CBlockIndex *pindexWalk = state->pindexLastCommonBlock;
// Never fetch further than the best block we know the peer has, or more than BLOCK_DOWNLOAD_WINDOW + 1 beyond the last
// linked block we have in common with this peer. The +1 is so we can detect stalling, namely if we would be able to
// download that next block if the window were 1 larger.
int nWindowEnd = state->pindexLastCommonBlock->nHeight + BLOCK_DOWNLOAD_WINDOW;
int nMaxHeight = std::min<int>(state->pindexBestKnownBlock->nHeight, nWindowEnd + 1);
NodeId waitingfor = -1;
while (pindexWalk->nHeight < nMaxHeight) {
// Read up to 128 (or more, if more blocks than that are needed) successors of pindexWalk (towards
// pindexBestKnownBlock) into vToFetch. We fetch 128, because CBlockIndex::GetAncestor may be as expensive
// as iterating over ~100 CBlockIndex* entries anyway.
int nToFetch = std::min(nMaxHeight - pindexWalk->nHeight, std::max<int>(count - vBlocks.size(), 128));
vToFetch.resize(nToFetch);
pindexWalk = state->pindexBestKnownBlock->GetAncestor(pindexWalk->nHeight + nToFetch);
vToFetch[nToFetch - 1] = pindexWalk;
for (unsigned int i = nToFetch - 1; i > 0; i--) {
vToFetch[i - 1] = vToFetch[i]->pprev;
}
// Iterate over those blocks in vToFetch (in forward direction), adding the ones that
// are not yet downloaded and not in flight to vBlocks. In the mean time, update
// pindexLastCommonBlock as long as all ancestors are already downloaded, or if it's
// already part of our chain (and therefore don't need it even if pruned).
for (CBlockIndex* pindex : vToFetch) {
if (!pindex->IsValid(BLOCK_VALID_TREE)) {
// We consider the chain that this peer is on invalid.
return;
}
if (pindex->nStatus & BLOCK_HAVE_DATA || chainActive.Contains(pindex)) {
if (pindex->nChainTx)
state->pindexLastCommonBlock = pindex;
} else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) {
// The block is not already downloaded, and not yet in flight.
if (pindex->nHeight > nWindowEnd) {
// We reached the end of the window.
if (vBlocks.size() == 0 && waitingfor != nodeid) {
// We aren't able to fetch anything, but we would be if the download window was one larger.
nodeStaller = waitingfor;
}
return;
}
vBlocks.push_back(pindex);
if (vBlocks.size() == count) {
return;
}
} else if (waitingfor == -1) {
// This is the first already-in-flight block.
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
}
}
}
}
} // anon namespace
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) {
LOCK(cs_main);
CNodeState *state = State(nodeid);
if (state == nullptr)
return false;
stats.nMisbehavior = state->nMisbehavior;
stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
stats.nCommonHeight = state->pindexLastCommonBlock ? state->pindexLastCommonBlock->nHeight : -1;
for (const QueuedBlock& queue : state->vBlocksInFlight) {
if (queue.pindex)
stats.vHeightInFlight.push_back(queue.pindex->nHeight);
}
return true;
}
void RegisterNodeSignals(CNodeSignals& nodeSignals)
{
nodeSignals.GetHeight.connect(&GetHeight);
nodeSignals.ProcessMessages.connect(&ProcessMessages);
nodeSignals.SendMessages.connect(&SendMessages);
nodeSignals.InitializeNode.connect(&InitializeNode);
nodeSignals.FinalizeNode.connect(&FinalizeNode);
}
void UnregisterNodeSignals(CNodeSignals& nodeSignals)
{
nodeSignals.GetHeight.disconnect(&GetHeight);
nodeSignals.ProcessMessages.disconnect(&ProcessMessages);
nodeSignals.SendMessages.disconnect(&SendMessages);
nodeSignals.InitializeNode.disconnect(&InitializeNode);
nodeSignals.FinalizeNode.disconnect(&FinalizeNode);
}
CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator)
{
// Find the first block the caller has in the main chain
for (const uint256& hash : locator.vHave) {
auto pindex = Blocks::Index::get(hash);
if (pindex) {
if (chain.Contains(pindex))
return pindex;
}
}
return chain.Genesis();
}
bool IsFinalTx(const Tx &tx, int nBlockHeight, int64_t nBlockTime)
{
Tx::Iterator iter(tx);
bool sequenceFailed = false;
while (iter.next(Tx::LockTime | Tx::Sequence) != Tx::End) {
if (iter.tag() == Tx::LockTime) {
auto lockTime = iter.uintData();
if (lockTime == 0)
return true;
if ((int64_t)lockTime < ((int64_t)lockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
return true;
}
else if (!sequenceFailed && iter.tag() == Tx::Sequence) {
auto sequence = iter.uintData();
if (sequence != CTxIn::SEQUENCE_FINAL)
sequenceFailed = true;
}
}
return !sequenceFailed;
}
bool CheckFinalTx(const Tx &tx, int flags)
{
AssertLockHeld(cs_main);
// By convention a negative value for flags indicates that the
// current network-enforced consensus rules should be used. In
// a future soft-fork scenario that would mean checking which
// rules would be enforced for the next block and setting the
// appropriate flags.
flags = std::max(flags, 0);
// CheckFinalTx() uses chainActive.Height()+1 to evaluate
// nLockTime because when IsFinalTx() is called within
// CBlock::AcceptBlock(), the height of the block *being*
// evaluated is what is used. Thus if we want to know if a
// transaction can be part of the *next* block, we need to call
// IsFinalTx() with one more than chainActive.Height().
const int nBlockHeight = chainActive.Height() + 1;
// BIP113 will require that time-locked transactions have nLockTime set to
// less than the median time of the previous block they're contained in.
// When the next block is created its previous block will be the current
// chain tip, so we use that to calculate the median time passed to
// IsFinalTx() if LOCKTIME_MEDIAN_TIME_PAST is set.
const int64_t nBlockTime = (flags & LOCKTIME_MEDIAN_TIME_PAST)
? chainActive.Tip()->GetMedianTimePast()
: GetAdjustedTime();
return IsFinalTx(tx, nBlockHeight, nBlockTime);
}
/**
* Calculates the block height and previous block's median time past at
* which the transaction will be considered final in the context of BIP 68.
* Also removes from the vector of input heights any entries which did not
* correspond to sequence locked inputs as they do not affect the calculation.
*/
static std::pair<int, int64_t> CalculateSequenceLocks(const CTransaction &tx, int flags, std::vector<int>* prevHeights, const CBlockIndex& block)
{
assert(prevHeights->size() == tx.vin.size());
// Will be set to the equivalent height- and time-based nLockTime
// values that would be necessary to satisfy all relative lock-
// time constraints given our view of block chain history.
// The semantics of nLockTime are the last invalid height/time, so
// use -1 to have the effect of any height or time being valid.
int nMinHeight = -1;
int64_t nMinTime = -1;
// tx.nVersion is signed integer so requires cast to unsigned otherwise
// we would be doing a signed comparison and half the range of nVersion
// wouldn't support BIP 68.
bool fEnforceBIP68 = static_cast<uint32_t>(tx.nVersion) >= 2
&& flags & LOCKTIME_VERIFY_SEQUENCE;
// Do not enforce sequence numbers as a relative lock time
// unless we have been instructed to
if (!fEnforceBIP68) {
return std::make_pair(nMinHeight, nMinTime);
}
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn& txin = tx.vin[txinIndex];
// Sequence numbers with the most significant bit set are not
// treated as relative lock-times, nor are they given any
// consensus-enforced meaning at this point.
if (txin.nSequence & CTxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG) {
// The height of this input is not relevant for sequence locks
(*prevHeights)[txinIndex] = 0;
continue;
}
int nCoinHeight = (*prevHeights)[txinIndex];
if (txin.nSequence & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) {
int64_t nCoinTime = block.GetAncestor(std::max(nCoinHeight-1, 0))->GetMedianTimePast();
// NOTE: Subtract 1 to maintain nLockTime semantics
// BIP 68 relative lock times have the semantics of calculating
// the first block or time at which the transaction would be
// valid. When calculating the effective block time or height
// for the entire transaction, we switch to using the
// semantics of nLockTime which is the last invalid block
// time or height. Thus we subtract 1 from the calculated
// time or height.
// Time-based relative lock-times are measured from the
// smallest allowed timestamp of the block containing the
// txout being spent, which is the median time past of the
// block prior.
nMinTime = std::max(nMinTime, nCoinTime + (int64_t)((txin.nSequence & CTxIn::SEQUENCE_LOCKTIME_MASK) << CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) - 1);
} else {
nMinHeight = std::max(nMinHeight, nCoinHeight + (int)(txin.nSequence & CTxIn::SEQUENCE_LOCKTIME_MASK) - 1);
}
}
return std::make_pair(nMinHeight, nMinTime);
}
static bool EvaluateSequenceLocks(const CBlockIndex& block, std::pair<int, int64_t> lockPair)
{
assert(block.pprev);
int64_t nBlockTime = block.pprev->GetMedianTimePast();
if (lockPair.first >= block.nHeight || lockPair.second >= nBlockTime)
return false;
return true;
}
bool SequenceLocks(const CTransaction &tx, int flags, std::vector<int>* prevHeights, const CBlockIndex& block)
{
return EvaluateSequenceLocks(block, CalculateSequenceLocks(tx, flags, prevHeights, block));
}
bool TestLockPointValidity(const LockPoints* lp)
{
AssertLockHeld(cs_main);
assert(lp);
// If there are relative lock times then the maxInputBlock will be set
// If there are no relative lock times, the LockPoints don't depend on the chain
if (lp->maxInputBlock) {
// Check whether chainActive is an extension of the block at which the LockPoints
// calculation was valid. If not LockPoints are no longer valid
if (!chainActive.Contains(lp->maxInputBlock)) {
return false;
}
}
// LockPoints still valid
return true;
}
bool CheckSequenceLocks(CTxMemPool &mp, const CTransaction &tx, int flags, LockPoints* lp, bool useExistingLockPoints, CBlockIndex *tip)
{
if (!tip) {
AssertLockHeld(cs_main);
tip = chainActive.Tip();
}
CBlockIndex index;
index.pprev = tip;
// CheckSequenceLocks() uses chainActive.Height()+1 to evaluate
// height based locks because when SequenceLocks() is called within
// ConnectBlock(), the height of the block *being*
// evaluated is what is used.
// Thus if we want to know if a transaction can be part of the
// *next* block, we need to use one more than chainActive.Height()
index.nHeight = tip->nHeight + 1;
std::pair<int, int64_t> lockPair;
if (useExistingLockPoints) {
assert(lp);
lockPair.first = lp->height;
lockPair.second = lp->time;
}
else {
std::vector<int> prevheights;
prevheights.resize(tx.vin.size());
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn& txin = tx.vin[txinIndex];
Tx prevTx;
if (mp.lookup(txin.prevout.hash, prevTx)) {
// Assume all mempool transaction confirm in the next block
prevheights[txinIndex] = tip->nHeight + 1;
} else {
// try UTXO
UnspentOutput output = g_utxo->find(txin.prevout.hash, txin.prevout.n);
if (!output.isValid()) {
logCritical(Log::BlockValidation) << __func__ << "Missing input";
return false;
}
prevheights[txinIndex] = output.blockHeight();
}
}
lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
if (lp) {
lp->height = lockPair.first;
lp->time = lockPair.second;
// Also store the hash of the block with the highest height of
// all the blocks which have sequence locked prevouts.
// This hash needs to still be on the chain
// for these LockPoint calculations to be valid
// Note: It is impossible to correctly calculate a maxInputBlock
// if any of the sequence locked inputs depend on unconfirmed txs,
// except in the special case where the relative lock time/height
// is 0, which is equivalent to no sequence lock. Since we assume
// input height of tip+1 for mempool txs and test the resulting
// lockPair from CalculateSequenceLocks against tip+1. We know
// EvaluateSequenceLocks will fail if there was a non-zero sequence
// lock on a mempool input, so we can use the return value of
// CheckSequenceLocks to indicate the LockPoints validity
int maxInputHeight = 0;
for (int height : prevheights) {
// Can ignore mempool inputs since we'll fail if they had non-zero locks
if (height != tip->nHeight+1) {
maxInputHeight = std::max(maxInputHeight, height);
}
}
lp->maxInputBlock = tip->GetAncestor(maxInputHeight);
}
}
return EvaluateSequenceLocks(index, lockPair);
}
void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) {
int expired = pool.Expire(GetTime() - age);
if (expired != 0)
LogPrint("mempool", "Expired %i transactions from the memory pool\n", expired);
pool.TrimToSize(limit, nullptr);
}
//////////////////////////////////////////////////////////////////////////////
//
// CBlock and CBlockIndex
//
bool ReadBlockFromDisk(MutableBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams)
{
block.setNull();
// Open history file to read
Block fb = Blocks::DB::instance()->loadBlock(pos);
if (fb.size() == 0) {
LogPrintf("ReadBlockFromDisk: Unable to open file %d\n", pos.nFile);
return false;
}
// Read block
try {
block = fb.createOldBlock();
}
catch (const std::exception& e) {
return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString());
}
// Check the header
if (!CheckProofOfWork(block.createHash(), block.nBits, consensusParams))
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
return true;
}
bool ReadBlockFromDisk(MutableBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
{
if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), consensusParams))
return false;
if (block.createHash() != pindex->GetBlockHash())
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
pindex->ToString(), pindex->GetBlockPos().ToString());
return true;
}
int64_t GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams)
{
int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
// Force block reward to zero when right shift is undefined.
if (halvings >= 64)
return 0;
int64_t nSubsidy = 50 * COIN;
// Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.
nSubsidy >>= halvings;
return nSubsidy;
}
bool IsInitialBlockDownload()
{
if (Blocks::DB::instance()->isReindexing())
return true;
return Blocks::DB::instance()->headerChain().Height() - chainActive.Height() > 1000;
}
void AlertNotify(const std::string& strMessage, bool fThread)
{
uiInterface.NotifyAlertChanged();
std::string strCmd = GetArg("-alertnotify", "");
if (strCmd.empty()) return;
// Alert text should be plain ascii coming from a trusted source, but to
// be safe we first strip anything not in safeChars, then add single quotes around
// the whole string before passing it to the shell:
std::string singleQuote("'");
std::string safeStatus = SanitizeString(strMessage);
safeStatus = singleQuote+safeStatus+singleQuote;
boost::replace_all(strCmd, "%s", safeStatus);
if (fThread)
boost::thread t(runCommand, strCmd); // thread runs free
else
runCommand(strCmd);
}
// Requires cs_main.
void Misbehaving(NodeId nodeId, int howmuch)
{
if (howmuch == 0)
return;
CNodeState *state = State(nodeId);
if (state == nullptr)
return;
state->nMisbehavior += howmuch;
int banscore = GetArg("-banscore", Settings::DefaultBanscoreThreshold);
if (!state->fShouldBan && state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore) {
logCritical(Log::Net) << "Id:" << nodeId << state->nMisbehavior-howmuch << "=>" << state->nMisbehavior
<< "Ban threshold exceeded";
state->fShouldBan = true;
addrman.increaseUselessness(state->address, 2);
Application::instance()->validation()->nodeBanned(nodeId);
} else {
logWarning(Log::Net) << "Misbehaving" << "Id:" << nodeId << state->nMisbehavior-howmuch << "=>" << state->nMisbehavior;
}
}
void queueRejectMessage(int peerId, const uint256 &blockHash, uint8_t rejectCode, const std::string &rejectReason)
{
LOCK(cs_main);
auto state = State(peerId);
if (state) {
CBlockReject reject = {rejectCode, rejectReason.substr(0, MAX_REJECT_MESSAGE_LENGTH), blockHash};
state->rejects.push_back(reject);
}
}
/** Abort with a message */
bool AbortNode(const std::string& strMessage, const std::string& userMessage="")
{
strMiscWarning = strMessage;
LogPrintf("*** %s\n", strMessage);
uiInterface.ThreadSafeMessageBox(
userMessage.empty() ? _("Error: A fatal internal error occurred, see hub.log for details") : userMessage,
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return false;
}
bool AbortNode(CValidationState& state, const std::string& strMessage, const std::string& userMessage="")
{
AbortNode(strMessage, userMessage);
return state.Error(strMessage);
}
bool FlushStateToDisk(CValidationState &state, FlushStateMode mode) {
LOCK2(cs_main, cs_LastBlockFile);
static int64_t nLastWrite = 0;
static int64_t nLastFlush = 0;
static int64_t nLastSetChain = 0;
try {
int64_t nNow = GetTimeMicros();
// Avoid writing/flushing immediately after startup.
if (nLastWrite == 0) {
nLastWrite = nNow;
}
if (nLastFlush == 0) {
nLastFlush = nNow;
}
if (nLastSetChain == 0) {
nLastSetChain = nNow;
}
// It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash.
bool fPeriodicWrite = mode == FLUSH_STATE_PERIODIC && nNow > nLastWrite + (int64_t)DATABASE_WRITE_INTERVAL * 1000000;
// It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage.
bool fPeriodicFlush = mode == FLUSH_STATE_PERIODIC && nNow > nLastFlush + (int64_t)DATABASE_FLUSH_INTERVAL * 1000000;
// Combine all conditions that result in a full cache flush.
bool fDoFullFlush = (mode == FLUSH_STATE_ALWAYS) || fPeriodicFlush;
// Write blocks and block index to disk.
if (fDoFullFlush || fPeriodicWrite) {
// Depend on nMinDiskSpace to ensure we can write block index
if (!CheckDiskSpace(0))
return state.Error("out of disk space");
// First make sure all block and undo data is flushed to disk.
// Then update all block file information (which may refer to block and undo files).
{
std::vector<std::pair<int, const Blocks::BlockFileInfo*> > vFiles;
vFiles.reserve(setDirtyFileInfo.size());
for (std::set<int>::iterator it = setDirtyFileInfo.begin(); it != setDirtyFileInfo.end(); ) {
vFiles.push_back(std::make_pair(*it, &vinfoBlockFile[*it]));
setDirtyFileInfo.erase(it++);
}
std::vector<const CBlockIndex*> vBlocks;
vBlocks.reserve(setDirtyBlockIndex.size());
for (std::set<CBlockIndex*>::iterator it = setDirtyBlockIndex.begin(); it != setDirtyBlockIndex.end(); ) {
vBlocks.push_back(*it);
setDirtyBlockIndex.erase(it++);
}
if (Blocks::DB::instance()) { // only when we actually finished init
if (!Blocks::DB::instance()->WriteBatchSync(vFiles, nLastBlockFile, vBlocks))
return AbortNode(state, "Files to write to block index database");
}
}
nLastWrite = nNow;
}
// Flush best chain related state. This can only be done if the blocks / block index write was also done.
if (fDoFullFlush) {
if (!CheckDiskSpace(50000000))
return state.Error("out of disk space");
nLastFlush = nNow;
}
if (fDoFullFlush || ((mode == FLUSH_STATE_ALWAYS || mode == FLUSH_STATE_PERIODIC) && nNow > nLastSetChain + (int64_t)DATABASE_WRITE_INTERVAL * 1000000)) {
// Update best block in wallet (so we can detect restored wallets).
ValidationNotifier().setBestChain(chainActive.GetLocator());
nLastSetChain = nNow;
}
} catch (const std::runtime_error& e) {
return AbortNode(state, std::string("System error while flushing: ") + e.what());
}
return true;
}
void FlushStateToDisk() {
CValidationState state;
FlushStateToDisk(state, FLUSH_STATE_ALWAYS);
}
bool CheckBlockHeader(const BlockHeader &block, CValidationState& state, bool fCheckPOW)
{
// Check proof of work matches claimed amount
if (fCheckPOW && !CheckProofOfWork(block.createHash(), block.nBits, Params().GetConsensus()))
return state.DoS(50, error("CheckBlockHeader(): proof of work failed"),
REJECT_INVALID, "high-hash");
// Check timestamp
if (block.blockTime() > GetAdjustedTime() + 2 * 60 * 60)
return state.Invalid(error("CheckBlockHeader(): block timestamp too far in the future"),
REJECT_INVALID, "time-too-new");
return true;
}
bool CheckDiskSpace(uint64_t nAdditionalBytes)
{
uint64_t nFreeBytesAvailable = boost::filesystem::space(GetDataDir()).available;
// Check for nMinDiskSpace bytes (currently 50MB)
if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes)
return AbortNode("Disk space is low!", _("Error: Disk space is low!"));
return true;
}
bool LoadBlockIndexDB(const UnspentOutputDatabase *utxo)
{
if (!Blocks::DB::instance()->CacheAllBlockInfos(utxo))
return false;
// Load block file info
if (Blocks::DB::instance()->ReadLastBlockFile(nLastBlockFile)) {
vinfoBlockFile.resize(nLastBlockFile + 1);
logInfo(Log::DB) << "last block file:" << nLastBlockFile;
for (int nFile = 0; nFile <= nLastBlockFile; nFile++) {
Blocks::DB::instance()->ReadBlockFileInfo(nFile, vinfoBlockFile[nFile]);
}
logInfo(Log::DB) << "last block file info:" << vinfoBlockFile[nLastBlockFile].ToString();
for (int nFile = nLastBlockFile + 1; true; nFile++) {
Blocks::BlockFileInfo info;
if (Blocks::DB::instance()->ReadBlockFileInfo(nFile, info)) {
vinfoBlockFile.push_back(info);
} else {
break;
}
}
}
// Check presence of blk files
logInfo(Log::DB) << "Checking all blk files are present...";
std::set<int> setBlkDataFiles = Blocks::Index::fileIndexes();
int count = 0;
auto blocksDb = Blocks::DB::instance();
for (std::set<int>::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++) {
Streaming::ConstBuffer dataFile = blocksDb->loadBlockFile(*it);
if (!dataFile.isValid())
return false;
// garbage collect every so many times to avoid hitting FD limits.
if ((++count % 100) == 0)
blocksDb->closeFiles();
}
return true;
}
void UnloadBlockIndex()
{
LOCK(cs_main);
chainActive.SetTip(nullptr);
pindexBestHeader = nullptr;
mempool.clear();
CTxOrphanCache::clear();
nSyncStarted = 0;
vinfoBlockFile.clear();
nLastBlockFile = 0;
nBlockSequenceId = 1;
mapBlockSource.clear();
mapBlocksInFlight.clear();
nPreferredDownload = 0;
setDirtyBlockIndex.clear();
setDirtyFileInfo.clear();
mapNodeState.clear();
recentRejects.reset(nullptr);
Blocks::Index::unload();
}
bool InitBlockIndex(const CChainParams& chainparams)
{
// Initialize global variables that cannot be constructed at startup.
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
// Check whether we're already initialized
if (chainActive.Genesis() != nullptr)
return true;
logCritical(Log::Bitcoin) << "Initializing databases...";
// Only add the genesis block if not reindexing (in which case we reuse the one already on disk)
if (!Blocks::DB::instance()->isReindexing()) {
auto *bv = Application::instance()->validation();
auto future = bv->addBlock(chainparams.GenesisBlock(), Validation::SaveGoodToDisk).start();
future.waitUntilFinished();
if (!future.error().empty()) {
logCritical(Log::Bitcoin) << "Failed to add the genesisblock due to:" << future.error();
return false;
}
// Force a chainstate write so that when we VerifyDB in a moment, it doesn't check stale data
CValidationState state;
return FlushStateToDisk(state, FLUSH_STATE_ALWAYS);
}
return true;
}
std::string GetWarnings(const std::string& strFor)
{
std::string strStatusBar;
std::string strRPC;
std::string strGUI;
if (!CLIENT_VERSION_IS_RELEASE) {
strStatusBar = "This is a pre-release test build - use at your own risk - do not use for mining or merchant applications";
strGUI = _("This is a pre-release test build - use at your own risk - do not use for mining or merchant applications");
}
if (GetBoolArg("-testsafemode", Settings::DefaultTestSafeMode))
strStatusBar = strRPC = strGUI = "testsafemode enabled";
// Misc warnings like out of disk space and clock is wrong
if (strMiscWarning != "")
{
strStatusBar = strGUI = strMiscWarning;
}
if (strFor == "gui")
return strGUI;
else if (strFor == "statusbar")
return strStatusBar;
else if (strFor == "rpc")
return strRPC;
assert(!"GetWarnings(): invalid parameter");
return "error";
}
//////////////////////////////////////////////////////////////////////////////
//
// Messages
//
bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
switch (inv.type)
{
case MSG_TX: {
auto validation = flApp->validation();
if (validation->isRecentlyRejectedTransaction(inv.hash))
return true;
return validation->mempool()->exists(inv.hash);
}
case MSG_BLOCK:
return Blocks::Index::exists(inv.hash);
case MSG_DOUBLESPENDPROOF:
return mempool.doubleSpendProofStorage()->exists(inv.hash)
|| mempool.doubleSpendProofStorage()->isRecentlyRejectedProof(inv.hash);
}
// Don't know what it is, just say we already got one
return true;
}
void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParams)
{
logDebug(106) << pfrom->vRecvGetData.size();
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
std::vector<CInv> vNotFound;
LOCK(cs_main);
while (it != pfrom->vRecvGetData.end()) {
// Don't bother if send buffer is too full to respond anyway
if (pfrom->nSendSize >= SendBufferSize())
break;
const CInv &inv = *it;
logDebug(106) << " + handling" << inv.ToString();
{
boost::this_thread::interruption_point();
it++;
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK
|| inv.type == MSG_THINBLOCK || inv.type == MSG_XTHINBLOCK)
{
bool send = false;
auto mi = Blocks::Index::get(inv.hash);
if (mi) {
if (chainActive.Contains(mi)) {
send = true;
} else {
static const int nOneMonth = 30 * 24 * 60 * 60;
// To prevent fingerprinting attacks, only send blocks outside of the active
// chain if they are valid, and no more than a month older (both in time, and in
// best equivalent proof of work) than the best header chain we know about.
send = mi->IsValid(BLOCK_VALID_SCRIPTS) && (pindexBestHeader != nullptr) &&
(pindexBestHeader->GetBlockTime() - mi->GetBlockTime() < nOneMonth) &&
(GetBlockProofEquivalentTime(*pindexBestHeader, *mi, *pindexBestHeader, consensusParams) < nOneMonth);
if (!send) {
logDebug(Log::Net) << "ProcessGetData ignoring request from peer"
<< pfrom->GetId() << "for old block that isn't in the main chain";
}
}
}
// disconnect node in case we have reached the outbound limit for serving historical blocks
// never disconnect whitelisted nodes
static const int nOneWeek = 7 * 24 * 60 * 60; // assume > 1 week = historical
if (send && CNode::OutboundTargetReached(true) && ( ((pindexBestHeader != nullptr) && (pindexBestHeader->GetBlockTime() - mi->GetBlockTime() > nOneWeek)) || inv.type == MSG_FILTERED_BLOCK) && !pfrom->fWhitelisted)
{
logCritical(Log::Net) << "historical block serving limit reached, disconnect peer" << pfrom->GetId();
//disconnect node
pfrom->fDisconnect = true;
send = false;
}
// Pruned nodes may have deleted the block, so check whether
// it's available before trying to send.
if (send && (mi->nStatus & BLOCK_HAVE_DATA))
{
logDebug(107) << " requested block available";
// Send block from disk
MutableBlock block;
if (!ReadBlockFromDisk(block, mi, consensusParams))
assert(!"cannot load block from disk");
bool sendFullBlock = true;
if (inv.type == MSG_XTHINBLOCK) {
CXThinBlock xThinBlock(block, pfrom->pThinBlockFilter);
if (!xThinBlock.collision) {
const int nSizeBlock = ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
// Only send a thinblock if smaller than a regular block
const int nSizeThinBlock = ::GetSerializeSize(xThinBlock, SER_NETWORK, PROTOCOL_VERSION);
if (nSizeThinBlock < nSizeBlock) {
pfrom->PushMessage(NetMsgType::XTHINBLOCK, xThinBlock);
sendFullBlock = false;
logInfo(Log::ThinBlocks) << "Sent xthinblock - size:" << nSizeThinBlock << "vs block size:"
<< nSizeBlock << "=> tx hashes:" << xThinBlock.vTxHashes.size()
<< "transactions:" << xThinBlock.vMissingTx.size() << "peerid"
<< pfrom->id;
}
}
}
else if (inv.type == MSG_FILTERED_BLOCK)
{
LOCK(pfrom->cs_filter);
if (pfrom->pfilter)
{
CMerkleBlock merkleBlock(block, *pfrom->pfilter);
pfrom->PushMessage(NetMsgType::MERKLEBLOCK, merkleBlock);
// CMerkleBlock just contains hashes, so also push any transactions in the block the client did not see
// This avoids hurting performance by pointlessly requiring a round-trip
// Note that there is currently no way for a node to request any single transactions we didn't send here -
// they must either disconnect and retry or request the full block.
// Thus, the protocol spec specified allows for us to provide duplicate txn here,
// however we MUST always provide at least what the remote peer needs
typedef std::pair<unsigned int, uint256> PairType;
for (PairType& pair : merkleBlock.vMatchedTxn)
pfrom->PushMessage(NetMsgType::TX, block.vtx[pair.first]);
sendFullBlock = false;
}
}
if (sendFullBlock) // if none of the other methods were actually executed;
pfrom->PushMessage(NetMsgType::BLOCK, block);
// Trigger the peer node to send a getblocks request for the next batch of inventory
if (inv.hash == pfrom->hashContinue)
{
// Bypass PushInventory, this must send even if redundant,
// and we want it right after the last block so they don't
// wait for other stuff first.
std::vector<CInv> vInv;
vInv.push_back(CInv(MSG_BLOCK, chainActive.Tip()->GetBlockHash()));
pfrom->PushMessage(NetMsgType::INV, vInv);
pfrom->hashContinue.SetNull();
}
}
}
else if (inv.IsKnownType()) {
// Send stream from relay memory
bool pushed = false;
{
LOCK(cs_mapRelay);
std::map<CInv, CDataStream>::iterator mi = mapRelay.find(inv);
if (mi != mapRelay.end()) {
pfrom->PushMessage(inv.GetCommand(), (*mi).second);
pushed = true;
}
}
if (!pushed && inv.type == MSG_TX) {
CTransaction tx;
if (mempool.lookup(inv.hash, tx)) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(1000);
ss << tx;
pfrom->PushMessage(NetMsgType::TX, ss);
pushed = true;
}
}
else if (inv.type == MSG_DOUBLESPENDPROOF) {
DoubleSpendProof dsp = mempool.doubleSpendProofStorage()->lookup(inv.hash);
if (!dsp.isEmpty()) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss.reserve(600);
ss << dsp;
pfrom->PushMessage(NetMsgType::DSPROOF, ss);
pushed = true;
}
}
if (!pushed)
vNotFound.push_back(inv);
}
// Track requests for our stuff.
ValidationNotifier().inventory(inv.hash);
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK
|| inv.type == MSG_THINBLOCK || inv.type == MSG_XTHINBLOCK)
break;
}
}
pfrom->vRecvGetData.erase(pfrom->vRecvGetData.begin(), it);
if (!vNotFound.empty()) {
// Let the peer know that we didn't find what it asked for, so it doesn't
// have to wait around forever. Currently only SPV clients actually care
// about this message: it's needed when they are recursively walking the
// dependencies of relevant unconfirmed transactions. SPV clients want to
// do that because they want to know about (and store and rebroadcast and
// risk analyze) the dependencies of transactions relevant to them, without
// having to download the entire memory pool.
pfrom->PushMessage(NetMsgType::NOTFOUND, vNotFound);
}
}
bool static ProcessMessage(CNode* pfrom, std::string strCommand, CDataStream& vRecv, int64_t nTimeReceived)
{
const CChainParams& chainparams = Params();
RandAddSeedPerfmon();
const bool fReindex = Blocks::DB::instance()->isReindexing();
logDebug(Log::Net) << "received:" << SanitizeString(strCommand) << "bytes:" << vRecv.size() << "peer:" << pfrom->id;
if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0)
{
LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n");
return true;
}
const bool xthinEnabled = IsThinBlocksEnabled();
if (strCommand == NetMsgType::VERSION)
{
// Each connection can only send one version message
if (pfrom->nVersion != 0)
{
pfrom->PushMessage(NetMsgType::REJECT, strCommand, REJECT_DUPLICATE, std::string("Duplicate version message"));
Misbehaving(pfrom->GetId(), 10);
return false;
}
int64_t nTime;
CAddress addrMe;
CAddress addrFrom;
uint64_t nNonce = 1;
int nVersion;
vRecv >> nVersion >> pfrom->nServices >> nTime >> addrMe;
if (nVersion < MIN_PEER_PROTO_VERSION)
{
// disconnect from peers older than this proto version
logWarning(Log::Net) << "peer:" << pfrom->id << "using obsolete version" << nVersion << "disconnecting";
pfrom->PushMessage(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION));
pfrom->fDisconnect = true;
addrman.increaseUselessness(pfrom->addr, 2);
return false;
}
if (nVersion == 10300)
nVersion = 300;
if (!vRecv.empty()) {
vRecv >> addrFrom;
vRecv >> nNonce;
}
if (!vRecv.empty()) {
vRecv >> LIMITED_STRING(pfrom->strSubVer, MAX_SUBVERSION_LENGTH);
pfrom->cleanSubVer = SanitizeString(pfrom->strSubVer);
}
if (!vRecv.empty())
vRecv >> pfrom->nStartingHeight;
if (!vRecv.empty())
vRecv >> pfrom->fRelayTxes; // set to true after we get the first filter* message
else
pfrom->fRelayTxes = true;
// only approve connection as the whole version message parsed correctly.
pfrom->nVersion = nVersion;
// Disconnect if we connected to ourself
if (nNonce == nLocalHostNonce && nNonce > 1)
{
logCritical(Log::Net) << "connected to self at" << pfrom->addr << "disconnecting";
pfrom->fDisconnect = true;
return true;
}
pfrom->addrLocal = addrMe;
if (pfrom->fInbound && addrMe.IsRoutable())
{
SeenLocal(addrMe);
}
// Be shy and don't send version until we hear
if (pfrom->fInbound)
pfrom->PushVersion();
pfrom->fClient = !(pfrom->nServices & NODE_NETWORK);
// Potentially mark this peer as a preferred download peer.
UpdatePreferredDownload(pfrom, State(pfrom->GetId()));
// Change version
pfrom->PushMessage(NetMsgType::VERACK);
pfrom->ssSend.SetVersion(std::min(pfrom->nVersion, PROTOCOL_VERSION));
if (!pfrom->fInbound)
{
// Advertise our address
if (fListen && !IsInitialBlockDownload())
{
CAddress addr = GetLocalAddress(&pfrom->addr);
if (addr.IsRoutable())
{
logInfo(Log::Net) << "ProcessMessages: advertising address" << addr;
pfrom->PushAddress(addr);
} else if (IsPeerAddrLocalGood(pfrom)) {
addr.SetIP(pfrom->addrLocal);
logInfo(Log::Net) << "ProcessMessages: advertising address" << addr;
pfrom->PushAddress(addr);
}
}
// Get recent addresses
if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION || addrman.size() < 1000)
{
pfrom->PushMessage(NetMsgType::GETADDR);
pfrom->fGetAddr = true;
}
addrman.Good(pfrom->addr);
} else {
if (((CNetAddr)pfrom->addr) == (CNetAddr)addrFrom)
{
addrman.Add(addrFrom, addrFrom);
addrman.Good(addrFrom);
}
}
pfrom->fSuccessfullyConnected = true;
logCritical(Log::Net) << "receive VERSION message:" << pfrom->cleanSubVer << "version:"
<< pfrom->nVersion << "blocks:" << pfrom->nStartingHeight << "id:" << pfrom->id << pfrom->addr;
int64_t nTimeOffset = nTime - GetTime();
pfrom->nTimeOffset = nTimeOffset;
AddTimeData(pfrom->addr, nTimeOffset);
}
else if (pfrom->nVersion == 0)
{
// Must have a version message before anything else
pfrom->fDisconnect = true;
return false;
}
else if (strCommand == NetMsgType::VERACK)
{
pfrom->SetRecvVersion(std::min(pfrom->nVersion, PROTOCOL_VERSION));
// Mark this node as currently connected, so we update its timestamp later.
if (pfrom->fNetworkNode) {
LOCK(cs_main);
State(pfrom->GetId())->fCurrentlyConnected = true;
}
if (pfrom->nVersion >= SENDHEADERS_VERSION) {
// Tell our peer we prefer to receive headers rather than inv's
// We send this to non-NODE NETWORK peers as well, because even
// non-NODE NETWORK peers can announce blocks (such as pruning
// nodes)
// BUIP010 Extreme Thinblocks: We only do inv/getdata for xthinblocks and so we must have headersfirst turned off
if (!xthinEnabled)
pfrom->PushMessage(NetMsgType::SENDHEADERS);
}
}
else if (strCommand == NetMsgType::ADDR && (pfrom->nServices & NODE_BITCOIN_CASH))
{
std::vector<CAddress> vAddr;
vRecv >> vAddr;
// Don't want addr from older versions unless seeding
if (pfrom->nVersion < CADDR_TIME_VERSION && addrman.size() > 1000)
return true;
if (vAddr.size() > 1000)
{
Misbehaving(pfrom->GetId(), 20);
return error("message addr size() = %u", vAddr.size());
}
// Store the new addresses
std::vector<CAddress> vAddrOk;
int64_t nNow = GetAdjustedTime();
int64_t nSince = nNow - 10 * 60;
for (CAddress& addr : vAddr) {
boost::this_thread::interruption_point();
if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60)
addr.nTime = nNow - 5 * 24 * 60 * 60;
pfrom->AddAddressKnown(addr);
bool fReachable = IsReachable(addr);
if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable())
{
// Relay to a limited number of other nodes
{
LOCK(cs_vNodes);
// Use deterministic randomness to send to the same nodes for 24 hours
// at a time so the addrKnowns of the chosen nodes prevent repeats
static uint256 hashSalt;
if (hashSalt.IsNull())
hashSalt = GetRandHash();
uint64_t hashAddr = addr.GetHash();
uint256 hashRand = ArithToUint256(UintToArith256(hashSalt) ^ (hashAddr<<32) ^ ((GetTime()+hashAddr)/(24*60*60)));
hashRand = Hash(BEGIN(hashRand), END(hashRand));
std::multimap<uint256, CNode*> mapMix;
for (CNode* pnode : vNodes) {
if (pnode->nVersion < CADDR_TIME_VERSION)
continue;
unsigned int nPointer;
memcpy(&nPointer, &pnode, sizeof(nPointer));
uint256 hashKey = ArithToUint256(UintToArith256(hashRand) ^ nPointer);
hashKey = Hash(BEGIN(hashKey), END(hashKey));
mapMix.insert(std::make_pair(hashKey, pnode));
}
int nRelayNodes = fReachable ? 2 : 1; // limited relaying of addresses outside our network(s)
for (std::multimap<uint256, CNode*>::iterator mi = mapMix.begin(); mi != mapMix.end() && nRelayNodes-- > 0; ++mi)
((*mi).second)->PushAddress(addr);
}
}
// Do not store addresses outside our network
if (fReachable)
vAddrOk.push_back(addr);
}
addrman.Add(vAddrOk, pfrom->addr, 2 * 60 * 60);
if (vAddr.size() < 1000)
pfrom->fGetAddr = false;
if (pfrom->fOneShot)
pfrom->fDisconnect = true;
}
else if (strCommand == NetMsgType::SENDHEADERS)
{
LOCK(cs_main);
// BUIP010 Xtreme Thinblocks: We only do inv/getdata for xthinblocks and so we must have headersfirst turned off
if (xthinEnabled)
State(pfrom->GetId())->fPreferHeaders = false;
else
State(pfrom->GetId())->fPreferHeaders = true;
}
else if (strCommand == NetMsgType::INV)
{
std::vector<CInv> vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) {
Misbehaving(pfrom->GetId(), 20);
return error("message inv size() = %u", vInv.size());
}
bool fBlocksOnly = GetBoolArg("-blocksonly", Settings::DefaultBlocksOnly);
// When catching up, avoid accepting transactions before we reach the tip, since they could get blacklisted.
if (Blocks::DB::instance()->headerChain().Height() - chainActive.Height() > 6)
fBlocksOnly = true;
// Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true
else if (pfrom->fWhitelisted && GetBoolArg("-whitelistrelay", Settings::DefaultWhitelistRelay))
fBlocksOnly = false;
LOCK(cs_main);
std::vector<CInv> vToFetch;
for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) {
const CInv &inv = vInv[nInv];
pfrom->AddInventoryKnown(inv);
bool fAlreadyHave = AlreadyHave(inv);
logDebug(Log::Net) << "got inv:" << inv << (fAlreadyHave ? "have." : "new.") << "Peer:" << pfrom->id;
if (inv.type == MSG_BLOCK) {
UpdateBlockAvailability(pfrom->GetId(), inv.hash);
if (!fAlreadyHave && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
// First request the headers preceding the announced block. In the normal fully-synced
// case where a new block is announced that succeeds the current tip (no reorganization),
// there are no such headers.
// Secondly, and only when we are close to being synced, we request the announced block directly,
// to avoid an extra round-trip. Note that we must *first* ask for the headers, so by the
// time the block arrives, the header chain leading up to it is already validated. Not
// doing this will result in the received block being rejected as an orphan in case it is
// not a direct successor.
pfrom->PushMessage(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash);
CNodeState *nodestate = State(pfrom->GetId());
if (CanDirectFetch(chainparams.GetConsensus()) &&
nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
// BUIP010 Xtreme Thinblocks: begin section
CInv inv2(inv);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
if (xthinEnabled && IsChainNearlySyncd()) {
if (HaveThinblockNodes() && CheckThinblockTimer(inv.hash)) {
// Must download a block from a ThinBlock peer
if (pfrom->mapThinBlocksInFlight.size() < 1 && pfrom->ThinBlockCapable()) { // We can only send one thinblock per peer at a time
pfrom->mapThinBlocksInFlight[inv2.hash] = GetTime();
inv2.type = MSG_XTHINBLOCK;
CBloomFilter filterMemPool = createSeededBloomFilter(CTxOrphanCache::instance()->fetchTransactionIds());
ss << inv2;
ss << filterMemPool;
pfrom->PushMessage(NetMsgType::GET_XTHIN, ss);
MarkBlockAsInFlight(pfrom->GetId(), inv.hash, chainparams.GetConsensus());
LogPrint("thin", "Requesting Thinblock %s from peer %s (%d)\n", inv2.hash.ToString(), pfrom->addrName.c_str(),pfrom->id);
}
}
else {
// Try to download a thinblock if possible otherwise just download a regular block
if (pfrom->mapThinBlocksInFlight.size() < 1 && pfrom->ThinBlockCapable()) { // We can only send one thinblock per peer at a time
pfrom->mapThinBlocksInFlight[inv2.hash] = GetTime();
inv2.type = MSG_XTHINBLOCK;
CBloomFilter filterMemPool = createSeededBloomFilter(CTxOrphanCache::instance()->fetchTransactionIds());
ss << inv2;
ss << filterMemPool;
pfrom->PushMessage(NetMsgType::GET_XTHIN, ss);
LogPrint("thin", "Requesting Thinblock %s from peer %s (%d)\n", inv2.hash.ToString(), pfrom->addrName.c_str(),pfrom->id);
}
else {
LogPrint("thin", "Requesting Regular Block %s from peer %s (%d)\n", inv2.hash.ToString(), pfrom->addrName.c_str(),pfrom->id);
vToFetch.push_back(inv2);
}
MarkBlockAsInFlight(pfrom->GetId(), inv.hash, chainparams.GetConsensus());
}
}
else {
vToFetch.push_back(inv2);
MarkBlockAsInFlight(pfrom->GetId(), inv.hash, chainparams.GetConsensus());
LogPrint("thin", "Requesting Regular Block %s from peer %s (%d)\n", inv2.hash.ToString(), pfrom->addrName.c_str(),pfrom->id);
}
// BUIP010 Xtreme Thinblocks: end section
}
logDebug(Log::Net) << "getheaders" << pindexBestHeader->nHeight << inv.hash
<< " to peer:" << pfrom->id;
}
}
else if ((inv.type == MSG_DOUBLESPENDPROOF || inv.type == MSG_TX)
&& !fBlocksOnly && !fAlreadyHave && !fReindex) {
pfrom->AskFor(inv);
}
// Track requests for our stuff
ValidationNotifier().inventory(inv.hash);
if (pfrom->nSendSize > (SendBufferSize() * 2)) {
Misbehaving(pfrom->GetId(), 50);
return error("send buffer size() = %u", pfrom->nSendSize);
}
}
if (!vToFetch.empty())
pfrom->PushMessage(NetMsgType::GETDATA, vToFetch);
}
else if (strCommand == NetMsgType::GETDATA)
{
std::vector<CInv> vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ)
{
Misbehaving(pfrom->GetId(), 20);
return error("message getdata size() = %u", vInv.size());
}
if (vInv.size() != 1)
logDebug(Log::Net) << "received getdata (" << vInv.size() << "invsz) peer:" << pfrom->id;
if ((vInv.size() > 0) || (vInv.size() == 1))
logDebug(Log::Net) << "received getdata for:" << vInv[0].ToString() << "peer:" << pfrom->id;
pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end());
ProcessGetData(pfrom, chainparams.GetConsensus());
}
else if (strCommand == NetMsgType::GETBLOCKS)
{
CBlockLocator locator;
uint256 hashStop;
vRecv >> locator >> hashStop;
LOCK(cs_main);
// Find the last block the caller has in the main chain
CBlockIndex* pindex = FindForkInGlobalIndex(chainActive, locator);
// Send the rest of the chain
if (pindex)
pindex = chainActive.Next(pindex);
int nLimit = 500;
LogPrint("net", "getblocks %d to %s limit %d from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit, pfrom->id);
for (; pindex; pindex = chainActive.Next(pindex)) {
if (pindex->GetBlockHash() == hashStop) {
LogPrint("net", " getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
break;
}
pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash()));
if (--nLimit <= 0) {
// When this block is requested, we'll send an inv that'll
// trigger the peer to getblocks the next batch of inventory.
LogPrint("net", " getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
pfrom->hashContinue = pindex->GetBlockHash();
break;
}
}
}
else if (strCommand == NetMsgType::GETHEADERS)
{
CBlockLocator locator;
uint256 hashStop;
vRecv >> locator >> hashStop;
LOCK(cs_main);
if (IsInitialBlockDownload() && !pfrom->fWhitelisted) {
logDebug(Log::Net) << "Ignoring getheaders from peer:" <<pfrom->id << "because node is in initial block download";
return true;
}
CNodeState *nodestate = State(pfrom->GetId());
CBlockIndex* pindex = nullptr;
if (locator.IsNull())
{
// If locator is null, return the hashStop block
pindex = Blocks::Index::get(hashStop);
if (!pindex)
return true;
}
else
{
// Find the last block the caller has in the main chain
pindex = FindForkInGlobalIndex(chainActive, locator);
if (pindex) {
if (pindex->nStatus & BLOCK_FAILED_MASK) {
// his TIP is one we rejected. We don't like them.
Misbehaving(pfrom->GetId(), 100);
return error("peer follows a different chain.");
}
pindex = chainActive.Next(pindex);
}
}
// we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end
std::vector<MutableBlock> vHeaders;
int nLimit = MAX_HEADERS_RESULTS;
logDebug(Log::Net) << "getheaders" << (pindex ? pindex->nHeight : -1) << "to"
<< hashStop << "from peer:" << pfrom->id;
for (; pindex; pindex = chainActive.Next(pindex)) {
vHeaders.push_back(pindex->GetBlockHeader());
if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop)
break;
}
// pindex can be nullptr either if we sent chainActive.Tip() OR
// if our peer has chainActive.Tip() (and thus we are sending an empty
// headers message). In both cases it's safe to update
// pindexBestHeaderSent to be our tip.
nodestate->pindexBestHeaderSent = pindex ? pindex : chainActive.Tip();
pfrom->PushMessage(NetMsgType::HEADERS, vHeaders);
}
else if (strCommand == NetMsgType::TX)
{
// Stop processing the transaction early if
// We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off
if (GetBoolArg("-blocksonly", Settings::DefaultBlocksOnly) && (!pfrom->fWhitelisted || !GetBoolArg("-whitelistrelay", Settings::DefaultWhitelistRelay))) {
LogPrint("net", "transaction sent in violation of protocol peer=%d\n", pfrom->id);
return true;
}
CTransaction tx;
vRecv >> tx;
CInv inv(MSG_TX, tx.GetHash());
pfrom->AddInventoryKnown(inv);
pfrom->setAskFor.erase(inv.hash);
{
LOCK(cs_main);
mapAlreadyAskedFor.erase(inv.hash);
}
uint32_t opts = 0;
if (!pfrom->fWhitelisted)
opts += Validation::PunishBadNode + Validation::RateLimitFreeTx;
flApp->validation()->addTransaction(Tx::fromOldTransaction(tx), opts, pfrom);
CValidationState val;
if (!FlushStateToDisk(val, FLUSH_STATE_PERIODIC))
AbortNode(val.GetRejectReason().c_str());
}
else if (strCommand == NetMsgType::HEADERS && !fReindex) // Ignore headers received while importing
{
std::vector<BlockHeaderFields> headers;
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
unsigned int nCount = ReadCompactSize(vRecv);
if (nCount > MAX_HEADERS_RESULTS) {
Misbehaving(pfrom->GetId(), 20);
return error("headers message size = %u", nCount);
}
headers.resize(nCount);
for (unsigned int n = 0; n < nCount; n++) {
vRecv >> headers[n];
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
}
if (nCount == 0) {
// Nothing interesting. Stop asking this peers for more headers.
return true;
}
auto *engine = Application::instance()->validation();
Streaming::BufferPool pool(100 * nCount);
std::list<Validation::Settings> futures;
for (const BlockHeaderFields& header : headers) {
auto block = Block::fromOldBlock(header, &pool);
futures.push_back(engine->addBlock(block, 0, pfrom).start());
}
for (const Validation::Settings &future : futures) {
future.waitHeaderFinished();
if (!future.error().empty()) {
logWarning(Log::Net) << "HEADERS received from peer:" << pfrom->GetId() << "returned:" << future.error();
if (!pfrom->fWhitelisted) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), Settings::DefaultBanscoreThreshold);
}
return false;
}
}
CBlockIndex *pindexLast = futures.back().blockIndex();
assert(pindexLast);
UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash());
if (nCount == MAX_HEADERS_RESULTS) {
// Headers message had its maximum size; the peer may have more headers.
// TODO: optimize: if pindexLast is an ancestor of chainActive.Tip or pindexBestHeader, continue
// from there instead.
logDebug(Log::Net).nospace() << "more getheaders (" << pindexLast->nHeight
<< ") to end to peer=" << pfrom->id << "(startheight:" << pfrom->nStartingHeight << ")";
pfrom->PushMessage(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexLast), uint256());
}
LOCK(cs_main);
bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus());
CNodeState *nodestate = State(pfrom->GetId());
// If this set of headers is valid and ends in a block with at least as
// much work as our tip, download as much as possible.
logDebug(106) << "canDirectFetch" << fCanDirectFetch << "tree" << pindexLast->IsValid(BLOCK_VALID_TREE) << "more chain work:" <<
(chainActive.Tip()->nChainWork <= pindexLast->nChainWork);
if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && chainActive.Tip()->nChainWork <= pindexLast->nChainWork) {
std::vector<CBlockIndex *> vToFetch;
CBlockIndex *pindexWalk = pindexLast;
// Calculate all the blocks we'd need to switch to pindexLast, up to a limit.
while (pindexWalk && !chainActive.Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
logDebug(106) << "starting fetch" << pindexWalk->nHeight;
if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) &&
!mapBlocksInFlight.count(pindexWalk->GetBlockHash())) {
// We don't have this block, and it's not yet in flight.
vToFetch.push_back(pindexWalk);
}
pindexWalk = pindexWalk->pprev;
}
logDebug(106) << " first block that has data;" << pindexWalk->nHeight;
logDebug(106) << " fetch" << vToFetch;
// If pindexWalk still isn't on our main chain, we're looking at a
// very large reorg at a time we think we're close to caught up to
// the main chain -- this shouldn't really happen. Bail out on the
// direct fetch and rely on parallel download instead.
if (!chainActive.Contains(pindexWalk)) {
logWarning(Log::Net) << "Large reorg, won't direct fetch to" << pindexLast->GetBlockHash() << "at height:" << pindexLast->nHeight;
} else if (!(xthinEnabled && pfrom->nServices & NODE_XTHIN)) { // xthin based downloads are done elsewhere
std::vector<CInv> vGetData;
// Download as much as possible, from earliest to latest.
BOOST_REVERSE_FOREACH(CBlockIndex *pindex, vToFetch) {
if (nodestate->nBlocksInFlight >= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
// Can't download any more from this peer
break;
}
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
MarkBlockAsInFlight(pfrom->GetId(), pindex->GetBlockHash(), chainparams.GetConsensus(), pindex);
logDebug(Log::Net) << "Requesting block" << pindex->GetBlockHash() << "from peer:" << pfrom->id;
}
if (vGetData.size() > 1) {
logDebug(Log::Net) << "Downloading blocks toward" << pindexLast->GetBlockHash() << "height:" << pindexLast->nHeight;
}
if (vGetData.size() > 0) {
pfrom->PushMessage(NetMsgType::GETDATA, vGetData);
}
}
}
}
// BUIP010 Xtreme Thinblocks: begin section
else if (strCommand == NetMsgType::GET_XTHIN && !fReindex) // Ignore blocks received while importing
{
if (!xthinEnabled) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
CBloomFilter filterMemPool;
CInv inv;
vRecv >> inv >> filterMemPool;
if (inv.type != MSG_XTHINBLOCK && inv.type != MSG_THINBLOCK) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 20);
return false;
}
LoadFilter(pfrom, &filterMemPool);
pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), inv);
ProcessGetData(pfrom, chainparams.GetConsensus());
}
else if (strCommand == NetMsgType::XTHINBLOCK && !fReindex) // Ignore blocks received while importing
{
if (!xthinEnabled) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
CXThinBlock thinBlock;
vRecv >> thinBlock;
logDebug(106) << "received XThinBlock" << thinBlock.createHash();
// Send expedited ASAP
CValidationState state;
if (!CheckBlockHeader(thinBlock.header(), state, true)) { // block header is bad
LogPrint("thin", "Thinblock %s received with bad header from peer %s (%d)\n", thinBlock.createHash().ToString(), pfrom->addrName.c_str(), pfrom->id);
Misbehaving(pfrom->id, 20);
return false;
}
else if (!IsRecentlyExpeditedAndStore(thinBlock.createHash()))
SendExpeditedBlock(thinBlock, 0, pfrom);
CInv inv(MSG_BLOCK, thinBlock.createHash());
bool fAlreadyHave = false;
// An expedited block or re-requested xthin can arrive and beat the original thin block request/response
if (!pfrom->mapThinBlocksInFlight.count(inv.hash)) {
LogPrint("thin", "Thinblock %s from peer %s (%d) received but we already have it\n", inv.hash.ToString(), pfrom->addrName.c_str(), pfrom->id);
LOCK(cs_main);
fAlreadyHave = AlreadyHave(inv); // I'll still continue processing if we don't have an accepted block yet
}
if (!fAlreadyHave) {
if (thinBlock.process(pfrom))
HandleBlockMessage(pfrom, strCommand, pfrom->thinBlock, thinBlock.GetInv()); // clears the thin block
}
else {
logDebug(106) << " already have this xthin block";
}
}
else if (strCommand == NetMsgType::XBLOCKTX && !fReindex) // handle Re-requested thinblock transactions
{
if (!xthinEnabled) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
if (pfrom->xThinBlockHashes.size() != pfrom->thinBlock.vtx.size()) { // crappy, but fast solution.
LogPrint("thin", "Inconsistent thin block data while processing xblock-tx\n");
return true;
}
CXThinBlockTx thinBlockTx;
vRecv >> thinBlockTx;
CInv inv(MSG_XTHINBLOCK, thinBlockTx.blockhash);
logDebug(Log::Net) << "received blocktxs for" << inv.hash << "peer" << pfrom->id;
if (!pfrom->mapThinBlocksInFlight.count(inv.hash)) {
LogPrint("thin", "ThinblockTx received but it was either not requested or it was beaten by another block %s peer=%d\n", inv.hash.ToString(), pfrom->id);
return true;
}
// Create the mapMissingTx from all the supplied tx's in the xthinblock
std::map<uint64_t, CTransaction> mapMissingTx;
for (const CTransaction &tx : thinBlockTx.vMissingTx) {
mapMissingTx[tx.GetHash().GetCheapHash()] = tx;
}
int count=0;
for (size_t i = 0; i < pfrom->thinBlock.vtx.size(); ++i) {
if (pfrom->thinBlock.vtx[i].IsNull()) {
auto val = mapMissingTx.find(pfrom->xThinBlockHashes[i]);
if (val != mapMissingTx.end()) {
pfrom->thinBlock.vtx[i] = val->second;
--pfrom->thinBlockWaitingForTxns;
}
count++;
}
}
LogPrint("thin", "Got %d Re-requested txs, needed %d of them\n", thinBlockTx.vMissingTx.size(), count);
if (pfrom->thinBlockWaitingForTxns == 0) {
// We have all the transactions now that are in this block: try to reassemble and process.
pfrom->thinBlockWaitingForTxns = -1;
pfrom->AddInventoryKnown(inv);
// For correctness sake, assume all came from the orphans cache
std::vector<uint256> orphans;
orphans.reserve(pfrom->thinBlock.vtx.size());
for (unsigned int i = 0; i < pfrom->thinBlock.vtx.size(); i++) {
orphans.push_back(pfrom->thinBlock.vtx[i].GetHash());
}
HandleBlockMessage(pfrom, strCommand, pfrom->thinBlock, inv);
CTxOrphanCache::instance()->eraseOrphans(orphans);
}
else {
LogPrint("thin", "Failed to retrieve all transactions for block\n");
}
}
else if (strCommand == NetMsgType::GET_XBLOCKTX && !fReindex) // return Re-requested xthinblock transactions
{
if (!xthinEnabled) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
CXRequestThinBlockTx thinRequestBlockTx;
vRecv >> thinRequestBlockTx;
if (thinRequestBlockTx.setCheapHashesToRequest.empty()) { // empty request??
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
// We use MSG_TX here even though we refer to blockhash because we need to track
// how many xblocktx requests we make in case of DOS
CInv inv(MSG_TX, thinRequestBlockTx.blockhash);
LogPrint("thin", "received get_xblocktx for %s peer=%d\n", inv.hash.ToString(), pfrom->id);
// Check for Misbehaving and DOS
// If they make more than 20 requests in 10 minutes then disconnect them
{
const uint64_t nNow = GetTime();
if (pfrom->nGetXBlockTxLastTime <= 0)
pfrom->nGetXBlockTxLastTime = nNow;
pfrom->nGetXBlockTxCount *= pow(1.0 - 1.0/600.0, (double)(nNow - pfrom->nGetXBlockTxLastTime));
pfrom->nGetXBlockTxLastTime = nNow;
pfrom->nGetXBlockTxCount += 1;
LogPrint("thin", "nGetXBlockTxCount is %f\n", pfrom->nGetXBlockTxCount);
if (pfrom->nGetXBlockTxCount >= 20) {
LogPrintf("DOS: Misbehaving - requesting too many xblocktx: %s\n", inv.hash.ToString());
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100); // If they exceed the limit then disconnect them
}
}
CBlockIndex *index = Blocks::Index::get(inv.hash);
if (!index) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
if (index->nHeight + 100 < chainActive.Height()) {
// a node that is behind should never use this method.
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 10);
return false;
}
if ((index->nStatus & BLOCK_HAVE_DATA) == 0) {
LogPrintf("GET_XBLOCKTX requested block-data not available %s\n", inv.hash.ToString().c_str());
return false;
}
MutableBlock block;
const Consensus::Params& consensusParams = chainparams.GetConsensus();
LOCK(cs_main);
if (!ReadBlockFromDisk(block, index, consensusParams)) {
LogPrintf("Internal error, file missing datafile %d (block: %d)\n", index->nFile, index->nHeight);
return false;
}
std::vector<CTransaction> vTx;
int todo = thinRequestBlockTx.setCheapHashesToRequest.size();
for (size_t i = 1; i < block.vtx.size(); i++) {
uint64_t cheapHash = block.vtx[i].GetHash().GetCheapHash();
if (thinRequestBlockTx.setCheapHashesToRequest.count(cheapHash)) {
vTx.push_back(block.vtx[i]);
if (--todo == 0)
break;
}
}
if (todo > 0) { // node send us a request for transactions which were not in the block.
Misbehaving(pfrom->GetId(), 100);
return false;
}
pfrom->AddInventoryKnown(inv);
CXThinBlockTx thinBlockTx(thinRequestBlockTx.blockhash, vTx);
pfrom->PushMessage(NetMsgType::XBLOCKTX, thinBlockTx);
}
// BUIP010 Xtreme Thinblocks: end section
else if (strCommand == NetMsgType::BLOCK && !fReindex) // Ignore blocks received while importing
{
logDebug(106) << "Received a block";
MutableBlock block;
try {
vRecv >> block;
} catch (std::exception &e) {
LogPrint("net", "ProcessMessage/block failed to parse message and got error: %s\n", e.what());
pfrom->fDisconnect = true;
return true;
}
logDebug(106) << "->" << block.createHash();
CInv inv(MSG_BLOCK, block.createHash());
logDebug(Log::Net) << "received" << inv << "peer" << pfrom->id;
pfrom->AddInventoryKnown(inv);
// BUIP010 Extreme Thinblocks: Handle Block Message
HandleBlockMessage(pfrom, strCommand, block, inv);
std::vector<uint256> orphans;
orphans.reserve(block.vtx.size());
for (unsigned int i = 0; i < block.vtx.size(); i++) {
orphans.push_back(block.vtx[i].GetHash());
}
CTxOrphanCache::instance()->eraseOrphans(orphans);
}
else if (strCommand == NetMsgType::GETADDR)
{
// This asymmetric behavior for inbound and outbound connections was introduced
// to prevent a fingerprinting attack: an attacker can send specific fake addresses
// to users' AddrMan and later request them by sending getaddr messages.
// Making nodes which are behind NAT and can only make outgoing connections ignore
// the getaddr message mitigates the attack.
if (!pfrom->fInbound) {
LogPrint("net", "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom->id);
return true;
}
// Only send one GetAddr response per connection to reduce resource waste
// and discourage addr stamping of INV announcements.
if (pfrom->fSentAddr) {
LogPrint("net", "Ignoring repeated \"getaddr\". peer=%d\n", pfrom->id);
return true;
}
pfrom->fSentAddr = true;
pfrom->vAddrToSend.clear();
std::vector<CAddress> vAddr = addrman.GetAddr();
for (const CAddress &addr : vAddr)
pfrom->PushAddress(addr);
}
else if (strCommand == NetMsgType::MEMPOOL)
{
if (CNode::OutboundTargetReached(false) && !pfrom->fWhitelisted)
{
LogPrint("net", "mempool request with bandwidth limit reached, disconnect peer=%d\n", pfrom->GetId());
pfrom->fDisconnect = true;
return true;
}
LOCK2(cs_main, pfrom->cs_filter);
std::vector<uint256> vtxid;
mempool.queryHashes(vtxid);
std::vector<CInv> vInv;
for (uint256& hash : vtxid) {
CInv inv(MSG_TX, hash);
if (pfrom->pfilter) {
CTransaction tx;
bool fInMemPool = mempool.lookup(hash, tx);
if (!fInMemPool) continue; // another thread removed since queryHashes, maybe...
if (!pfrom->pfilter->isRelevantAndUpdate(tx)) continue;
}
vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) {
pfrom->PushMessage(NetMsgType::INV, vInv);
vInv.clear();
}
}
if (vInv.size() > 0)
pfrom->PushMessage(NetMsgType::INV, vInv);
}
else if (strCommand == NetMsgType::PING)
{
if (pfrom->nVersion > BIP0031_VERSION)
{
uint64_t nonce = 0;
vRecv >> nonce;
// Echo the message back with the nonce. This allows for two useful features:
//
// 1) A remote node can quickly check if the connection is operational
// 2) Remote nodes can measure the latency of the network thread. If this node
// is overloaded it won't respond to pings quickly and the remote node can
// avoid sending us more work, like chain download requests.
//
// The nonce stops the remote getting confused between different pings: without
// it, if the remote node sends a ping once per second and this node takes 5
// seconds to respond to each, the 5th ping the remote sends would appear to
// return very quickly.
pfrom->PushMessage(NetMsgType::PONG, nonce);
}
}
else if (strCommand == NetMsgType::PONG)
{
int64_t pingUsecEnd = nTimeReceived;
uint64_t nonce = 0;
size_t nAvail = vRecv.in_avail();
bool bPingFinished = false;
std::string sProblem;
if (nAvail >= sizeof(nonce)) {
vRecv >> nonce;
// Only process pong message if there is an outstanding ping (old ping without nonce should never pong)
if (pfrom->nPingNonceSent != 0) {
if (nonce == pfrom->nPingNonceSent) {
// Matching pong received, this ping is no longer outstanding
bPingFinished = true;
int64_t pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart;
if (pingUsecTime > 0) {
// Successful ping time measurement, replace previous
pfrom->nPingUsecTime = pingUsecTime;
pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime, pingUsecTime);
} else {
// This should never happen
sProblem = "Timing mishap";
}
} else {
// Nonce mismatches are normal when pings are overlapping
sProblem = "Nonce mismatch";
if (nonce == 0) {
// This is most likely a bug in another implementation somewhere; cancel this ping
bPingFinished = true;
sProblem = "Nonce zero";
}
}
} else {
sProblem = "Unsolicited pong without ping";
}
} else {
// This is most likely a bug in another implementation somewhere; cancel this ping
bPingFinished = true;
sProblem = "Short payload";
}
if (!(sProblem.empty())) {
LogPrint("net", "pong peer=%d: %s, %x expected, %x received, %u bytes\n",
pfrom->id,
sProblem,
pfrom->nPingNonceSent,
nonce,
nAvail);
}
if (bPingFinished) {
pfrom->nPingNonceSent = 0;
}
}
else if (strCommand == NetMsgType::FILTERLOAD)
{
if (!GetBoolArg("-peerbloomfilters", true)) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
CBloomFilter filter;
vRecv >> filter;
if (!filter.isWithinSizeConstraints()) {
// There is no excuse for sending a too-large filter
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
} else {
LOCK(pfrom->cs_filter);
delete pfrom->pfilter;
pfrom->pfilter = new CBloomFilter(filter);
pfrom->pfilter->updateEmptyFull();
}
pfrom->fRelayTxes = true;
}
else if (strCommand == NetMsgType::FILTERADD)
{
if (!GetBoolArg("-peerbloomfilters", true)) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
std::vector<unsigned char> vData;
vRecv >> vData;
// Nodes must NEVER send a data item > 520 bytes (the max size for a script data object,
// and thus, the maximum size any matched object can have) in a filteradd message
if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
} else {
LOCK(pfrom->cs_filter);
if (pfrom->pfilter)
pfrom->pfilter->insert(vData);
else
Misbehaving(pfrom->GetId(), 100);
}
}
else if (strCommand == NetMsgType::FILTERCLEAR)
{
if (!GetBoolArg("-peerbloomfilters", true)) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
return false;
}
LOCK(pfrom->cs_filter);
delete pfrom->pfilter;
pfrom->pfilter = new CBloomFilter();
pfrom->fRelayTxes = true;
}
else if (strCommand == NetMsgType::DSPROOF) {
logInfo(Log::DSProof) << "Got DSPROOF message";
uint256 hash;
try {
DoubleSpendProof dsp;
vRecv >> dsp;
if (dsp.isEmpty())
throw std::runtime_error("DSP empty");
hash = dsp.createHash();
const CInv inv(MSG_DOUBLESPENDPROOF, hash);
logDebug(Log::DSProof) << " DSP" << inv;
pfrom->setAskFor.erase(inv.hash);
{
LOCK(cs_main);
mapAlreadyAskedFor.erase(hash);
}
switch (dsp.validate(mempool)) {
case DoubleSpendProof::Valid: {
const auto tx = mempool.addDoubleSpendProof(dsp);
if (tx.size() > 0) { // added to mempool correctly, then forward to nodes.
logDebug(Log::DSProof) << " Good DSP, broadcasting an INV";
ValidationNotifier().doubleSpendFound(tx, dsp);
CTransaction dspTx = tx.createOldTransaction();
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
if (!pnode->fRelayTxes || pnode == pfrom)
continue;
LOCK(pnode->cs_filter);
if (pnode->pfilter) {
// For nodes that we sent this Tx before, send a proof.
if (pnode->pfilter->isRelevantAndUpdate(dspTx))
pnode->PushInventory(inv);
} else {
pnode->PushInventory(inv);
}
}
}
else logDebug(Log::DSProof) << " valid, not propagating";
break;
}
case DoubleSpendProof::MissingUTXO:
case DoubleSpendProof::MissingTransaction:
logInfo(Log::DSProof) << "DoubleSpend Proof postponed: is orphan";
mempool.doubleSpendProofStorage()->addOrphan(dsp, pfrom->fWhitelisted ? -1 : pfrom->id);
break;
case DoubleSpendProof::Invalid:
throw std::runtime_error("DSProof didn't validate");
default:
assert(false);
return false;
}
} catch (const std::exception &e) {
logWarning(Log::DSProof) << "Failure handling double spend proof. Peer:" << pfrom->GetId() << "Reason:" << e;
if (!hash.IsNull())
mempool.doubleSpendProofStorage()->markProofRejected(hash);
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 15);
return false;
}
}
else if (strCommand == NetMsgType::REJECT)
{
#ifndef NDEBUG
try {
std::string strMsg; unsigned char ccode; std::string strReason;
vRecv >> LIMITED_STRING(strMsg, CMessageHeader::COMMAND_SIZE) >> ccode >> LIMITED_STRING(strReason, MAX_REJECT_MESSAGE_LENGTH);
std::ostringstream ss;
ss << strMsg << " code " << itostr(ccode) << ": " << strReason;
if (strMsg == NetMsgType::BLOCK || strMsg == NetMsgType::TX)
{
uint256 hash;
vRecv >> hash;
ss << ": hash " << hash.ToString();
}
LogPrint("net", "Reject %s\n", SanitizeString(ss.str()));
} catch (const std::ios_base::failure&) {
// Avoid feedback loops by preventing reject messages from triggering a new reject message.
LogPrint("net", "Unparseable reject message received\n");
}
#endif
}
else
{
// Ignore unknown commands for extensibility
logDebug(Log::Net) << "Unknown command" << SanitizeString(strCommand) << "from peer:" << pfrom->id;
}
return true;
}
// requires LOCK(cs_vRecvMsg)
bool ProcessMessages(CNode* pfrom)
{
const CChainParams& chainparams = Params();
//if (fDebug)
// LogPrintf("%s(%u messages)\n", __func__, pfrom->vRecvMsg.size());
//
// Message format
// (4) message start
// (12) command
// (4) size
// (4) checksum
// (x) data
//
bool fOk = true;
if (!pfrom->vRecvGetData.empty())
ProcessGetData(pfrom, chainparams.GetConsensus());
// this maintains the order of responses
if (!pfrom->vRecvGetData.empty()) return fOk;
std::deque<CNetMessage>::iterator it = pfrom->vRecvMsg.begin();
while (!pfrom->fDisconnect && it != pfrom->vRecvMsg.end()) {
// Don't bother if send buffer is too full to respond anyway
if (pfrom->nSendSize >= SendBufferSize())
break;
// get next message
CNetMessage& msg = *it;
//if (fDebug)
// LogPrintf("%s(message %u msgsz, %u bytes, complete:%s)\n", __func__,
// msg.hdr.nMessageSize, msg.vRecv.size(),
// msg.complete() ? "Y" : "N");
// end, if an incomplete message is found
if (!msg.complete())
break;
// at this point, any failure means we can delete the current message
it++;
// Scan for message start
if (pfrom->nVersion == 0) { // uninitialized.
if (!pfrom->fInbound // we already set isCashNode bool to right value.
&& memcmp(msg.hdr.pchMessageStart, chainparams.magic(), MESSAGE_START_SIZE) != 0) {
addrman.increaseUselessness(pfrom->addr);
fOk = false;
break;
}
if (memcmp(msg.hdr.pchMessageStart, chainparams.magic(), MESSAGE_START_SIZE) != 0) {
logWarning(Log::Net) << "ProcessMessage: handshake invalid messageStart"
<< SanitizeString(msg.hdr.GetCommand()) << "peer:" << pfrom->id
<< pfrom->addr.ToString();
addrman.increaseUselessness(pfrom->addr);
fOk = false;
break;
}
assert (memcmp(msg.hdr.pchMessageStart, Params().magic(), MESSAGE_START_SIZE) == 0);
addrman.increaseUselessness(pfrom->addr, -1);
}
// Read header
CMessageHeader& hdr = msg.hdr;
if (!hdr.IsValid(Params().magic())) {
logWarning(Log::Net) << "PROCESSMESSAGE: ERRORS IN HEADER" << SanitizeString(msg.hdr.GetCommand()) << "peer:" << pfrom->id;
LOCK(cs_main);
Misbehaving(pfrom->id, 5);
continue;
}
std::string strCommand = hdr.GetCommand();
// Message size
unsigned int nMessageSize = hdr.nMessageSize;
// Checksum
CDataStream& vRecv = msg.vRecv;
uint256 hash = Hash(vRecv.begin(), vRecv.begin() + nMessageSize);
unsigned int nChecksum = ReadLE32((unsigned char*)&hash);
if (nChecksum != hdr.nChecksum)
{
LogPrintf("%s(%s, %u bytes): CHECKSUM ERROR nChecksum=%08x hdr.nChecksum=%08x\n", __func__,
SanitizeString(strCommand), nMessageSize, nChecksum, hdr.nChecksum);
continue;
}
// Process message
bool fRet = false;
try
{
fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.nTime);
boost::this_thread::interruption_point();
}
catch (const std::ios_base::failure& e)
{
pfrom->PushMessage(NetMsgType::REJECT, strCommand, REJECT_MALFORMED, std::string("error parsing message"));
if (strstr(e.what(), "end of data"))
{
// Allow exceptions from under-length message on vRecv
LogPrintf("%s(%s, %u bytes): Exception '%s' caught, normally caused by a message being shorter than its stated length\n", __func__, SanitizeString(strCommand), nMessageSize, e.what());
}
else if (strstr(e.what(), "size too large"))
{
// Allow exceptions from over-long size
LogPrintf("%s(%s, %u bytes): Exception '%s' caught\n", __func__, SanitizeString(strCommand), nMessageSize, e.what());
}
else
{
PrintExceptionContinue(&e, "ProcessMessages()");
}
}
catch (const boost::thread_interrupted&) {
throw;
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, "ProcessMessages()");
} catch (...) {
PrintExceptionContinue(nullptr, "ProcessMessages()");
}
if (!fRet)
LogPrintf("%s(%s, %u bytes) FAILED peer=%d\n", __func__, SanitizeString(strCommand), nMessageSize, pfrom->id);
break;
}
// In case the connection got shut down, its receive buffer was wiped
if (!pfrom->fDisconnect)
pfrom->vRecvMsg.erase(pfrom->vRecvMsg.begin(), it);
return fOk;
}
bool SendMessages(CNode* pto)
{
const bool fReindex = Blocks::DB::instance()->isReindexing();
const Consensus::Params& consensusParams = Params().GetConsensus();
{
// Don't send anything until we get its version message
if (pto->nVersion == 0)
return true;
//
// Message: ping
//
bool pingSend = false;
if (pto->fPingQueued) {
// RPC ping request by user
pingSend = true;
}
if (pto->nPingNonceSent == 0 && pto->nPingUsecStart + PING_INTERVAL * 1000000 < GetTimeMicros()) {
// Ping automatically sent as a latency probe & keepalive.
pingSend = true;
}
if (pingSend) {
uint64_t nonce = 0;
while (nonce == 0) {
GetRandBytes((unsigned char*)&nonce, sizeof(nonce));
}
pto->fPingQueued = false;
pto->nPingUsecStart = GetTimeMicros();
if (pto->nVersion > BIP0031_VERSION) {
pto->nPingNonceSent = nonce;
pto->PushMessage(NetMsgType::PING, nonce);
} else {
// Peer is too old to support ping command with nonce, pong will never arrive.
pto->nPingNonceSent = 0;
pto->PushMessage(NetMsgType::PING);
}
}
TRY_LOCK(cs_main, lockMain); // Acquire cs_main for IsInitialBlockDownload() and CNodeState()
if (!lockMain)
return true;
// Address refresh broadcast
if (pindexBestHeader == nullptr)
pindexBestHeader = chainActive.Tip();
int64_t nNow = GetTimeMicros();
if (!IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) {
AdvertiseLocal(pto);
pto->nNextLocalAddrSend = nNow + AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL * 1000000 + rand() % 500000000;
}
//
// Message: addr
//
if (pto->nNextAddrSend < nNow) {
pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL);
std::vector<CAddress> vAddr;
vAddr.reserve(pto->vAddrToSend.size());
for (const CAddress& addr : pto->vAddrToSend) {
if (!pto->addrKnown.contains(addr.GetKey()))
{
pto->addrKnown.insert(addr.GetKey());
vAddr.push_back(addr);
// receiver rejects addr messages larger than 1000
if (vAddr.size() >= 1000)
{
pto->PushMessage(NetMsgType::ADDR, vAddr);
vAddr.clear();
}
}
}
pto->vAddrToSend.clear();
if (!vAddr.empty())
pto->PushMessage(NetMsgType::ADDR, vAddr);
}
CNodeState &state = *State(pto->GetId());
if (state.fShouldBan) {
if (pto->fWhitelisted)
LogPrintf("Warning: not punishing whitelisted peer %s!\n", pto->addr.ToString());
else {
pto->fDisconnect = true;
if (pto->addr.IsLocal())
LogPrintf("Warning: not banning local peer %s!\n", pto->addr.ToString());
else
{
CNode::Ban(pto->addr, BanReasonNodeMisbehaving);
}
}
state.fShouldBan = false;
}
for (const CBlockReject& reject : state.rejects)
pto->PushMessage(NetMsgType::REJECT, (std::string)NetMsgType::BLOCK, reject.chRejectCode, reject.strRejectReason, reject.hashBlock);
state.rejects.clear();
// Start block sync
bool fFetch = state.fPreferredDownload || (nPreferredDownload == 0 && !pto->fClient && !pto->fOneShot); // Download if this is a nice peer, or we have no nice peers and this one might do.
if (!state.fSyncStarted && !pto->fClient && !fReindex) {
// Only actively request headers from small number of peers, unless we're close to today.
if (nSyncStarted < 5 || pindexBestHeader->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) {
state.fSyncStarted = true;
nSyncStarted++;
const CBlockIndex *pindexStart = pindexBestHeader;
/* If possible, start at the block preceding the currently
best known header. This ensures that we always get a
non-empty list of headers back as long as the peer
is up-to-date. With a non-empty response, we can initialise
the peer's known best block. This wouldn't be possible
if we requested starting at pindexBestHeader and
got back an empty response. */
if (pindexStart->pprev)
pindexStart = pindexStart->pprev;
logDebug(Log::Net) << "initial getheaders" << pindexStart->nHeight << "to peer:" << pto->id <<"startheight:" << pto->nStartingHeight;
pto->PushMessage(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexStart), uint256());
}
}
// Resend wallet transactions that haven't gotten in a block yet
// Except during reindex, importing and IBD, when old wallet
// transactions become unconfirmed and spams other nodes.
if (!fReindex && !IsInitialBlockDownload())
{
ValidationNotifier().resendWalletTransactions(Blocks::DB::instance()->headerChain().Tip()->GetBlockTime());
}
//
// Try sending block announcements via headers
//
{
// If we have less than MAX_BLOCKS_TO_ANNOUNCE in our
// list of block hashes we're relaying, and our peer wants
// headers announcements, then find the first header
// not yet known to our peer but would connect, and send.
// If no header would connect, or if we have too many
// blocks, or if the peer doesn't want headers, just
// add all to the inv queue.
LOCK(pto->cs_inventory);
std::vector<MutableBlock> vHeaders;
bool fRevertToInv = (!state.fPreferHeaders || pto->vBlockHashesToAnnounce.size() > MAX_BLOCKS_TO_ANNOUNCE);
CBlockIndex *pBestIndex = nullptr; // last header queued for delivery
ProcessBlockAvailability(pto->id); // ensure pindexBestKnownBlock is up-to-date
if (!fRevertToInv) {
bool fFoundStartingHeader = false;
// Try to find first header that our peer doesn't have, and
// then send all headers past that one. If we come across any
// headers that aren't on chainActive, give up.
for (const uint256 &hash : pto->vBlockHashesToAnnounce) {
CBlockIndex *pindex = Blocks::Index::get(hash);
assert(pindex);
if (chainActive[pindex->nHeight] != pindex) {
// Bail out if we reorged away from this block
fRevertToInv = true;
break;
}
if (pBestIndex != nullptr && pindex->pprev != pBestIndex) {
// This means that the list of blocks to announce don't
// connect to each other.
// This shouldn't really be possible to hit during
// regular operation (because reorgs should take us to
// a chain that has some block not on the prior chain,
// which should be caught by the prior check), but one
// way this could happen is by using invalidateblock /
// reconsiderblock repeatedly on the tip, causing it to
// be added multiple times to vBlockHashesToAnnounce.
// Robustly deal with this rare situation by reverting
// to an inv.
fRevertToInv = true;
break;
}
pBestIndex = pindex;
if (fFoundStartingHeader) {
// add this to the headers message
vHeaders.push_back(pindex->GetBlockHeader());
} else if (PeerHasHeader(&state, pindex)) {
continue; // keep looking for the first new block
} else if (pindex->pprev == nullptr || PeerHasHeader(&state, pindex->pprev)) {
// Peer doesn't have this header but they do have the prior one.
// Start sending headers.
fFoundStartingHeader = true;
vHeaders.push_back(pindex->GetBlockHeader());
} else {
// Peer doesn't have this header or the prior one -- nothing will
// connect, so bail out.
fRevertToInv = true;
break;
}
}
}
if (fRevertToInv) {
// If falling back to using an inv, just try to inv the tip.
// The last entry in vBlockHashesToAnnounce was our tip at some point
// in the past.
if (!pto->vBlockHashesToAnnounce.empty()) {
const uint256 &hashToAnnounce = pto->vBlockHashesToAnnounce.back();
CBlockIndex *pindex = Blocks::Index::get(hashToAnnounce);
assert(pindex);
// Warn if we're announcing a block that is not on the main chain.
// This should be very rare and could be optimized out.
// Just log for now.
if (chainActive[pindex->nHeight] != pindex) {
LogPrint("net", "Announcing block %s not on main chain (tip=%s)\n",
hashToAnnounce.ToString(), chainActive.Tip()->GetBlockHash().ToString());
}
// If the peer announced this block to us, don't inv it back.
// (Since block announcements may not be via inv's, we can't solely rely on
// setInventoryKnown to track this.)
if (!PeerHasHeader(&state, pindex)) {
pto->PushInventory(CInv(MSG_BLOCK, hashToAnnounce));
LogPrint("net", "%s: sending inv peer=%d hash=%s\n", __func__,
pto->id, hashToAnnounce.ToString());
}
}
} else if (!vHeaders.empty()) {
if (vHeaders.size() > 1) {
LogPrint("net", "%s: %u headers, range (%s, %s), to peer=%d\n", __func__,
vHeaders.size(),
vHeaders.front().createHash().ToString(),
vHeaders.back().createHash().ToString(), pto->id);
} else {
LogPrint("net", "%s: sending header %s to peer=%d\n", __func__,
vHeaders.front().createHash().ToString(), pto->id);
}
pto->PushMessage(NetMsgType::HEADERS, vHeaders);
state.pindexBestHeaderSent = pBestIndex;
}
pto->vBlockHashesToAnnounce.clear();
}
//
// Message: inventory
//
std::vector<CInv> vInv;
std::vector<CInv> vInvWait;
{
bool fSendTrickle = pto->fWhitelisted;
if (pto->nNextInvSend < nNow) {
fSendTrickle = true;
pto->nNextInvSend = PoissonNextSend(nNow, AVG_INVENTORY_BROADCAST_INTERVAL);
}
LOCK(pto->cs_inventory);
vInv.reserve(std::min<size_t>(1000, pto->vInventoryToSend.size()));
vInvWait.reserve(pto->vInventoryToSend.size());
for (const CInv& inv : pto->vInventoryToSend) {
if (inv.type == MSG_TX && pto->filterInventoryKnown.contains(inv.hash))
continue;
// trickle out tx inv to protect privacy
if (inv.type == MSG_TX && !fSendTrickle)
{
// 1/4 of tx invs blast to all immediately
static uint256 hashSalt;
if (hashSalt.IsNull())
hashSalt = GetRandHash();
uint256 hashRand = ArithToUint256(UintToArith256(inv.hash) ^ UintToArith256(hashSalt));
hashRand = Hash(BEGIN(hashRand), END(hashRand));
bool fTrickleWait = ((UintToArith256(hashRand) & 3) != 0);
if (fTrickleWait)
{
vInvWait.push_back(inv);
continue;
}
}
pto->filterInventoryKnown.insert(inv.hash);
vInv.push_back(inv);
if (vInv.size() >= 1000)
{
pto->PushMessage(NetMsgType::INV, vInv);
vInv.clear();
}
}
pto->vInventoryToSend = vInvWait;
}
if (!vInv.empty())
pto->PushMessage(NetMsgType::INV, vInv);
// Detect whether we're stalling
nNow = GetTimeMicros();
if (!pto->fDisconnect && state.nStallingSince && state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) {
// Stalling only triggers when the block download window cannot move. During normal steady state,
// the download window should be much larger than the to-be-downloaded set of blocks, so disconnection
// should only happen during initial block download.
logCritical(Log::Net) << "Peer" << pto->id << "is stalling block download, disconnecting";
pto->fDisconnect = true;
}
// In case there is a block that has been in flight from this peer for 2 + 0.5 * N times the block interval
// (with N the number of peers from which we're downloading validated blocks), disconnect due to timeout.
// We compensate for other peers to prevent killing off peers due to our own downstream link
// being saturated. We only count validated in-flight blocks so peers can't advertise non-existing block hashes
// to unreasonably increase our timeout.
if (!pto->fDisconnect && state.vBlocksInFlight.size() > 0) {
QueuedBlock &queuedBlock = state.vBlocksInFlight.front();
int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0);
if (nNow > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) {
logCritical(Log::Net) << "Timeout downloading block" << queuedBlock.hash << "from peer" << pto->id << "disconnecting";
pto->fDisconnect = true;
}
}
//
// Message: getdata (blocks)
//
std::vector<CInv> vGetData;
if (!pto->fDisconnect && !pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
std::vector<CBlockIndex*> vToDownload;
NodeId staller = -1;
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller);
for (CBlockIndex *pindex : vToDownload) {
// BUIP010 Xtreme Thinblocks: begin section
if (IsThinBlocksEnabled() && IsChainNearlySyncd()) {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
if (HaveThinblockNodes() && CheckThinblockTimer(pindex->GetBlockHash())) {
// Must download a block from a ThinBlock peer
if (pto->mapThinBlocksInFlight.size() < 1 && pto->ThinBlockCapable()) { // We can only send one thinblock per peer at a time
pto->mapThinBlocksInFlight[pindex->GetBlockHash()] = GetTime();
CBloomFilter filterMemPool = createSeededBloomFilter(CTxOrphanCache::instance()->fetchTransactionIds());
ss << CInv(MSG_XTHINBLOCK, pindex->GetBlockHash());
ss << filterMemPool;
pto->PushMessage(NetMsgType::GET_XTHIN, ss);
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
LogPrint("thin", "Requesting thinblock %s (%d) from peer %s (%d)\n", pindex->GetBlockHash().ToString(),
pindex->nHeight, pto->addrName.c_str(), pto->id);
}
}
else {
// Try to download a thinblock if possible otherwise just download a regular block
if (pto->mapThinBlocksInFlight.size() < 1 && pto->ThinBlockCapable()) { // We can only send one thinblock per peer at a time
pto->mapThinBlocksInFlight[pindex->GetBlockHash()] = GetTime();
CBloomFilter filterMemPool = createSeededBloomFilter(CTxOrphanCache::instance()->fetchTransactionIds());
ss << CInv(MSG_XTHINBLOCK, pindex->GetBlockHash());
ss << filterMemPool;
pto->PushMessage(NetMsgType::GET_XTHIN, ss);
LogPrint("thin", "Requesting Thinblock %s (%d) from peer %s (%d)\n", pindex->GetBlockHash().ToString(),
pindex->nHeight, pto->addrName.c_str(), pto->id);
}
else {
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
logDebug(Log::Net) << "Requesting block" << pindex->GetBlockHash() << pindex->nHeight << "from peer" << pto->addrName << pto->id;
}
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
}
}
else {
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), consensusParams, pindex);
logDebug(Log::Net) << "Requesting block" << pindex->GetBlockHash() << pindex->nHeight << "from peer" << pto->id;
}
// BUIP010 Xtreme Thinblocks: end section
}
if (state.nBlocksInFlight == 0 && staller != -1) {
if (State(staller)->nStallingSince == 0) {
State(staller)->nStallingSince = nNow;
logDebug(Log::Net) << "Stall started peer" << staller;
}
}
}
//
// Message: getdata (non-blocks)
//
while (!pto->fDisconnect && !pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow)
{
const CInv& inv = (*pto->mapAskFor.begin()).second;
if (!AlreadyHave(inv))
{
logDebug(Log::Net) << "Requesting" << inv << "peer:" << pto->id;
vGetData.push_back(inv);
if (vGetData.size() >= 1000)
{
pto->PushMessage(NetMsgType::GETDATA, vGetData);
vGetData.clear();
}
} else {
//If we're not going to ask, don't expect a response.
pto->setAskFor.erase(inv.hash);
}
pto->mapAskFor.erase(pto->mapAskFor.begin());
}
if (!vGetData.empty())
pto->PushMessage(NetMsgType::GETDATA, vGetData);
}
return true;
}
void MarkIndexUnsaved(CBlockIndex *index)
{
LOCK(cs_main);
setDirtyBlockIndex.insert(index);
}
class CMainCleanup
{
public:
CMainCleanup() {}
~CMainCleanup() {
Blocks::Index::unload();
}
} instance_of_cmaincleanup;