Skip to content

Commit

Permalink
Backport Bitcoin PR#8084: Add recently accepted blocks and txn to Att…
Browse files Browse the repository at this point in the history
…emptToEvictConnection (#1522)

* Add recently accepted blocks and txn to AttemptToEvictConnection.

This protects any not-already-protected peers who were the most
 recent four to relay transactions and most recent four to send
 blocks to us.

* Allow disconnecting a netgroup with only one member in eviction.

With the latest additions there are enough protective measures that
 we can take the training wheels off.
  • Loading branch information
OlegGirko authored and UdjinM6 committed Jul 12, 2017
1 parent 5a1961e commit 7b5556a
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 14 deletions.
16 changes: 11 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3963,8 +3963,9 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
}

/** Store block on disk. If dbp is non-NULL, the file is known to already reside on disk */
static bool AcceptBlock(const CBlock& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp)
static bool AcceptBlock(const CBlock& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock)
{
if (fNewBlock) *fNewBlock = false;
AssertLockHeld(cs_main);

CBlockIndex *pindexDummy = NULL;
Expand Down Expand Up @@ -3993,6 +3994,7 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, const CCha
if (!fHasMoreWork) return true; // Don't process less-work chains
if (fTooFarAhead) return true; // Block height is too high
}
if (fNewBlock) *fNewBlock = true;

if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) {
if (state.IsInvalid() && !state.CorruptionPossible()) {
Expand Down Expand Up @@ -4040,7 +4042,7 @@ static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned
}


bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, const CNode* pfrom, const CBlock* pblock, bool fForceProcessing, const CDiskBlockPos* dbp)
bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, CNode* pfrom, const CBlock* pblock, bool fForceProcessing, const CDiskBlockPos* dbp)
{
{
LOCK(cs_main);
Expand All @@ -4049,9 +4051,11 @@ bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, c

// Store to disk
CBlockIndex *pindex = NULL;
bool ret = AcceptBlock(*pblock, state, chainparams, &pindex, fRequested, dbp);
bool fNewBlock = false;
bool ret = AcceptBlock(*pblock, state, chainparams, &pindex, fRequested, dbp, &fNewBlock);
if (pindex && pfrom) {
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
if (fNewBlock) pfrom->nLastBlockTime = GetTime();
}
CheckBlockIndex(chainparams.GetConsensus());
if (!ret)
Expand Down Expand Up @@ -4633,7 +4637,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB
if (mapBlockIndex.count(hash) == 0 || (mapBlockIndex[hash]->nStatus & BLOCK_HAVE_DATA) == 0) {
LOCK(cs_main);
CValidationState state;
if (AcceptBlock(block, state, chainparams, NULL, true, dbp))
if (AcceptBlock(block, state, chainparams, NULL, true, dbp, NULL))
nLoaded++;
if (state.IsError())
break;
Expand Down Expand Up @@ -4666,7 +4670,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB
head.ToString());
LOCK(cs_main);
CValidationState dummy;
if (AcceptBlock(block, dummy, chainparams, NULL, true, &it->second))
if (AcceptBlock(block, dummy, chainparams, NULL, true, &it->second, NULL))
{
nLoaded++;
queue.push_back(block.GetHash());
Expand Down Expand Up @@ -5878,6 +5882,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
RelayTransaction(tx);
vWorkQueue.push_back(inv.hash);

pfrom->nLastTXTime = GetTime();

LogPrint("mempool", "AcceptToMemoryPool: peer=%d: accepted %s (poolsz %u txn, %u kB)\n",
pfrom->id,
tx.GetHash().ToString(),
Expand Down
2 changes: 1 addition & 1 deletion src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ void UnregisterNodeSignals(CNodeSignals& nodeSignals);
* @param[out] dbp The already known disk position of pblock, or NULL if not yet stored.
* @return True if state.IsValid()
*/
bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, const CNode* pfrom, const CBlock* pblock, bool fForceProcessing, const CDiskBlockPos* dbp);
bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, CNode* pfrom, const CBlock* pblock, bool fForceProcessing, const CDiskBlockPos* dbp);
/** Check whether enough disk space is available for an incoming block */
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0);
/** Open a block file (blk?????.dat) */
Expand Down
61 changes: 53 additions & 8 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,13 +870,23 @@ struct NodeEvictionCandidate
: id(pnode->id),
nTimeConnected(pnode->nTimeConnected),
nMinPingUsecTime(pnode->nMinPingUsecTime),
nLastBlockTime(pnode->nLastBlockTime),
nLastTXTime(pnode->nLastTXTime),
fNetworkNode(pnode->fNetworkNode),
fRelayTxes(pnode->fRelayTxes),
fBloomFilter(pnode->pfilter != NULL),
vchNetGroup(pnode->addr.GetGroup()),
vchKeyedNetGroup(pnode->vchKeyedNetGroup)
{}

int id;
int64_t nTimeConnected;
int64_t nMinPingUsecTime;
int64_t nLastBlockTime;
int64_t nLastTXTime;
bool fNetworkNode;
bool fRelayTxes;
bool fBloomFilter;
std::vector<unsigned char> vchNetGroup;
std::vector<unsigned char> vchKeyedNetGroup;
};
Expand All @@ -896,7 +906,32 @@ static bool CompareKeyedNetGroup(const NodeEvictionCandidate& a, const NodeEvict
return a.vchKeyedNetGroup < b.vchKeyedNetGroup;
}

