Skip to content

Commit fa579ee

Browse files
author
MarcoFalke
committed
net: Make stale tip check time type-safe, extend test
* GetTime is the non-type-safe and deprecated version of GetTime<> * Extend test with ASSERT_DEBUG_LOG * Extend test with case where a block is in flight from a node that is about to be evicted
1 parent faee067 commit fa579ee

File tree

9 files changed

+123
-33
lines changed

9 files changed

+123
-33
lines changed

src/net_processing.cpp

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000; // 1ms/head
5050
static constexpr int32_t MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT = 4;
5151
/** Timeout for (unprotected) outbound peers to sync to our chainwork, in seconds */
5252
static constexpr int64_t CHAIN_SYNC_TIMEOUT = 20 * 60; // 20 minutes
53-
/** How frequently to check for stale tips, in seconds */
54-
static constexpr int64_t STALE_CHECK_INTERVAL = 10 * 60; // 10 minutes
55-
/** How frequently to check for extra outbound peers and disconnect, in seconds */
56-
static constexpr int64_t EXTRA_PEER_CHECK_INTERVAL = 45;
57-
/** Minimum time an outbound-peer-eviction candidate must be connected for, in order to evict, in seconds */
58-
static constexpr int64_t MINIMUM_CONNECT_TIME = 30;
53+
/** How frequently to check for stale tips */
54+
static constexpr std::chrono::minutes STALE_CHECK_INTERVAL{10};
55+
/** How frequently to check for extra outbound peers and disconnect */
56+
static constexpr std::chrono::seconds EXTRA_PEER_CHECK_INTERVAL{45};
57+
/** Minimum time an outbound-peer-eviction candidate must be connected for, in order to evict */
58+
static constexpr std::chrono::seconds MINIMUM_CONNECT_TIME{30};
5959
/** SHA256("main address relay")[0:8] */
6060
static constexpr uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL;
6161
/// Age after which a stale block will no longer be served if requested as
@@ -177,7 +177,7 @@ namespace {
177177
int g_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0;
178178

179179
/** When our tip was last updated. */
180-
std::atomic<int64_t> g_last_tip_update(0);
180+
std::atomic<std::chrono::seconds> g_last_tip_update{std::chrono::seconds{0}};
181181

182182
/** Relay map */
183183
typedef std::map<uint256, CTransactionRef> MapRelay;
@@ -578,10 +578,10 @@ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connma
578578
static bool TipMayBeStale(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
579579
{
580580
AssertLockHeld(cs_main);
581-
if (g_last_tip_update == 0) {
582-
g_last_tip_update = GetTime();
581+
if (g_last_tip_update.load().count() == 0) {
582+
g_last_tip_update = GetTime<std::chrono::seconds>();
583583
}
584-
return g_last_tip_update < GetTime() - consensusParams.nPowTargetSpacing * 3 && mapBlocksInFlight.empty();
584+
return g_last_tip_update.load() < GetTime<std::chrono::seconds>() - std::chrono::seconds{consensusParams.nPowTargetSpacing} * 3 && mapBlocksInFlight.empty();
585585
}
586586

587587
static bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
@@ -758,6 +758,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds)
758758
LOCK(cs_main);
759759
CNodeState *state = State(node);
760760
if (state) state->m_last_block_announcement = time_in_seconds;
761+
if (state) state->fHaveWitness = true;
761762
}
762763

763764
// Returns true for outbound peers, excluding manual connections, feelers, and
@@ -1127,7 +1128,7 @@ PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CS
11271128
// combine them in one function and schedule at the quicker (peer-eviction)
11281129
// timer.
11291130
static_assert(EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, "peer eviction timer should be less than stale tip check timer");
1130-
scheduler.scheduleEvery([this, consensusParams] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL});
1131+
scheduler.scheduleEvery([this, consensusParams] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, EXTRA_PEER_CHECK_INTERVAL);
11311132
}
11321133

