mirror of
https://github.com/dashpay/dash.git
synced 2024-12-29 05:49:11 +01:00
51ce901aa3
There are 3 pieces of data that are maintained on disk. The actual block and undo data, the block index (which can refer to positions on disk), and the chainstate (which refers to the best block hash). Earlier, there was no guarantee that blocks were written to disk before block index entries referring to them were written. This commit introduces dirty flags for block index data, and delays writing entries until the actual block data is flushed. With this stricter ordering in writes, it is now safe to not always flush after every block, so there is no need for the IsInitialBlockDownload() check there - instead we just write whenever enough time has passed or the cache size grows too large. Also updating the wallet's best known block is delayed until this is done, otherwise the wallet may end up referring to an unknown block. In addition, only do a write inside the block processing loop if necessary (because of cache size exceeded). Otherwise, move the writing to a point after processing is done, after relaying.
4715 lines
178 KiB
C++
4715 lines
178 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2014 The Bitcoin developers
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "main.h"
|
|
|
|
#include "addrman.h"
|
|
#include "alert.h"
|
|
#include "chainparams.h"
|
|
#include "checkpoints.h"
|
|
#include "checkqueue.h"
|
|
#include "init.h"
|
|
#include "net.h"
|
|
#include "pow.h"
|
|
#include "txdb.h"
|
|
#include "txmempool.h"
|
|
#include "ui_interface.h"
|
|
#include "util.h"
|
|
#include "utilmoneystr.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
#include <boost/thread.hpp>
|
|
|
|
using namespace boost;
|
|
using namespace std;
|
|
|
|
#if defined(NDEBUG)
|
|
# error "Bitcoin cannot be compiled without assertions."
|
|
#endif
|
|
|
|
//
|
|
// Global state
|
|
//
|
|
|
|
CCriticalSection cs_main;
|
|
|
|
BlockMap mapBlockIndex;
|
|
CChain chainActive;
|
|
CBlockIndex *pindexBestHeader = NULL;
|
|
int64_t nTimeBestReceived = 0;
|
|
CWaitableCriticalSection csBestBlock;
|
|
CConditionVariable cvBlockChange;
|
|
int nScriptCheckThreads = 0;
|
|
bool fImporting = false;
|
|
bool fReindex = false;
|
|
bool fTxIndex = false;
|
|
bool fIsBareMultisigStd = true;
|
|
unsigned int nCoinCacheSize = 5000;
|
|
|
|
|
|
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
|
|
CFeeRate minRelayTxFee = CFeeRate(1000);
|
|
|
|
CTxMemPool mempool(::minRelayTxFee);
|
|
|
|
struct COrphanTx {
|
|
CTransaction tx;
|
|
NodeId fromPeer;
|
|
};
|
|
map<uint256, COrphanTx> mapOrphanTransactions;
|
|
map<uint256, set<uint256> > mapOrphanTransactionsByPrev;
|
|
void EraseOrphansFor(NodeId peer);
|
|
|
|
// Constant stuff for coinbase transactions we create:
|
|
CScript COINBASE_FLAGS;
|
|
|
|
const string strMessageMagic = "Bitcoin Signed Message:\n";
|
|
|
|
// Internal stuff
|
|
namespace {
|
|
|
|
struct CBlockIndexWorkComparator
|
|
{
|
|
bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
|
|
// 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;
|
|
}
|
|
};
|
|
|
|
CBlockIndex *pindexBestInvalid;
|
|
|
|
// The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS or better that are at least
|
|
// as good as our current tip. Entries may be failed, though.
|
|
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
|
|
// Number of nodes with fSyncStarted.
|
|
int nSyncStarted = 0;
|
|
// All pairs A->B, where A (or one if its ancestors) misses transactions, but B has transactions.
|
|
multimap<CBlockIndex*, CBlockIndex*> mapBlocksUnlinked;
|
|
|
|
CCriticalSection cs_LastBlockFile;
|
|
std::vector<CBlockFileInfo> vinfoBlockFile;
|
|
int nLastBlockFile = 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, to be able to send them reject messages or ban
|
|
// them, if processing happens afterwards. Protected by cs_main.
|
|
map<uint256, NodeId> mapBlockSource;
|
|
|
|
// 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.
|
|
int64_t nTime; // Time of "getdata" request in microseconds.
|
|
};
|
|
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> > mapBlocksInFlight;
|
|
|
|
// Number of preferrable block download peers.
|
|
int nPreferredDownload = 0;
|
|
|
|
// Dirty block index entries.
|
|
set<CBlockIndex*> setDirtyBlockIndex;
|
|
|
|
// Dirty block file entries.
|
|
set<int> setDirtyFileInfo;
|
|
} // anon namespace
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// dispatching functions
|
|
//
|
|
|
|
// These functions dispatch to one or all registered wallets
|
|
|
|
namespace {
|
|
|
|
struct CMainSignals {
|
|
// Notifies listeners of updated transaction data (transaction, and optionally the block it is found in.
|
|
boost::signals2::signal<void (const CTransaction &, const CBlock *)> SyncTransaction;
|
|
// Notifies listeners of an erased transaction (currently disabled, requires transaction replacement).
|
|
boost::signals2::signal<void (const uint256 &)> EraseTransaction;
|
|
// Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible).
|
|
boost::signals2::signal<void (const uint256 &)> UpdatedTransaction;
|
|
// Notifies listeners of a new active block chain.
|
|
boost::signals2::signal<void (const CBlockLocator &)> SetBestChain;
|
|
// Notifies listeners about an inventory item being seen on the network.
|
|
boost::signals2::signal<void (const uint256 &)> Inventory;
|
|
// Tells listeners to broadcast their data.
|
|
boost::signals2::signal<void ()> Broadcast;
|
|
// Notifies listeners of a block validation result
|
|
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
|
|
} g_signals;
|
|
|
|
} // anon namespace
|
|
|
|
void RegisterValidationInterface(CValidationInterface* pwalletIn) {
|
|
g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2));
|
|
g_signals.EraseTransaction.connect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
|
|
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
|
|
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
|
|
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
|
|
g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn));
|
|
g_signals.BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
|
|
}
|
|
|
|
void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
|
|
g_signals.BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
|
|
g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn));
|
|
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
|
|
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
|
|
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
|
|
g_signals.EraseTransaction.disconnect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
|
|
g_signals.SyncTransaction.disconnect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2));
|
|
}
|
|
|
|
void UnregisterAllValidationInterfaces() {
|
|
g_signals.BlockChecked.disconnect_all_slots();
|
|
g_signals.Broadcast.disconnect_all_slots();
|
|
g_signals.Inventory.disconnect_all_slots();
|
|
g_signals.SetBestChain.disconnect_all_slots();
|
|
g_signals.UpdatedTransaction.disconnect_all_slots();
|
|
g_signals.EraseTransaction.disconnect_all_slots();
|
|
g_signals.SyncTransaction.disconnect_all_slots();
|
|
}
|
|
|
|
void SyncWithWallets(const CTransaction &tx, const CBlock *pblock) {
|
|
g_signals.SyncTransaction(tx, pblock);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Registration of network node signals.
|
|
//
|
|
|
|
namespace {
|
|
|
|
struct CBlockReject {
|
|
unsigned char chRejectCode;
|
|
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 {
|
|
// Accumulated misbehaviour score for this peer.
|
|
int nMisbehavior;
|
|
// Whether this peer should be disconnected and banned (unless whitelisted).
|
|
bool fShouldBan;
|
|
// String name of this peer (debugging/logging purposes).
|
|
std::string name;
|
|
// 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;
|
|
// 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;
|
|
list<QueuedBlock> vBlocksInFlight;
|
|
int nBlocksInFlight;
|
|
// Whether we consider this a preferred download peer.
|
|
bool fPreferredDownload;
|
|
|
|
CNodeState() {
|
|
nMisbehavior = 0;
|
|
fShouldBan = false;
|
|
pindexBestKnownBlock = NULL;
|
|
hashLastUnknownBlock = uint256(0);
|
|
pindexLastCommonBlock = NULL;
|
|
fSyncStarted = false;
|
|
nStallingSince = 0;
|
|
nBlocksInFlight = 0;
|
|
fPreferredDownload = false;
|
|
}
|
|
};
|
|
|
|
// Map maintaining per-node state. Requires cs_main.
|
|
map<NodeId, CNodeState> mapNodeState;
|
|
|
|
// Requires cs_main.
|
|
CNodeState *State(NodeId pnode) {
|
|
map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode);
|
|
if (it == mapNodeState.end())
|
|
return NULL;
|
|
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.name = pnode->addrName;
|
|
}
|
|
|
|
void FinalizeNode(NodeId nodeid) {
|
|
LOCK(cs_main);
|
|
CNodeState *state = State(nodeid);
|
|
|
|
if (state->fSyncStarted)
|
|
nSyncStarted--;
|
|
|
|
BOOST_FOREACH(const QueuedBlock& entry, state->vBlocksInFlight)
|
|
mapBlocksInFlight.erase(entry.hash);
|
|
EraseOrphansFor(nodeid);
|
|
nPreferredDownload -= state->fPreferredDownload;
|
|
|
|
mapNodeState.erase(nodeid);
|
|
}
|
|
|
|
// Requires cs_main.
|
|
void MarkBlockAsReceived(const uint256& hash) {
|
|
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash);
|
|
if (itInFlight != mapBlocksInFlight.end()) {
|
|
CNodeState *state = State(itInFlight->second.first);
|
|
state->vBlocksInFlight.erase(itInFlight->second.second);
|
|
state->nBlocksInFlight--;
|
|
state->nStallingSince = 0;
|
|
mapBlocksInFlight.erase(itInFlight);
|
|
}
|
|
}
|
|
|
|
// Requires cs_main.
|
|
void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, CBlockIndex *pindex = NULL) {
|
|
CNodeState *state = State(nodeid);
|
|
assert(state != NULL);
|
|
|
|
// Make sure it's not listed somewhere already.
|
|
MarkBlockAsReceived(hash);
|
|
|
|
QueuedBlock newentry = {hash, pindex, GetTimeMicros()};
|
|
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
|
|
state->nBlocksInFlight++;
|
|
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
|
|
}
|
|
|
|
/** Check whether the last unknown block a peer advertized is not yet known. */
|
|
void ProcessBlockAvailability(NodeId nodeid) {
|
|
CNodeState *state = State(nodeid);
|
|
assert(state != NULL);
|
|
|
|
if (state->hashLastUnknownBlock != 0) {
|
|
BlockMap::iterator itOld = mapBlockIndex.find(state->hashLastUnknownBlock);
|
|
if (itOld != mapBlockIndex.end() && itOld->second->nChainWork > 0) {
|
|
if (state->pindexBestKnownBlock == NULL || itOld->second->nChainWork >= state->pindexBestKnownBlock->nChainWork)
|
|
state->pindexBestKnownBlock = itOld->second;
|
|
state->hashLastUnknownBlock = uint256(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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 != NULL);
|
|
|
|
ProcessBlockAvailability(nodeid);
|
|
|
|
BlockMap::iterator it = mapBlockIndex.find(hash);
|
|
if (it != mapBlockIndex.end() && it->second->nChainWork > 0) {
|
|
// An actually better block was announced.
|
|
if (state->pindexBestKnownBlock == NULL || it->second->nChainWork >= state->pindexBestKnownBlock->nChainWork)
|
|
state->pindexBestKnownBlock = it->second;
|
|
} else {
|
|
// An unknown block was announced; just assume that the latest one is the best one.
|
|
state->hashLastUnknownBlock = hash;
|
|
}
|
|
}
|
|
|
|
/** Find the last common ancestor two blocks have.
|
|
* Both pa and pb must be non-NULL. */
|
|
CBlockIndex* LastCommonAncestor(CBlockIndex* pa, CBlockIndex* pb) {
|
|
if (pa->nHeight > pb->nHeight) {
|
|
pa = pa->GetAncestor(pb->nHeight);
|
|
} else if (pb->nHeight > pa->nHeight) {
|
|
pb = pb->GetAncestor(pa->nHeight);
|
|
}
|
|
|
|
while (pa != pb && pa && pb) {
|
|
pa = pa->pprev;
|
|
pb = pb->pprev;
|
|
}
|
|
|
|
// Eventually all chain branches meet at the genesis block.
|
|
assert(pa == pb);
|
|
return pa;
|
|
}
|
|
|
|
/** 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 != NULL);
|
|
|
|
// Make sure pindexBestKnownBlock is up to date, we'll need it.
|
|
ProcessBlockAvailability(nodeid);
|
|
|
|
if (state->pindexBestKnownBlock == NULL || state->pindexBestKnownBlock->nChainWork < chainActive.Tip()->nChainWork) {
|
|
// This peer has nothing interesting.
|
|
return;
|
|
}
|
|
|
|
if (state->pindexLastCommonBlock == NULL) {
|
|
// 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 their current tip anymore. Go back enough to fix that.
|
|
state->pindexLastCommonBlock = 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.
|
|
BOOST_FOREACH(CBlockIndex* pindex, vToFetch) {
|
|
if (pindex->nStatus & BLOCK_HAVE_DATA) {
|
|
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 == NULL)
|
|
return false;
|
|
stats.nMisbehavior = state->nMisbehavior;
|
|
stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
|
|
stats.nCommonHeight = state->pindexLastCommonBlock ? state->pindexLastCommonBlock->nHeight : -1;
|
|
BOOST_FOREACH(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
|
|
BOOST_FOREACH(const uint256& hash, locator.vHave) {
|
|
BlockMap::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
if (chain.Contains(pindex))
|
|
return pindex;
|
|
}
|
|
}
|
|
return chain.Genesis();
|
|
}
|
|
|
|
CCoinsViewCache *pcoinsTip = NULL;
|
|
CBlockTreeDB *pblocktree = NULL;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// mapOrphanTransactions
|
|
//
|
|
|
|
bool AddOrphanTx(const CTransaction& tx, NodeId peer)
|
|
{
|
|
uint256 hash = tx.GetHash();
|
|
if (mapOrphanTransactions.count(hash))
|
|
return false;
|
|
|
|
// Ignore big transactions, to avoid a
|
|
// send-big-orphans memory exhaustion attack. If a peer has a legitimate
|
|
// large transaction with a missing parent then we assume
|
|
// it will rebroadcast it later, after the parent transaction(s)
|
|
// have been mined or received.
|
|
// 10,000 orphans, each of which is at most 5,000 bytes big is
|
|
// at most 500 megabytes of orphans:
|
|
unsigned int sz = tx.GetSerializeSize(SER_NETWORK, CTransaction::CURRENT_VERSION);
|
|
if (sz > 5000)
|
|
{
|
|
LogPrint("mempool", "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString());
|
|
return false;
|
|
}
|
|
|
|
mapOrphanTransactions[hash].tx = tx;
|
|
mapOrphanTransactions[hash].fromPeer = peer;
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
mapOrphanTransactionsByPrev[txin.prevout.hash].insert(hash);
|
|
|
|
LogPrint("mempool", "stored orphan tx %s (mapsz %u prevsz %u)\n", hash.ToString(),
|
|
mapOrphanTransactions.size(), mapOrphanTransactionsByPrev.size());
|
|
return true;
|
|
}
|
|
|
|
void static EraseOrphanTx(uint256 hash)
|
|
{
|
|
map<uint256, COrphanTx>::iterator it = mapOrphanTransactions.find(hash);
|
|
if (it == mapOrphanTransactions.end())
|
|
return;
|
|
BOOST_FOREACH(const CTxIn& txin, it->second.tx.vin)
|
|
{
|
|
map<uint256, set<uint256> >::iterator itPrev = mapOrphanTransactionsByPrev.find(txin.prevout.hash);
|
|
if (itPrev == mapOrphanTransactionsByPrev.end())
|
|
continue;
|
|
itPrev->second.erase(hash);
|
|
if (itPrev->second.empty())
|
|
mapOrphanTransactionsByPrev.erase(itPrev);
|
|
}
|
|
mapOrphanTransactions.erase(it);
|
|
}
|
|
|
|
void EraseOrphansFor(NodeId peer)
|
|
{
|
|
int nErased = 0;
|
|
map<uint256, COrphanTx>::iterator iter = mapOrphanTransactions.begin();
|
|
while (iter != mapOrphanTransactions.end())
|
|
{
|
|
map<uint256, COrphanTx>::iterator maybeErase = iter++; // increment to avoid iterator becoming invalid
|
|
if (maybeErase->second.fromPeer == peer)
|
|
{
|
|
EraseOrphanTx(maybeErase->second.tx.GetHash());
|
|
++nErased;
|
|
}
|
|
}
|
|
if (nErased > 0) LogPrint("mempool", "Erased %d orphan tx from peer %d\n", nErased, peer);
|
|
}
|
|
|
|
|
|
unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans)
|
|
{
|
|
unsigned int nEvicted = 0;
|
|
while (mapOrphanTransactions.size() > nMaxOrphans)
|
|
{
|
|
// Evict a random orphan:
|
|
uint256 randomhash = GetRandHash();
|
|
map<uint256, COrphanTx>::iterator it = mapOrphanTransactions.lower_bound(randomhash);
|
|
if (it == mapOrphanTransactions.end())
|
|
it = mapOrphanTransactions.begin();
|
|
EraseOrphanTx(it->first);
|
|
++nEvicted;
|
|
}
|
|
return nEvicted;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool IsStandardTx(const CTransaction& tx, string& reason)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
if (tx.nVersion > CTransaction::CURRENT_VERSION || tx.nVersion < 1) {
|
|
reason = "version";
|
|
return false;
|
|
}
|
|
|
|
// Treat non-final transactions as non-standard to prevent a specific type
|
|
// of double-spend attack, as well as DoS attacks. (if the transaction
|
|
// can't be mined, the attacker isn't expending resources broadcasting it)
|
|
// Basically we don't want to propagate transactions that can't be included in
|
|
// the next block.
|
|
//
|
|
// However, IsFinalTx() is confusing... Without arguments, it uses
|
|
// chainActive.Height() to evaluate nLockTime; when a block is accepted, chainActive.Height()
|
|
// is set to the value of nHeight in the block. However, 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().
|
|
//
|
|
// Timestamps on the other hand don't get any special treatment, because we
|
|
// can't know what timestamp the next block will have, and there aren't
|
|
// timestamp applications where it matters.
|
|
if (!IsFinalTx(tx, chainActive.Height() + 1)) {
|
|
reason = "non-final";
|
|
return false;
|
|
}
|
|
|
|
// Extremely large transactions with lots of inputs can cost the network
|
|
// almost as much to process as they cost the sender in fees, because
|
|
// computing signature hashes is O(ninputs*txsize). Limiting transactions
|
|
// to MAX_STANDARD_TX_SIZE mitigates CPU exhaustion attacks.
|
|
unsigned int sz = tx.GetSerializeSize(SER_NETWORK, CTransaction::CURRENT_VERSION);
|
|
if (sz >= MAX_STANDARD_TX_SIZE) {
|
|
reason = "tx-size";
|
|
return false;
|
|
}
|
|
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
{
|
|
// Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed
|
|
// keys. (remember the 520 byte limit on redeemScript size) That works
|
|
// out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627
|
|
// bytes of scriptSig, which we round off to 1650 bytes for some minor
|
|
// future-proofing. That's also enough to spend a 20-of-20
|
|
// CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not
|
|
// considered standard)
|
|
if (txin.scriptSig.size() > 1650) {
|
|
reason = "scriptsig-size";
|
|
return false;
|
|
}
|
|
if (!txin.scriptSig.IsPushOnly()) {
|
|
reason = "scriptsig-not-pushonly";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
unsigned int nDataOut = 0;
|
|
txnouttype whichType;
|
|
BOOST_FOREACH(const CTxOut& txout, tx.vout) {
|
|
if (!::IsStandard(txout.scriptPubKey, whichType)) {
|
|
reason = "scriptpubkey";
|
|
return false;
|
|
}
|
|
|
|
if (whichType == TX_NULL_DATA)
|
|
nDataOut++;
|
|
else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) {
|
|
reason = "bare-multisig";
|
|
return false;
|
|
} else if (txout.IsDust(::minRelayTxFee)) {
|
|
reason = "dust";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// only one OP_RETURN txout is permitted
|
|
if (nDataOut > 1) {
|
|
reason = "multi-op-return";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
// Time based nLockTime implemented in 0.1.6
|
|
if (tx.nLockTime == 0)
|
|
return true;
|
|
if (nBlockHeight == 0)
|
|
nBlockHeight = chainActive.Height();
|
|
if (nBlockTime == 0)
|
|
nBlockTime = GetAdjustedTime();
|
|
if ((int64_t)tx.nLockTime < ((int64_t)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
|
|
return true;
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
if (!txin.IsFinal())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Check transaction inputs to mitigate two
|
|
// potential denial-of-service attacks:
|
|
//
|
|
// 1. scriptSigs with extra data stuffed into them,
|
|
// not consumed by scriptPubKey (or P2SH script)
|
|
// 2. P2SH scripts with a crazy number of expensive
|
|
// CHECKSIG/CHECKMULTISIG operations
|
|
//
|
|
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
|
{
|
|
if (tx.IsCoinBase())
|
|
return true; // Coinbases don't use vin normally
|
|
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
|
{
|
|
const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]);
|
|
|
|
vector<vector<unsigned char> > vSolutions;
|
|
txnouttype whichType;
|
|
// get the scriptPubKey corresponding to this input:
|
|
const CScript& prevScript = prev.scriptPubKey;
|
|
if (!Solver(prevScript, whichType, vSolutions))
|
|
return false;
|
|
int nArgsExpected = ScriptSigArgsExpected(whichType, vSolutions);
|
|
if (nArgsExpected < 0)
|
|
return false;
|
|
|
|
// Transactions with extra stuff in their scriptSigs are
|
|
// non-standard. Note that this EvalScript() call will
|
|
// be quick, because if there are any operations
|
|
// beside "push data" in the scriptSig
|
|
// IsStandard() will have already returned false
|
|
// and this method isn't called.
|
|
vector<vector<unsigned char> > stack;
|
|
if (!EvalScript(stack, tx.vin[i].scriptSig, false, BaseSignatureChecker()))
|
|
return false;
|
|
|
|
if (whichType == TX_SCRIPTHASH)
|
|
{
|
|
if (stack.empty())
|
|
return false;
|
|
CScript subscript(stack.back().begin(), stack.back().end());
|
|
vector<vector<unsigned char> > vSolutions2;
|
|
txnouttype whichType2;
|
|
if (Solver(subscript, whichType2, vSolutions2))
|
|
{
|
|
int tmpExpected = ScriptSigArgsExpected(whichType2, vSolutions2);
|
|
if (tmpExpected < 0)
|
|
return false;
|
|
nArgsExpected += tmpExpected;
|
|
}
|
|
else
|
|
{
|
|
// Any other Script with less than 15 sigops OK:
|
|
unsigned int sigops = subscript.GetSigOpCount(true);
|
|
// ... extra data left on the stack after execution is OK, too:
|
|
return (sigops <= MAX_P2SH_SIGOPS);
|
|
}
|
|
}
|
|
|
|
if (stack.size() != (unsigned int)nArgsExpected)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int GetLegacySigOpCount(const CTransaction& tx)
|
|
{
|
|
unsigned int nSigOps = 0;
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
{
|
|
nSigOps += txin.scriptSig.GetSigOpCount(false);
|
|
}
|
|
BOOST_FOREACH(const CTxOut& txout, tx.vout)
|
|
{
|
|
nSigOps += txout.scriptPubKey.GetSigOpCount(false);
|
|
}
|
|
return nSigOps;
|
|
}
|
|
|
|
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& inputs)
|
|
{
|
|
if (tx.IsCoinBase())
|
|
return 0;
|
|
|
|
unsigned int nSigOps = 0;
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
|
{
|
|
const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]);
|
|
if (prevout.scriptPubKey.IsPayToScriptHash())
|
|
nSigOps += prevout.scriptPubKey.GetSigOpCount(tx.vin[i].scriptSig);
|
|
}
|
|
return nSigOps;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CheckTransaction(const CTransaction& tx, CValidationState &state)
|
|
{
|
|
// Basic checks that don't depend on any context
|
|
if (tx.vin.empty())
|
|
return state.DoS(10, error("CheckTransaction() : vin empty"),
|
|
REJECT_INVALID, "bad-txns-vin-empty");
|
|
if (tx.vout.empty())
|
|
return state.DoS(10, error("CheckTransaction() : vout empty"),
|
|
REJECT_INVALID, "bad-txns-vout-empty");
|
|
// Size limits
|
|
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
|
|
return state.DoS(100, error("CheckTransaction() : size limits failed"),
|
|
REJECT_INVALID, "bad-txns-oversize");
|
|
|
|
// Check for negative or overflow output values
|
|
CAmount nValueOut = 0;
|
|
BOOST_FOREACH(const CTxOut& txout, tx.vout)
|
|
{
|
|
if (txout.nValue < 0)
|
|
return state.DoS(100, error("CheckTransaction() : txout.nValue negative"),
|
|
REJECT_INVALID, "bad-txns-vout-negative");
|
|
if (txout.nValue > MAX_MONEY)
|
|
return state.DoS(100, error("CheckTransaction() : txout.nValue too high"),
|
|
REJECT_INVALID, "bad-txns-vout-toolarge");
|
|
nValueOut += txout.nValue;
|
|
if (!MoneyRange(nValueOut))
|
|
return state.DoS(100, error("CheckTransaction() : txout total out of range"),
|
|
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
|
}
|
|
|
|
// Check for duplicate inputs
|
|
set<COutPoint> vInOutPoints;
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
{
|
|
if (vInOutPoints.count(txin.prevout))
|
|
return state.DoS(100, error("CheckTransaction() : duplicate inputs"),
|
|
REJECT_INVALID, "bad-txns-inputs-duplicate");
|
|
vInOutPoints.insert(txin.prevout);
|
|
}
|
|
|
|
if (tx.IsCoinBase())
|
|
{
|
|
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
|
|
return state.DoS(100, error("CheckTransaction() : coinbase script size"),
|
|
REJECT_INVALID, "bad-cb-length");
|
|
}
|
|
else
|
|
{
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
if (txin.prevout.IsNull())
|
|
return state.DoS(10, error("CheckTransaction() : prevout is null"),
|
|
REJECT_INVALID, "bad-txns-prevout-null");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree)
|
|
{
|
|
{
|
|
LOCK(mempool.cs);
|
|
uint256 hash = tx.GetHash();
|
|
double dPriorityDelta = 0;
|
|
CAmount nFeeDelta = 0;
|
|
mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta);
|
|
if (dPriorityDelta > 0 || nFeeDelta > 0)
|
|
return 0;
|
|
}
|
|
|
|
CAmount nMinFee = ::minRelayTxFee.GetFee(nBytes);
|
|
|
|
if (fAllowFree)
|
|
{
|
|
// There is a free transaction area in blocks created by most miners,
|
|
// * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
|
|
// to be considered to fall into this category. We don't want to encourage sending
|
|
// multiple transactions instead of one big transaction to avoid fees.
|
|
if (nBytes < (DEFAULT_BLOCK_PRIORITY_SIZE - 1000))
|
|
nMinFee = 0;
|
|
}
|
|
|
|
if (!MoneyRange(nMinFee))
|
|
nMinFee = MAX_MONEY;
|
|
return nMinFee;
|
|
}
|
|
|
|
|
|
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,
|
|
bool* pfMissingInputs, bool fRejectInsaneFee)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
if (pfMissingInputs)
|
|
*pfMissingInputs = false;
|
|
|
|
if (!CheckTransaction(tx, state))
|
|
return error("AcceptToMemoryPool: : CheckTransaction failed");
|
|
|
|
// Coinbase is only valid in a block, not as a loose transaction
|
|
if (tx.IsCoinBase())
|
|
return state.DoS(100, error("AcceptToMemoryPool: : coinbase as individual tx"),
|
|
REJECT_INVALID, "coinbase");
|
|
|
|
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
|
|
string reason;
|
|
if (Params().RequireStandard() && !IsStandardTx(tx, reason))
|
|
return state.DoS(0,
|
|
error("AcceptToMemoryPool : nonstandard transaction: %s", reason),
|
|
REJECT_NONSTANDARD, reason);
|
|
|
|
// is it already in the memory pool?
|
|
uint256 hash = tx.GetHash();
|
|
if (pool.exists(hash))
|
|
return false;
|
|
|
|
// Check for conflicts with in-memory transactions
|
|
{
|
|
LOCK(pool.cs); // protect pool.mapNextTx
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
|
{
|
|
COutPoint outpoint = tx.vin[i].prevout;
|
|
if (pool.mapNextTx.count(outpoint))
|
|
{
|
|
// Disable replacement feature for now
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
CCoinsView dummy;
|
|
CCoinsViewCache view(&dummy);
|
|
|
|
CAmount nValueIn = 0;
|
|
{
|
|
LOCK(pool.cs);
|
|
CCoinsViewMemPool viewMemPool(pcoinsTip, pool);
|
|
view.SetBackend(viewMemPool);
|
|
|
|
// do we already have it?
|
|
if (view.HaveCoins(hash))
|
|
return false;
|
|
|
|
// do all inputs exist?
|
|
// Note that this does not check for the presence of actual outputs (see the next check for that),
|
|
// only helps filling in pfMissingInputs (to determine missing vs spent).
|
|
BOOST_FOREACH(const CTxIn txin, tx.vin) {
|
|
if (!view.HaveCoins(txin.prevout.hash)) {
|
|
if (pfMissingInputs)
|
|
*pfMissingInputs = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// are the actual inputs available?
|
|
if (!view.HaveInputs(tx))
|
|
return state.Invalid(error("AcceptToMemoryPool : inputs already spent"),
|
|
REJECT_DUPLICATE, "bad-txns-inputs-spent");
|
|
|
|
// Bring the best block into scope
|
|
view.GetBestBlock();
|
|
|
|
nValueIn = view.GetValueIn(tx);
|
|
|
|
// we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool
|
|
view.SetBackend(dummy);
|
|
}
|
|
|
|
// Check for non-standard pay-to-script-hash in inputs
|
|
if (Params().RequireStandard() && !AreInputsStandard(tx, view))
|
|
return error("AcceptToMemoryPool: : nonstandard transaction input");
|
|
|
|
// Check that the transaction doesn't have an excessive number of
|
|
// sigops, making it impossible to mine. Since the coinbase transaction
|
|
// itself can contain sigops MAX_TX_SIGOPS is less than
|
|
// MAX_BLOCK_SIGOPS; we still consider this an invalid rather than
|
|
// merely non-standard transaction.
|
|
unsigned int nSigOps = GetLegacySigOpCount(tx);
|
|
nSigOps += GetP2SHSigOpCount(tx, view);
|
|
if (nSigOps > MAX_TX_SIGOPS)
|
|
return state.DoS(0,
|
|
error("AcceptToMemoryPool : too many sigops %s, %d > %d",
|
|
hash.ToString(), nSigOps, MAX_TX_SIGOPS),
|
|
REJECT_NONSTANDARD, "bad-txns-too-many-sigops");
|
|
|
|
CAmount nValueOut = tx.GetValueOut();
|
|
CAmount nFees = nValueIn-nValueOut;
|
|
double dPriority = view.GetPriority(tx, chainActive.Height());
|
|
|
|
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height());
|
|
unsigned int nSize = entry.GetTxSize();
|
|
|
|
// Don't accept it if it can't get into a block
|
|
CAmount txMinFee = GetMinRelayFee(tx, nSize, true);
|
|
if (fLimitFree && nFees < txMinFee)
|
|
return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %d < %d",
|
|
hash.ToString(), nFees, txMinFee),
|
|
REJECT_INSUFFICIENTFEE, "insufficient fee");
|
|
|
|
// Continuously rate-limit free (really, very-low-fee)transactions
|
|
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
|
|
// be annoying or make others' transactions take longer to confirm.
|
|
if (fLimitFree && nFees < ::minRelayTxFee.GetFee(nSize))
|
|
{
|
|
static CCriticalSection csFreeLimiter;
|
|
static double dFreeCount;
|
|
static int64_t nLastTime;
|
|
int64_t nNow = GetTime();
|
|
|
|
LOCK(csFreeLimiter);
|
|
|
|
// Use an exponentially decaying ~10-minute window:
|
|
dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
|
|
nLastTime = nNow;
|
|
// -limitfreerelay unit is thousand-bytes-per-minute
|
|
// At default rate it would take over a month to fill 1GB
|
|
if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000)
|
|
return state.DoS(0, error("AcceptToMemoryPool : free transaction rejected by rate limiter"),
|
|
REJECT_INSUFFICIENTFEE, "insufficient priority");
|
|
LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
|
|
dFreeCount += nSize;
|
|
}
|
|
|
|
if (fRejectInsaneFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000)
|
|
return error("AcceptToMemoryPool: : insane fees %s, %d > %d",
|
|
hash.ToString(),
|
|
nFees, ::minRelayTxFee.GetFee(nSize) * 10000);
|
|
|
|
// Check against previous transactions
|
|
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
|
if (!CheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true))
|
|
{
|
|
return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString());
|
|
}
|
|
// Store transaction in memory
|
|
pool.addUnchecked(hash, entry);
|
|
}
|
|
|
|
SyncWithWallets(tx, NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock
|
|
bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow)
|
|
{
|
|
CBlockIndex *pindexSlow = NULL;
|
|
{
|
|
LOCK(cs_main);
|
|
{
|
|
if (mempool.lookup(hash, txOut))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (fTxIndex) {
|
|
CDiskTxPos postx;
|
|
if (pblocktree->ReadTxIndex(hash, postx)) {
|
|
CAutoFile file(OpenBlockFile(postx, true), SER_DISK, CLIENT_VERSION);
|
|
CBlockHeader header;
|
|
try {
|
|
file >> header;
|
|
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
|
|
file >> txOut;
|
|
} catch (std::exception &e) {
|
|
return error("%s : Deserialize or I/O error - %s", __func__, e.what());
|
|
}
|
|
hashBlock = header.GetHash();
|
|
if (txOut.GetHash() != hash)
|
|
return error("%s : txid mismatch", __func__);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
|
|
int nHeight = -1;
|
|
{
|
|
CCoinsViewCache &view = *pcoinsTip;
|
|
const CCoins* coins = view.AccessCoins(hash);
|
|
if (coins)
|
|
nHeight = coins->nHeight;
|
|
}
|
|
if (nHeight > 0)
|
|
pindexSlow = chainActive[nHeight];
|
|
}
|
|
}
|
|
|
|
if (pindexSlow) {
|
|
CBlock block;
|
|
if (ReadBlockFromDisk(block, pindexSlow)) {
|
|
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
|
|
if (tx.GetHash() == hash) {
|
|
txOut = tx;
|
|
hashBlock = pindexSlow->GetBlockHash();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CBlock and CBlockIndex
|
|
//
|
|
|
|
bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos)
|
|
{
|
|
// Open history file to append
|
|
CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
|
|
if (fileout.IsNull())
|
|
return error("WriteBlockToDisk : OpenBlockFile failed");
|
|
|
|
// Write index header
|
|
unsigned int nSize = fileout.GetSerializeSize(block);
|
|
fileout << FLATDATA(Params().MessageStart()) << nSize;
|
|
|
|
// Write block
|
|
long fileOutPos = ftell(fileout.Get());
|
|
if (fileOutPos < 0)
|
|
return error("WriteBlockToDisk : ftell failed");
|
|
pos.nPos = (unsigned int)fileOutPos;
|
|
fileout << block;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos)
|
|
{
|
|
block.SetNull();
|
|
|
|
// Open history file to read
|
|
CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION);
|
|
if (filein.IsNull())
|
|
return error("ReadBlockFromDisk : OpenBlockFile failed");
|
|
|
|
// Read block
|
|
try {
|
|
filein >> block;
|
|
}
|
|
catch (std::exception &e) {
|
|
return error("%s : Deserialize or I/O error - %s", __func__, e.what());
|
|
}
|
|
|
|
// Check the header
|
|
if (!CheckProofOfWork(block.GetHash(), block.nBits))
|
|
return error("ReadBlockFromDisk : Errors in block header");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex)
|
|
{
|
|
if (!ReadBlockFromDisk(block, pindex->GetBlockPos()))
|
|
return false;
|
|
if (block.GetHash() != pindex->GetBlockHash())
|
|
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*) : GetHash() doesn't match index");
|
|
return true;
|
|
}
|
|
|
|
CAmount GetBlockValue(int nHeight, const CAmount& nFees)
|
|
{
|
|
int64_t nSubsidy = 50 * COIN;
|
|
int halvings = nHeight / Params().SubsidyHalvingInterval();
|
|
|
|
// Force block reward to zero when right shift is undefined.
|
|
if (halvings >= 64)
|
|
return nFees;
|
|
|
|
// Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.
|
|
nSubsidy >>= halvings;
|
|
|
|
return nSubsidy + nFees;
|
|
}
|
|
|
|
bool IsInitialBlockDownload()
|
|
{
|
|
LOCK(cs_main);
|
|
if (fImporting || fReindex || chainActive.Height() < Checkpoints::GetTotalBlocksEstimate())
|
|
return true;
|
|
static int64_t nLastUpdate;
|
|
static CBlockIndex* pindexLastBest;
|
|
if (chainActive.Tip() != pindexLastBest)
|
|
{
|
|
pindexLastBest = chainActive.Tip();
|
|
nLastUpdate = GetTime();
|
|
}
|
|
return (GetTime() - nLastUpdate < 10 &&
|
|
chainActive.Tip()->GetBlockTime() < GetTime() - 24 * 60 * 60);
|
|
}
|
|
|
|
bool fLargeWorkForkFound = false;
|
|
bool fLargeWorkInvalidChainFound = false;
|
|
CBlockIndex *pindexBestForkTip = NULL, *pindexBestForkBase = NULL;
|
|
|
|
void CheckForkWarningConditions()
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
// Before we get past initial download, we cannot reliably alert about forks
|
|
// (we assume we don't get stuck on a fork before the last checkpoint)
|
|
if (IsInitialBlockDownload())
|
|
return;
|
|
|
|
// If our best fork is no longer within 72 blocks (+/- 12 hours if no one mines it)
|
|
// of our head, drop it
|
|
if (pindexBestForkTip && chainActive.Height() - pindexBestForkTip->nHeight >= 72)
|
|
pindexBestForkTip = NULL;
|
|
|
|
if (pindexBestForkTip || (pindexBestInvalid && pindexBestInvalid->nChainWork > chainActive.Tip()->nChainWork + (GetBlockProof(*chainActive.Tip()) * 6)))
|
|
{
|
|
if (!fLargeWorkForkFound)
|
|
{
|
|
std::string warning = std::string("'Warning: Large-work fork detected, forking after block ") +
|
|
pindexBestForkBase->phashBlock->ToString() + std::string("'");
|
|
CAlert::Notify(warning, true);
|
|
}
|
|
if (pindexBestForkTip)
|
|
{
|
|
LogPrintf("CheckForkWarningConditions: Warning: Large valid fork found\n forking the chain at height %d (%s)\n lasting to height %d (%s).\nChain state database corruption likely.\n",
|
|
pindexBestForkBase->nHeight, pindexBestForkBase->phashBlock->ToString(),
|
|
pindexBestForkTip->nHeight, pindexBestForkTip->phashBlock->ToString());
|
|
fLargeWorkForkFound = true;
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("CheckForkWarningConditions: Warning: Found invalid chain at least ~6 blocks longer than our best chain.\nChain state database corruption likely.\n");
|
|
fLargeWorkInvalidChainFound = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fLargeWorkForkFound = false;
|
|
fLargeWorkInvalidChainFound = false;
|
|
}
|
|
}
|
|
|
|
void CheckForkWarningConditionsOnNewFork(CBlockIndex* pindexNewForkTip)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
// If we are on a fork that is sufficiently large, set a warning flag
|
|
CBlockIndex* pfork = pindexNewForkTip;
|
|
CBlockIndex* plonger = chainActive.Tip();
|
|
while (pfork && pfork != plonger)
|
|
{
|
|
while (plonger && plonger->nHeight > pfork->nHeight)
|
|
plonger = plonger->pprev;
|
|
if (pfork == plonger)
|
|
break;
|
|
pfork = pfork->pprev;
|
|
}
|
|
|
|
// We define a condition which we should warn the user about as a fork of at least 7 blocks
|
|
// who's tip is within 72 blocks (+/- 12 hours if no one mines it) of ours
|
|
// We use 7 blocks rather arbitrarily as it represents just under 10% of sustained network
|
|
// hash rate operating on the fork.
|
|
// or a chain that is entirely longer than ours and invalid (note that this should be detected by both)
|
|
// We define it this way because it allows us to only store the highest fork tip (+ base) which meets
|
|
// the 7-block condition and from this always have the most-likely-to-cause-warning fork
|
|
if (pfork && (!pindexBestForkTip || (pindexBestForkTip && pindexNewForkTip->nHeight > pindexBestForkTip->nHeight)) &&
|
|
pindexNewForkTip->nChainWork - pfork->nChainWork > (GetBlockProof(*pfork) * 7) &&
|
|
chainActive.Height() - pindexNewForkTip->nHeight < 72)
|
|
{
|
|
pindexBestForkTip = pindexNewForkTip;
|
|
pindexBestForkBase = pfork;
|
|
}
|
|
|
|
CheckForkWarningConditions();
|
|
}
|
|
|
|
// Requires cs_main.
|
|
void Misbehaving(NodeId pnode, int howmuch)
|
|
{
|
|
if (howmuch == 0)
|
|
return;
|
|
|
|
CNodeState *state = State(pnode);
|
|
if (state == NULL)
|
|
return;
|
|
|
|
state->nMisbehavior += howmuch;
|
|
int banscore = GetArg("-banscore", 100);
|
|
if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore)
|
|
{
|
|
LogPrintf("Misbehaving: %s (%d -> %d) BAN THRESHOLD EXCEEDED\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
|
|
state->fShouldBan = true;
|
|
} else
|
|
LogPrintf("Misbehaving: %s (%d -> %d)\n", state->name, state->nMisbehavior-howmuch, state->nMisbehavior);
|
|
}
|
|
|
|
void static InvalidChainFound(CBlockIndex* pindexNew)
|
|
{
|
|
if (!pindexBestInvalid || pindexNew->nChainWork > pindexBestInvalid->nChainWork)
|
|
pindexBestInvalid = pindexNew;
|
|
|
|
LogPrintf("InvalidChainFound: invalid block=%s height=%d log2_work=%.8g date=%s\n",
|
|
pindexNew->GetBlockHash().ToString(), pindexNew->nHeight,
|
|
log(pindexNew->nChainWork.getdouble())/log(2.0), DateTimeStrFormat("%Y-%m-%d %H:%M:%S",
|
|
pindexNew->GetBlockTime()));
|
|
LogPrintf("InvalidChainFound: current best=%s height=%d log2_work=%.8g date=%s\n",
|
|
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0),
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()));
|
|
CheckForkWarningConditions();
|
|
}
|
|
|
|
void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) {
|
|
int nDoS = 0;
|
|
if (state.IsInvalid(nDoS)) {
|
|
std::map<uint256, NodeId>::iterator it = mapBlockSource.find(pindex->GetBlockHash());
|
|
if (it != mapBlockSource.end() && State(it->second)) {
|
|
CBlockReject reject = {state.GetRejectCode(), state.GetRejectReason(), pindex->GetBlockHash()};
|
|
State(it->second)->rejects.push_back(reject);
|
|
if (nDoS > 0)
|
|
Misbehaving(it->second, nDoS);
|
|
}
|
|
}
|
|
if (!state.CorruptionPossible()) {
|
|
pindex->nStatus |= BLOCK_FAILED_VALID;
|
|
setDirtyBlockIndex.insert(pindex);
|
|
setBlockIndexCandidates.erase(pindex);
|
|
InvalidChainFound(pindex);
|
|
}
|
|
}
|
|
|
|
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
|
|
{
|
|
// mark inputs spent
|
|
if (!tx.IsCoinBase()) {
|
|
txundo.vprevout.reserve(tx.vin.size());
|
|
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
|
|
txundo.vprevout.push_back(CTxInUndo());
|
|
bool ret = inputs.ModifyCoins(txin.prevout.hash)->Spend(txin.prevout, txundo.vprevout.back());
|
|
assert(ret);
|
|
}
|
|
}
|
|
|
|
// add outputs
|
|
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
|
|
}
|
|
|
|
bool CScriptCheck::operator()() const {
|
|
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
|
|
if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingSignatureChecker(*ptxTo, nIn, cacheStore)))
|
|
return error("CScriptCheck() : %s:%d VerifySignature failed", ptxTo->GetHash().ToString(), nIn);
|
|
return true;
|
|
}
|
|
|
|
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks)
|
|
{
|
|
if (!tx.IsCoinBase())
|
|
{
|
|
if (pvChecks)
|
|
pvChecks->reserve(tx.vin.size());
|
|
|
|
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
|
|
// for an attacker to attempt to split the network.
|
|
if (!inputs.HaveInputs(tx))
|
|
return state.Invalid(error("CheckInputs() : %s inputs unavailable", tx.GetHash().ToString()));
|
|
|
|
// While checking, GetBestBlock() refers to the parent block.
|
|
// This is also true for mempool checks.
|
|
CBlockIndex *pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
|
|
int nSpendHeight = pindexPrev->nHeight + 1;
|
|
CAmount nValueIn = 0;
|
|
CAmount nFees = 0;
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
|
{
|
|
const COutPoint &prevout = tx.vin[i].prevout;
|
|
const CCoins *coins = inputs.AccessCoins(prevout.hash);
|
|
assert(coins);
|
|
|
|
// If prev is coinbase, check that it's matured
|
|
if (coins->IsCoinBase()) {
|
|
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY)
|
|
return state.Invalid(
|
|
error("CheckInputs() : tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight),
|
|
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase");
|
|
}
|
|
|
|
// Check for negative or overflow input values
|
|
nValueIn += coins->vout[prevout.n].nValue;
|
|
if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn))
|
|
return state.DoS(100, error("CheckInputs() : txin values out of range"),
|
|
REJECT_INVALID, "bad-txns-inputvalues-outofrange");
|
|
|
|
}
|
|
|
|
if (nValueIn < tx.GetValueOut())
|
|
return state.DoS(100, error("CheckInputs() : %s value in (%s) < value out (%s)",
|
|
tx.GetHash().ToString(), FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())),
|
|
REJECT_INVALID, "bad-txns-in-belowout");
|
|
|
|
// Tally transaction fees
|
|
CAmount nTxFee = nValueIn - tx.GetValueOut();
|
|
if (nTxFee < 0)
|
|
return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString()),
|
|
REJECT_INVALID, "bad-txns-fee-negative");
|
|
nFees += nTxFee;
|
|
if (!MoneyRange(nFees))
|
|
return state.DoS(100, error("CheckInputs() : nFees out of range"),
|
|
REJECT_INVALID, "bad-txns-fee-outofrange");
|
|
|
|
// The first loop above does all the inexpensive checks.
|
|
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
|
|
// Helps prevent CPU exhaustion attacks.
|
|
|
|
// Skip ECDSA signature verification when connecting blocks
|
|
// before the last block chain checkpoint. This is safe because block merkle hashes are
|
|
// still computed and checked, and any change will be caught at the next checkpoint.
|
|
if (fScriptChecks) {
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
|
const COutPoint &prevout = tx.vin[i].prevout;
|
|
const CCoins* coins = inputs.AccessCoins(prevout.hash);
|
|
assert(coins);
|
|
|
|
// Verify signature
|
|
CScriptCheck check(*coins, tx, i, flags, cacheStore);
|
|
if (pvChecks) {
|
|
pvChecks->push_back(CScriptCheck());
|
|
check.swap(pvChecks->back());
|
|
} else if (!check()) {
|
|
if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
|
|
// Check whether the failure was caused by a
|
|
// non-mandatory script verification check, such as
|
|
// non-standard DER encodings or non-null dummy
|
|
// arguments; if so, don't trigger DoS protection to
|
|
// avoid splitting the network between upgraded and
|
|
// non-upgraded nodes.
|
|
CScriptCheck check(*coins, tx, i,
|
|
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore);
|
|
if (check())
|
|
return state.Invalid(false, REJECT_NONSTANDARD, "non-mandatory-script-verify-flag");
|
|
}
|
|
// Failures of other flags indicate a transaction that is
|
|
// invalid in new blocks, e.g. a invalid P2SH. We DoS ban
|
|
// such nodes as they are not following the protocol. That
|
|
// said during an upgrade careful thought should be taken
|
|
// as to the correct behavior - we may want to continue
|
|
// peering with non-upgraded nodes even after a soft-fork
|
|
// super-majority vote has passed.
|
|
return state.DoS(100,false, REJECT_INVALID, "mandatory-script-verify-flag-failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean)
|
|
{
|
|
assert(pindex->GetBlockHash() == view.GetBestBlock());
|
|
|
|
if (pfClean)
|
|
*pfClean = false;
|
|
|
|
bool fClean = true;
|
|
|
|
CBlockUndo blockUndo;
|
|
CDiskBlockPos pos = pindex->GetUndoPos();
|
|
if (pos.IsNull())
|
|
return error("DisconnectBlock() : no undo data available");
|
|
if (!blockUndo.ReadFromDisk(pos, pindex->pprev->GetBlockHash()))
|
|
return error("DisconnectBlock() : failure reading undo data");
|
|
|
|
if (blockUndo.vtxundo.size() + 1 != block.vtx.size())
|
|
return error("DisconnectBlock() : block and undo data inconsistent");
|
|
|
|
// undo transactions in reverse order
|
|
for (int i = block.vtx.size() - 1; i >= 0; i--) {
|
|
const CTransaction &tx = block.vtx[i];
|
|
uint256 hash = tx.GetHash();
|
|
|
|
// Check that all outputs are available and match the outputs in the block itself
|
|
// exactly. Note that transactions with only provably unspendable outputs won't
|
|
// have outputs available even in the block itself, so we handle that case
|
|
// specially with outsEmpty.
|
|
{
|
|
CCoins outsEmpty;
|
|
CCoinsModifier outs = view.ModifyCoins(hash);
|
|
outs->ClearUnspendable();
|
|
|
|
CCoins outsBlock(tx, pindex->nHeight);
|
|
// The CCoins serialization does not serialize negative numbers.
|
|
// No network rules currently depend on the version here, so an inconsistency is harmless
|
|
// but it must be corrected before txout nversion ever influences a network rule.
|
|
if (outsBlock.nVersion < 0)
|
|
outs->nVersion = outsBlock.nVersion;
|
|
if (*outs != outsBlock)
|
|
fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted");
|
|
|
|
// remove outputs
|
|
outs->Clear();
|
|
}
|
|
|
|
// restore inputs
|
|
if (i > 0) { // not coinbases
|
|
const CTxUndo &txundo = blockUndo.vtxundo[i-1];
|
|
if (txundo.vprevout.size() != tx.vin.size())
|
|
return error("DisconnectBlock() : transaction and undo data inconsistent");
|
|
for (unsigned int j = tx.vin.size(); j-- > 0;) {
|
|
const COutPoint &out = tx.vin[j].prevout;
|
|
const CTxInUndo &undo = txundo.vprevout[j];
|
|
CCoinsModifier coins = view.ModifyCoins(out.hash);
|
|
if (undo.nHeight != 0) {
|
|
// undo data contains height: this is the last output of the prevout tx being spent
|
|
if (!coins->IsPruned())
|
|
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction");
|
|
coins->Clear();
|
|
coins->fCoinBase = undo.fCoinBase;
|
|
coins->nHeight = undo.nHeight;
|
|
coins->nVersion = undo.nVersion;
|
|
} else {
|
|
if (coins->IsPruned())
|
|
fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction");
|
|
}
|
|
if (coins->IsAvailable(out.n))
|
|
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing output");
|
|
if (coins->vout.size() < out.n+1)
|
|
coins->vout.resize(out.n+1);
|
|
coins->vout[out.n] = undo.txout;
|
|
}
|
|
}
|
|
}
|
|
|
|
// move best block pointer to prevout block
|
|
view.SetBestBlock(pindex->pprev->GetBlockHash());
|
|
|
|
if (pfClean) {
|
|
*pfClean = fClean;
|
|
return true;
|
|
} else {
|
|
return fClean;
|
|
}
|
|
}
|
|
|
|
void static FlushBlockFile(bool fFinalize = false)
|
|
{
|
|
LOCK(cs_LastBlockFile);
|
|
|
|
CDiskBlockPos posOld(nLastBlockFile, 0);
|
|
|
|
FILE *fileOld = OpenBlockFile(posOld);
|
|
if (fileOld) {
|
|
if (fFinalize)
|
|
TruncateFile(fileOld, vinfoBlockFile[nLastBlockFile].nSize);
|
|
FileCommit(fileOld);
|
|
fclose(fileOld);
|
|
}
|
|
|
|
fileOld = OpenUndoFile(posOld);
|
|
if (fileOld) {
|
|
if (fFinalize)
|
|
TruncateFile(fileOld, vinfoBlockFile[nLastBlockFile].nUndoSize);
|
|
FileCommit(fileOld);
|
|
fclose(fileOld);
|
|
}
|
|
}
|
|
|
|
bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigned int nAddSize);
|
|
|
|
static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
|
|
|
|
void ThreadScriptCheck() {
|
|
RenameThread("bitcoin-scriptch");
|
|
scriptcheckqueue.Thread();
|
|
}
|
|
|
|
static int64_t nTimeVerify = 0;
|
|
static int64_t nTimeConnect = 0;
|
|
static int64_t nTimeIndex = 0;
|
|
static int64_t nTimeCallbacks = 0;
|
|
static int64_t nTimeTotal = 0;
|
|
|
|
bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
// Check it again in case a previous version let a bad block in
|
|
if (!CheckBlock(block, state, !fJustCheck, !fJustCheck))
|
|
return false;
|
|
|
|
// verify that the view's current state corresponds to the previous block
|
|
uint256 hashPrevBlock = pindex->pprev == NULL ? uint256(0) : pindex->pprev->GetBlockHash();
|
|
assert(hashPrevBlock == view.GetBestBlock());
|
|
|
|
// Special case for the genesis block, skipping connection of its transactions
|
|
// (its coinbase is unspendable)
|
|
if (block.GetHash() == Params().HashGenesisBlock()) {
|
|
view.SetBestBlock(pindex->GetBlockHash());
|
|
return true;
|
|
}
|
|
|
|
bool fScriptChecks = pindex->nHeight >= Checkpoints::GetTotalBlocksEstimate();
|
|
|
|
// Do not allow blocks that contain transactions which 'overwrite' older transactions,
|
|
// unless those are already completely spent.
|
|
// If such overwrites are allowed, coinbases and transactions depending upon those
|
|
// can be duplicated to remove the ability to spend the first instance -- even after
|
|
// being sent to another address.
|
|
// See BIP30 and http://r6.ca/blog/20120206T005236Z.html for more information.
|
|
// This logic is not necessary for memory pool transactions, as AcceptToMemoryPool
|
|
// already refuses previously-known transaction ids entirely.
|
|
// This rule was originally applied all blocks whose timestamp was after March 15, 2012, 0:00 UTC.
|
|
// Now that the whole chain is irreversibly beyond that time it is applied to all blocks except the
|
|
// two in the chain that violate it. This prevents exploiting the issue against nodes in their
|
|
// initial block download.
|
|
bool fEnforceBIP30 = (!pindex->phashBlock) || // Enforce on CreateNewBlock invocations which don't have a hash.
|
|
!((pindex->nHeight==91842 && pindex->GetBlockHash() == uint256("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) ||
|
|
(pindex->nHeight==91880 && pindex->GetBlockHash() == uint256("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")));
|
|
if (fEnforceBIP30) {
|
|
BOOST_FOREACH(const CTransaction& tx, block.vtx) {
|
|
const CCoins* coins = view.AccessCoins(tx.GetHash());
|
|
if (coins && !coins->IsPruned())
|
|
return state.DoS(100, error("ConnectBlock() : tried to overwrite transaction"),
|
|
REJECT_INVALID, "bad-txns-BIP30");
|
|
}
|
|
}
|
|
|
|
// BIP16 didn't become active until Apr 1 2012
|
|
int64_t nBIP16SwitchTime = 1333238400;
|
|
bool fStrictPayToScriptHash = (pindex->GetBlockTime() >= nBIP16SwitchTime);
|
|
|
|
unsigned int flags = fStrictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE;
|
|
|
|
CBlockUndo blockundo;
|
|
|
|
CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : NULL);
|
|
|
|
int64_t nTimeStart = GetTimeMicros();
|
|
CAmount nFees = 0;
|
|
int nInputs = 0;
|
|
unsigned int nSigOps = 0;
|
|
CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
|
|
std::vector<std::pair<uint256, CDiskTxPos> > vPos;
|
|
vPos.reserve(block.vtx.size());
|
|
blockundo.vtxundo.reserve(block.vtx.size() - 1);
|
|
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
|
{
|
|
const CTransaction &tx = block.vtx[i];
|
|
|
|
nInputs += tx.vin.size();
|
|
nSigOps += GetLegacySigOpCount(tx);
|
|
if (nSigOps > MAX_BLOCK_SIGOPS)
|
|
return state.DoS(100, error("ConnectBlock() : too many sigops"),
|
|
REJECT_INVALID, "bad-blk-sigops");
|
|
|
|
if (!tx.IsCoinBase())
|
|
{
|
|
if (!view.HaveInputs(tx))
|
|
return state.DoS(100, error("ConnectBlock() : inputs missing/spent"),
|
|
REJECT_INVALID, "bad-txns-inputs-missingorspent");
|
|
|
|
if (fStrictPayToScriptHash)
|
|
{
|
|
// Add in sigops done by pay-to-script-hash inputs;
|
|
// this is to prevent a "rogue miner" from creating
|
|
// an incredibly-expensive-to-validate block.
|
|
nSigOps += GetP2SHSigOpCount(tx, view);
|
|
if (nSigOps > MAX_BLOCK_SIGOPS)
|
|
return state.DoS(100, error("ConnectBlock() : too many sigops"),
|
|
REJECT_INVALID, "bad-blk-sigops");
|
|
}
|
|
|
|
nFees += view.GetValueIn(tx)-tx.GetValueOut();
|
|
|
|
std::vector<CScriptCheck> vChecks;
|
|
if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL))
|
|
return false;
|
|
control.Add(vChecks);
|
|
}
|
|
|
|
CTxUndo undoDummy;
|
|
if (i > 0) {
|
|
blockundo.vtxundo.push_back(CTxUndo());
|
|
}
|
|
UpdateCoins(tx, state, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
|
|
|
|
vPos.push_back(std::make_pair(tx.GetHash(), pos));
|
|
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
|
|
}
|
|
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
|
|
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
|
|
|
|
if (block.vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees))
|
|
return state.DoS(100,
|
|
error("ConnectBlock() : coinbase pays too much (actual=%d vs limit=%d)",
|
|
block.vtx[0].GetValueOut(), GetBlockValue(pindex->nHeight, nFees)),
|
|
REJECT_INVALID, "bad-cb-amount");
|
|
|
|
if (!control.Wait())
|
|
return state.DoS(100, false);
|
|
int64_t nTime2 = GetTimeMicros(); nTimeVerify += nTime2 - nTimeStart;
|
|
LogPrint("bench", " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs]\n", nInputs - 1, 0.001 * (nTime2 - nTimeStart), nInputs <= 1 ? 0 : 0.001 * (nTime2 - nTimeStart) / (nInputs-1), nTimeVerify * 0.000001);
|
|
|
|
if (fJustCheck)
|
|
return true;
|
|
|
|
// Write undo information to disk
|
|
if (pindex->GetUndoPos().IsNull() || !pindex->IsValid(BLOCK_VALID_SCRIPTS))
|
|
{
|
|
if (pindex->GetUndoPos().IsNull()) {
|
|
CDiskBlockPos pos;
|
|
if (!FindUndoPos(state, pindex->nFile, pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 40))
|
|
return error("ConnectBlock() : FindUndoPos failed");
|
|
if (!blockundo.WriteToDisk(pos, pindex->pprev->GetBlockHash()))
|
|
return state.Abort("Failed to write undo data");
|
|
|
|
// update nUndoPos in block index
|
|
pindex->nUndoPos = pos.nPos;
|
|
pindex->nStatus |= BLOCK_HAVE_UNDO;
|
|
}
|
|
|
|
pindex->RaiseValidity(BLOCK_VALID_SCRIPTS);
|
|
setDirtyBlockIndex.insert(pindex);
|
|
}
|
|
|
|
if (fTxIndex)
|
|
if (!pblocktree->WriteTxIndex(vPos))
|
|
return state.Abort("Failed to write transaction index");
|
|
|
|
// add this block to the view's block chain
|
|
view.SetBestBlock(pindex->GetBlockHash());
|
|
|
|
int64_t nTime3 = GetTimeMicros(); nTimeIndex += nTime3 - nTime2;
|
|
LogPrint("bench", " - Index writing: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeIndex * 0.000001);
|
|
|
|
// Watch for changes to the previous coinbase transaction.
|
|
static uint256 hashPrevBestCoinBase;
|
|
g_signals.UpdatedTransaction(hashPrevBestCoinBase);
|
|
hashPrevBestCoinBase = block.vtx[0].GetHash();
|
|
|
|
int64_t nTime4 = GetTimeMicros(); nTimeCallbacks += nTime4 - nTime3;
|
|
LogPrint("bench", " - Callbacks: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3), nTimeCallbacks * 0.000001);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Update the on-disk chain state.
|
|
* The caches and indexes are flushed if either they're too large, forceWrite is set, or
|
|
* fast is not set and it's been a while since the last write.
|
|
*/
|
|
bool static FlushStateToDisk(CValidationState &state, bool fast = false, bool forceWrite = false) {
|
|
LOCK(cs_main);
|
|
static int64_t nLastWrite = 0;
|
|
if (forceWrite || pcoinsTip->GetCacheSize() > nCoinCacheSize ||
|
|
(!fast && GetTimeMicros() > nLastWrite + DATABASE_WRITE_INTERVAL * 1000000)) {
|
|
// Typical CCoins structures on disk are around 100 bytes in size.
|
|
// Pushing a new one to the database can cause it to be written
|
|
// twice (once in the log, and once in the tables). This is already
|
|
// an overestimation, as most will delete an existing entry or
|
|
// overwrite one. Still, use a conservative safety factor of 2.
|
|
if (!CheckDiskSpace(100 * 2 * 2 * pcoinsTip->GetCacheSize()))
|
|
return state.Error("out of disk space");
|
|
// First make sure all block and undo data is flushed to disk.
|
|
FlushBlockFile();
|
|
// Then update all block file information (which may refer to block and undo files).
|
|
bool fileschanged = false;
|
|
for (set<int>::iterator it = setDirtyFileInfo.begin(); it != setDirtyFileInfo.end(); ) {
|
|
if (!pblocktree->WriteBlockFileInfo(*it, vinfoBlockFile[*it])) {
|
|
return state.Abort("Failed to write to block index");
|
|
}
|
|
fileschanged = true;
|
|
setDirtyFileInfo.erase(it++);
|
|
}
|
|
if (fileschanged && !pblocktree->WriteLastBlockFile(nLastBlockFile)) {
|
|
return state.Abort("Failed to write to block index");
|
|
}
|
|
for (set<CBlockIndex*>::iterator it = setDirtyBlockIndex.begin(); it != setDirtyBlockIndex.end(); ) {
|
|
if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(*it))) {
|
|
return state.Abort("Failed to write to block index");
|
|
}
|
|
setDirtyBlockIndex.erase(it++);
|
|
}
|
|
pblocktree->Sync();
|
|
// Finally flush the chainstate (which may refer to block index entries).
|
|
if (!pcoinsTip->Flush())
|
|
return state.Abort("Failed to write to coin database");
|
|
// Update best block in wallet (so we can detect restored wallets).
|
|
if (forceWrite || !fast) {
|
|
g_signals.SetBestChain(chainActive.GetLocator());
|
|
}
|
|
nLastWrite = GetTimeMicros();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FlushStateToDisk() {
|
|
CValidationState state;
|
|
FlushStateToDisk(state, false, true);
|
|
}
|
|
|
|
// Update chainActive and related internal data structures.
|
|
void static UpdateTip(CBlockIndex *pindexNew) {
|
|
chainActive.SetTip(pindexNew);
|
|
|
|
// New best block
|
|
nTimeBestReceived = GetTime();
|
|
mempool.AddTransactionsUpdated(1);
|
|
|
|
LogPrintf("UpdateTip: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f cache=%u\n",
|
|
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)chainActive.Tip()->nChainTx,
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
|
|
Checkpoints::GuessVerificationProgress(chainActive.Tip()), (unsigned int)pcoinsTip->GetCacheSize());
|
|
|
|
cvBlockChange.notify_all();
|
|
|
|
// Check the version of the last 100 blocks to see if we need to upgrade:
|
|
static bool fWarned = false;
|
|
if (!IsInitialBlockDownload() && !fWarned)
|
|
{
|
|
int nUpgraded = 0;
|
|
const CBlockIndex* pindex = chainActive.Tip();
|
|
for (int i = 0; i < 100 && pindex != NULL; i++)
|
|
{
|
|
if (pindex->nVersion > CBlock::CURRENT_VERSION)
|
|
++nUpgraded;
|
|
pindex = pindex->pprev;
|
|
}
|
|
if (nUpgraded > 0)
|
|
LogPrintf("SetBestChain: %d of last 100 blocks above version %d\n", nUpgraded, (int)CBlock::CURRENT_VERSION);
|
|
if (nUpgraded > 100/2)
|
|
{
|
|
// strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user:
|
|
strMiscWarning = _("Warning: This version is obsolete, upgrade required!");
|
|
CAlert::Notify(strMiscWarning, true);
|
|
fWarned = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Disconnect chainActive's tip.
|
|
bool static DisconnectTip(CValidationState &state) {
|
|
CBlockIndex *pindexDelete = chainActive.Tip();
|
|
assert(pindexDelete);
|
|
mempool.check(pcoinsTip);
|
|
// Read block from disk.
|
|
CBlock block;
|
|
if (!ReadBlockFromDisk(block, pindexDelete))
|
|
return state.Abort("Failed to read block");
|
|
// Apply the block atomically to the chain state.
|
|
int64_t nStart = GetTimeMicros();
|
|
{
|
|
CCoinsViewCache view(pcoinsTip);
|
|
if (!DisconnectBlock(block, state, pindexDelete, view))
|
|
return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString());
|
|
assert(view.Flush());
|
|
}
|
|
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
|
|
// Write the chain state to disk, if necessary.
|
|
if (!FlushStateToDisk(state, true))
|
|
return false;
|
|
// Resurrect mempool transactions from the disconnected block.
|
|
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
|
|
// ignore validation errors in resurrected transactions
|
|
list<CTransaction> removed;
|
|
CValidationState stateDummy;
|
|
if (!tx.IsCoinBase())
|
|
if (!AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
|
|
mempool.remove(tx, removed, true);
|
|
}
|
|
mempool.check(pcoinsTip);
|
|
// Update chainActive and related variables.
|
|
UpdateTip(pindexDelete->pprev);
|
|
// Let wallets know transactions went from 1-confirmed to
|
|
// 0-confirmed or conflicted:
|
|
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
|
|
SyncWithWallets(tx, NULL);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int64_t nTimeReadFromDisk = 0;
|
|
static int64_t nTimeConnectTotal = 0;
|
|
static int64_t nTimeFlush = 0;
|
|
static int64_t nTimeChainState = 0;
|
|
static int64_t nTimePostConnect = 0;
|
|
|
|
// Connect a new block to chainActive. pblock is either NULL or a pointer to a CBlock
|
|
// corresponding to pindexNew, to bypass loading it again from disk.
|
|
bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *pblock) {
|
|
assert(pindexNew->pprev == chainActive.Tip());
|
|
mempool.check(pcoinsTip);
|
|
// Read block from disk.
|
|
int64_t nTime1 = GetTimeMicros();
|
|
CBlock block;
|
|
if (!pblock) {
|
|
if (!ReadBlockFromDisk(block, pindexNew))
|
|
return state.Abort("Failed to read block");
|
|
pblock = █
|
|
}
|
|
// Apply the block atomically to the chain state.
|
|
int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
|
|
int64_t nTime3;
|
|
LogPrint("bench", " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001);
|
|
{
|
|
CCoinsViewCache view(pcoinsTip);
|
|
CInv inv(MSG_BLOCK, pindexNew->GetBlockHash());
|
|
bool rv = ConnectBlock(*pblock, state, pindexNew, view);
|
|
g_signals.BlockChecked(*pblock, state);
|
|
if (!rv) {
|
|
if (state.IsInvalid())
|
|
InvalidBlockFound(pindexNew, state);
|
|
return error("ConnectTip() : ConnectBlock %s failed", pindexNew->GetBlockHash().ToString());
|
|
}
|
|
mapBlockSource.erase(inv.hash);
|
|
nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2;
|
|
LogPrint("bench", " - Connect total: %.2fms [%.2fs]\n", (nTime3 - nTime2) * 0.001, nTimeConnectTotal * 0.000001);
|
|
assert(view.Flush());
|
|
}
|
|
int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3;
|
|
LogPrint("bench", " - Flush: %.2fms [%.2fs]\n", (nTime4 - nTime3) * 0.001, nTimeFlush * 0.000001);
|
|
// Write the chain state to disk, if necessary.
|
|
if (!FlushStateToDisk(state, true))
|
|
return false;
|
|
int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
|
|
LogPrint("bench", " - Writing chainstate: %.2fms [%.2fs]\n", (nTime5 - nTime4) * 0.001, nTimeChainState * 0.000001);
|
|
// Remove conflicting transactions from the mempool.
|
|
list<CTransaction> txConflicted;
|
|
mempool.removeForBlock(pblock->vtx, pindexNew->nHeight, txConflicted);
|
|
mempool.check(pcoinsTip);
|
|
// Update chainActive & related variables.
|
|
UpdateTip(pindexNew);
|
|
// Tell wallet about transactions that went from mempool
|
|
// to conflicted:
|
|
BOOST_FOREACH(const CTransaction &tx, txConflicted) {
|
|
SyncWithWallets(tx, NULL);
|
|
}
|
|
// ... and about transactions that got confirmed:
|
|
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
|
|
SyncWithWallets(tx, pblock);
|
|
}
|
|
|
|
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
|
|
LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001);
|
|
LogPrint("bench", "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001);
|
|
return true;
|
|
}
|
|
|
|
// Return the tip of the chain with the most work in it, that isn't
|
|
// known to be invalid (it's however far from certain to be valid).
|
|
static CBlockIndex* FindMostWorkChain() {
|
|
do {
|
|
CBlockIndex *pindexNew = NULL;
|
|
|
|
// Find the best candidate header.
|
|
{
|
|
std::set<CBlockIndex*, CBlockIndexWorkComparator>::reverse_iterator it = setBlockIndexCandidates.rbegin();
|
|
if (it == setBlockIndexCandidates.rend())
|
|
return NULL;
|
|
pindexNew = *it;
|
|
}
|
|
|
|
// Check whether all blocks on the path between the currently active chain and the candidate are valid.
|
|
// Just going until the active chain is an optimization, as we know all blocks in it are valid already.
|
|
CBlockIndex *pindexTest = pindexNew;
|
|
bool fInvalidAncestor = false;
|
|
while (pindexTest && !chainActive.Contains(pindexTest)) {
|
|
assert(pindexTest->nStatus & BLOCK_HAVE_DATA);
|
|
assert(pindexTest->nChainTx || pindexTest->nHeight == 0);
|
|
if (pindexTest->nStatus & BLOCK_FAILED_MASK) {
|
|
// Candidate has an invalid ancestor, remove entire chain from the set.
|
|
if (pindexBestInvalid == NULL || pindexNew->nChainWork > pindexBestInvalid->nChainWork)
|
|
pindexBestInvalid = pindexNew;
|
|
CBlockIndex *pindexFailed = pindexNew;
|
|
while (pindexTest != pindexFailed) {
|
|
pindexFailed->nStatus |= BLOCK_FAILED_CHILD;
|
|
setBlockIndexCandidates.erase(pindexFailed);
|
|
pindexFailed = pindexFailed->pprev;
|
|
}
|
|
setBlockIndexCandidates.erase(pindexTest);
|
|
fInvalidAncestor = true;
|
|
break;
|
|
}
|
|
pindexTest = pindexTest->pprev;
|
|
}
|
|
if (!fInvalidAncestor)
|
|
return pindexNew;
|
|
} while(true);
|
|
}
|
|
|
|
// Try to make some progress towards making pindexMostWork the active block.
|
|
// pblock is either NULL or a pointer to a CBlock corresponding to pindexMostWork.
|
|
static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMostWork, CBlock *pblock) {
|
|
AssertLockHeld(cs_main);
|
|
bool fInvalidFound = false;
|
|
const CBlockIndex *pindexOldTip = chainActive.Tip();
|
|
const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork);
|
|
|
|
// Disconnect active blocks which are no longer in the best chain.
|
|
while (chainActive.Tip() && chainActive.Tip() != pindexFork) {
|
|
if (!DisconnectTip(state))
|
|
return false;
|
|
}
|
|
|
|
// Build list of new blocks to connect.
|
|
std::vector<CBlockIndex*> vpindexToConnect;
|
|
bool fContinue = true;
|
|
int nHeight = pindexFork ? pindexFork->nHeight : -1;
|
|
while (fContinue && nHeight != pindexMostWork->nHeight) {
|
|
// Don't iterate the entire list of potential improvements toward the best tip, as we likely only need
|
|
// a few blocks along the way.
|
|
int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
|
|
vpindexToConnect.clear();
|
|
vpindexToConnect.reserve(nTargetHeight - nHeight);
|
|
CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
|
|
while (pindexIter && pindexIter->nHeight != nHeight) {
|
|
vpindexToConnect.push_back(pindexIter);
|
|
pindexIter = pindexIter->pprev;
|
|
}
|
|
nHeight = nTargetHeight;
|
|
|
|
// Connect new blocks.
|
|
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
|
|
if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
|
|
if (state.IsInvalid()) {
|
|
// The block violates a consensus rule.
|
|
if (!state.CorruptionPossible())
|
|
InvalidChainFound(vpindexToConnect.back());
|
|
state = CValidationState();
|
|
fInvalidFound = true;
|
|
fContinue = false;
|
|
break;
|
|
} else {
|
|
// A system error occurred (disk space, database error, ...).
|
|
return false;
|
|
}
|
|
} else {
|
|
// Delete all entries in setBlockIndexCandidates that are worse than our new current block.
|
|
// Note that we can't delete the current block itself, as we may need to return to it later in case a
|
|
// reorganization to a better block fails.
|
|
std::set<CBlockIndex*, CBlockIndexWorkComparator>::iterator it = setBlockIndexCandidates.begin();
|
|
while (setBlockIndexCandidates.value_comp()(*it, chainActive.Tip())) {
|
|
setBlockIndexCandidates.erase(it++);
|
|
}
|
|
// Either the current tip or a successor of it we're working towards is left in setBlockIndexCandidates.
|
|
assert(!setBlockIndexCandidates.empty());
|
|
if (!pindexOldTip || chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) {
|
|
// We're in a better position than we were. Return temporarily to release the lock.
|
|
fContinue = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Callbacks/notifications for a new best chain.
|
|
if (fInvalidFound)
|
|
CheckForkWarningConditionsOnNewFork(vpindexToConnect.back());
|
|
else
|
|
CheckForkWarningConditions();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Make the best chain active, in multiple steps. The result is either failure
|
|
// or an activated best chain. pblock is either NULL or a pointer to a block
|
|
// that is already loaded (to avoid loading it again from disk).
|
|
bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
|
|
CBlockIndex *pindexNewTip = NULL;
|
|
CBlockIndex *pindexMostWork = NULL;
|
|
do {
|
|
boost::this_thread::interruption_point();
|
|
|
|
bool fInitialDownload;
|
|
{
|
|
LOCK(cs_main);
|
|
pindexMostWork = FindMostWorkChain();
|
|
|
|
// Whether we have anything to do at all.
|
|
if (pindexMostWork == NULL || pindexMostWork == chainActive.Tip())
|
|
return true;
|
|
|
|
if (!ActivateBestChainStep(state, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : NULL))
|
|
return false;
|
|
|
|
pindexNewTip = chainActive.Tip();
|
|
fInitialDownload = IsInitialBlockDownload();
|
|
}
|
|
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
|
|
|
|
// Notifications/callbacks that can run without cs_main
|
|
if (!fInitialDownload) {
|
|
uint256 hashNewTip = pindexNewTip->GetBlockHash();
|
|
// Relay inventory, but don't relay old inventory during initial block download.
|
|
int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate();
|
|
{
|
|
LOCK(cs_vNodes);
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
if (chainActive.Height() > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
|
|
pnode->PushInventory(CInv(MSG_BLOCK, hashNewTip));
|
|
}
|
|
// Notify external listeners about the new tip.
|
|
uiInterface.NotifyBlockTip(hashNewTip);
|
|
}
|
|
} while(pindexMostWork != chainActive.Tip());
|
|
|
|
// Write changes periodically to disk, after relay.
|
|
if (!FlushStateToDisk(state)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CBlockIndex* AddToBlockIndex(const CBlockHeader& block)
|
|
{
|
|
// Check for duplicate
|
|
uint256 hash = block.GetHash();
|
|
BlockMap::iterator it = mapBlockIndex.find(hash);
|
|
if (it != mapBlockIndex.end())
|
|
return it->second;
|
|
|
|
// Construct new block index object
|
|
CBlockIndex* pindexNew = new CBlockIndex(block);
|
|
assert(pindexNew);
|
|
// We assign the sequence id to blocks only when the full data is available,
|
|
// to avoid miners withholding blocks but broadcasting headers, to get a
|
|
// competitive advantage.
|
|
pindexNew->nSequenceId = 0;
|
|
BlockMap::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
|
|
pindexNew->phashBlock = &((*mi).first);
|
|
BlockMap::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock);
|
|
if (miPrev != mapBlockIndex.end())
|
|
{
|
|
pindexNew->pprev = (*miPrev).second;
|
|
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
|
|
pindexNew->BuildSkip();
|
|
}
|
|
pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew);
|
|
pindexNew->RaiseValidity(BLOCK_VALID_TREE);
|
|
if (pindexBestHeader == NULL || pindexBestHeader->nChainWork < pindexNew->nChainWork)
|
|
pindexBestHeader = pindexNew;
|
|
|
|
setDirtyBlockIndex.insert(pindexNew);
|
|
|
|
return pindexNew;
|
|
}
|
|
|
|
// Mark a block as having its data received and checked (up to BLOCK_VALID_TRANSACTIONS).
|
|
bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBlockIndex *pindexNew, const CDiskBlockPos& pos)
|
|
{
|
|
pindexNew->nTx = block.vtx.size();
|
|
pindexNew->nChainTx = 0;
|
|
pindexNew->nFile = pos.nFile;
|
|
pindexNew->nDataPos = pos.nPos;
|
|
pindexNew->nUndoPos = 0;
|
|
pindexNew->nStatus |= BLOCK_HAVE_DATA;
|
|
pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS);
|
|
{
|
|
LOCK(cs_nBlockSequenceId);
|
|
pindexNew->nSequenceId = nBlockSequenceId++;
|
|
}
|
|
setDirtyBlockIndex.insert(pindexNew);
|
|
|
|
if (pindexNew->pprev == NULL || pindexNew->pprev->nChainTx) {
|
|
// If pindexNew is the genesis block or all parents are BLOCK_VALID_TRANSACTIONS.
|
|
deque<CBlockIndex*> queue;
|
|
queue.push_back(pindexNew);
|
|
|
|
// Recursively process any descendant blocks that now may be eligible to be connected.
|
|
while (!queue.empty()) {
|
|
CBlockIndex *pindex = queue.front();
|
|
queue.pop_front();
|
|
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
|
|
setBlockIndexCandidates.insert(pindex);
|
|
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex);
|
|
while (range.first != range.second) {
|
|
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
|
|
queue.push_back(it->second);
|
|
range.first++;
|
|
mapBlocksUnlinked.erase(it);
|
|
}
|
|
}
|
|
} else {
|
|
if (pindexNew->pprev && pindexNew->pprev->IsValid(BLOCK_VALID_TREE)) {
|
|
mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FindBlockPos(CValidationState &state, CDiskBlockPos &pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown = false)
|
|
{
|
|
LOCK(cs_LastBlockFile);
|
|
|
|
unsigned int nFile = fKnown ? pos.nFile : nLastBlockFile;
|
|
if (vinfoBlockFile.size() <= nFile) {
|
|
vinfoBlockFile.resize(nFile + 1);
|
|
}
|
|
|
|
if (!fKnown) {
|
|
while (vinfoBlockFile[nFile].nSize + nAddSize >= MAX_BLOCKFILE_SIZE) {
|
|
LogPrintf("Leaving block file %i: %s\n", nFile, vinfoBlockFile[nFile].ToString());
|
|
FlushBlockFile(true);
|
|
nFile++;
|
|
if (vinfoBlockFile.size() <= nFile) {
|
|
vinfoBlockFile.resize(nFile + 1);
|
|
}
|
|
}
|
|
pos.nFile = nFile;
|
|
pos.nPos = vinfoBlockFile[nFile].nSize;
|
|
}
|
|
|
|
nLastBlockFile = nFile;
|
|
vinfoBlockFile[nFile].nSize += nAddSize;
|
|
vinfoBlockFile[nFile].AddBlock(nHeight, nTime);
|
|
|
|
if (!fKnown) {
|
|
unsigned int nOldChunks = (pos.nPos + BLOCKFILE_CHUNK_SIZE - 1) / BLOCKFILE_CHUNK_SIZE;
|
|
unsigned int nNewChunks = (vinfoBlockFile[nFile].nSize + BLOCKFILE_CHUNK_SIZE - 1) / BLOCKFILE_CHUNK_SIZE;
|
|
if (nNewChunks > nOldChunks) {
|
|
if (CheckDiskSpace(nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos)) {
|
|
FILE *file = OpenBlockFile(pos);
|
|
if (file) {
|
|
LogPrintf("Pre-allocating up to position 0x%x in blk%05u.dat\n", nNewChunks * BLOCKFILE_CHUNK_SIZE, pos.nFile);
|
|
AllocateFileRange(file, pos.nPos, nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos);
|
|
fclose(file);
|
|
}
|
|
}
|
|
else
|
|
return state.Error("out of disk space");
|
|
}
|
|
}
|
|
|
|
setDirtyFileInfo.insert(nFile);
|
|
return true;
|
|
}
|
|
|
|
bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigned int nAddSize)
|
|
{
|
|
pos.nFile = nFile;
|
|
|
|
LOCK(cs_LastBlockFile);
|
|
|
|
unsigned int nNewSize;
|
|
pos.nPos = vinfoBlockFile[nFile].nUndoSize;
|
|
nNewSize = vinfoBlockFile[nFile].nUndoSize += nAddSize;
|
|
setDirtyFileInfo.insert(nFile);
|
|
|
|
unsigned int nOldChunks = (pos.nPos + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
|
|
unsigned int nNewChunks = (nNewSize + UNDOFILE_CHUNK_SIZE - 1) / UNDOFILE_CHUNK_SIZE;
|
|
if (nNewChunks > nOldChunks) {
|
|
if (CheckDiskSpace(nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos)) {
|
|
FILE *file = OpenUndoFile(pos);
|
|
if (file) {
|
|
LogPrintf("Pre-allocating up to position 0x%x in rev%05u.dat\n", nNewChunks * UNDOFILE_CHUNK_SIZE, pos.nFile);
|
|
AllocateFileRange(file, pos.nPos, nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos);
|
|
fclose(file);
|
|
}
|
|
}
|
|
else
|
|
return state.Error("out of disk space");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, bool fCheckPOW)
|
|
{
|
|
// Check proof of work matches claimed amount
|
|
if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits))
|
|
return state.DoS(50, error("CheckBlockHeader() : proof of work failed"),
|
|
REJECT_INVALID, "high-hash");
|
|
|
|
// Check timestamp
|
|
if (block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
|
|
return state.Invalid(error("CheckBlockHeader() : block timestamp too far in the future"),
|
|
REJECT_INVALID, "time-too-new");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bool fCheckMerkleRoot)
|
|
{
|
|
// These are checks that are independent of context.
|
|
|
|
// Check that the header is valid (particularly PoW). This is mostly
|
|
// redundant with the call in AcceptBlockHeader.
|
|
if (!CheckBlockHeader(block, state, fCheckPOW))
|
|
return false;
|
|
|
|
// Check the merkle root.
|
|
if (fCheckMerkleRoot) {
|
|
bool mutated;
|
|
uint256 hashMerkleRoot2 = block.BuildMerkleTree(&mutated);
|
|
if (block.hashMerkleRoot != hashMerkleRoot2)
|
|
return state.DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"),
|
|
REJECT_INVALID, "bad-txnmrklroot", true);
|
|
|
|
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences
|
|
// of transactions in a block without affecting the merkle root of a block,
|
|
// while still invalidating it.
|
|
if (mutated)
|
|
return state.DoS(100, error("CheckBlock() : duplicate transaction"),
|
|
REJECT_INVALID, "bad-txns-duplicate", true);
|
|
}
|
|
|
|
// All potential-corruption validation must be done before we do any
|
|
// transaction validation, as otherwise we may mark the header as invalid
|
|
// because we receive the wrong transactions for it.
|
|
|
|
// Size limits
|
|
if (block.vtx.empty() || block.vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
|
|
return state.DoS(100, error("CheckBlock() : size limits failed"),
|
|
REJECT_INVALID, "bad-blk-length");
|
|
|
|
// First transaction must be coinbase, the rest must not be
|
|
if (block.vtx.empty() || !block.vtx[0].IsCoinBase())
|
|
return state.DoS(100, error("CheckBlock() : first tx is not coinbase"),
|
|
REJECT_INVALID, "bad-cb-missing");
|
|
for (unsigned int i = 1; i < block.vtx.size(); i++)
|
|
if (block.vtx[i].IsCoinBase())
|
|
return state.DoS(100, error("CheckBlock() : more than one coinbase"),
|
|
REJECT_INVALID, "bad-cb-multiple");
|
|
|
|
// Check transactions
|
|
BOOST_FOREACH(const CTransaction& tx, block.vtx)
|
|
if (!CheckTransaction(tx, state))
|
|
return error("CheckBlock() : CheckTransaction failed");
|
|
|
|
unsigned int nSigOps = 0;
|
|
BOOST_FOREACH(const CTransaction& tx, block.vtx)
|
|
{
|
|
nSigOps += GetLegacySigOpCount(tx);
|
|
}
|
|
if (nSigOps > MAX_BLOCK_SIGOPS)
|
|
return state.DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"),
|
|
REJECT_INVALID, "bad-blk-sigops", true);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& state, CBlockIndex * const pindexPrev)
|
|
{
|
|
uint256 hash = block.GetHash();
|
|
if (hash == Params().HashGenesisBlock())
|
|
return true;
|
|
|
|
assert(pindexPrev);
|
|
|
|
int nHeight = pindexPrev->nHeight+1;
|
|
|
|
// Check proof of work
|
|
if ((!Params().SkipProofOfWorkCheck()) &&
|
|
(block.nBits != GetNextWorkRequired(pindexPrev, &block)))
|
|
return state.DoS(100, error("%s : incorrect proof of work", __func__),
|
|
REJECT_INVALID, "bad-diffbits");
|
|
|
|
// Check timestamp against prev
|
|
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
|
|
return state.Invalid(error("%s : block's timestamp is too early", __func__),
|
|
REJECT_INVALID, "time-too-old");
|
|
|
|
// Check that the block chain matches the known block chain up to a checkpoint
|
|
if (!Checkpoints::CheckBlock(nHeight, hash))
|
|
return state.DoS(100, error("%s : rejected by checkpoint lock-in at %d", __func__, nHeight),
|
|
REJECT_CHECKPOINT, "checkpoint mismatch");
|
|
|
|
// Don't accept any forks from the main chain prior to last checkpoint
|
|
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint();
|
|
if (pcheckpoint && nHeight < pcheckpoint->nHeight)
|
|
return state.DoS(100, error("%s : forked chain older than last checkpoint (height %d)", __func__, nHeight));
|
|
|
|
// Reject block.nVersion=1 blocks when 95% (75% on testnet) of the network has upgraded:
|
|
if (block.nVersion < 2 &&
|
|
CBlockIndex::IsSuperMajority(2, pindexPrev, Params().RejectBlockOutdatedMajority()))
|
|
{
|
|
return state.Invalid(error("%s : rejected nVersion=1 block", __func__),
|
|
REJECT_OBSOLETE, "bad-version");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIndex * const pindexPrev)
|
|
{
|
|
const int nHeight = pindexPrev == NULL ? 0 : pindexPrev->nHeight + 1;
|
|
|
|
// Check that all transactions are finalized
|
|
BOOST_FOREACH(const CTransaction& tx, block.vtx)
|
|
if (!IsFinalTx(tx, nHeight, block.GetBlockTime())) {
|
|
return state.DoS(10, error("%s : contains a non-final transaction", __func__), REJECT_INVALID, "bad-txns-nonfinal");
|
|
}
|
|
|
|
// Enforce block.nVersion=2 rule that the coinbase starts with serialized block height
|
|
// if 750 of the last 1,000 blocks are version 2 or greater (51/100 if testnet):
|
|
if (block.nVersion >= 2 &&
|
|
CBlockIndex::IsSuperMajority(2, pindexPrev, Params().EnforceBlockUpgradeMajority()))
|
|
{
|
|
CScript expect = CScript() << nHeight;
|
|
if (block.vtx[0].vin[0].scriptSig.size() < expect.size() ||
|
|
!std::equal(expect.begin(), expect.end(), block.vtx[0].vin[0].scriptSig.begin())) {
|
|
return state.DoS(100, error("%s : block height mismatch in coinbase", __func__), REJECT_INVALID, "bad-cb-height");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBlockIndex** ppindex)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
// Check for duplicate
|
|
uint256 hash = block.GetHash();
|
|
BlockMap::iterator miSelf = mapBlockIndex.find(hash);
|
|
CBlockIndex *pindex = NULL;
|
|
if (miSelf != mapBlockIndex.end()) {
|
|
// Block header is already known.
|
|
pindex = miSelf->second;
|
|
if (ppindex)
|
|
*ppindex = pindex;
|
|
if (pindex->nStatus & BLOCK_FAILED_MASK)
|
|
return state.Invalid(error("%s : block is marked invalid", __func__), 0, "duplicate");
|
|
return true;
|
|
}
|
|
|
|
if (!CheckBlockHeader(block, state))
|
|
return false;
|
|
|
|
// Get prev block index
|
|
CBlockIndex* pindexPrev = NULL;
|
|
if (hash != Params().HashGenesisBlock()) {
|
|
BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
|
|
if (mi == mapBlockIndex.end())
|
|
return state.DoS(10, error("%s : prev block not found", __func__), 0, "bad-prevblk");
|
|
pindexPrev = (*mi).second;
|
|
}
|
|
|
|
if (!ContextualCheckBlockHeader(block, state, pindexPrev))
|
|
return false;
|
|
|
|
if (pindex == NULL)
|
|
pindex = AddToBlockIndex(block);
|
|
|
|
if (ppindex)
|
|
*ppindex = pindex;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, CDiskBlockPos* dbp)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
|
|
CBlockIndex *&pindex = *ppindex;
|
|
|
|
if (!AcceptBlockHeader(block, state, &pindex))
|
|
return false;
|
|
|
|
if (pindex->nStatus & BLOCK_HAVE_DATA) {
|
|
// TODO: deal better with duplicate blocks.
|
|
// return state.DoS(20, error("AcceptBlock() : already have block %d %s", pindex->nHeight, pindex->GetBlockHash().ToString()), REJECT_DUPLICATE, "duplicate");
|
|
return true;
|
|
}
|
|
|
|
if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) {
|
|
if (state.IsInvalid() && !state.CorruptionPossible()) {
|
|
pindex->nStatus |= BLOCK_FAILED_VALID;
|
|
setDirtyBlockIndex.insert(pindex);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int nHeight = pindex->nHeight;
|
|
|
|
// Write block to history file
|
|
try {
|
|
unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION);
|
|
CDiskBlockPos blockPos;
|
|
if (dbp != NULL)
|
|
blockPos = *dbp;
|
|
if (!FindBlockPos(state, blockPos, nBlockSize+8, nHeight, block.GetBlockTime(), dbp != NULL))
|
|
return error("AcceptBlock() : FindBlockPos failed");
|
|
if (dbp == NULL)
|
|
if (!WriteBlockToDisk(block, blockPos))
|
|
return state.Abort("Failed to write block");
|
|
if (!ReceivedBlockTransactions(block, state, pindex, blockPos))
|
|
return error("AcceptBlock() : ReceivedBlockTransactions failed");
|
|
} catch(std::runtime_error &e) {
|
|
return state.Abort(std::string("System error: ") + e.what());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CBlockIndex::IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired)
|
|
{
|
|
unsigned int nToCheck = Params().ToCheckBlockUpgradeMajority();
|
|
unsigned int nFound = 0;
|
|
for (unsigned int i = 0; i < nToCheck && nFound < nRequired && pstart != NULL; i++)
|
|
{
|
|
if (pstart->nVersion >= minVersion)
|
|
++nFound;
|
|
pstart = pstart->pprev;
|
|
}
|
|
return (nFound >= nRequired);
|
|
}
|
|
|
|
/** Turn the lowest '1' bit in the binary representation of a number into a '0'. */
|
|
int static inline InvertLowestOne(int n) { return n & (n - 1); }
|
|
|
|
/** Compute what height to jump back to with the CBlockIndex::pskip pointer. */
|
|
int static inline GetSkipHeight(int height) {
|
|
if (height < 2)
|
|
return 0;
|
|
|
|
// Determine which height to jump back to. Any number strictly lower than height is acceptable,
|
|
// but the following expression seems to perform well in simulations (max 110 steps to go back
|
|
// up to 2**18 blocks).
|
|
return (height & 1) ? InvertLowestOne(InvertLowestOne(height - 1)) + 1 : InvertLowestOne(height);
|
|
}
|
|
|
|
CBlockIndex* CBlockIndex::GetAncestor(int height)
|
|
{
|
|
if (height > nHeight || height < 0)
|
|
return NULL;
|
|
|
|
CBlockIndex* pindexWalk = this;
|
|
int heightWalk = nHeight;
|
|
while (heightWalk > height) {
|
|
int heightSkip = GetSkipHeight(heightWalk);
|
|
int heightSkipPrev = GetSkipHeight(heightWalk - 1);
|
|
if (heightSkip == height ||
|
|
(heightSkip > height && !(heightSkipPrev < heightSkip - 2 &&
|
|
heightSkipPrev >= height))) {
|
|
// Only follow pskip if pprev->pskip isn't better than pskip->pprev.
|
|
pindexWalk = pindexWalk->pskip;
|
|
heightWalk = heightSkip;
|
|
} else {
|
|
pindexWalk = pindexWalk->pprev;
|
|
heightWalk--;
|
|
}
|
|
}
|
|
return pindexWalk;
|
|
}
|
|
|
|
const CBlockIndex* CBlockIndex::GetAncestor(int height) const
|
|
{
|
|
return const_cast<CBlockIndex*>(this)->GetAncestor(height);
|
|
}
|
|
|
|
void CBlockIndex::BuildSkip()
|
|
{
|
|
if (pprev)
|
|
pskip = pprev->GetAncestor(GetSkipHeight(nHeight));
|
|
}
|
|
|
|
bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBlockPos *dbp)
|
|
{
|
|
// Preliminary checks
|
|
bool checked = CheckBlock(*pblock, state);
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
MarkBlockAsReceived(pblock->GetHash());
|
|
if (!checked) {
|
|
return error("%s : CheckBlock FAILED", __func__);
|
|
}
|
|
|
|
// Store to disk
|
|
CBlockIndex *pindex = NULL;
|
|
bool ret = AcceptBlock(*pblock, state, &pindex, dbp);
|
|
if (pindex && pfrom) {
|
|
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
|
|
}
|
|
if (!ret)
|
|
return error("%s : AcceptBlock FAILED", __func__);
|
|
}
|
|
|
|
if (!ActivateBestChain(state, pblock))
|
|
return error("%s : ActivateBestChain failed", __func__);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex * const pindexPrev, bool fCheckPOW, bool fCheckMerkleRoot)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
assert(pindexPrev == chainActive.Tip());
|
|
|
|
CCoinsViewCache viewNew(pcoinsTip);
|
|
CBlockIndex indexDummy(block);
|
|
indexDummy.pprev = pindexPrev;
|
|
indexDummy.nHeight = pindexPrev->nHeight + 1;
|
|
|
|
// NOTE: CheckBlockHeader is called by CheckBlock
|
|
if (!ContextualCheckBlockHeader(block, state, pindexPrev))
|
|
return false;
|
|
if (!CheckBlock(block, state, fCheckPOW, fCheckMerkleRoot))
|
|
return false;
|
|
if (!ContextualCheckBlock(block, state, pindexPrev))
|
|
return false;
|
|
if (!ConnectBlock(block, state, &indexDummy, viewNew, true))
|
|
return false;
|
|
assert(state.IsValid());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CMerkleBlock::CMerkleBlock(const CBlock& block, CBloomFilter& filter)
|
|
{
|
|
header = block.GetBlockHeader();
|
|
|
|
vector<bool> vMatch;
|
|
vector<uint256> vHashes;
|
|
|
|
vMatch.reserve(block.vtx.size());
|
|
vHashes.reserve(block.vtx.size());
|
|
|
|
for (unsigned int i = 0; i < block.vtx.size(); i++)
|
|
{
|
|
const uint256& hash = block.vtx[i].GetHash();
|
|
if (filter.IsRelevantAndUpdate(block.vtx[i]))
|
|
{
|
|
vMatch.push_back(true);
|
|
vMatchedTxn.push_back(make_pair(i, hash));
|
|
}
|
|
else
|
|
vMatch.push_back(false);
|
|
vHashes.push_back(hash);
|
|
}
|
|
|
|
txn = CPartialMerkleTree(vHashes, vMatch);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint256 CPartialMerkleTree::CalcHash(int height, unsigned int pos, const std::vector<uint256> &vTxid) {
|
|
if (height == 0) {
|
|
// hash at height 0 is the txids themself
|
|
return vTxid[pos];
|
|
} else {
|
|
// calculate left hash
|
|
uint256 left = CalcHash(height-1, pos*2, vTxid), right;
|
|
// calculate right hash if not beyong the end of the array - copy left hash otherwise1
|
|
if (pos*2+1 < CalcTreeWidth(height-1))
|
|
right = CalcHash(height-1, pos*2+1, vTxid);
|
|
else
|
|
right = left;
|
|
// combine subhashes
|
|
return Hash(BEGIN(left), END(left), BEGIN(right), END(right));
|
|
}
|
|
}
|
|
|
|
void CPartialMerkleTree::TraverseAndBuild(int height, unsigned int pos, const std::vector<uint256> &vTxid, const std::vector<bool> &vMatch) {
|
|
// determine whether this node is the parent of at least one matched txid
|
|
bool fParentOfMatch = false;
|
|
for (unsigned int p = pos << height; p < (pos+1) << height && p < nTransactions; p++)
|
|
fParentOfMatch |= vMatch[p];
|
|
// store as flag bit
|
|
vBits.push_back(fParentOfMatch);
|
|
if (height==0 || !fParentOfMatch) {
|
|
// if at height 0, or nothing interesting below, store hash and stop
|
|
vHash.push_back(CalcHash(height, pos, vTxid));
|
|
} else {
|
|
// otherwise, don't store any hash, but descend into the subtrees
|
|
TraverseAndBuild(height-1, pos*2, vTxid, vMatch);
|
|
if (pos*2+1 < CalcTreeWidth(height-1))
|
|
TraverseAndBuild(height-1, pos*2+1, vTxid, vMatch);
|
|
}
|
|
}
|
|
|
|
uint256 CPartialMerkleTree::TraverseAndExtract(int height, unsigned int pos, unsigned int &nBitsUsed, unsigned int &nHashUsed, std::vector<uint256> &vMatch) {
|
|
if (nBitsUsed >= vBits.size()) {
|
|
// overflowed the bits array - failure
|
|
fBad = true;
|
|
return 0;
|
|
}
|
|
bool fParentOfMatch = vBits[nBitsUsed++];
|
|
if (height==0 || !fParentOfMatch) {
|
|
// if at height 0, or nothing interesting below, use stored hash and do not descend
|
|
if (nHashUsed >= vHash.size()) {
|
|
// overflowed the hash array - failure
|
|
fBad = true;
|
|
return 0;
|
|
}
|
|
const uint256 &hash = vHash[nHashUsed++];
|
|
if (height==0 && fParentOfMatch) // in case of height 0, we have a matched txid
|
|
vMatch.push_back(hash);
|
|
return hash;
|
|
} else {
|
|
// otherwise, descend into the subtrees to extract matched txids and hashes
|
|
uint256 left = TraverseAndExtract(height-1, pos*2, nBitsUsed, nHashUsed, vMatch), right;
|
|
if (pos*2+1 < CalcTreeWidth(height-1))
|
|
right = TraverseAndExtract(height-1, pos*2+1, nBitsUsed, nHashUsed, vMatch);
|
|
else
|
|
right = left;
|
|
// and combine them before returning
|
|
return Hash(BEGIN(left), END(left), BEGIN(right), END(right));
|
|
}
|
|
}
|
|
|
|
CPartialMerkleTree::CPartialMerkleTree(const std::vector<uint256> &vTxid, const std::vector<bool> &vMatch) : nTransactions(vTxid.size()), fBad(false) {
|
|
// reset state
|
|
vBits.clear();
|
|
vHash.clear();
|
|
|
|
// calculate height of tree
|
|
int nHeight = 0;
|
|
while (CalcTreeWidth(nHeight) > 1)
|
|
nHeight++;
|
|
|
|
// traverse the partial tree
|
|
TraverseAndBuild(nHeight, 0, vTxid, vMatch);
|
|
}
|
|
|
|
CPartialMerkleTree::CPartialMerkleTree() : nTransactions(0), fBad(true) {}
|
|
|
|
uint256 CPartialMerkleTree::ExtractMatches(std::vector<uint256> &vMatch) {
|
|
vMatch.clear();
|
|
// An empty set will not work
|
|
if (nTransactions == 0)
|
|
return 0;
|
|
// check for excessively high numbers of transactions
|
|
if (nTransactions > MAX_BLOCK_SIZE / 60) // 60 is the lower bound for the size of a serialized CTransaction
|
|
return 0;
|
|
// there can never be more hashes provided than one for every txid
|
|
if (vHash.size() > nTransactions)
|
|
return 0;
|
|
// there must be at least one bit per node in the partial tree, and at least one node per hash
|
|
if (vBits.size() < vHash.size())
|
|
return 0;
|
|
// calculate height of tree
|
|
int nHeight = 0;
|
|
while (CalcTreeWidth(nHeight) > 1)
|
|
nHeight++;
|
|
// traverse the partial tree
|
|
unsigned int nBitsUsed = 0, nHashUsed = 0;
|
|
uint256 hashMerkleRoot = TraverseAndExtract(nHeight, 0, nBitsUsed, nHashUsed, vMatch);
|
|
// verify that no problems occured during the tree traversal
|
|
if (fBad)
|
|
return 0;
|
|
// verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence)
|
|
if ((nBitsUsed+7)/8 != (vBits.size()+7)/8)
|
|
return 0;
|
|
// verify that all hashes were consumed
|
|
if (nHashUsed != vHash.size())
|
|
return 0;
|
|
return hashMerkleRoot;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 occured, see debug.log for details") : userMessage,
|
|
"", CClientUIInterface::MSG_ERROR);
|
|
StartShutdown();
|
|
return false;
|
|
}
|
|
|
|
bool CheckDiskSpace(uint64_t nAdditionalBytes)
|
|
{
|
|
uint64_t nFreeBytesAvailable = 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;
|
|
}
|
|
|
|
FILE* OpenDiskFile(const CDiskBlockPos &pos, const char *prefix, bool fReadOnly)
|
|
{
|
|
if (pos.IsNull())
|
|
return NULL;
|
|
boost::filesystem::path path = GetBlockPosFilename(pos, prefix);
|
|
boost::filesystem::create_directories(path.parent_path());
|
|
FILE* file = fopen(path.string().c_str(), "rb+");
|
|
if (!file && !fReadOnly)
|
|
file = fopen(path.string().c_str(), "wb+");
|
|
if (!file) {
|
|
LogPrintf("Unable to open file %s\n", path.string());
|
|
return NULL;
|
|
}
|
|
if (pos.nPos) {
|
|
if (fseek(file, pos.nPos, SEEK_SET)) {
|
|
LogPrintf("Unable to seek to position %u of %s\n", pos.nPos, path.string());
|
|
fclose(file);
|
|
return NULL;
|
|
}
|
|
}
|
|
return file;
|
|
}
|
|
|
|
FILE* OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly) {
|
|
return OpenDiskFile(pos, "blk", fReadOnly);
|
|
}
|
|
|
|
FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly) {
|
|
return OpenDiskFile(pos, "rev", fReadOnly);
|
|
}
|
|
|
|
boost::filesystem::path GetBlockPosFilename(const CDiskBlockPos &pos, const char *prefix)
|
|
{
|
|
return GetDataDir() / "blocks" / strprintf("%s%05u.dat", prefix, pos.nFile);
|
|
}
|
|
|
|
CBlockIndex * InsertBlockIndex(uint256 hash)
|
|
{
|
|
if (hash == 0)
|
|
return NULL;
|
|
|
|
// Return existing
|
|
BlockMap::iterator mi = mapBlockIndex.find(hash);
|
|
if (mi != mapBlockIndex.end())
|
|
return (*mi).second;
|
|
|
|
// Create new
|
|
CBlockIndex* pindexNew = new CBlockIndex();
|
|
if (!pindexNew)
|
|
throw runtime_error("LoadBlockIndex() : new CBlockIndex failed");
|
|
mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
|
|
pindexNew->phashBlock = &((*mi).first);
|
|
|
|
return pindexNew;
|
|
}
|
|
|
|
bool static LoadBlockIndexDB()
|
|
{
|
|
if (!pblocktree->LoadBlockIndexGuts())
|
|
return false;
|
|
|
|
boost::this_thread::interruption_point();
|
|
|
|
// Calculate nChainWork
|
|
vector<pair<int, CBlockIndex*> > vSortedByHeight;
|
|
vSortedByHeight.reserve(mapBlockIndex.size());
|
|
BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)
|
|
{
|
|
CBlockIndex* pindex = item.second;
|
|
vSortedByHeight.push_back(make_pair(pindex->nHeight, pindex));
|
|
}
|
|
sort(vSortedByHeight.begin(), vSortedByHeight.end());
|
|
BOOST_FOREACH(const PAIRTYPE(int, CBlockIndex*)& item, vSortedByHeight)
|
|
{
|
|
CBlockIndex* pindex = item.second;
|
|
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
|
|
if (pindex->nStatus & BLOCK_HAVE_DATA) {
|
|
if (pindex->pprev) {
|
|
if (pindex->pprev->nChainTx) {
|
|
pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
|
|
} else {
|
|
pindex->nChainTx = 0;
|
|
mapBlocksUnlinked.insert(std::make_pair(pindex->pprev, pindex));
|
|
}
|
|
} else {
|
|
pindex->nChainTx = pindex->nTx;
|
|
}
|
|
}
|
|
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == NULL))
|
|
setBlockIndexCandidates.insert(pindex);
|
|
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
|
|
pindexBestInvalid = pindex;
|
|
if (pindex->pprev)
|
|
pindex->BuildSkip();
|
|
if (pindex->IsValid(BLOCK_VALID_TREE) && (pindexBestHeader == NULL || CBlockIndexWorkComparator()(pindexBestHeader, pindex)))
|
|
pindexBestHeader = pindex;
|
|
}
|
|
|
|
// Load block file info
|
|
pblocktree->ReadLastBlockFile(nLastBlockFile);
|
|
vinfoBlockFile.resize(nLastBlockFile + 1);
|
|
LogPrintf("%s: last block file = %i\n", __func__, nLastBlockFile);
|
|
for (int nFile = 0; nFile <= nLastBlockFile; nFile++) {
|
|
pblocktree->ReadBlockFileInfo(nFile, vinfoBlockFile[nFile]);
|
|
}
|
|
LogPrintf("%s: last block file info: %s\n", __func__, vinfoBlockFile[nLastBlockFile].ToString());
|
|
for (int nFile = nLastBlockFile + 1; true; nFile++) {
|
|
CBlockFileInfo info;
|
|
if (pblocktree->ReadBlockFileInfo(nFile, info)) {
|
|
vinfoBlockFile.push_back(info);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check presence of blk files
|
|
LogPrintf("Checking all blk files are present...\n");
|
|
set<int> setBlkDataFiles;
|
|
BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex)
|
|
{
|
|
CBlockIndex* pindex = item.second;
|
|
if (pindex->nStatus & BLOCK_HAVE_DATA) {
|
|
setBlkDataFiles.insert(pindex->nFile);
|
|
}
|
|
}
|
|
for (std::set<int>::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++)
|
|
{
|
|
CDiskBlockPos pos(*it, 0);
|
|
if (CAutoFile(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION).IsNull()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check whether we need to continue reindexing
|
|
bool fReindexing = false;
|
|
pblocktree->ReadReindexing(fReindexing);
|
|
fReindex |= fReindexing;
|
|
|
|
// Check whether we have a transaction index
|
|
pblocktree->ReadFlag("txindex", fTxIndex);
|
|
LogPrintf("LoadBlockIndexDB(): transaction index %s\n", fTxIndex ? "enabled" : "disabled");
|
|
|
|
// Load pointer to end of best chain
|
|
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
|
|
if (it == mapBlockIndex.end())
|
|
return true;
|
|
chainActive.SetTip(it->second);
|
|
LogPrintf("LoadBlockIndexDB(): hashBestChain=%s height=%d date=%s progress=%f\n",
|
|
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(),
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()),
|
|
Checkpoints::GuessVerificationProgress(chainActive.Tip()));
|
|
|
|
return true;
|
|
}
|
|
|
|
CVerifyDB::CVerifyDB()
|
|
{
|
|
uiInterface.ShowProgress(_("Verifying blocks..."), 0);
|
|
}
|
|
|
|
CVerifyDB::~CVerifyDB()
|
|
{
|
|
uiInterface.ShowProgress("", 100);
|
|
}
|
|
|
|
bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth)
|
|
{
|
|
LOCK(cs_main);
|
|
if (chainActive.Tip() == NULL || chainActive.Tip()->pprev == NULL)
|
|
return true;
|
|
|
|
// Verify blocks in the best chain
|
|
if (nCheckDepth <= 0)
|
|
nCheckDepth = 1000000000; // suffices until the year 19000
|
|
if (nCheckDepth > chainActive.Height())
|
|
nCheckDepth = chainActive.Height();
|
|
nCheckLevel = std::max(0, std::min(4, nCheckLevel));
|
|
LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel);
|
|
CCoinsViewCache coins(coinsview);
|
|
CBlockIndex* pindexState = chainActive.Tip();
|
|
CBlockIndex* pindexFailure = NULL;
|
|
int nGoodTransactions = 0;
|
|
CValidationState state;
|
|
for (CBlockIndex* pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev)
|
|
{
|
|
boost::this_thread::interruption_point();
|
|
uiInterface.ShowProgress(_("Verifying blocks..."), std::max(1, std::min(99, (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))));
|
|
if (pindex->nHeight < chainActive.Height()-nCheckDepth)
|
|
break;
|
|
CBlock block;
|
|
// check level 0: read from disk
|
|
if (!ReadBlockFromDisk(block, pindex))
|
|
return error("VerifyDB() : *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
|
|
// check level 1: verify block validity
|
|
if (nCheckLevel >= 1 && !CheckBlock(block, state))
|
|
return error("VerifyDB() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
|
|
// check level 2: verify undo validity
|
|
if (nCheckLevel >= 2 && pindex) {
|
|
CBlockUndo undo;
|
|
CDiskBlockPos pos = pindex->GetUndoPos();
|
|
if (!pos.IsNull()) {
|
|
if (!undo.ReadFromDisk(pos, pindex->pprev->GetBlockHash()))
|
|
return error("VerifyDB() : *** found bad undo data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
|
|
}
|
|
}
|
|
// check level 3: check for inconsistencies during memory-only disconnect of tip blocks
|
|
if (nCheckLevel >= 3 && pindex == pindexState && (coins.GetCacheSize() + pcoinsTip->GetCacheSize()) <= nCoinCacheSize) {
|
|
bool fClean = true;
|
|
if (!DisconnectBlock(block, state, pindex, coins, &fClean))
|
|
return error("VerifyDB() : *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
|
|
pindexState = pindex->pprev;
|
|
if (!fClean) {
|
|
nGoodTransactions = 0;
|
|
pindexFailure = pindex;
|
|
} else
|
|
nGoodTransactions += block.vtx.size();
|
|
}
|
|
}
|
|
if (pindexFailure)
|
|
return error("VerifyDB() : *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainActive.Height() - pindexFailure->nHeight + 1, nGoodTransactions);
|
|
|
|
// check level 4: try reconnecting blocks
|
|
if (nCheckLevel >= 4) {
|
|
CBlockIndex *pindex = pindexState;
|
|
while (pindex != chainActive.Tip()) {
|
|
boost::this_thread::interruption_point();
|
|
uiInterface.ShowProgress(_("Verifying blocks..."), std::max(1, std::min(99, 100 - (int)(((double)(chainActive.Height() - pindex->nHeight)) / (double)nCheckDepth * 50))));
|
|
pindex = chainActive.Next(pindex);
|
|
CBlock block;
|
|
if (!ReadBlockFromDisk(block, pindex))
|
|
return error("VerifyDB() : *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
|
|
if (!ConnectBlock(block, state, pindex, coins))
|
|
return error("VerifyDB() : *** found unconnectable block at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
|
|
}
|
|
}
|
|
|
|
LogPrintf("No coin database inconsistencies in last %i blocks (%i transactions)\n", chainActive.Height() - pindexState->nHeight, nGoodTransactions);
|
|
|
|
return true;
|
|
}
|
|
|
|
void UnloadBlockIndex()
|
|
{
|
|
mapBlockIndex.clear();
|
|
setBlockIndexCandidates.clear();
|
|
chainActive.SetTip(NULL);
|
|
pindexBestInvalid = NULL;
|
|
}
|
|
|
|
bool LoadBlockIndex()
|
|
{
|
|
// Load block index from databases
|
|
if (!fReindex && !LoadBlockIndexDB())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool InitBlockIndex() {
|
|
LOCK(cs_main);
|
|
// Check whether we're already initialized
|
|
if (chainActive.Genesis() != NULL)
|
|
return true;
|
|
|
|
// Use the provided setting for -txindex in the new database
|
|
fTxIndex = GetBoolArg("-txindex", false);
|
|
pblocktree->WriteFlag("txindex", fTxIndex);
|
|
LogPrintf("Initializing databases...\n");
|
|
|
|
// Only add the genesis block if not reindexing (in which case we reuse the one already on disk)
|
|
if (!fReindex) {
|
|
try {
|
|
CBlock &block = const_cast<CBlock&>(Params().GenesisBlock());
|
|
// Start new block file
|
|
unsigned int nBlockSize = ::GetSerializeSize(block, SER_DISK, CLIENT_VERSION);
|
|
CDiskBlockPos blockPos;
|
|
CValidationState state;
|
|
if (!FindBlockPos(state, blockPos, nBlockSize+8, 0, block.GetBlockTime()))
|
|
return error("LoadBlockIndex() : FindBlockPos failed");
|
|
if (!WriteBlockToDisk(block, blockPos))
|
|
return error("LoadBlockIndex() : writing genesis block to disk failed");
|
|
CBlockIndex *pindex = AddToBlockIndex(block);
|
|
if (!ReceivedBlockTransactions(block, state, pindex, blockPos))
|
|
return error("LoadBlockIndex() : genesis block not accepted");
|
|
if (!ActivateBestChain(state, &block))
|
|
return error("LoadBlockIndex() : genesis block cannot be activated");
|
|
// Force a chainstate write so that when we VerifyDB in a moment, it doesnt check stale data
|
|
return FlushStateToDisk(state, false, true);
|
|
} catch(std::runtime_error &e) {
|
|
return error("LoadBlockIndex() : failed to initialize block database: %s", e.what());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void PrintBlockTree()
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
// pre-compute tree structure
|
|
map<CBlockIndex*, vector<CBlockIndex*> > mapNext;
|
|
for (BlockMap::iterator mi = mapBlockIndex.begin(); mi != mapBlockIndex.end(); ++mi)
|
|
{
|
|
CBlockIndex* pindex = (*mi).second;
|
|
mapNext[pindex->pprev].push_back(pindex);
|
|
// test
|
|
//while (rand() % 3 == 0)
|
|
// mapNext[pindex->pprev].push_back(pindex);
|
|
}
|
|
|
|
vector<pair<int, CBlockIndex*> > vStack;
|
|
vStack.push_back(make_pair(0, chainActive.Genesis()));
|
|
|
|
int nPrevCol = 0;
|
|
while (!vStack.empty())
|
|
{
|
|
int nCol = vStack.back().first;
|
|
CBlockIndex* pindex = vStack.back().second;
|
|
vStack.pop_back();
|
|
|
|
// print split or gap
|
|
if (nCol > nPrevCol)
|
|
{
|
|
for (int i = 0; i < nCol-1; i++)
|
|
LogPrintf("| ");
|
|
LogPrintf("|\\\n");
|
|
}
|
|
else if (nCol < nPrevCol)
|
|
{
|
|
for (int i = 0; i < nCol; i++)
|
|
LogPrintf("| ");
|
|
LogPrintf("|\n");
|
|
}
|
|
nPrevCol = nCol;
|
|
|
|
// print columns
|
|
for (int i = 0; i < nCol; i++)
|
|
LogPrintf("| ");
|
|
|
|
// print item
|
|
CBlock block;
|
|
ReadBlockFromDisk(block, pindex);
|
|
LogPrintf("%d (blk%05u.dat:0x%x) %s tx %u\n",
|
|
pindex->nHeight,
|
|
pindex->GetBlockPos().nFile, pindex->GetBlockPos().nPos,
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", block.GetBlockTime()),
|
|
block.vtx.size());
|
|
|
|
// put the main time-chain first
|
|
vector<CBlockIndex*>& vNext = mapNext[pindex];
|
|
for (unsigned int i = 0; i < vNext.size(); i++)
|
|
{
|
|
if (chainActive.Next(vNext[i]))
|
|
{
|
|
swap(vNext[0], vNext[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// iterate children
|
|
for (unsigned int i = 0; i < vNext.size(); i++)
|
|
vStack.push_back(make_pair(nCol+i, vNext[i]));
|
|
}
|
|
}
|
|
|
|
bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp)
|
|
{
|
|
// Map of disk positions for blocks with unknown parent (only used for reindex)
|
|
static std::multimap<uint256, CDiskBlockPos> mapBlocksUnknownParent;
|
|
int64_t nStart = GetTimeMillis();
|
|
|
|
int nLoaded = 0;
|
|
try {
|
|
// This takes over fileIn and calls fclose() on it in the CBufferedFile destructor
|
|
CBufferedFile blkdat(fileIn, 2*MAX_BLOCK_SIZE, MAX_BLOCK_SIZE+8, SER_DISK, CLIENT_VERSION);
|
|
uint64_t nRewind = blkdat.GetPos();
|
|
while (!blkdat.eof()) {
|
|
boost::this_thread::interruption_point();
|
|
|
|
blkdat.SetPos(nRewind);
|
|
nRewind++; // start one byte further next time, in case of failure
|
|
blkdat.SetLimit(); // remove former limit
|
|
unsigned int nSize = 0;
|
|
try {
|
|
// locate a header
|
|
unsigned char buf[MESSAGE_START_SIZE];
|
|
blkdat.FindByte(Params().MessageStart()[0]);
|
|
nRewind = blkdat.GetPos()+1;
|
|
blkdat >> FLATDATA(buf);
|
|
if (memcmp(buf, Params().MessageStart(), MESSAGE_START_SIZE))
|
|
continue;
|
|
// read size
|
|
blkdat >> nSize;
|
|
if (nSize < 80 || nSize > MAX_BLOCK_SIZE)
|
|
continue;
|
|
} catch (const std::exception &) {
|
|
// no valid block header found; don't complain
|
|
break;
|
|
}
|
|
try {
|
|
// read block
|
|
uint64_t nBlockPos = blkdat.GetPos();
|
|
if (dbp)
|
|
dbp->nPos = nBlockPos;
|
|
blkdat.SetLimit(nBlockPos + nSize);
|
|
blkdat.SetPos(nBlockPos);
|
|
CBlock block;
|
|
blkdat >> block;
|
|
nRewind = blkdat.GetPos();
|
|
|
|
// detect out of order blocks, and store them for later
|
|
uint256 hash = block.GetHash();
|
|
if (hash != Params().HashGenesisBlock() && mapBlockIndex.find(block.hashPrevBlock) == mapBlockIndex.end()) {
|
|
LogPrint("reindex", "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(),
|
|
block.hashPrevBlock.ToString());
|
|
if (dbp)
|
|
mapBlocksUnknownParent.insert(std::make_pair(block.hashPrevBlock, *dbp));
|
|
continue;
|
|
}
|
|
|
|
// process in case the block isn't known yet
|
|
if (mapBlockIndex.count(hash) == 0 || (mapBlockIndex[hash]->nStatus & BLOCK_HAVE_DATA) == 0) {
|
|
CValidationState state;
|
|
if (ProcessNewBlock(state, NULL, &block, dbp))
|
|
nLoaded++;
|
|
if (state.IsError())
|
|
break;
|
|
} else if (hash != Params().HashGenesisBlock() && mapBlockIndex[hash]->nHeight % 1000 == 0) {
|
|
LogPrintf("Block Import: already had block %s at height %d\n", hash.ToString(), mapBlockIndex[hash]->nHeight);
|
|
}
|
|
|
|
// Recursively process earlier encountered successors of this block
|
|
deque<uint256> queue;
|
|
queue.push_back(hash);
|
|
while (!queue.empty()) {
|
|
uint256 head = queue.front();
|
|
queue.pop_front();
|
|
std::pair<std::multimap<uint256, CDiskBlockPos>::iterator, std::multimap<uint256, CDiskBlockPos>::iterator> range = mapBlocksUnknownParent.equal_range(head);
|
|
while (range.first != range.second) {
|
|
std::multimap<uint256, CDiskBlockPos>::iterator it = range.first;
|
|
if (ReadBlockFromDisk(block, it->second))
|
|
{
|
|
LogPrintf("%s: Processing out of order child %s of %s\n", __func__, block.GetHash().ToString(),
|
|
head.ToString());
|
|
CValidationState dummy;
|
|
if (ProcessNewBlock(dummy, NULL, &block, &it->second))
|
|
{
|
|
nLoaded++;
|
|
queue.push_back(block.GetHash());
|
|
}
|
|
}
|
|
range.first++;
|
|
mapBlocksUnknownParent.erase(it);
|
|
}
|
|
}
|
|
} catch (std::exception &e) {
|
|
LogPrintf("%s : Deserialize or I/O error - %s", __func__, e.what());
|
|
}
|
|
}
|
|
} catch(std::runtime_error &e) {
|
|
AbortNode(std::string("System error: ") + e.what());
|
|
}
|
|
if (nLoaded > 0)
|
|
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart);
|
|
return nLoaded > 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CAlert
|
|
//
|
|
|
|
string GetWarnings(string strFor)
|
|
{
|
|
int nPriority = 0;
|
|
string strStatusBar;
|
|
string strRPC;
|
|
|
|
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");
|
|
|
|
if (GetBoolArg("-testsafemode", false))
|
|
strStatusBar = strRPC = "testsafemode enabled";
|
|
|
|
// Misc warnings like out of disk space and clock is wrong
|
|
if (strMiscWarning != "")
|
|
{
|
|
nPriority = 1000;
|
|
strStatusBar = strMiscWarning;
|
|
}
|
|
|
|
if (fLargeWorkForkFound)
|
|
{
|
|
nPriority = 2000;
|
|
strStatusBar = strRPC = _("Warning: The network does not appear to fully agree! Some miners appear to be experiencing issues.");
|
|
}
|
|
else if (fLargeWorkInvalidChainFound)
|
|
{
|
|
nPriority = 2000;
|
|
strStatusBar = strRPC = _("Warning: We do not appear to fully agree with our peers! You may need to upgrade, or other nodes may need to upgrade.");
|
|
}
|
|
|
|
// Alerts
|
|
{
|
|
LOCK(cs_mapAlerts);
|
|
BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
|
|
{
|
|
const CAlert& alert = item.second;
|
|
if (alert.AppliesToMe() && alert.nPriority > nPriority)
|
|
{
|
|
nPriority = alert.nPriority;
|
|
strStatusBar = alert.strStatusBar;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strFor == "statusbar")
|
|
return strStatusBar;
|
|
else if (strFor == "rpc")
|
|
return strRPC;
|
|
assert(!"GetWarnings() : invalid parameter");
|
|
return "error";
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Messages
|
|
//
|
|
|
|
|
|
bool static AlreadyHave(const CInv& inv)
|
|
{
|
|
switch (inv.type)
|
|
{
|
|
case MSG_TX:
|
|
{
|
|
bool txInMap = false;
|
|
txInMap = mempool.exists(inv.hash);
|
|
return txInMap || mapOrphanTransactions.count(inv.hash) ||
|
|
pcoinsTip->HaveCoins(inv.hash);
|
|
}
|
|
case MSG_BLOCK:
|
|
return mapBlockIndex.count(inv.hash);
|
|
}
|
|
// Don't know what it is, just say we already got one
|
|
return true;
|
|
}
|
|
|
|
|
|
void static ProcessGetData(CNode* pfrom)
|
|
{
|
|
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
|
|
|
|
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;
|
|
{
|
|
boost::this_thread::interruption_point();
|
|
it++;
|
|
|
|
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
|
|
{
|
|
bool send = false;
|
|
BlockMap::iterator mi = mapBlockIndex.find(inv.hash);
|
|
if (mi != mapBlockIndex.end())
|
|
{
|
|
// If the requested block is at a height below our last
|
|
// checkpoint, only serve it if it's in the checkpointed chain
|
|
int nHeight = mi->second->nHeight;
|
|
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint();
|
|
if (pcheckpoint && nHeight < pcheckpoint->nHeight) {
|
|
if (!chainActive.Contains(mi->second))
|
|
{
|
|
LogPrintf("ProcessGetData(): ignoring request for old block that isn't in the main chain\n");
|
|
} else {
|
|
send = true;
|
|
}
|
|
} else {
|
|
send = true;
|
|
}
|
|
}
|
|
if (send)
|
|
{
|
|
// Send block from disk
|
|
CBlock block;
|
|
if (!ReadBlockFromDisk(block, (*mi).second))
|
|
assert(!"cannot load block from disk");
|
|
if (inv.type == MSG_BLOCK)
|
|
pfrom->PushMessage("block", block);
|
|
else // MSG_FILTERED_BLOCK)
|
|
{
|
|
LOCK(pfrom->cs_filter);
|
|
if (pfrom->pfilter)
|
|
{
|
|
CMerkleBlock merkleBlock(block, *pfrom->pfilter);
|
|
pfrom->PushMessage("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 didnt 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;
|
|
BOOST_FOREACH(PairType& pair, merkleBlock.vMatchedTxn)
|
|
if (!pfrom->setInventoryKnown.count(CInv(MSG_TX, pair.second)))
|
|
pfrom->PushMessage("tx", block.vtx[pair.first]);
|
|
}
|
|
// else
|
|
// no response
|
|
}
|
|
|
|
// Trigger them 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.
|
|
vector<CInv> vInv;
|
|
vInv.push_back(CInv(MSG_BLOCK, chainActive.Tip()->GetBlockHash()));
|
|
pfrom->PushMessage("inv", vInv);
|
|
pfrom->hashContinue = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (inv.IsKnownType())
|
|
{
|
|
// Send stream from relay memory
|
|
bool pushed = false;
|
|
{
|
|
LOCK(cs_mapRelay);
|
|
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("tx", ss);
|
|
pushed = true;
|
|
}
|
|
}
|
|
if (!pushed) {
|
|
vNotFound.push_back(inv);
|
|
}
|
|
}
|
|
|
|
// Track requests for our stuff.
|
|
g_signals.Inventory(inv.hash);
|
|
|
|
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK)
|
|
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("notfound", vNotFound);
|
|
}
|
|
}
|
|
|
|
bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, int64_t nTimeReceived)
|
|
{
|
|
RandAddSeedPerfmon();
|
|
LogPrint("net", "received: %s (%u bytes) peer=%d\n", strCommand, vRecv.size(), pfrom->id);
|
|
if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0)
|
|
{
|
|
LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strCommand == "version")
|
|
{
|
|
// Each connection can only send one version message
|
|
if (pfrom->nVersion != 0)
|
|
{
|
|
pfrom->PushMessage("reject", strCommand, REJECT_DUPLICATE, string("Duplicate version message"));
|
|
Misbehaving(pfrom->GetId(), 1);
|
|
return false;
|
|
}
|
|
|
|
int64_t nTime;
|
|
CAddress addrMe;
|
|
CAddress addrFrom;
|
|
uint64_t nNonce = 1;
|
|
vRecv >> pfrom->nVersion >> pfrom->nServices >> nTime >> addrMe;
|
|
if (pfrom->nVersion < MIN_PEER_PROTO_VERSION)
|
|
{
|
|
// disconnect from peers older than this proto version
|
|
LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion);
|
|
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
|
|
strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION));
|
|
pfrom->fDisconnect = true;
|
|
return false;
|
|
}
|
|
|
|
if (pfrom->nVersion == 10300)
|
|
pfrom->nVersion = 300;
|
|
if (!vRecv.empty())
|
|
vRecv >> addrFrom >> nNonce;
|
|
if (!vRecv.empty()) {
|
|
vRecv >> LIMITED_STRING(pfrom->strSubVer, 256);
|
|
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;
|
|
|
|
// Disconnect if we connected to ourself
|
|
if (nNonce == nLocalHostNonce && nNonce > 1)
|
|
{
|
|
LogPrintf("connected to self at %s, disconnecting\n", pfrom->addr.ToString());
|
|
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("verack");
|
|
pfrom->ssSend.SetVersion(min(pfrom->nVersion, PROTOCOL_VERSION));
|
|
|
|
if (!pfrom->fInbound)
|
|
{
|
|
// Advertise our address
|
|
if (fListen && !IsInitialBlockDownload())
|
|
{
|
|
CAddress addr = GetLocalAddress(&pfrom->addr);
|
|
if (addr.IsRoutable())
|
|
{
|
|
pfrom->PushAddress(addr);
|
|
} else if (IsPeerAddrLocalGood(pfrom)) {
|
|
addr.SetIP(pfrom->addrLocal);
|
|
pfrom->PushAddress(addr);
|
|
}
|
|
}
|
|
|
|
// Get recent addresses
|
|
if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION || addrman.size() < 1000)
|
|
{
|
|
pfrom->PushMessage("getaddr");
|
|
pfrom->fGetAddr = true;
|
|
}
|
|
addrman.Good(pfrom->addr);
|
|
} else {
|
|
if (((CNetAddr)pfrom->addr) == (CNetAddr)addrFrom)
|
|
{
|
|
addrman.Add(addrFrom, addrFrom);
|
|
addrman.Good(addrFrom);
|
|
}
|
|
}
|
|
|
|
// Relay alerts
|
|
{
|
|
LOCK(cs_mapAlerts);
|
|
BOOST_FOREACH(PAIRTYPE(const uint256, CAlert)& item, mapAlerts)
|
|
item.second.RelayTo(pfrom);
|
|
}
|
|
|
|
pfrom->fSuccessfullyConnected = true;
|
|
|
|
string remoteAddr;
|
|
if (fLogIPs)
|
|
remoteAddr = ", peeraddr=" + pfrom->addr.ToString();
|
|
|
|
LogPrintf("receive version message: %s: version %d, blocks=%d, us=%s, peer=%d%s\n",
|
|
pfrom->cleanSubVer, pfrom->nVersion,
|
|
pfrom->nStartingHeight, addrMe.ToString(), pfrom->id,
|
|
remoteAddr);
|
|
|
|
AddTimeData(pfrom->addr, nTime);
|
|
}
|
|
|
|
|
|
else if (pfrom->nVersion == 0)
|
|
{
|
|
// Must have a version message before anything else
|
|
Misbehaving(pfrom->GetId(), 1);
|
|
return false;
|
|
}
|
|
|
|
|
|
else if (strCommand == "verack")
|
|
{
|
|
pfrom->SetRecvVersion(min(pfrom->nVersion, PROTOCOL_VERSION));
|
|
}
|
|
|
|
|
|
else if (strCommand == "addr")
|
|
{
|
|
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
|
|
vector<CAddress> vAddrOk;
|
|
int64_t nNow = GetAdjustedTime();
|
|
int64_t nSince = nNow - 10 * 60;
|
|
BOOST_FOREACH(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 setAddrKnowns of the chosen nodes prevent repeats
|
|
static uint256 hashSalt;
|
|
if (hashSalt == 0)
|
|
hashSalt = GetRandHash();
|
|
uint64_t hashAddr = addr.GetHash();
|
|
uint256 hashRand = hashSalt ^ (hashAddr<<32) ^ ((GetTime()+hashAddr)/(24*60*60));
|
|
hashRand = Hash(BEGIN(hashRand), END(hashRand));
|
|
multimap<uint256, CNode*> mapMix;
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
{
|
|
if (pnode->nVersion < CADDR_TIME_VERSION)
|
|
continue;
|
|
unsigned int nPointer;
|
|
memcpy(&nPointer, &pnode, sizeof(nPointer));
|
|
uint256 hashKey = hashRand ^ nPointer;
|
|
hashKey = Hash(BEGIN(hashKey), END(hashKey));
|
|
mapMix.insert(make_pair(hashKey, pnode));
|
|
}
|
|
int nRelayNodes = fReachable ? 2 : 1; // limited relaying of addresses outside our network(s)
|
|
for (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 == "inv")
|
|
{
|
|
vector<CInv> vInv;
|
|
vRecv >> vInv;
|
|
if (vInv.size() > MAX_INV_SZ)
|
|
{
|
|
Misbehaving(pfrom->GetId(), 20);
|
|
return error("message inv size() = %u", vInv.size());
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
std::vector<CInv> vToFetch;
|
|
|
|
for (unsigned int nInv = 0; nInv < vInv.size(); nInv++)
|
|
{
|
|
const CInv &inv = vInv[nInv];
|
|
|
|
boost::this_thread::interruption_point();
|
|
pfrom->AddInventoryKnown(inv);
|
|
|
|
bool fAlreadyHave = AlreadyHave(inv);
|
|
LogPrint("net", "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->id);
|
|
|
|
if (!fAlreadyHave && !fImporting && !fReindex && inv.type != MSG_BLOCK)
|
|
pfrom->AskFor(inv);
|
|
|
|
if (inv.type == MSG_BLOCK) {
|
|
UpdateBlockAvailability(pfrom->GetId(), inv.hash);
|
|
if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
|
|
// First request the headers preceeding 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("getheaders", chainActive.GetLocator(pindexBestHeader), inv.hash);
|
|
if (chainActive.Tip()->GetBlockTime() > GetAdjustedTime() - Params().TargetSpacing() * 20) {
|
|
vToFetch.push_back(inv);
|
|
// Mark block as in flight already, even though the actual "getdata" message only goes out
|
|
// later (within the same cs_main lock, though).
|
|
MarkBlockAsInFlight(pfrom->GetId(), inv.hash);
|
|
}
|
|
LogPrint("net", "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->id);
|
|
}
|
|
}
|
|
|
|
// Track requests for our stuff
|
|
g_signals.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("getdata", vToFetch);
|
|
}
|
|
|
|
|
|
else if (strCommand == "getdata")
|
|
{
|
|
vector<CInv> vInv;
|
|
vRecv >> vInv;
|
|
if (vInv.size() > MAX_INV_SZ)
|
|
{
|
|
Misbehaving(pfrom->GetId(), 20);
|
|
return error("message getdata size() = %u", vInv.size());
|
|
}
|
|
|
|
if (fDebug || (vInv.size() != 1))
|
|
LogPrint("net", "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom->id);
|
|
|
|
if ((fDebug && vInv.size() > 0) || (vInv.size() == 1))
|
|
LogPrint("net", "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom->id);
|
|
|
|
pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end());
|
|
ProcessGetData(pfrom);
|
|
}
|
|
|
|
|
|
else if (strCommand == "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==uint256(0) ? "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 make them
|
|
// 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 == "getheaders")
|
|
{
|
|
CBlockLocator locator;
|
|
uint256 hashStop;
|
|
vRecv >> locator >> hashStop;
|
|
|
|
LOCK(cs_main);
|
|
|
|
CBlockIndex* pindex = NULL;
|
|
if (locator.IsNull())
|
|
{
|
|
// If locator is null, return the hashStop block
|
|
BlockMap::iterator mi = mapBlockIndex.find(hashStop);
|
|
if (mi == mapBlockIndex.end())
|
|
return true;
|
|
pindex = (*mi).second;
|
|
}
|
|
else
|
|
{
|
|
// Find the last block the caller has in the main chain
|
|
pindex = FindForkInGlobalIndex(chainActive, locator);
|
|
if (pindex)
|
|
pindex = chainActive.Next(pindex);
|
|
}
|
|
|
|
// we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end
|
|
vector<CBlock> vHeaders;
|
|
int nLimit = MAX_HEADERS_RESULTS;
|
|
LogPrint("net", "getheaders %d to %s from peer=%d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString(), pfrom->id);
|
|
for (; pindex; pindex = chainActive.Next(pindex))
|
|
{
|
|
vHeaders.push_back(pindex->GetBlockHeader());
|
|
if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop)
|
|
break;
|
|
}
|
|
pfrom->PushMessage("headers", vHeaders);
|
|
}
|
|
|
|
|
|
else if (strCommand == "tx")
|
|
{
|
|
vector<uint256> vWorkQueue;
|
|
vector<uint256> vEraseQueue;
|
|
CTransaction tx;
|
|
vRecv >> tx;
|
|
|
|
CInv inv(MSG_TX, tx.GetHash());
|
|
pfrom->AddInventoryKnown(inv);
|
|
|
|
LOCK(cs_main);
|
|
|
|
bool fMissingInputs = false;
|
|
CValidationState state;
|
|
|
|
mapAlreadyAskedFor.erase(inv);
|
|
|
|
if (AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs))
|
|
{
|
|
mempool.check(pcoinsTip);
|
|
RelayTransaction(tx);
|
|
vWorkQueue.push_back(inv.hash);
|
|
vEraseQueue.push_back(inv.hash);
|
|
|
|
LogPrint("mempool", "AcceptToMemoryPool: peer=%d %s : accepted %s (poolsz %u)\n",
|
|
pfrom->id, pfrom->cleanSubVer,
|
|
tx.GetHash().ToString(),
|
|
mempool.mapTx.size());
|
|
|
|
// Recursively process any orphan transactions that depended on this one
|
|
set<NodeId> setMisbehaving;
|
|
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
|
|
{
|
|
map<uint256, set<uint256> >::iterator itByPrev = mapOrphanTransactionsByPrev.find(vWorkQueue[i]);
|
|
if (itByPrev == mapOrphanTransactionsByPrev.end())
|
|
continue;
|
|
for (set<uint256>::iterator mi = itByPrev->second.begin();
|
|
mi != itByPrev->second.end();
|
|
++mi)
|
|
{
|
|
const uint256& orphanHash = *mi;
|
|
const CTransaction& orphanTx = mapOrphanTransactions[orphanHash].tx;
|
|
NodeId fromPeer = mapOrphanTransactions[orphanHash].fromPeer;
|
|
bool fMissingInputs2 = false;
|
|
// Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan
|
|
// resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get
|
|
// anyone relaying LegitTxX banned)
|
|
CValidationState stateDummy;
|
|
|
|
vEraseQueue.push_back(orphanHash);
|
|
|
|
if (setMisbehaving.count(fromPeer))
|
|
continue;
|
|
if (AcceptToMemoryPool(mempool, stateDummy, orphanTx, true, &fMissingInputs2))
|
|
{
|
|
LogPrint("mempool", " accepted orphan tx %s\n", orphanHash.ToString());
|
|
RelayTransaction(orphanTx);
|
|
vWorkQueue.push_back(orphanHash);
|
|
}
|
|
else if (!fMissingInputs2)
|
|
{
|
|
int nDos = 0;
|
|
if (stateDummy.IsInvalid(nDos) && nDos > 0)
|
|
{
|
|
// Punish peer that gave us an invalid orphan tx
|
|
Misbehaving(fromPeer, nDos);
|
|
setMisbehaving.insert(fromPeer);
|
|
LogPrint("mempool", " invalid orphan tx %s\n", orphanHash.ToString());
|
|
}
|
|
// too-little-fee orphan
|
|
LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString());
|
|
}
|
|
mempool.check(pcoinsTip);
|
|
}
|
|
}
|
|
|
|
BOOST_FOREACH(uint256 hash, vEraseQueue)
|
|
EraseOrphanTx(hash);
|
|
}
|
|
else if (fMissingInputs)
|
|
{
|
|
AddOrphanTx(tx, pfrom->GetId());
|
|
|
|
// DoS prevention: do not allow mapOrphanTransactions to grow unbounded
|
|
unsigned int nMaxOrphanTx = (unsigned int)std::max((int64_t)0, GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS));
|
|
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
|
|
if (nEvicted > 0)
|
|
LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted);
|
|
} else if (pfrom->fWhitelisted) {
|
|
// Always relay transactions received from whitelisted peers, even
|
|
// if they are already in the mempool (allowing the node to function
|
|
// as a gateway for nodes hidden behind it).
|
|
RelayTransaction(tx);
|
|
}
|
|
int nDoS = 0;
|
|
if (state.IsInvalid(nDoS))
|
|
{
|
|
LogPrint("mempool", "%s from peer=%d %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString(),
|
|
pfrom->id, pfrom->cleanSubVer,
|
|
state.GetRejectReason());
|
|
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
|
|
state.GetRejectReason(), inv.hash);
|
|
if (nDoS > 0)
|
|
Misbehaving(pfrom->GetId(), nDoS);
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "headers" && !fImporting && !fReindex) // Ignore headers received while importing
|
|
{
|
|
std::vector<CBlockHeader> 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.
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
if (nCount == 0) {
|
|
// Nothing interesting. Stop asking this peers for more headers.
|
|
return true;
|
|
}
|
|
|
|
CBlockIndex *pindexLast = NULL;
|
|
BOOST_FOREACH(const CBlockHeader& header, headers) {
|
|
CValidationState state;
|
|
if (pindexLast != NULL && header.hashPrevBlock != pindexLast->GetBlockHash()) {
|
|
Misbehaving(pfrom->GetId(), 20);
|
|
return error("non-continuous headers sequence");
|
|
}
|
|
if (!AcceptBlockHeader(header, state, &pindexLast)) {
|
|
int nDoS;
|
|
if (state.IsInvalid(nDoS)) {
|
|
if (nDoS > 0)
|
|
Misbehaving(pfrom->GetId(), nDoS);
|
|
return error("invalid header received");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pindexLast)
|
|
UpdateBlockAvailability(pfrom->GetId(), pindexLast->GetBlockHash());
|
|
|
|
if (nCount == MAX_HEADERS_RESULTS && pindexLast) {
|
|
// 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.
|
|
LogPrint("net", "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->id, pfrom->nStartingHeight);
|
|
pfrom->PushMessage("getheaders", chainActive.GetLocator(pindexLast), uint256(0));
|
|
}
|
|
}
|
|
|
|
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing
|
|
{
|
|
CBlock block;
|
|
vRecv >> block;
|
|
|
|
CInv inv(MSG_BLOCK, block.GetHash());
|
|
LogPrint("net", "received block %s peer=%d\n", inv.hash.ToString(), pfrom->id);
|
|
|
|
pfrom->AddInventoryKnown(inv);
|
|
|
|
CValidationState state;
|
|
ProcessNewBlock(state, pfrom, &block);
|
|
int nDoS;
|
|
if (state.IsInvalid(nDoS)) {
|
|
pfrom->PushMessage("reject", strCommand, state.GetRejectCode(),
|
|
state.GetRejectReason(), inv.hash);
|
|
if (nDoS > 0) {
|
|
LOCK(cs_main);
|
|
Misbehaving(pfrom->GetId(), nDoS);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
else if (strCommand == "getaddr")
|
|
{
|
|
pfrom->vAddrToSend.clear();
|
|
vector<CAddress> vAddr = addrman.GetAddr();
|
|
BOOST_FOREACH(const CAddress &addr, vAddr)
|
|
pfrom->PushAddress(addr);
|
|
}
|
|
|
|
|
|
else if (strCommand == "mempool")
|
|
{
|
|
LOCK2(cs_main, pfrom->cs_filter);
|
|
|
|
std::vector<uint256> vtxid;
|
|
mempool.queryHashes(vtxid);
|
|
vector<CInv> vInv;
|
|
BOOST_FOREACH(uint256& hash, vtxid) {
|
|
CInv inv(MSG_TX, hash);
|
|
CTransaction tx;
|
|
bool fInMemPool = mempool.lookup(hash, tx);
|
|
if (!fInMemPool) continue; // another thread removed since queryHashes, maybe...
|
|
if ((pfrom->pfilter && pfrom->pfilter->IsRelevantAndUpdate(tx)) ||
|
|
(!pfrom->pfilter))
|
|
vInv.push_back(inv);
|
|
if (vInv.size() == MAX_INV_SZ) {
|
|
pfrom->PushMessage("inv", vInv);
|
|
vInv.clear();
|
|
}
|
|
}
|
|
if (vInv.size() > 0)
|
|
pfrom->PushMessage("inv", vInv);
|
|
}
|
|
|
|
|
|
else if (strCommand == "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("pong", nonce);
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "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;
|
|
} 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: %s, %x expected, %x received, %u bytes\n",
|
|
pfrom->id,
|
|
pfrom->cleanSubVer,
|
|
sProblem,
|
|
pfrom->nPingNonceSent,
|
|
nonce,
|
|
nAvail);
|
|
}
|
|
if (bPingFinished) {
|
|
pfrom->nPingNonceSent = 0;
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "alert")
|
|
{
|
|
CAlert alert;
|
|
vRecv >> alert;
|
|
|
|
uint256 alertHash = alert.GetHash();
|
|
if (pfrom->setKnown.count(alertHash) == 0)
|
|
{
|
|
if (alert.ProcessAlert())
|
|
{
|
|
// Relay
|
|
pfrom->setKnown.insert(alertHash);
|
|
{
|
|
LOCK(cs_vNodes);
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
alert.RelayTo(pnode);
|
|
}
|
|
}
|
|
else {
|
|
// Small DoS penalty so peers that send us lots of
|
|
// duplicate/expired/invalid-signature/whatever alerts
|
|
// eventually get banned.
|
|
// This isn't a Misbehaving(100) (immediate ban) because the
|
|
// peer might be an older or different implementation with
|
|
// a different signature key, etc.
|
|
Misbehaving(pfrom->GetId(), 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "filterload")
|
|
{
|
|
CBloomFilter filter;
|
|
vRecv >> filter;
|
|
|
|
if (!filter.IsWithinSizeConstraints())
|
|
// There is no excuse for sending a too-large filter
|
|
Misbehaving(pfrom->GetId(), 100);
|
|
else
|
|
{
|
|
LOCK(pfrom->cs_filter);
|
|
delete pfrom->pfilter;
|
|
pfrom->pfilter = new CBloomFilter(filter);
|
|
pfrom->pfilter->UpdateEmptyFull();
|
|
}
|
|
pfrom->fRelayTxes = true;
|
|
}
|
|
|
|
|
|
else if (strCommand == "filteradd")
|
|
{
|
|
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)
|
|
{
|
|
Misbehaving(pfrom->GetId(), 100);
|
|
} else {
|
|
LOCK(pfrom->cs_filter);
|
|
if (pfrom->pfilter)
|
|
pfrom->pfilter->insert(vData);
|
|
else
|
|
Misbehaving(pfrom->GetId(), 100);
|
|
}
|
|
}
|
|
|
|
|
|
else if (strCommand == "filterclear")
|
|
{
|
|
LOCK(pfrom->cs_filter);
|
|
delete pfrom->pfilter;
|
|
pfrom->pfilter = new CBloomFilter();
|
|
pfrom->fRelayTxes = true;
|
|
}
|
|
|
|
|
|
else if (strCommand == "reject")
|
|
{
|
|
if (fDebug) {
|
|
try {
|
|
string strMsg; unsigned char ccode; string strReason;
|
|
vRecv >> LIMITED_STRING(strMsg, CMessageHeader::COMMAND_SIZE) >> ccode >> LIMITED_STRING(strReason, 111);
|
|
|
|
ostringstream ss;
|
|
ss << strMsg << " code " << itostr(ccode) << ": " << strReason;
|
|
|
|
if (strMsg == "block" || strMsg == "tx")
|
|
{
|
|
uint256 hash;
|
|
vRecv >> hash;
|
|
ss << ": hash " << hash.ToString();
|
|
}
|
|
LogPrint("net", "Reject %s\n", SanitizeString(ss.str()));
|
|
} catch (std::ios_base::failure& e) {
|
|
// Avoid feedback loops by preventing reject messages from triggering a new reject message.
|
|
LogPrint("net", "Unparseable reject message received\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
// Ignore unknown commands for extensibility
|
|
LogPrint("net", "Unknown command \"%s\" from peer=%d\n", SanitizeString(strCommand), pfrom->id);
|
|
}
|
|
|
|
|
|
// Update the last seen time for this node's address
|
|
if (pfrom->fNetworkNode)
|
|
if (strCommand == "version" || strCommand == "addr" || strCommand == "inv" || strCommand == "getdata" || strCommand == "ping")
|
|
AddressCurrentlyConnected(pfrom->addr);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
// requires LOCK(cs_vRecvMsg)
|
|
bool ProcessMessages(CNode* pfrom)
|
|
{
|
|
//if (fDebug)
|
|
// LogPrintf("ProcessMessages(%u messages)\n", 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);
|
|
|
|
// 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("ProcessMessages(message %u msgsz, %u bytes, complete:%s)\n",
|
|
// 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 (memcmp(msg.hdr.pchMessageStart, Params().MessageStart(), MESSAGE_START_SIZE) != 0) {
|
|
LogPrintf("PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n", msg.hdr.GetCommand(), pfrom->id);
|
|
fOk = false;
|
|
break;
|
|
}
|
|
|
|
// Read header
|
|
CMessageHeader& hdr = msg.hdr;
|
|
if (!hdr.IsValid())
|
|
{
|
|
LogPrintf("PROCESSMESSAGE: ERRORS IN HEADER %s peer=%d\n", hdr.GetCommand(), pfrom->id);
|
|
continue;
|
|
}
|
|
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 = 0;
|
|
memcpy(&nChecksum, &hash, sizeof(nChecksum));
|
|
if (nChecksum != hdr.nChecksum)
|
|
{
|
|
LogPrintf("ProcessMessages(%s, %u bytes) : CHECKSUM ERROR nChecksum=%08x hdr.nChecksum=%08x\n",
|
|
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 (std::ios_base::failure& e)
|
|
{
|
|
pfrom->PushMessage("reject", strCommand, REJECT_MALFORMED, string("error parsing message"));
|
|
if (strstr(e.what(), "end of data"))
|
|
{
|
|
// Allow exceptions from under-length message on vRecv
|
|
LogPrintf("ProcessMessages(%s, %u bytes) : Exception '%s' caught, normally caused by a message being shorter than its stated length\n", strCommand, nMessageSize, e.what());
|
|
}
|
|
else if (strstr(e.what(), "size too large"))
|
|
{
|
|
// Allow exceptions from over-long size
|
|
LogPrintf("ProcessMessages(%s, %u bytes) : Exception '%s' caught\n", strCommand, nMessageSize, e.what());
|
|
}
|
|
else
|
|
{
|
|
PrintExceptionContinue(&e, "ProcessMessages()");
|
|
}
|
|
}
|
|
catch (boost::thread_interrupted) {
|
|
throw;
|
|
}
|
|
catch (std::exception& e) {
|
|
PrintExceptionContinue(&e, "ProcessMessages()");
|
|
} catch (...) {
|
|
PrintExceptionContinue(NULL, "ProcessMessages()");
|
|
}
|
|
|
|
if (!fRet)
|
|
LogPrintf("ProcessMessage(%s, %u bytes) FAILED peer=%d\n", 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, bool fSendTrickle)
|
|
{
|
|
{
|
|
// Don't send anything until we get their 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("ping", nonce);
|
|
} else {
|
|
// Peer is too old to support ping command with nonce, pong will never arrive.
|
|
pto->nPingNonceSent = 0;
|
|
pto->PushMessage("ping");
|
|
}
|
|
}
|
|
|
|
TRY_LOCK(cs_main, lockMain); // Acquire cs_main for IsInitialBlockDownload() and CNodeState()
|
|
if (!lockMain)
|
|
return true;
|
|
|
|
// Address refresh broadcast
|
|
static int64_t nLastRebroadcast;
|
|
if (!IsInitialBlockDownload() && (GetTime() - nLastRebroadcast > 24 * 60 * 60))
|
|
{
|
|
LOCK(cs_vNodes);
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
{
|
|
// Periodically clear setAddrKnown to allow refresh broadcasts
|
|
if (nLastRebroadcast)
|
|
pnode->setAddrKnown.clear();
|
|
|
|
// Rebroadcast our address
|
|
AdvertizeLocal(pnode);
|
|
}
|
|
if (!vNodes.empty())
|
|
nLastRebroadcast = GetTime();
|
|
}
|
|
|
|
//
|
|
// Message: addr
|
|
//
|
|
if (fSendTrickle)
|
|
{
|
|
vector<CAddress> vAddr;
|
|
vAddr.reserve(pto->vAddrToSend.size());
|
|
BOOST_FOREACH(const CAddress& addr, pto->vAddrToSend)
|
|
{
|
|
// returns true if wasn't already contained in the set
|
|
if (pto->setAddrKnown.insert(addr).second)
|
|
{
|
|
vAddr.push_back(addr);
|
|
// receiver rejects addr messages larger than 1000
|
|
if (vAddr.size() >= 1000)
|
|
{
|
|
pto->PushMessage("addr", vAddr);
|
|
vAddr.clear();
|
|
}
|
|
}
|
|
}
|
|
pto->vAddrToSend.clear();
|
|
if (!vAddr.empty())
|
|
pto->PushMessage("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);
|
|
}
|
|
}
|
|
state.fShouldBan = false;
|
|
}
|
|
|
|
BOOST_FOREACH(const CBlockReject& reject, state.rejects)
|
|
pto->PushMessage("reject", (string)"block", reject.chRejectCode, reject.strRejectReason, reject.hashBlock);
|
|
state.rejects.clear();
|
|
|
|
// Start block sync
|
|
if (pindexBestHeader == NULL)
|
|
pindexBestHeader = chainActive.Tip();
|
|
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 && fFetch && !fImporting && !fReindex) {
|
|
// Only actively request headers from a single peer, unless we're close to today.
|
|
if (nSyncStarted == 0 || pindexBestHeader->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) {
|
|
state.fSyncStarted = true;
|
|
nSyncStarted++;
|
|
CBlockIndex *pindexStart = pindexBestHeader->pprev ? pindexBestHeader->pprev : pindexBestHeader;
|
|
LogPrint("net", "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->id, pto->nStartingHeight);
|
|
pto->PushMessage("getheaders", chainActive.GetLocator(pindexStart), uint256(0));
|
|
}
|
|
}
|
|
|
|
// 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 && !fImporting && !IsInitialBlockDownload())
|
|
{
|
|
g_signals.Broadcast();
|
|
}
|
|
|
|
//
|
|
// Message: inventory
|
|
//
|
|
vector<CInv> vInv;
|
|
vector<CInv> vInvWait;
|
|
{
|
|
LOCK(pto->cs_inventory);
|
|
vInv.reserve(pto->vInventoryToSend.size());
|
|
vInvWait.reserve(pto->vInventoryToSend.size());
|
|
BOOST_FOREACH(const CInv& inv, pto->vInventoryToSend)
|
|
{
|
|
if (pto->setInventoryKnown.count(inv))
|
|
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 == 0)
|
|
hashSalt = GetRandHash();
|
|
uint256 hashRand = inv.hash ^ hashSalt;
|
|
hashRand = Hash(BEGIN(hashRand), END(hashRand));
|
|
bool fTrickleWait = ((hashRand & 3) != 0);
|
|
|
|
if (fTrickleWait)
|
|
{
|
|
vInvWait.push_back(inv);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// returns true if wasn't already contained in the set
|
|
if (pto->setInventoryKnown.insert(inv).second)
|
|
{
|
|
vInv.push_back(inv);
|
|
if (vInv.size() >= 1000)
|
|
{
|
|
pto->PushMessage("inv", vInv);
|
|
vInv.clear();
|
|
}
|
|
}
|
|
}
|
|
pto->vInventoryToSend = vInvWait;
|
|
}
|
|
if (!vInv.empty())
|
|
pto->PushMessage("inv", vInv);
|
|
|
|
// Detect whether we're stalling
|
|
int64_t 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.
|
|
LogPrintf("Peer=%d is stalling block download, disconnecting\n", pto->id);
|
|
pto->fDisconnect = true;
|
|
}
|
|
|
|
//
|
|
// Message: getdata (blocks)
|
|
//
|
|
vector<CInv> vGetData;
|
|
if (!pto->fDisconnect && !pto->fClient && fFetch && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
|
vector<CBlockIndex*> vToDownload;
|
|
NodeId staller = -1;
|
|
FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller);
|
|
BOOST_FOREACH(CBlockIndex *pindex, vToDownload) {
|
|
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
|
MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex);
|
|
LogPrint("net", "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(),
|
|
pindex->nHeight, pto->id);
|
|
}
|
|
if (state.nBlocksInFlight == 0 && staller != -1) {
|
|
if (State(staller)->nStallingSince == 0) {
|
|
State(staller)->nStallingSince = nNow;
|
|
LogPrint("net", "Stall started peer=%d\n", 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))
|
|
{
|
|
if (fDebug)
|
|
LogPrint("net", "Requesting %s peer=%d\n", inv.ToString(), pto->id);
|
|
vGetData.push_back(inv);
|
|
if (vGetData.size() >= 1000)
|
|
{
|
|
pto->PushMessage("getdata", vGetData);
|
|
vGetData.clear();
|
|
}
|
|
}
|
|
pto->mapAskFor.erase(pto->mapAskFor.begin());
|
|
}
|
|
if (!vGetData.empty())
|
|
pto->PushMessage("getdata", vGetData);
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool CBlockUndo::WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock)
|
|
{
|
|
// Open history file to append
|
|
CAutoFile fileout(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
|
|
if (fileout.IsNull())
|
|
return error("CBlockUndo::WriteToDisk : OpenUndoFile failed");
|
|
|
|
// Write index header
|
|
unsigned int nSize = fileout.GetSerializeSize(*this);
|
|
fileout << FLATDATA(Params().MessageStart()) << nSize;
|
|
|
|
// Write undo data
|
|
long fileOutPos = ftell(fileout.Get());
|
|
if (fileOutPos < 0)
|
|
return error("CBlockUndo::WriteToDisk : ftell failed");
|
|
pos.nPos = (unsigned int)fileOutPos;
|
|
fileout << *this;
|
|
|
|
// calculate & write checksum
|
|
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
|
hasher << hashBlock;
|
|
hasher << *this;
|
|
fileout << hasher.GetHash();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CBlockUndo::ReadFromDisk(const CDiskBlockPos &pos, const uint256 &hashBlock)
|
|
{
|
|
// Open history file to read
|
|
CAutoFile filein(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION);
|
|
if (filein.IsNull())
|
|
return error("CBlockUndo::ReadFromDisk : OpenBlockFile failed");
|
|
|
|
// Read block
|
|
uint256 hashChecksum;
|
|
try {
|
|
filein >> *this;
|
|
filein >> hashChecksum;
|
|
}
|
|
catch (std::exception &e) {
|
|
return error("%s : Deserialize or I/O error - %s", __func__, e.what());
|
|
}
|
|
|
|
// Verify checksum
|
|
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
|
|
hasher << hashBlock;
|
|
hasher << *this;
|
|
if (hashChecksum != hasher.GetHash())
|
|
return error("CBlockUndo::ReadFromDisk : Checksum mismatch");
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string CBlockFileInfo::ToString() const {
|
|
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst), DateTimeStrFormat("%Y-%m-%d", nTimeLast));
|
|
}
|
|
|
|
|
|
|
|
class CMainCleanup
|
|
{
|
|
public:
|
|
CMainCleanup() {}
|
|
~CMainCleanup() {
|
|
// block headers
|
|
BlockMap::iterator it1 = mapBlockIndex.begin();
|
|
for (; it1 != mapBlockIndex.end(); it1++)
|
|
delete (*it1).second;
|
|
mapBlockIndex.clear();
|
|
|
|
// orphan transactions
|
|
mapOrphanTransactions.clear();
|
|
mapOrphanTransactionsByPrev.clear();
|
|
}
|
|
} instance_of_cmaincleanup;
|