Skip to content

Commit 1cf73fb

Browse files
committed
Merge bitcoin#19607: [p2p] Add Peer struct for per-peer data in net processing
8e35bf5 scripted-diff: rename misbehavior members (John Newbery) 1f96d2e [net processing] Move misbehavior tracking state to Peer (John Newbery) 7cd4159 [net processing] Add Peer (John Newbery) aba0335 [net processing] Remove CNodeState.name (John Newbery) Pull request description: We currently have two structures for per-peer data: - `CNode` in net, which should just contain connection layer data (eg socket, send/recv buffers, etc), but currently also contains some application layer data (eg tx/block inventory). - `CNodeState` in net processing, which contains p2p application layer data, but requires cs_main to be locked for access. This PR adds a third struct `Peer`, which is for p2p application layer data, and doesn't require cs_main. Eventually all application layer data from `CNode` should be moved to `Peer`, and any data that doesn't strictly require cs_main should be moved from `CNodeState` to `Peer` (probably all of `CNodeState` eventually). `Peer` objects are stored as shared pointers in a net processing global map `g_peer_map`, which is protected by `g_peer_mutex`. To use a `Peer` object, `g_peer_mutex` is locked, a copy of the shared pointer is taken, and the lock is released. Individual members of `Peer` are protected by different mutexes that guard related data. The lifetime of the `Peer` object is managed by the shared_ptr refcount. This PR adds the `Peer` object and moves the misbehaving data from `CNodeState` to `Peer`. This allows us to immediately remove 15 `LOCK(cs_main)` instances. For more motivation see bitcoin#19398 ACKs for top commit: laanwj: Code review ACK 8e35bf5 troygiorshev: reACK 8e35bf5 via `git range-diff master 9510938 8e35bf5` theuni: ACK 8e35bf5. jonatack: ACK 8e35bf5 keeping in mind Cory's comment (bitcoin#19607 (comment)) for the follow-up Tree-SHA512: ad84a92b78fb34c9f43813ca3dfbc7282c887d55300ea2ce0994d134da3e0c7dbc44d54380e00b13bb75a57c28857ac3236bea9135467075d78026767a19e4b1
2 parents ca30d34 + 8e35bf5 commit 1cf73fb

File tree

4 files changed

+104
-80
lines changed

4 files changed

+104
-80
lines changed

src/net_processing.cpp

