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.
This commit is contained in:
parent
32b7294177
commit
5d0ca81f74
4 changed files with 61 additions and 10 deletions
16
src/main.cpp
16
src/main.cpp
|
@ -3449,8 +3449,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 */
|
/** 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);
|
AssertLockHeld(cs_main);
|
||||||
|
|
||||||
CBlockIndex *pindexDummy = NULL;
|
CBlockIndex *pindexDummy = NULL;
|
||||||
|
@ -3479,6 +3480,7 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, const CCha
|
||||||
if (!fHasMoreWork) return true; // Don't process less-work chains
|
if (!fHasMoreWork) return true; // Don't process less-work chains
|
||||||
if (fTooFarAhead) return true; // Block height is too high
|
if (fTooFarAhead) return true; // Block height is too high
|
||||||
}
|
}
|
||||||
|
if (fNewBlock) *fNewBlock = true;
|
||||||
|
|
||||||
if ((!CheckBlock(block, state, chainparams.GetConsensus(), GetAdjustedTime())) || !ContextualCheckBlock(block, state, pindex->pprev)) {
|
if ((!CheckBlock(block, state, chainparams.GetConsensus(), GetAdjustedTime())) || !ContextualCheckBlock(block, state, pindex->pprev)) {
|
||||||
if (state.IsInvalid() && !state.CorruptionPossible()) {
|
if (state.IsInvalid() && !state.CorruptionPossible()) {
|
||||||
|
@ -3526,7 +3528,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);
|
LOCK(cs_main);
|
||||||
|
@ -3535,9 +3537,11 @@ bool ProcessNewBlock(CValidationState& state, const CChainParams& chainparams, c
|
||||||
|
|
||||||
// Store to disk
|
// Store to disk
|
||||||
CBlockIndex *pindex = NULL;
|
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) {
|
if (pindex && pfrom) {
|
||||||
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
|
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
|
||||||
|
if (fNewBlock) pfrom->nLastBlockTime = GetTime();
|
||||||
}
|
}
|
||||||
CheckBlockIndex(chainparams.GetConsensus());
|
CheckBlockIndex(chainparams.GetConsensus());
|
||||||
if (!ret)
|
if (!ret)
|
||||||
|
@ -4107,7 +4111,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB
|
||||||
if (mapBlockIndex.count(hash) == 0 || (mapBlockIndex[hash]->nStatus & BLOCK_HAVE_DATA) == 0) {
|
if (mapBlockIndex.count(hash) == 0 || (mapBlockIndex[hash]->nStatus & BLOCK_HAVE_DATA) == 0) {
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
if (AcceptBlock(block, state, chainparams, NULL, true, dbp))
|
if (AcceptBlock(block, state, chainparams, NULL, true, dbp, NULL))
|
||||||
nLoaded++;
|
nLoaded++;
|
||||||
if (state.IsError())
|
if (state.IsError())
|
||||||
break;
|
break;
|
||||||
|
@ -4140,7 +4144,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, CDiskB
|
||||||
head.ToString());
|
head.ToString());
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
CValidationState dummy;
|
CValidationState dummy;
|
||||||
if (AcceptBlock(block, dummy, chainparams, NULL, true, &it->second))
|
if (AcceptBlock(block, dummy, chainparams, NULL, true, &it->second, NULL))
|
||||||
{
|
{
|
||||||
nLoaded++;
|
nLoaded++;
|
||||||
queue.push_back(block.GetHash());
|
queue.push_back(block.GetHash());
|
||||||
|
@ -5040,6 +5044,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
|
||||||
RelayTransaction(tx);
|
RelayTransaction(tx);
|
||||||
vWorkQueue.push_back(inv.hash);
|
vWorkQueue.push_back(inv.hash);
|
||||||
|
|
||||||
|
pfrom->nLastTXTime = GetTime();
|
||||||
|
|
||||||
LogPrint("mempool", "AcceptToMemoryPool: peer=%d: accepted %s (poolsz %u txn, %u kB)\n",
|
LogPrint("mempool", "AcceptToMemoryPool: peer=%d: accepted %s (poolsz %u txn, %u kB)\n",
|
||||||
pfrom->id,
|
pfrom->id,
|
||||||
tx.GetHash().ToString(),
|
tx.GetHash().ToString(),
|
||||||
|
|
|
@ -215,7 +215,7 @@ void UnregisterNodeSignals(CNodeSignals& nodeSignals);
|
||||||
* @param[out] dbp The already known disk position of pblock, or NULL if not yet stored.
|
* @param[out] dbp The already known disk position of pblock, or NULL if not yet stored.
|
||||||
* @return True if state.IsValid()
|
* @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 */
|
/** Check whether enough disk space is available for an incoming block */
|
||||||
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0);
|
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0);
|
||||||
/** Open a block file (blk?????.dat) */
|
/** Open a block file (blk?????.dat) */
|
||||||
|
|
48
src/net.cpp
48
src/net.cpp
|
@ -838,6 +838,11 @@ struct NodeEvictionCandidate
|
||||||
NodeId id;
|
NodeId id;
|
||||||
int64_t nTimeConnected;
|
int64_t nTimeConnected;
|
||||||
int64_t nMinPingUsecTime;
|
int64_t nMinPingUsecTime;
|
||||||
|
int64_t nLastBlockTime;
|
||||||
|
int64_t nLastTXTime;
|
||||||
|
bool fNetworkNode;
|
||||||
|
bool fRelayTxes;
|
||||||
|
bool fBloomFilter;
|
||||||
CAddress addr;
|
CAddress addr;
|
||||||
uint64_t nKeyedNetGroup;
|
uint64_t nKeyedNetGroup;
|
||||||
};
|
};
|
||||||
|
@ -854,7 +859,24 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, cons
|
||||||
|
|
||||||
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
|
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
|
||||||
return a.nKeyedNetGroup < b.nKeyedNetGroup;
|
return a.nKeyedNetGroup < b.nKeyedNetGroup;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
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.
|
/** Try to find a connection to evict when the node is full.
|
||||||
* Extreme care must be taken to avoid opening the node to attacker
|
* Extreme care must be taken to avoid opening the node to attacker
|
||||||
|
@ -864,7 +886,7 @@ static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvict
|
||||||
* to forge. In order to partition a node the attacker must be
|
* to forge. In order to partition a node the attacker must be
|
||||||
* simultaneously better at all of them than honest peers.
|
* simultaneously better at all of them than honest peers.
|
||||||
*/
|
*/
|
||||||
static bool AttemptToEvictConnection(bool fPreferNewConnection) {
|
static bool AttemptToEvictConnection() {
|
||||||
std::vector<NodeEvictionCandidate> vEvictionCandidates;
|
std::vector<NodeEvictionCandidate> vEvictionCandidates;
|
||||||
{
|
{
|
||||||
LOCK(cs_vNodes);
|
LOCK(cs_vNodes);
|
||||||
|
@ -876,7 +898,9 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
|
||||||
continue;
|
continue;
|
||||||
if (node->fDisconnect)
|
if (node->fDisconnect)
|
||||||
continue;
|
continue;
|
||||||
NodeEvictionCandidate candidate = {node->id, node->nTimeConnected, node->nMinPingUsecTime, node->addr, node->nKeyedNetGroup};
|
NodeEvictionCandidate candidate = {node->id, node->nTimeConnected, node->nMinPingUsecTime,
|
||||||
|
node->nLastBlockTime, node->nLastTXTime, node->fNetworkNode,
|
||||||
|
node->fRelayTxes, node->pfilter != NULL, node->addr, node->nKeyedNetGroup};
|
||||||
vEvictionCandidates.push_back(candidate);
|
vEvictionCandidates.push_back(candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -899,6 +923,20 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
|
||||||
|
|
||||||
if (vEvictionCandidates.empty()) return false;
|
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.
|
// Protect the half of the remaining nodes which have been connected the longest.
|
||||||
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
|
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
|
||||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
|
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
|
||||||
|
@ -999,7 +1037,7 @@ static void AcceptConnection(const ListenSocket& hListenSocket) {
|
||||||
|
|
||||||
if (nInbound >= nMaxInbound)
|
if (nInbound >= nMaxInbound)
|
||||||
{
|
{
|
||||||
if (!AttemptToEvictConnection(whitelisted)) {
|
if (!AttemptToEvictConnection()) {
|
||||||
// No connection to evict, disconnect the new connection
|
// No connection to evict, disconnect the new connection
|
||||||
LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n");
|
LogPrint("net", "failed to find an eviction candidate - connection dropped (full)\n");
|
||||||
CloseSocket(hSocket);
|
CloseSocket(hSocket);
|
||||||
|
@ -2358,6 +2396,8 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
|
||||||
fSentAddr = false;
|
fSentAddr = false;
|
||||||
pfilter = new CBloomFilter();
|
pfilter = new CBloomFilter();
|
||||||
timeLastMempoolReq = 0;
|
timeLastMempoolReq = 0;
|
||||||
|
nLastBlockTime = 0;
|
||||||
|
nLastTXTime = 0;
|
||||||
nPingNonceSent = 0;
|
nPingNonceSent = 0;
|
||||||
nPingUsecStart = 0;
|
nPingUsecStart = 0;
|
||||||
nPingUsecTime = 0;
|
nPingUsecTime = 0;
|
||||||
|
|
|
@ -416,6 +416,11 @@ public:
|
||||||
|
|
||||||
// Last time a "MEMPOOL" request was serviced.
|
// Last time a "MEMPOOL" request was serviced.
|
||||||
std::atomic<int64_t> timeLastMempoolReq;
|
std::atomic<int64_t> timeLastMempoolReq;
|
||||||
|
|
||||||
|
// Block and TXN accept times
|
||||||
|
std::atomic<int64_t> nLastBlockTime;
|
||||||
|
std::atomic<int64_t> nLastTXTime;
|
||||||
|
|
||||||
// Ping time measurement:
|
// Ping time measurement:
|
||||||
// The pong reply we're expecting, or 0 if no pong expected.
|
// The pong reply we're expecting, or 0 if no pong expected.
|
||||||
uint64_t nPingNonceSent;
|
uint64_t nPingNonceSent;
|
||||||
|
|
Loading…
Reference in a new issue