11331134
/**
@@ -1165,7 +1166,7 @@ void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pb
11651166
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
11661167
}
11671168

1168-
g_last_tip_update = GetTime();
1169+
g_last_tip_update = GetTime<std::chrono::seconds>();
11691170
}
11701171
{
11711172
LOCK(g_cs_recent_confirmed_transactions);
@@ -3430,7 +3431,7 @@ void PeerLogicValidation::ConsiderEviction(CNode *pto, int64_t time_in_seconds)
34303431
}
34313432
}
34323433

3433-
void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
3434+
void PeerLogicValidation::EvictExtraOutboundPeers(std::chrono::seconds time)
34343435
{
34353436
// Check whether we have too many outbound peers
34363437
int extra_peers = connman->GetExtraOutboundCount();
@@ -3467,8 +3468,8 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
34673468
// it time for new information to have arrived.
34683469
// Also don't disconnect any peer we're trying to download a
34693470
// block from.
3470-
CNodeState &state = *State(pnode->GetId());
3471-
if (time_in_seconds - pnode->nTimeConnected > MINIMUM_CONNECT_TIME && state.nBlocksInFlight == 0) {
3471+
CNodeState& state = *State(pnode->GetId());
3472+
if (time - std::chrono::seconds{pnode->nTimeConnected} > MINIMUM_CONNECT_TIME && state.nBlocksInFlight == 0) {
34723473
LogPrint(BCLog::NET, "disconnecting extra outbound peer=%d (last block announcement received at time %d)\n", pnode->GetId(), oldest_block_announcement);
34733474
pnode->fDisconnect = true;
34743475
return true;
@@ -3495,20 +3496,20 @@ void PeerLogicValidation::CheckForStaleTipAndEvictPeers(const Consensus::Params
34953496

34963497
if (connman == nullptr) return;
34973498

3498-
int64_t time_in_seconds = GetTime();
3499+
const auto time = GetTime<std::chrono::seconds>();
34993500

3500-
EvictExtraOutboundPeers(time_in_seconds);
3501+
EvictExtraOutboundPeers(time);
35013502

3502-
if (time_in_seconds > m_stale_tip_check_time) {
3503+
if (time > m_stale_tip_check_time) {
35033504
// Check whether our tip is stale, and if so, allow using an extra
35043505
// outbound peer
35053506
if (!fImporting && !fReindex && connman->GetNetworkActive() && connman->GetUseAddrmanOutgoing() && TipMayBeStale(consensusParams)) {
3506-
LogPrintf("Potential stale tip detected, will try using extra outbound peer (last tip update: %d seconds ago)\n", time_in_seconds - g_last_tip_update);
3507+
LogPrintf("Potential stale tip detected, will try using extra outbound peer (last tip update: %d seconds ago)\n", count_seconds(time - g_last_tip_update.load()));
35073508
connman->SetTryNewOutboundPeer(true);
35083509
} else if (connman->GetTryNewOutboundPeer()) {
35093510
connman->SetTryNewOutboundPeer(false);
35103511
}
3511-
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
3512+
m_stale_tip_check_time = time + STALE_CHECK_INTERVAL;
35123513
}
35133514
}
35143515

src/net_processing.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ class PeerLogicValidation final : public CValidationInterface, public NetEventsI
7575
/** Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound */
7676
void CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams);
7777
/** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */
78-
void EvictExtraOutboundPeers(int64_t time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
78+
void EvictExtraOutboundPeers(std::chrono::seconds time) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
7979

8080
private:
81-
int64_t m_stale_tip_check_time; //!< Next time to check for stale tip
81+
std::chrono::seconds m_stale_tip_check_time; //!< Next time to check for stale tip
8282
};
8383

8484
struct CNodeStateStats {

src/test/denialofservice_tests.cpp

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88
#include <chainparams.h>
99
#include <net.h>
1010
#include <net_processing.h>
11+
#include <netmessagemaker.h>
12+
#include <pow.h>
1113
#include <script/sign.h>
1214
#include <script/signingprovider.h>
1315
#include <script/standard.h>
1416
#include <serialize.h>
17+
#include <test/util/logging.h>
18+
#include <test/util/mining.h>
1519
#include <test/util/net.h>
1620
#include <test/util/setup_common.h>
21+
#include <test/util/wallet.h>
1722
#include <util/memory.h>
1823
#include <util/string.h>
1924
#include <util/system.h>
@@ -46,7 +51,7 @@ static CService ip(uint32_t i)
4651

4752
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds);
4853