+98-62
Original file line numberDiff line numberDiff line change
@@ -278,12 +278,6 @@ struct CNodeState {
278278
const CService address;
279279
//! Whether we have a fully established connection.
280280
bool fCurrentlyConnected;
281-
//! Accumulated misbehaviour score for this peer.
282-
int nMisbehavior;
283-
//! Whether this peer should be disconnected and marked as discouraged (unless it has the noban permission).
284-
bool m_should_discourage;
285-
//! String name of this peer (debugging/logging purposes).
286-
const std::string name;
287281
//! The best known block we know this peer has announced.
288282
const CBlockIndex *pindexBestKnownBlock;
289283
//! The hash of the last unknown block this peer has announced.
@@ -432,13 +426,10 @@ struct CNodeState {
432426
//! Whether this peer relays txs via wtxid
433427
bool m_wtxid_relay{false};
434428

435-
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
436-
address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
437-
m_is_manual_connection (is_manual)
429+
CNodeState(CAddress addrIn, bool is_inbound, bool is_manual)
430+
: address(addrIn), m_is_inbound(is_inbound), m_is_manual_connection(is_manual)
438431
{
439432
fCurrentlyConnected = false;
440-
nMisbehavior = 0;
441-
m_should_discourage = false;
442433
pindexBestKnownBlock = nullptr;
443434
hashLastUnknownBlock.SetNull();
444435
pindexLastCommonBlock = nullptr;
@@ -476,6 +467,50 @@ static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
476467
return &it->second;
477468
}
478469

470+
/**
471+
* Data structure for an individual peer. This struct is not protected by
472+
* cs_main since it does not contain validation-critical data.
473+
*
474+
* Memory is owned by shared pointers and this object is destructed when
475+
* the refcount drops to zero.
476+
*
477+
* TODO: move most members from CNodeState to this structure.
478+
* TODO: move remaining application-layer data members from CNode to this structure.
479+
*/
480+
struct Peer {
481+
/** Same id as the CNode object for this peer */
482+
const NodeId m_id{0};
483+
484+
/** Protects misbehavior data members */
485+
Mutex m_misbehavior_mutex;
486+
/** Accumulated misbehavior score for this peer */
487+
int m_misbehavior_score GUARDED_BY(m_misbehavior_mutex){0};
488+
/** Whether this peer should be disconnected and marked as discouraged (unless it has the noban permission). */
489+
bool m_should_discourage GUARDED_BY(m_misbehavior_mutex){false};
490+
491+
Peer(NodeId id) : m_id(id) {}
492+
};
493+
494+
using PeerRef = std::shared_ptr<Peer>;
495+
496+
/**
497+
* Map of all Peer objects, keyed by peer id. This map is protected
498+
* by the global g_peer_mutex. Once a shared pointer reference is
499+
* taken, the lock may be released. Individual fields are protected by
500+
* their own locks.
501+
*/
502+
Mutex g_peer_mutex;
503+
static std::map<NodeId, PeerRef> g_peer_map GUARDED_BY(g_peer_mutex);
504+
505+
/** Get a shared pointer to the Peer object.
506+
* May return nullptr if the Peer object can't be found. */
507+
static PeerRef GetPeerRef(NodeId id)
508+
{
509+
LOCK(g_peer_mutex);
510+
auto it = g_peer_map.find(id);
511+
return it != g_peer_map.end() ? it->second : nullptr;
512+
}
513+
479514
static void UpdatePreferredDownload(const CNode& node, CNodeState* state) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
480515
{
481516
nPreferredDownload -= state->fPreferredDownload;
@@ -841,7 +876,12 @@ void PeerLogicValidation::InitializeNode(CNode *pnode) {
841876
NodeId nodeid = pnode->GetId();
842877
{
843878
LOCK(cs_main);
844-
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName), pnode->IsInboundConn(), pnode->IsManualConn()));
879+
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, pnode->IsInboundConn(), pnode->IsManualConn()));
880+
}
881+
{
882+
PeerRef peer = std::make_shared<Peer>(nodeid);
883+
LOCK(g_peer_mutex);
884+
g_peer_map.emplace_hint(g_peer_map.end(), nodeid, std::move(peer));
845885
}
846886
if(!pnode->IsInboundConn())
847887
PushNodeVersion(*pnode, m_connman, GetTime());
@@ -870,13 +910,21 @@ void PeerLogicValidation::ReattemptInitialBroadcast(CScheduler& scheduler) const
870910
void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTime) {
871911
fUpdateConnectionTime = false;
872912
LOCK(cs_main);
913+
int misbehavior{0};
914+
{
915+
PeerRef peer = GetPeerRef(nodeid);
916+
assert(peer != nullptr);
917+
misbehavior = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score);
918+
LOCK(g_peer_mutex);
919+
g_peer_map.erase(nodeid);
920+
}
873921
CNodeState *state = State(nodeid);
874922
assert(state != nullptr);
875923

876924
if (state->fSyncStarted)
877925
nSyncStarted--;
878926

879-
if (state->nMisbehavior == 0 && state->fCurrentlyConnected) {
927+
if (misbehavior == 0 && state->fCurrentlyConnected) {
880928
fUpdateConnectionTime = true;
881929
}
882930

@@ -906,17 +954,23 @@ void PeerLogicValidation::FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTim
906954
}
907955

908956
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) {
909-
LOCK(cs_main);
910-
CNodeState *state = State(nodeid);
911-
if (state == nullptr)
912-
return false;
913-
stats.nMisbehavior = state->nMisbehavior;
914-
stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
915-
stats.nCommonHeight = state->pindexLastCommonBlock ? state->pindexLastCommonBlock->nHeight : -1;
916-
for (const QueuedBlock& queue : state->vBlocksInFlight) {
917-
if (queue.pindex)
918-
stats.vHeightInFlight.push_back(queue.pindex->nHeight);
957+
{
958+
LOCK(cs_main);
959+
CNodeState* state = State(nodeid);
960+
if (state == nullptr)
961+
return false;
962+
stats.nSyncHeight = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
963+
stats.nCommonHeight = state->pindexLastCommonBlock ? state->pindexLastCommonBlock->nHeight : -1;
964+
for (const QueuedBlock& queue : state->vBlocksInFlight) {
965+
if (queue.pindex)
966+
stats.vHeightInFlight.push_back(queue.pindex->nHeight);
967+
}
919968
}
969+
970+
PeerRef peer = GetPeerRef(nodeid);
971+
if (peer == nullptr) return false;
972+
stats.m_misbehavior_score = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score);
973+
920974
return true;
921975
}
922976

