diff --git a/src/avalanche.h b/src/avalanche.h --- a/src/avalanche.h +++ b/src/avalanche.h @@ -33,6 +33,10 @@ * Finalization score. */ static int AVALANCHE_FINALIZATION_SCORE = 128; +/** + * How long before we consider that a query timed out. + */ +static int AVALANCHE_DEFAULT_QUERY_TIMEOUT_DURATION_MILLISECONDS = 10000; /** * Special NodeId that represent no node. */ @@ -214,6 +218,7 @@ class AvalancheProcessor { private: CConnman *connman; + std::chrono::milliseconds queryTimeoutDuration; /** * Blocks to run avalanche on. @@ -291,9 +296,16 @@ public: AvalancheProcessor(CConnman *connmanIn) - : connman(connmanIn), round(0), stopRequest(false), running(false) {} + : connman(connmanIn), + queryTimeoutDuration( + AVALANCHE_DEFAULT_QUERY_TIMEOUT_DURATION_MILLISECONDS), + round(0), stopRequest(false), running(false) {} ~AvalancheProcessor() { stopEventLoop(); } + void setQueryTimeoutDuration(std::chrono::milliseconds d) { + queryTimeoutDuration = d; + } + bool addBlockToReconcile(const CBlockIndex *pindex); bool isAccepted(const CBlockIndex *pindex) const; diff --git a/src/avalanche.cpp b/src/avalanche.cpp --- a/src/avalanche.cpp +++ b/src/avalanche.cpp @@ -283,6 +283,17 @@ } void AvalancheProcessor::runEventLoop() { + auto now = std::chrono::steady_clock::now(); + + { + // Clear expired requests. + auto w = queries.getWriteView(); + auto it = w->get().begin(); + while (it != w->get().end() && it->timeout < now) { + w->get().erase(it++); + } + } + std::vector invs = getInvsForNextPoll(); if (invs.empty()) { // If there are no invs to poll, we are done. @@ -306,7 +317,7 @@ { // Compute the time at which this requests times out. auto timeout = - std::chrono::steady_clock::now() + std::chrono::seconds(10); + std::chrono::steady_clock::now() + queryTimeoutDuration; // Register the query. queries.getWriteView()->insert( {pnode->GetId(), current_round, timeout, invs}); diff --git a/src/test/avalanche_tests.cpp b/src/test/avalanche_tests.cpp --- a/src/test/avalanche_tests.cpp +++ b/src/test/avalanche_tests.cpp @@ -21,7 +21,7 @@ return p.getSuitableNodeToQuery(); } - static uint32_t getRound(const AvalancheProcessor &p) { return p.round; } + static uint64_t getRound(const AvalancheProcessor &p) { return p.round; } }; BOOST_FIXTURE_TEST_SUITE(avalanche_tests, TestChain100Setup) @@ -506,6 +506,26 @@ BOOST_CHECK_EQUAL(updates.size(), 0); BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); + // Expire requests after some time. + p.setQueryTimeoutDuration(std::chrono::milliseconds(10)); + for (int i = 0; i < 10; i++) { + resp = {AvalancheTest::getRound(p), 0, {AvalancheVote(0, blockHash)}}; + AvalancheTest::runEventLoop(p); + // NB: This could wait longer than 1ms in some cases and make the + // test flacky. We'll have to come up with a better solution to test + // this if that were to be the case. I never was able to trigger this + // myself, so it's probably good enough. + boost::this_thread::sleep_for(boost::chrono::milliseconds(1)); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(p.registerVotes(avanodeid, next(resp), updates)); + + // Now try again but wait. + AvalancheTest::runEventLoop(p); + boost::this_thread::sleep_for(boost::chrono::milliseconds(10)); + AvalancheTest::runEventLoop(p); + BOOST_CHECK(!p.registerVotes(avanodeid, next(resp), updates)); + } + CConnmanTest::ClearNodes(); }