49-
BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
54+
BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, RegTestingSetup)
5055

5156
// Test eviction of an outbound peer whose chain never advances
5257
// Mock a node connection, and use mocktime to simulate a peer
@@ -140,6 +145,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
140145
options.nMaxConnections = 125;
141146
options.m_max_outbound_full_relay = max_outbound_full_relay;
142147
options.nMaxFeeler = 1;
148+
options.m_msgproc = peerLogic.get();
143149

144150
connman->Init(options);
145151
std::vector<CNode *> vNodes;
@@ -156,11 +162,16 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
156162
BOOST_CHECK(node->fDisconnect == false);
157163
}
158164

159-
SetMockTime(GetTime() + 3*consensusParams.nPowTargetSpacing + 1);
165+
auto mocked_time = GetTime() + 3 * consensusParams.nPowTargetSpacing + 1;
166+
SetMockTime(mocked_time);
160167

161168
// Now tip should definitely be stale, and we should look for an extra
162169
// outbound peer
163-
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
170+
{
171+
ASSERT_DEBUG_LOG("Potential stale tip detected, will try using extra outbound peer (last tip update: 1801 seconds ago)");
172+
ASSERT_DEBUG_LOG("net: setting try another outbound peer=true");
173+
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
174+
}
164175
BOOST_CHECK(connman->GetTryNewOutboundPeer());
165176

166177
// Still no peers should be marked for disconnection
@@ -173,7 +184,11 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
173184
// required time connected check should be satisfied).
174185
AddRandomOutboundPeer(vNodes, *peerLogic, *connman);
175186

176-
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
187+
{
188+
ASSERT_DEBUG_LOG("disconnecting extra outbound peer=8 (last block announcement received at time 0)");
189+
ASSERT_DEBUG_LOG("net: setting try another outbound peer=false");
190+
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
191+
}
177192
for (int i=0; i<max_outbound_full_relay; ++i) {
178193
BOOST_CHECK(vNodes[i]->fDisconnect == false);
179194
}
@@ -186,13 +201,64 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
186201
// peer, and check that the next newest node gets evicted.
187202
UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime());
188203

189-
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
204+
{
205+
ASSERT_DEBUG_LOG("disconnecting extra outbound peer=7 (last block announcement received at time 0)");
206+
ASSERT_DEBUG_LOG("net: setting try another outbound peer=false");
207+
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
208+
}
190209
for (int i=0; i<max_outbound_full_relay-1; ++i) {
191210
BOOST_CHECK(vNodes[i]->fDisconnect == false);
192211
}
193212
BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true);
194213
BOOST_CHECK(vNodes.back()->fDisconnect == false);
195214

