Changeset View
Changeset View
Standalone View
Standalone View
src/avalanche/test/peermanager_tests.cpp
// Copyright (c) 2020 The Bitcoin developers | // Copyright (c) 2020 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <avalanche/delegationbuilder.h> | #include <avalanche/delegationbuilder.h> | ||||
#include <avalanche/peermanager.h> | #include <avalanche/peermanager.h> | ||||
#include <avalanche/proofbuilder.h> | #include <avalanche/proofbuilder.h> | ||||
#include <avalanche/proofcomparator.h> | #include <avalanche/proofcomparator.h> | ||||
#include <avalanche/statistics.h> | |||||
#include <avalanche/test/util.h> | #include <avalanche/test/util.h> | ||||
#include <config.h> | #include <config.h> | ||||
#include <script/standard.h> | #include <script/standard.h> | ||||
#include <util/time.h> | #include <util/time.h> | ||||
#include <util/translation.h> | #include <util/translation.h> | ||||
#include <validation.h> | #include <validation.h> | ||||
#include <test/util/setup_common.h> | #include <test/util/setup_common.h> | ||||
▲ Show 20 Lines • Show All 2,068 Lines • ▼ Show 20 Lines | BOOST_AUTO_TEST_CASE(proof_expiry) { | ||||
BOOST_CHECK(!pm.exists(proofToExpire->getId())); | BOOST_CHECK(!pm.exists(proofToExpire->getId())); | ||||
// The conflicting proof has been pulled back to the valid pool | // The conflicting proof has been pulled back to the valid pool | ||||
BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId())); | BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId())); | ||||
gArgs.ClearForcedArg("-avalancheconflictingproofcooldown"); | gArgs.ClearForcedArg("-avalancheconflictingproofcooldown"); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(peer_availability_score) { | |||||
ChainstateManager &chainman = *Assert(m_node.chainman); | |||||
avalanche::PeerManager pm(PROOF_DUST_THRESHOLD, chainman); | |||||
Chainstate &active_chainstate = chainman.ActiveChainstate(); | |||||
const std::vector<std::tuple<uint32_t, uint32_t, double>> testCases = { | |||||
// {step, tau, decay_factor} | |||||
{10, 100, 1. - std::exp(-1. * 10 / 100)}, | |||||
// Current defaults | |||||
{AVALANCHE_STATISTICS_REFRESH_PERIOD.count(), | |||||
AVALANCHE_STATISTICS_TIME_CONSTANT.count(), | |||||
AVALANCHE_STATISTICS_DECAY_FACTOR}, | |||||
}; | |||||
for (const auto &[step, tau, decayFactor] : testCases) { | |||||
// Add a peer | |||||
auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE); | |||||
BOOST_CHECK(pm.registerProof(proof)); | |||||
auto proofid = proof->getId(); | |||||
// Add some nodes for this peer | |||||
const int numNodesPerPeer = 5; | |||||
for (auto nodeid = 0; nodeid < numNodesPerPeer; nodeid++) { | |||||
BOOST_CHECK(pm.addNode(nodeid, proofid)); | |||||
} | |||||
auto getNodeAvailabilityScore = [&](double avgScore, | |||||
NodeId nodeid) -> double { | |||||
// Spread scores over a range of values such that their average is | |||||
// the provided value. | |||||
return (nodeid - numNodesPerPeer / 2) * 2 + avgScore; | |||||
}; | |||||
auto getAvailabilityScore = [&]() { | |||||
double score{0.0}; | |||||
pm.forPeer(proofid, [&](auto &peer) { | |||||
score = peer.availabilityScore; | |||||
return true; | |||||
}); | |||||
return score; | |||||
}; | |||||
double previousScore = getAvailabilityScore(); | |||||
BOOST_CHECK_SMALL(previousScore, 1e-6); | |||||
// Check the statistics follow an exponential response for 1 to 10 tau | |||||
for (size_t i = 1; i <= 10; i++) { | |||||
for (uint32_t j = 0; j < tau; j += step) { | |||||
// Nodes respond to polls > 50% of the time (positive score) | |||||
pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) { | |||||
return getNodeAvailabilityScore(1.0, nodeid); | |||||
}); | |||||
// Expect a monotonic rise | |||||
double currentScore = getAvailabilityScore(); | |||||
BOOST_CHECK_GE(currentScore, previousScore); | |||||
previousScore = currentScore; | |||||
} | |||||
// We expect (1 - e^-i) after i * tau. The tolerance is expressed | |||||
// as a percentage, and we add a (large) 0.1% margin to account for | |||||
// floating point errors. | |||||
BOOST_CHECK_CLOSE(previousScore, -1 * std::expm1(-1. * i), | |||||
100.1 / tau); | |||||
} | |||||
// After 10 tau we should be very close to 100% (about 99.995%) | |||||
BOOST_CHECK_CLOSE(previousScore, 1., 0.01); | |||||
// Make the proof invalid | |||||
BOOST_CHECK(pm.rejectProof( | |||||
proofid, avalanche::PeerManager::RejectionMode::INVALIDATE)); | |||||
BOOST_CHECK(!pm.isBoundToPeer(proofid)); | |||||
BOOST_CHECK(!pm.exists(proofid)); | |||||
// Re-register the proof | |||||
BOOST_CHECK(pm.registerProof(proof)); | |||||
pm.forPeer(proofid, [&](auto &peer) { | |||||
int nodeCount = 0; | |||||
pm.forEachNode(peer, [&](const auto &node) { nodeCount++; }); | |||||
BOOST_CHECK_EQUAL(nodeCount, numNodesPerPeer); | |||||
return true; | |||||
}); | |||||
// Peer score should have reset even though nodes are still connected | |||||
previousScore = getAvailabilityScore(); | |||||
BOOST_CHECK_SMALL(previousScore, 1e-6); | |||||
// Bring the score back up to where we were | |||||
for (size_t i = 1; i <= 10; i++) { | |||||
for (uint32_t j = 0; j < tau; j += step) { | |||||
pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) { | |||||
return getNodeAvailabilityScore(1.0, nodeid); | |||||
}); | |||||
} | |||||
} | |||||
previousScore = getAvailabilityScore(); | |||||
BOOST_CHECK_CLOSE(previousScore, 1., 0.01); | |||||
for (size_t i = 1; i <= 3; i++) { | |||||
for (uint32_t j = 0; j < tau; j += step) { | |||||
// Nodes only respond to polls 50% of the time (0 score) | |||||
pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) { | |||||
return getNodeAvailabilityScore(0.0, nodeid); | |||||
}); | |||||
// Expect a monotonic fall | |||||
double currentScore = getAvailabilityScore(); | |||||
BOOST_CHECK_LE(currentScore, previousScore); | |||||
previousScore = currentScore; | |||||
} | |||||
// There is a slight error in the expected value because we did not | |||||
// start the decay at exactly 100%, but the 0.1% margin is at least | |||||
// an order of magnitude larger than the expected error so it | |||||
// doesn't matter. | |||||
BOOST_CHECK_CLOSE(previousScore, 1. + std::expm1(-1. * i), | |||||
100.1 / tau); | |||||
} | |||||
// After 3 more tau we should be under 5% | |||||
BOOST_CHECK_LT(previousScore, .05); | |||||
for (size_t i = 1; i <= 100; i++) { | |||||
// Nodes respond to polls < 50% of the time (negative score) | |||||
pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) { | |||||
return getNodeAvailabilityScore(-10.0, nodeid); | |||||
}); | |||||
// It's still a monotonic fall, and the score should turn negative. | |||||
double currentScore = getAvailabilityScore(); | |||||
BOOST_CHECK_LE(currentScore, previousScore); | |||||
BOOST_CHECK_LE(currentScore, 0.); | |||||
previousScore = currentScore; | |||||
} | |||||
} | |||||
} | |||||
BOOST_AUTO_TEST_SUITE_END() | BOOST_AUTO_TEST_SUITE_END() |