static bool AttemptToEvictConnection(bool fPreferNewConnection) {
static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
{
// There is a fall-through here because it is common for a node to have many peers which have not yet relayed a block.
if (a.nLastBlockTime != b.nLastBlockTime) return a.nLastBlockTime < b.nLastBlockTime;
if (a.fNetworkNode != b.fNetworkNode) return b.fNetworkNode;
return a.nTimeConnected > b.nTimeConnected;
}

static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
{
// There is a fall-through here because it is common for a node to have more than a few peers that have not yet relayed txn.
if (a.nLastTXTime != b.nLastTXTime) return a.nLastTXTime < b.nLastTXTime;
if (a.fRelayTxes != b.fRelayTxes) return b.fRelayTxes;
if (a.fBloomFilter != b.fBloomFilter) return a.fBloomFilter;
return a.nTimeConnected > b.nTimeConnected;
}

/** Try to find a connection to evict when the node is full.
* Extreme care must be taken to avoid opening the node to attacker
* triggered network partitioning.
* The strategy used here is to protect a small number of peers
* for each of several distinct characteristics which are difficult
* to forge. In order to partition a node the attacker must be
* simultaneously better at all of them than honest peers.
*/
static bool AttemptToEvictConnection() {
std::vector<NodeEvictionCandidate> vEvictionCandidates;
{
LOCK(cs_vNodes);
Expand Down Expand Up @@ -931,6 +966,20 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {

if (vEvictionCandidates.empty()) return false;

// Protect 4 nodes that most recently sent us transactions.
// An attacker cannot manipulate this metric without performing useful work.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeTXTime);
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());

if (vEvictionCandidates.empty()) return false;

// Protect 4 nodes that most recently sent us blocks.
// An attacker cannot manipulate this metric without performing useful work.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockTime);
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());

if (vEvictionCandidates.empty()) return false;

// Protect the half of the remaining nodes which have been connected the longest.
// This replicates the existing implicit behavior.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
Expand Down Expand Up @@ -964,12 +1013,6 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
return false;
}

// Do not disconnect peers if there is only one unprotected connection from their network group.
if (vEvictionNodes.size() <= 1)
// unless we prefer the new connection (for whitelisted peers)
if (!fPreferNewConnection)
return false;

// Disconnect from the network group with the most connections
int nEvictionId = vEvictionNodes[0].id;
{
Expand Down Expand Up @@ -1039,7 +1082,7 @@ static void AcceptConnection(const ListenSocket& hListenSocket) {

if (nInbound >= nMaxInbound)
{
if (!AttemptToEvictConnection(whitelisted)) {
if (!AttemptToEvictConnection()) {
// No connection to evict, disconnect the new connection
LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n");
CloseSocket(hSocket);
Expand Down Expand Up @@ -2498,6 +2541,8 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
nNextInvSend = 0;
fRelayTxes = false;
pfilter = new CBloomFilter();
nLastBlockTime = 0;
nLastTXTime = 0;
nPingNonceSent = 0;
nPingUsecStart = 0;
nPingUsecTime = 0;
Expand Down
4 changes: 4 additions & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ class CNode
// Also protected by cs_inventory
std::vector<uint256> vBlockHashesToAnnounce;

// Block and TXN accept times
std::atomic<int64_t> nLastBlockTime;
std::atomic<int64_t> nLastTXTime;

// Ping time measurement:
// The pong reply we're expecting, or 0 if no pong expected.
uint64_t nPingNonceSent;
Expand Down

0 comments on commit 7b5556a

Please sign in to comment.