@@ -1060,21 +1114,21 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans)
10601114
* Increment peer's misbehavior score. If the new value >= DISCOURAGEMENT_THRESHOLD, mark the node
10611115
* to be discouraged, meaning the peer might be disconnected and added to the discouragement filter.
10621116
*/
1063-
void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
1117+
void Misbehaving(const NodeId pnode, const int howmuch, const std::string& message)
10641118
{
10651119
assert(howmuch > 0);
10661120

1067-
CNodeState* const state = State(pnode);
1068-
if (state == nullptr) return;
1121+
PeerRef peer = GetPeerRef(pnode);
1122+
if (peer == nullptr) return;
10691123

1070-
state->nMisbehavior += howmuch;
1124+
LOCK(peer->m_misbehavior_mutex);
1125+
peer->m_misbehavior_score += howmuch;
10711126
const std::string message_prefixed = message.empty() ? "" : (": " + message);
1072-
if (state->nMisbehavior >= DISCOURAGEMENT_THRESHOLD && state->nMisbehavior - howmuch < DISCOURAGEMENT_THRESHOLD)
1073-
{
1074-
LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d) DISCOURAGE THRESHOLD EXCEEDED%s\n", pnode, state->nMisbehavior - howmuch, state->nMisbehavior, message_prefixed);
1075-
state->m_should_discourage = true;
1127+
if (peer->m_misbehavior_score >= DISCOURAGEMENT_THRESHOLD && peer->m_misbehavior_score - howmuch < DISCOURAGEMENT_THRESHOLD) {
1128+
LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d) DISCOURAGE THRESHOLD EXCEEDED%s\n", pnode, peer->m_misbehavior_score - howmuch, peer->m_misbehavior_score, message_prefixed);
1129+
peer->m_should_discourage = true;
10761130
} else {
1077-
LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s\n", pnode, state->nMisbehavior - howmuch, state->nMisbehavior, message_prefixed);
1131+
LogPrint(BCLog::NET, "Misbehaving: peer=%d (%d -> %d)%s\n", pnode, peer->m_misbehavior_score - howmuch, peer->m_misbehavior_score, message_prefixed);
10781132
}
10791133
}
10801134