215+
vNodes[max_outbound_full_relay-1]->fDisconnect = false;
216+
UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), 0);
217+
218+
// Set CanDirectFetch() to true by generating a block
219+
{
220+
auto block = PrepareBlock(m_node, CScript{OP_TRUE});
221+
block->nTime = mocked_time;
222+
SolvePow(*block);
223+
ProcessNewBlock(Params(), block, true, nullptr);
224+
}
225+
// Last node pretends to send a block
226+
std::vector<CBlock> headers;
227+
{
228+
{
229+
LOCK(cs_main);
230+
const auto tip = ::ChainActive().Tip();
231+
CBlock dummy{tip->GetBlockHeader()};
232+
dummy.hashPrevBlock = tip->GetBlockHash();
233+
dummy.nTime = mocked_time + 1;
234+
SolvePow(dummy);
235+
236+
headers.emplace_back(dummy);
237+
}
238+
239+
connman->ReceiveMsgFrom(*vNodes.back(), NetMsgType::HEADERS, headers);
240+
241+
vNodes.back()->fPauseSend = false;
242+
}
243+
{
244+
LogPrintTest("Check that node is protected when it pretends to have a block");
245+
LOCK2(cs_main, vNodes.back()->cs_sendProcessing);
246+
ASSERT_DEBUG_LOG("received: headers (82 bytes) peer=8");
247+
ASSERT_DEBUG_LOG("sending getdata (37 bytes) peer=8");
248+
ASSERT_DEBUG_LOG("Requesting block " + headers.front().GetHash().ToString() + " from peer=8");
249+
ASSERT_DEBUG_LOG("Protecting outbound peer=8 from eviction");
250+
connman->ProcessMessagesOnce(*vNodes.back());
251+
}
252+
{
253+
ASSERT_DEBUG_LOG("disconnecting extra outbound peer=7 (last block announcement received at time 0)");
254+
ASSERT_DEBUG_LOG("net: setting try another outbound peer=false");
255+
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
256+
}
257+
for (int i = 0; i < max_outbound_full_relay; ++i) {
258+
const bool is_8th{i == 7};
259+
BOOST_CHECK(vNodes[i]->fDisconnect == is_8th);
260+
}
261+
196262
bool dummy;
197263
for (const CNode *node : vNodes) {
198264
peerLogic->FinalizeNode(node->GetId(), dummy);

src/test/util/logging.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include <stdexcept>
1313

14+
void LogPrintTest(const std::string& info) { LogPrintf("[test] %s\n", info); }
15+
1416
DebugLogHelper::DebugLogHelper(std::string message)
1517
: m_message{std::move(message)}
1618
{

src/test/util/logging.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#include <list>
1212
#include <string>
1313

14+
/** Insert log from a test */
15+
void LogPrintTest(const std::string& info);
16+
1417
class DebugLogHelper
1518
{
1619
const std::string m_message;

src/test/util/mining.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ CTxIn generatetoaddress(const NodeContext& node, const std::string& address)
2222
return MineBlock(node, coinbase_script);
2323
}
2424

25-
CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
25+
void SolvePow(CBlock& block)
2626
{
27-
auto block = PrepareBlock(node, coinbase_scriptPubKey);
28-
29-
while (!CheckProofOfWork(block->GetHash(), block->nBits, Params().GetConsensus())) {
30-
++block->nNonce;
31-
assert(block->nNonce);
27+
while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) {
28+
++block.nNonce;
29+
assert(block.nNonce);
3230
}
31+
}
3332

33+
CTxIn MineBlock(const NodeContext& node, const CScript& coinbase_scriptPubKey)
34+
{
35+
auto block = PrepareBlock(node, coinbase_scriptPubKey);
36+
SolvePow(*block);
3437
bool processed{ProcessNewBlock(Params(), block, true, nullptr)};
3538
assert(processed);
3639

src/test/util/mining.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class CScript;
1313
class CTxIn;
1414
struct NodeContext;
1515

16+
/** Work on the proof-of-work puzzle */
17+
void SolvePow(CBlock& block);
18+
1619
/** Returns the generated coin */
1720
CTxIn MineBlock(const NodeContext&, const CScript& coinbase_scriptPubKey);
1821

src/test/util/net.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_TEST_UTIL_NET_H
77

88
#include <net.h>
9+
#include <netmessagemaker.h>
910

1011
struct ConnmanTestMsg : public CConnman {
1112
using CConnman::CConnman;
@@ -28,6 +29,15 @@ struct ConnmanTestMsg : public CConnman {
2829
void NodeReceiveMsgBytes(CNode& node, const char* pch, unsigned int nBytes, bool& complete) const;
2930

3031
bool ReceiveMsgFrom(CNode& node, CSerializedNetMsg& ser_msg) const;
32+
33+
template <typename P>
34+
void ReceiveMsgFrom(CNode& node, const std::string& type, const P& payload)
35+
{
36+
const CNetMsgMaker msg_maker(PROTOCOL_VERSION);
37+
CSerializedNetMsg m_ser = msg_maker.Make(type.c_str(), payload);
38+
39+
assert(ReceiveMsgFrom(node, m_ser));
40+
}
3141
};
3242

3343
#endif // BITCOIN_TEST_UTIL_NET_H

src/test/util/setup_common.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName)
7272
SelectParams(chainName);
7373
SeedInsecureRand();
7474
gArgs.ForceSetArg("-printtoconsole", "0");
75+
gArgs.ForceSetArg("-debug", "1");
7576
if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN);
7677
InitLogging();
78+
AppInitParameterInteraction();
7779
LogInstance().StartLogging();
7880
SHA256AutoDetect();
7981
ECC_Start();

0 commit comments

Comments
 (0)