@@ -1096,7 +1150,6 @@ static bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& s
10961150
case BlockValidationResult::BLOCK_CONSENSUS:
10971151
case BlockValidationResult::BLOCK_MUTATED:
10981152
if (!via_compact_block) {
1099-
LOCK(cs_main);
11001153
Misbehaving(nodeid, 100, message);
11011154
return true;
11021155
}
@@ -1120,18 +1173,12 @@ static bool MaybePunishNodeForBlock(NodeId nodeid, const BlockValidationState& s
11201173
case BlockValidationResult::BLOCK_INVALID_HEADER:
11211174
case BlockValidationResult::BLOCK_CHECKPOINT:
11221175
case BlockValidationResult::BLOCK_INVALID_PREV:
1123-
{
1124-
LOCK(cs_main);
1125-
Misbehaving(nodeid, 100, message);
1126-
}
1176+
Misbehaving(nodeid, 100, message);
11271177
return true;
11281178
// Conflicting (but not necessarily invalid) data or different policy:
11291179
case BlockValidationResult::BLOCK_MISSING_PREV:
1130-
{
1131-
// TODO: Handle this much more gracefully (10 DoS points is super arbitrary)
1132-
LOCK(cs_main);
1133-
Misbehaving(nodeid, 10, message);
1134-
}
1180+
// TODO: Handle this much more gracefully (10 DoS points is super arbitrary)
1181+
Misbehaving(nodeid, 10, message);
11351182
return true;
11361183
case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE:
11371184
case BlockValidationResult::BLOCK_TIME_FUTURE:
@@ -1155,11 +1202,8 @@ static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState& state,
11551202
break;
11561203
// The node is providing invalid data:
11571204
case TxValidationResult::TX_CONSENSUS:
1158-
{
1159-
LOCK(cs_main);
1160-
Misbehaving(nodeid, 100, message);
1161-
return true;
1162-
}
1205+
Misbehaving(nodeid, 100, message);
1206+
return true;
11631207
// Conflicting (but not necessarily invalid) data or different policy:
11641208
case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE:
11651209
case TxValidationResult::TX_INPUTS_NOT_STANDARD:
@@ -1806,7 +1850,6 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac
18061850
BlockTransactions resp(req);
18071851
for (size_t i = 0; i < req.indexes.size(); i++) {
18081852
if (req.indexes[i] >= block.vtx.size()) {
1809-
LOCK(cs_main);
18101853
Misbehaving(pfrom.GetId(), 100, "getblocktxn with out-of-bounds tx indices");
18111854
return;
18121855
}
@@ -2318,7 +2361,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
23182361
// Each connection can only send one version message
23192362
if (pfrom.nVersion != 0)
23202363
{
2321-
LOCK(cs_main);
23222364
Misbehaving(pfrom.GetId(), 1, "redundant version message");
23232365
return;
23242366
}
@@ -2478,7 +2520,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
24782520

24792521
if (pfrom.nVersion == 0) {
24802522
// Must have a version message before anything else
2481-
LOCK(cs_main);
24822523
Misbehaving(pfrom.GetId(), 1, "non-version message before version handshake");
24832524
return;
24842525
}
@@ -2545,7 +2586,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
25452586

25462587
if (!pfrom.fSuccessfullyConnected) {
25472588
// Must have a verack message before anything else
2548-
LOCK(cs_main);
25492589
Misbehaving(pfrom.GetId(), 1, "non-verack message before version handshake");
25502590
return;
25512591
}
@@ -2559,7 +2599,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
25592599
}
25602600
if (vAddr.size() > MAX_ADDR_TO_SEND)
25612601
{
2562-
LOCK(cs_main);
25632602
Misbehaving(pfrom.GetId(), 20, strprintf("addr message size = %u", vAddr.size()));
25642603
return;
25652604
}
@@ -2638,7 +2677,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
26382677
vRecv >> vInv;
26392678
if (vInv.size() > MAX_INV_SZ)
26402679
{
2641-
LOCK(cs_main);
26422680
Misbehaving(pfrom.GetId(), 20, strprintf("inv message size = %u", vInv.size()));
26432681
return;
26442682
}
@@ -2714,7 +2752,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
27142752
vRecv >> vInv;
27152753
if (vInv.size() > MAX_INV_SZ)
27162754
{
2717-
LOCK(cs_main);
27182755
Misbehaving(pfrom.GetId(), 20, strprintf("getdata message size = %u", vInv.size()));
27192756
return;
27202757
}
@@ -3439,7 +3476,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
34393476
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
34403477
unsigned int nCount = ReadCompactSize(vRecv);
34413478
if (nCount > MAX_HEADERS_RESULTS) {
3442-
LOCK(cs_main);
34433479
Misbehaving(pfrom.GetId(), 20, strprintf("headers message size = %u", nCount));
34443480
return;
34453481
}
@@ -3641,7 +3677,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
36413677
if (!filter.IsWithinSizeConstraints())
36423678
{
36433679
// There is no excuse for sending a too-large filter
3644-
LOCK(cs_main);
36453680
Misbehaving(pfrom.GetId(), 100, "too-large bloom filter");
36463681
}
36473682
else if (pfrom.m_tx_relay != nullptr)
@@ -3675,7 +3710,6 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
36753710
}
36763711
}
36773712
if (bad) {
3678-
LOCK(cs_main);
36793713
Misbehaving(pfrom.GetId(), 100, "bad filteradd message");
36803714
}
36813715
return;
@@ -3761,15 +3795,17 @@ void PeerLogicValidation::ProcessMessage(CNode& pfrom, const std::string& msg_ty
37613795
bool PeerLogicValidation::MaybeDiscourageAndDisconnect(CNode& pnode)
37623796
{
37633797
const NodeId peer_id{pnode.GetId()};
3798+
PeerRef peer = GetPeerRef(peer_id);
3799+
if (peer == nullptr) return false;
3800+
37643801
{
3765-
LOCK(cs_main);
3766-
CNodeState& state = *State(peer_id);
3802+
LOCK(peer->m_misbehavior_mutex);
37673803

37683804
// There's nothing to do if the m_should_discourage flag isn't set
3769-
if (!state.m_should_discourage) return false;
3805+
if (!peer->m_should_discourage) return false;
37703806

3771-
state.m_should_discourage = false;
3772-
} // cs_main
3807+
peer->m_should_discourage = false;
3808+
} // peer.m_misbehavior_mutex
37733809

37743810
if (pnode.HasPermission(PF_NOBAN)) {
37753811
// We never disconnect or discourage peers for bad behavior if they have the NOBAN permission flag

src/net_processing.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class PeerLogicValidation final : public CValidationInterface, public NetEventsI
9797
};
9898

9999
struct CNodeStateStats {
100-
int nMisbehavior = 0;
100+
int m_misbehavior_score = 0;
101101
int nSyncHeight = -1;
102102
int nCommonHeight = -1;
103103
std::vector<int> vHeightInFlight;

src/rpc/net.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
197197
if (fStateStats) {
198198
if (IsDeprecatedRPCEnabled("banscore")) {
199199
// banscore is deprecated in v0.21 for removal in v0.22
200-
obj.pushKV("banscore", statestats.nMisbehavior);
200+
obj.pushKV("banscore", statestats.m_misbehavior_score);
201201
}
202202
obj.pushKV("synced_headers", statestats.nSyncHeight);
203203
obj.pushKV("synced_blocks", statestats.nCommonHeight);

src/test/denialofservice_tests.cpp

+4-16
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
232232
peerLogic->InitializeNode(&dummyNode1);
233233
dummyNode1.nVersion = 1;
234234
dummyNode1.fSuccessfullyConnected = true;
235-
{
236-
LOCK(cs_main);
237-
Misbehaving(dummyNode1.GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged
238-
}
235+
Misbehaving(dummyNode1.GetId(), DISCOURAGEMENT_THRESHOLD); // Should be discouraged
239236
{
240237
LOCK(dummyNode1.cs_sendProcessing);
241238
BOOST_CHECK(peerLogic->SendMessages(&dummyNode1));
@@ -249,20 +246,14 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
249246
peerLogic->InitializeNode(&dummyNode2);
250247
dummyNode2.nVersion = 1;
251248
dummyNode2.fSuccessfullyConnected = true;
252-
{
253-
LOCK(cs_main);
254-
Misbehaving(dummyNode2.GetId(), DISCOURAGEMENT_THRESHOLD - 1);
255-
}
249+
Misbehaving(dummyNode2.GetId(), DISCOURAGEMENT_THRESHOLD - 1);
256250
{
257251
LOCK(dummyNode2.cs_sendProcessing);
258252
BOOST_CHECK(peerLogic->SendMessages(&dummyNode2));
259253
}
260254
BOOST_CHECK(!banman->IsDiscouraged(addr2)); // 2 not discouraged yet...
261255
BOOST_CHECK(banman->IsDiscouraged(addr1)); // ... but 1 still should be
262-
{
263-
LOCK(cs_main);
264-
Misbehaving(dummyNode2.GetId(), 1); // 2 reaches discouragement threshold
265-
}
256+
Misbehaving(dummyNode2.GetId(), 1); // 2 reaches discouragement threshold
266257
{
267258
LOCK(dummyNode2.cs_sendProcessing);
268259
BOOST_CHECK(peerLogic->SendMessages(&dummyNode2));
@@ -292,10 +283,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
292283
dummyNode.nVersion = 1;
293284
dummyNode.fSuccessfullyConnected = true;
294285

295-
{
296-
LOCK(cs_main);
297-
Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD);
298-
}
286+
Misbehaving(dummyNode.GetId(), DISCOURAGEMENT_THRESHOLD);
299287
{
300288
LOCK(dummyNode.cs_sendProcessing);
301289
BOOST_CHECK(peerLogic->SendMessages(&dummyNode));

0 commit comments

Comments
 (0)