Changeset View
Changeset View
Standalone View
Standalone View
src/avalanche/test/processor_tests.cpp
Show First 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | CService ip(uint32_t i) { | ||||
s.s_addr = i; | s.s_addr = i; | ||||
return CService(CNetAddr(s), Params().GetDefaultPort()); | return CService(CNetAddr(s), Params().GetDefaultPort()); | ||||
} | } | ||||
struct AvalancheTestingSetup : public TestChain100Setup { | struct AvalancheTestingSetup : public TestChain100Setup { | ||||
const Config &config; | const Config &config; | ||||
CConnmanTest *m_connman; | CConnmanTest *m_connman; | ||||
std::unique_ptr<Processor> m_processor; | |||||
CKey masterpriv; | CKey masterpriv; | ||||
AvalancheTestingSetup() | AvalancheTestingSetup() | ||||
: TestChain100Setup(), config(GetConfig()), masterpriv() { | : TestChain100Setup(), config(GetConfig()), masterpriv() { | ||||
// Deterministic randomness for tests. | // Deterministic randomness for tests. | ||||
auto connman = std::make_unique<CConnmanTest>(config, 0x1337, 0x1337); | auto connman = std::make_unique<CConnmanTest>(config, 0x1337, 0x1337); | ||||
m_connman = connman.get(); | m_connman = connman.get(); | ||||
m_node.connman = std::move(connman); | m_node.connman = std::move(connman); | ||||
m_node.peer_logic = std::make_unique<PeerLogicValidation>( | m_node.peer_logic = std::make_unique<PeerLogicValidation>( | ||||
m_connman, m_node.banman.get(), *m_node.scheduler, | m_connman, m_node.banman.get(), *m_node.scheduler, | ||||
*m_node.chainman); | *m_node.chainman); | ||||
m_node.chain = interfaces::MakeChain(m_node, config.GetChainParams()); | m_node.chain = interfaces::MakeChain(m_node, config.GetChainParams()); | ||||
// Get the processor ready. | |||||
m_processor = | |||||
std::make_unique<Processor>(*m_node.chain, m_node.connman.get()); | |||||
// The master private key we delegate to. | // The master private key we delegate to. | ||||
masterpriv.MakeNewKey(true); | masterpriv.MakeNewKey(true); | ||||
} | } | ||||
~AvalancheTestingSetup() { m_connman->ClearNodes(); } | ~AvalancheTestingSetup() { m_connman->ClearNodes(); } | ||||
CNode *ConnectNode(ServiceFlags nServices) { | CNode *ConnectNode(ServiceFlags nServices) { | ||||
static NodeId id = 0; | static NodeId id = 0; | ||||
Show All 18 Lines | Proof GetProof() { | ||||
const CTransaction &coinbase = *m_coinbase_txns[current_coinbase]; | const CTransaction &coinbase = *m_coinbase_txns[current_coinbase]; | ||||
ProofBuilder pb(0, 0, masterpriv.GetPubKey()); | ProofBuilder pb(0, 0, masterpriv.GetPubKey()); | ||||
BOOST_CHECK(pb.addUTXO(COutPoint(coinbase.GetId(), 0), | BOOST_CHECK(pb.addUTXO(COutPoint(coinbase.GetId(), 0), | ||||
coinbase.vout[0].nValue, current_coinbase + 1, | coinbase.vout[0].nValue, current_coinbase + 1, | ||||
true, coinbaseKey)); | true, coinbaseKey)); | ||||
return pb.build(); | return pb.build(); | ||||
} | } | ||||
std::array<CNode *, 8> ConnectNodes(Processor &p) { | std::array<CNode *, 8> ConnectNodes() { | ||||
PeerManager &pm = AvalancheTest::getPeerManager(p); | PeerManager &pm = getPeerManager(); | ||||
Proof proof = GetProof(); | Proof proof = GetProof(); | ||||
std::array<CNode *, 8> nodes; | std::array<CNode *, 8> nodes; | ||||
for (CNode *&n : nodes) { | for (CNode *&n : nodes) { | ||||
n = ConnectNode(NODE_AVALANCHE); | n = ConnectNode(NODE_AVALANCHE); | ||||
BOOST_CHECK(pm.addNode(n->GetId(), proof, masterpriv.GetPubKey())); | BOOST_CHECK(pm.addNode(n->GetId(), proof, masterpriv.GetPubKey())); | ||||
} | } | ||||
return nodes; | return nodes; | ||||
} | } | ||||
void runEventLoop() { AvalancheTest::runEventLoop(*m_processor); } | |||||
NodeId getSuitableNodeToQuery() { | |||||
return AvalancheTest::getSuitableNodeToQuery(*m_processor); | |||||
} | |||||
std::vector<CInv> getInvsForNextPoll() { | |||||
return AvalancheTest::getInvsForNextPoll(*m_processor); | |||||
} | |||||
PeerManager &getPeerManager() { | |||||
return AvalancheTest::getPeerManager(*m_processor); | |||||
} | |||||
uint64_t getRound() const { return AvalancheTest::getRound(*m_processor); } | |||||
}; | }; | ||||
} // namespace | } // namespace | ||||
BOOST_FIXTURE_TEST_SUITE(processor_tests, AvalancheTestingSetup) | BOOST_FIXTURE_TEST_SUITE(processor_tests, AvalancheTestingSetup) | ||||
#define REGISTER_VOTE_AND_CHECK(vr, vote, state, finalized, confidence) \ | #define REGISTER_VOTE_AND_CHECK(vr, vote, state, finalized, confidence) \ | ||||
vr.registerVote(NO_NODE, vote); \ | vr.registerVote(NO_NODE, vote); \ | ||||
BOOST_CHECK_EQUAL(vr.isAccepted(), state); \ | BOOST_CHECK_EQUAL(vr.isAccepted(), state); \ | ||||
▲ Show 20 Lines • Show All 122 Lines • ▼ Show 20 Lines | |||||
Response next(Response &r) { | Response next(Response &r) { | ||||
auto copy = r; | auto copy = r; | ||||
r = {r.getRound() + 1, r.getCooldown(), r.GetVotes()}; | r = {r.getRound() + 1, r.getCooldown(), r.GetVotes()}; | ||||
return copy; | return copy; | ||||
} | } | ||||
} // namespace | } // namespace | ||||
BOOST_AUTO_TEST_CASE(block_register) { | BOOST_AUTO_TEST_CASE(block_register) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
CBlock block = CreateAndProcessBlock({}, CScript()); | CBlock block = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash = block.GetHash(); | const BlockHash blockHash = block.GetHash(); | ||||
const CBlockIndex *pindex; | const CBlockIndex *pindex; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex = LookupBlockIndex(blockHash); | pindex = LookupBlockIndex(blockHash); | ||||
} | } | ||||
// Create nodes that supports avalanche. | // Create nodes that supports avalanche. | ||||
auto avanodes = ConnectNodes(p); | auto avanodes = ConnectNodes(); | ||||
// Querying for random block returns false. | // Querying for random block returns false. | ||||
BOOST_CHECK(!p.isAccepted(pindex)); | BOOST_CHECK(!m_processor->isAccepted(pindex)); | ||||
// Add a new block. Check it is added to the polls. | // Add a new block. Check it is added to the polls. | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
auto invs = AvalancheTest::getInvsForNextPoll(p); | auto invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
// Newly added blocks' state reflect the blockchain. | // Newly added blocks' state reflect the blockchain. | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
int nextNodeIndex = 0; | int nextNodeIndex = 0; | ||||
auto registerNewVote = [&](const Response &resp) { | auto registerNewVote = [&](const Response &resp) { | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId(); | auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId(); | ||||
BOOST_CHECK(p.registerVotes(nodeid, resp, updates)); | BOOST_CHECK(m_processor->registerVotes(nodeid, resp, updates)); | ||||
}; | }; | ||||
// Let's vote for this block a few times. | // Let's vote for this block a few times. | ||||
Response resp{0, 0, {Vote(0, blockHash)}}; | Response resp{0, 0, {Vote(0, blockHash)}}; | ||||
for (int i = 0; i < 6; i++) { | for (int i = 0; i < 6; i++) { | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 0); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), 0); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// A single neutral vote do not change anything. | // A single neutral vote do not change anything. | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote(-1, blockHash)}}; | resp = {getRound(), 0, {Vote(-1, blockHash)}}; | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 0); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), 0); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}}; | resp = {getRound(), 0, {Vote(0, blockHash)}}; | ||||
for (int i = 1; i < 7; i++) { | for (int i = 1; i < 7; i++) { | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), i); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), i); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// Two neutral votes will stall progress. | // Two neutral votes will stall progress. | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote(-1, blockHash)}}; | resp = {getRound(), 0, {Vote(-1, blockHash)}}; | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 6); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), 6); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 6); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), 6); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}}; | resp = {getRound(), 0, {Vote(0, blockHash)}}; | ||||
for (int i = 2; i < 8; i++) { | for (int i = 2; i < 8; i++) { | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 6); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), 6); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// We vote for it numerous times to finalize it. | // We vote for it numerous times to finalize it. | ||||
for (int i = 7; i < AVALANCHE_FINALIZATION_SCORE; i++) { | for (int i = 7; i < AVALANCHE_FINALIZATION_SCORE; i++) { | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), i); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), i); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// As long as it is not finalized, we poll. | // As long as it is not finalized, we poll. | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
// Now finalize the decision. | // Now finalize the decision. | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 1); | BOOST_CHECK_EQUAL(updates.size(), 1); | ||||
BOOST_CHECK(updates[0].getBlockIndex() == pindex); | BOOST_CHECK(updates[0].getBlockIndex() == pindex); | ||||
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized); | BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized); | ||||
updates = {}; | updates = {}; | ||||
// Once the decision is finalized, there is no poll for it. | // Once the decision is finalized, there is no poll for it. | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 0); | BOOST_CHECK_EQUAL(invs.size(), 0); | ||||
// Now let's undo this and finalize rejection. | // Now let's undo this and finalize rejection. | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote(1, blockHash)}}; | resp = {getRound(), 0, {Vote(1, blockHash)}}; | ||||
for (int i = 0; i < 6; i++) { | for (int i = 0; i < 6; i++) { | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// Now the state will flip. | // Now the state will flip. | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(!p.isAccepted(pindex)); | BOOST_CHECK(!m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 1); | BOOST_CHECK_EQUAL(updates.size(), 1); | ||||
BOOST_CHECK(updates[0].getBlockIndex() == pindex); | BOOST_CHECK(updates[0].getBlockIndex() == pindex); | ||||
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Rejected); | BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Rejected); | ||||
updates = {}; | updates = {}; | ||||
// Now it is rejected, but we can vote for it numerous times. | // Now it is rejected, but we can vote for it numerous times. | ||||
for (int i = 1; i < AVALANCHE_FINALIZATION_SCORE; i++) { | for (int i = 1; i < AVALANCHE_FINALIZATION_SCORE; i++) { | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(!p.isAccepted(pindex)); | BOOST_CHECK(!m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// As long as it is not finalized, we poll. | // As long as it is not finalized, we poll. | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
// Now finalize the decision. | // Now finalize the decision. | ||||
registerNewVote(next(resp)); | registerNewVote(next(resp)); | ||||
BOOST_CHECK(!p.isAccepted(pindex)); | BOOST_CHECK(!m_processor->isAccepted(pindex)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 1); | BOOST_CHECK_EQUAL(updates.size(), 1); | ||||
BOOST_CHECK(updates[0].getBlockIndex() == pindex); | BOOST_CHECK(updates[0].getBlockIndex() == pindex); | ||||
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Invalid); | BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Invalid); | ||||
updates = {}; | updates = {}; | ||||
// Once the decision is finalized, there is no poll for it. | // Once the decision is finalized, there is no poll for it. | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 0); | BOOST_CHECK_EQUAL(invs.size(), 0); | ||||
// Adding the block twice does nothing. | // Adding the block twice does nothing. | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
BOOST_CHECK(!p.addBlockToReconcile(pindex)); | BOOST_CHECK(!m_processor->addBlockToReconcile(pindex)); | ||||
BOOST_CHECK(p.isAccepted(pindex)); | BOOST_CHECK(m_processor->isAccepted(pindex)); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(multi_block_register) { | BOOST_AUTO_TEST_CASE(multi_block_register) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
CBlockIndex indexA, indexB; | CBlockIndex indexA, indexB; | ||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
// Create several nodes that support avalanche. | // Create several nodes that support avalanche. | ||||
auto avanodes = ConnectNodes(p); | auto avanodes = ConnectNodes(); | ||||
// Make sure the block has a hash. | // Make sure the block has a hash. | ||||
CBlock blockA = CreateAndProcessBlock({}, CScript()); | CBlock blockA = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHashA = blockA.GetHash(); | const BlockHash blockHashA = blockA.GetHash(); | ||||
CBlock blockB = CreateAndProcessBlock({}, CScript()); | CBlock blockB = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHashB = blockB.GetHash(); | const BlockHash blockHashB = blockB.GetHash(); | ||||
const CBlockIndex *pindexA; | const CBlockIndex *pindexA; | ||||
const CBlockIndex *pindexB; | const CBlockIndex *pindexB; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindexA = LookupBlockIndex(blockHashA); | pindexA = LookupBlockIndex(blockHashA); | ||||
pindexB = LookupBlockIndex(blockHashB); | pindexB = LookupBlockIndex(blockHashB); | ||||
} | } | ||||
// Querying for random block returns false. | // Querying for random block returns false. | ||||
BOOST_CHECK(!p.isAccepted(pindexA)); | BOOST_CHECK(!m_processor->isAccepted(pindexA)); | ||||
BOOST_CHECK(!p.isAccepted(pindexB)); | BOOST_CHECK(!m_processor->isAccepted(pindexB)); | ||||
// Start voting on block A. | // Start voting on block A. | ||||
BOOST_CHECK(p.addBlockToReconcile(pindexA)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindexA)); | ||||
auto invs = AvalancheTest::getInvsForNextPoll(p); | auto invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHashA); | BOOST_CHECK(invs[0].hash == blockHashA); | ||||
uint64_t round = AvalancheTest::getRound(p); | uint64_t round = getRound(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(p.registerVotes(avanodes[0]->GetId(), | BOOST_CHECK(m_processor->registerVotes( | ||||
{round, 0, {Vote(0, blockHashA)}}, updates)); | avanodes[0]->GetId(), {round, 0, {Vote(0, blockHashA)}}, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
// Start voting on block B after one vote. | // Start voting on block B after one vote. | ||||
Response resp{round + 1, 0, {Vote(0, blockHashB), Vote(0, blockHashA)}}; | Response resp{round + 1, 0, {Vote(0, blockHashB), Vote(0, blockHashA)}}; | ||||
BOOST_CHECK(p.addBlockToReconcile(pindexB)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindexB)); | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 2); | BOOST_CHECK_EQUAL(invs.size(), 2); | ||||
// Ensure B comes before A because it has accumulated more PoW. | // Ensure B comes before A because it has accumulated more PoW. | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHashB); | BOOST_CHECK(invs[0].hash == blockHashB); | ||||
BOOST_CHECK_EQUAL(invs[1].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[1].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[1].hash == blockHashA); | BOOST_CHECK(invs[1].hash == blockHashA); | ||||
// Let's vote for these blocks a few times. | // Let's vote for these blocks a few times. | ||||
for (int i = 0; i < 4; i++) { | for (int i = 0; i < 4; i++) { | ||||
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p); | NodeId nodeid = getSuitableNodeToQuery(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(p.registerVotes(nodeid, next(resp), updates)); | BOOST_CHECK(m_processor->registerVotes(nodeid, next(resp), updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// Now it is accepted, but we can vote for it numerous times. | // Now it is accepted, but we can vote for it numerous times. | ||||
for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { | for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { | ||||
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p); | NodeId nodeid = getSuitableNodeToQuery(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(p.registerVotes(nodeid, next(resp), updates)); | BOOST_CHECK(m_processor->registerVotes(nodeid, next(resp), updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
} | } | ||||
// Running two iterration of the event loop so that vote gets triggered on A | // Running two iterration of the event loop so that vote gets triggered on A | ||||
// and B. | // and B. | ||||
NodeId firstNodeid = AvalancheTest::getSuitableNodeToQuery(p); | NodeId firstNodeid = getSuitableNodeToQuery(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
NodeId secondNodeid = AvalancheTest::getSuitableNodeToQuery(p); | NodeId secondNodeid = getSuitableNodeToQuery(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(firstNodeid != secondNodeid); | BOOST_CHECK(firstNodeid != secondNodeid); | ||||
// Next vote will finalize block A. | // Next vote will finalize block A. | ||||
BOOST_CHECK(p.registerVotes(firstNodeid, next(resp), updates)); | BOOST_CHECK(m_processor->registerVotes(firstNodeid, next(resp), updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 1); | BOOST_CHECK_EQUAL(updates.size(), 1); | ||||
BOOST_CHECK(updates[0].getBlockIndex() == pindexA); | BOOST_CHECK(updates[0].getBlockIndex() == pindexA); | ||||
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized); | BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized); | ||||
updates = {}; | updates = {}; | ||||
// We do not vote on A anymore. | // We do not vote on A anymore. | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHashB); | BOOST_CHECK(invs[0].hash == blockHashB); | ||||
// Next vote will finalize block B. | // Next vote will finalize block B. | ||||
BOOST_CHECK(p.registerVotes(secondNodeid, resp, updates)); | BOOST_CHECK(m_processor->registerVotes(secondNodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 1); | BOOST_CHECK_EQUAL(updates.size(), 1); | ||||
BOOST_CHECK(updates[0].getBlockIndex() == pindexB); | BOOST_CHECK(updates[0].getBlockIndex() == pindexB); | ||||
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized); | BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized); | ||||
updates = {}; | updates = {}; | ||||
// There is nothing left to vote on. | // There is nothing left to vote on. | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 0); | BOOST_CHECK_EQUAL(invs.size(), 0); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(poll_and_response) { | BOOST_AUTO_TEST_CASE(poll_and_response) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
CBlock block = CreateAndProcessBlock({}, CScript()); | CBlock block = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash = block.GetHash(); | const BlockHash blockHash = block.GetHash(); | ||||
const CBlockIndex *pindex; | const CBlockIndex *pindex; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex = LookupBlockIndex(blockHash); | pindex = LookupBlockIndex(blockHash); | ||||
} | } | ||||
// There is no node to query. | // There is no node to query. | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), NO_NODE); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), NO_NODE); | ||||
// Create a node that supports avalanche and one that doesn't. | // Create a node that supports avalanche and one that doesn't. | ||||
ConnectNode(NODE_NONE); | ConnectNode(NODE_NONE); | ||||
auto avanode = ConnectNode(NODE_AVALANCHE); | auto avanode = ConnectNode(NODE_AVALANCHE); | ||||
NodeId avanodeid = avanode->GetId(); | NodeId avanodeid = avanode->GetId(); | ||||
BOOST_CHECK(p.addNode(avanodeid, GetProof(), CPubKey())); | BOOST_CHECK(m_processor->addNode(avanodeid, GetProof(), CPubKey())); | ||||
// It returns the avalanche peer. | // It returns the avalanche peer. | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// Register a block and check it is added to the list of elements to poll. | // Register a block and check it is added to the list of elements to poll. | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
auto invs = AvalancheTest::getInvsForNextPoll(p); | auto invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
// Trigger a poll on avanode. | // Trigger a poll on avanode. | ||||
uint64_t round = AvalancheTest::getRound(p); | uint64_t round = getRound(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
// There is no more suitable peer available, so return nothing. | // There is no more suitable peer available, so return nothing. | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), NO_NODE); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), NO_NODE); | ||||
// Respond to the request. | // Respond to the request. | ||||
Response resp = {round, 0, {Vote(0, blockHash)}}; | Response resp = {round, 0, {Vote(0, blockHash)}}; | ||||
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
// Now that avanode fullfilled his request, it is added back to the list of | // Now that avanode fullfilled his request, it is added back to the list of | ||||
// queriable nodes. | // queriable nodes. | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// Sending a response when not polled fails. | // Sending a response when not polled fails. | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, next(resp), updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid, next(resp), updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
// Trigger a poll on avanode. | // Trigger a poll on avanode. | ||||
round = AvalancheTest::getRound(p); | round = getRound(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), NO_NODE); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), NO_NODE); | ||||
// Sending responses that do not match the request also fails. | // Sending responses that do not match the request also fails. | ||||
// 1. Too many results. | // 1. Too many results. | ||||
resp = {round, 0, {Vote(0, blockHash), Vote(0, blockHash)}}; | resp = {round, 0, {Vote(0, blockHash), Vote(0, blockHash)}}; | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// 2. Not enough results. | // 2. Not enough results. | ||||
resp = {AvalancheTest::getRound(p), 0, {}}; | resp = {getRound(), 0, {}}; | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// 3. Do not match the poll. | // 3. Do not match the poll. | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote()}}; | resp = {getRound(), 0, {Vote()}}; | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// 4. Invalid round count. Request is not discarded. | // 4. Invalid round count. Request is not discarded. | ||||
uint64_t queryRound = AvalancheTest::getRound(p); | uint64_t queryRound = getRound(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
resp = {queryRound + 1, 0, {Vote()}}; | resp = {queryRound + 1, 0, {Vote()}}; | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
resp = {queryRound - 1, 0, {Vote()}}; | resp = {queryRound - 1, 0, {Vote()}}; | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
// 5. Making request for invalid nodes do not work. Request is not | // 5. Making request for invalid nodes do not work. Request is not | ||||
// discarded. | // discarded. | ||||
resp = {queryRound, 0, {Vote(0, blockHash)}}; | resp = {queryRound, 0, {Vote(0, blockHash)}}; | ||||
BOOST_CHECK(!p.registerVotes(avanodeid + 1234, resp, updates)); | BOOST_CHECK(!m_processor->registerVotes(avanodeid + 1234, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
// Proper response gets processed and avanode is available again. | // Proper response gets processed and avanode is available again. | ||||
resp = {queryRound, 0, {Vote(0, blockHash)}}; | resp = {queryRound, 0, {Vote(0, blockHash)}}; | ||||
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// Out of order response are rejected. | // Out of order response are rejected. | ||||
CBlock block2 = CreateAndProcessBlock({}, CScript()); | CBlock block2 = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash2 = block2.GetHash(); | const BlockHash blockHash2 = block2.GetHash(); | ||||
CBlockIndex *pindex2; | CBlockIndex *pindex2; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex2 = LookupBlockIndex(blockHash2); | pindex2 = LookupBlockIndex(blockHash2); | ||||
} | } | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex2)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex2)); | ||||
resp = {AvalancheTest::getRound(p), | resp = {getRound(), 0, {Vote(0, blockHash), Vote(0, blockHash2)}}; | ||||
0, | runEventLoop(); | ||||
{Vote(0, blockHash), Vote(0, blockHash2)}}; | BOOST_CHECK(!m_processor->registerVotes(avanodeid, resp, updates)); | ||||
AvalancheTest::runEventLoop(p); | |||||
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates)); | |||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// But they are accepted in order. | // But they are accepted in order. | ||||
resp = {AvalancheTest::getRound(p), | resp = {getRound(), 0, {Vote(0, blockHash2), Vote(0, blockHash)}}; | ||||
0, | runEventLoop(); | ||||
{Vote(0, blockHash2), Vote(0, blockHash)}}; | BOOST_CHECK(m_processor->registerVotes(avanodeid, resp, updates)); | ||||
AvalancheTest::runEventLoop(p); | |||||
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates)); | |||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
// When a block is marked invalid, stop polling. | // When a block is marked invalid, stop polling. | ||||
pindex2->nStatus = pindex2->nStatus.withFailed(); | pindex2->nStatus = pindex2->nStatus.withFailed(); | ||||
resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}}; | resp = {getRound(), 0, {Vote(0, blockHash)}}; | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates)); | BOOST_CHECK(m_processor->registerVotes(avanodeid, resp, updates)); | ||||
BOOST_CHECK_EQUAL(updates.size(), 0); | BOOST_CHECK_EQUAL(updates.size(), 0); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(poll_inflight_timeout, *boost::unit_test::timeout(60)) { | BOOST_AUTO_TEST_CASE(poll_inflight_timeout, *boost::unit_test::timeout(60)) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
CBlock block = CreateAndProcessBlock({}, CScript()); | CBlock block = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash = block.GetHash(); | const BlockHash blockHash = block.GetHash(); | ||||
const CBlockIndex *pindex; | const CBlockIndex *pindex; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex = LookupBlockIndex(blockHash); | pindex = LookupBlockIndex(blockHash); | ||||
} | } | ||||
// Add the block | // Add the block | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
// Create a node that supports avalanche. | // Create a node that supports avalanche. | ||||
auto avanode = ConnectNode(NODE_AVALANCHE); | auto avanode = ConnectNode(NODE_AVALANCHE); | ||||
NodeId avanodeid = avanode->GetId(); | NodeId avanodeid = avanode->GetId(); | ||||
BOOST_CHECK(p.addNode(avanodeid, GetProof(), CPubKey())); | BOOST_CHECK(m_processor->addNode(avanodeid, GetProof(), CPubKey())); | ||||
// Expire requests after some time. | // Expire requests after some time. | ||||
auto queryTimeDuration = std::chrono::milliseconds(10); | auto queryTimeDuration = std::chrono::milliseconds(10); | ||||
p.setQueryTimeoutDuration(queryTimeDuration); | m_processor->setQueryTimeoutDuration(queryTimeDuration); | ||||
for (int i = 0; i < 10; i++) { | for (int i = 0; i < 10; i++) { | ||||
Response resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}}; | Response resp = {getRound(), 0, {Vote(0, blockHash)}}; | ||||
auto start = std::chrono::steady_clock::now(); | auto start = std::chrono::steady_clock::now(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
// We cannot guarantee that we'll wait for just 1ms, so we have to bail | // We cannot guarantee that we'll wait for just 1ms, so we have to bail | ||||
// if we aren't within the proper time range. | // if we aren't within the proper time range. | ||||
std::this_thread::sleep_for(std::chrono::milliseconds(1)); | std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
bool ret = p.registerVotes(avanodeid, next(resp), updates); | bool ret = m_processor->registerVotes(avanodeid, next(resp), updates); | ||||
if (std::chrono::steady_clock::now() > start + queryTimeDuration) { | if (std::chrono::steady_clock::now() > start + queryTimeDuration) { | ||||
// We waited for too long, bail. Because we can't know for sure when | // We waited for too long, bail. Because we can't know for sure when | ||||
// previous steps ran, ret is not deterministic and we do not check | // previous steps ran, ret is not deterministic and we do not check | ||||
// it. | // it. | ||||
i--; | i--; | ||||
continue; | continue; | ||||
} | } | ||||
// We are within time bounds, so the vote should have worked. | // We are within time bounds, so the vote should have worked. | ||||
BOOST_CHECK(ret); | BOOST_CHECK(ret); | ||||
// Now try again but wait for expiration. | // Now try again but wait for expiration. | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
std::this_thread::sleep_for(queryTimeDuration); | std::this_thread::sleep_for(queryTimeDuration); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(!p.registerVotes(avanodeid, next(resp), updates)); | BOOST_CHECK( | ||||
!m_processor->registerVotes(avanodeid, next(resp), updates)); | |||||
} | } | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(poll_inflight_count) { | BOOST_AUTO_TEST_CASE(poll_inflight_count) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
// Create enough nodes so that we run into the inflight request limit. | // Create enough nodes so that we run into the inflight request limit. | ||||
PeerManager &pm = AvalancheTest::getPeerManager(p); | PeerManager &pm = getPeerManager(); | ||||
Proof proof = GetProof(); | Proof proof = GetProof(); | ||||
std::array<CNode *, AVALANCHE_MAX_INFLIGHT_POLL + 1> nodes; | std::array<CNode *, AVALANCHE_MAX_INFLIGHT_POLL + 1> nodes; | ||||
for (auto &n : nodes) { | for (auto &n : nodes) { | ||||
n = ConnectNode(NODE_AVALANCHE); | n = ConnectNode(NODE_AVALANCHE); | ||||
BOOST_CHECK(pm.addNode(n->GetId(), proof, CPubKey())); | BOOST_CHECK(pm.addNode(n->GetId(), proof, CPubKey())); | ||||
} | } | ||||
// Add a block to poll | // Add a block to poll | ||||
CBlock block = CreateAndProcessBlock({}, CScript()); | CBlock block = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash = block.GetHash(); | const BlockHash blockHash = block.GetHash(); | ||||
const CBlockIndex *pindex; | const CBlockIndex *pindex; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex = LookupBlockIndex(blockHash); | pindex = LookupBlockIndex(blockHash); | ||||
} | } | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
// Ensure there are enough requests in flight. | // Ensure there are enough requests in flight. | ||||
std::map<NodeId, uint64_t> node_round_map; | std::map<NodeId, uint64_t> node_round_map; | ||||
for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) { | for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) { | ||||
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p); | NodeId nodeid = getSuitableNodeToQuery(); | ||||
BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end()); | BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end()); | ||||
node_round_map[nodeid] = AvalancheTest::getRound(p); | node_round_map[nodeid] = getRound(); | ||||
auto invs = AvalancheTest::getInvsForNextPoll(p); | auto invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
} | } | ||||
// Now that we have enough in flight requests, we shouldn't poll. | // Now that we have enough in flight requests, we shouldn't poll. | ||||
auto suitablenodeid = AvalancheTest::getSuitableNodeToQuery(p); | auto suitablenodeid = getSuitableNodeToQuery(); | ||||
BOOST_CHECK(suitablenodeid != NO_NODE); | BOOST_CHECK(suitablenodeid != NO_NODE); | ||||
auto invs = AvalancheTest::getInvsForNextPoll(p); | auto invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 0); | BOOST_CHECK_EQUAL(invs.size(), 0); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), suitablenodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), suitablenodeid); | ||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
// Send one response, now we can poll again. | // Send one response, now we can poll again. | ||||
auto it = node_round_map.begin(); | auto it = node_round_map.begin(); | ||||
Response resp = {it->second, 0, {Vote(0, blockHash)}}; | Response resp = {it->second, 0, {Vote(0, blockHash)}}; | ||||
BOOST_CHECK(p.registerVotes(it->first, resp, updates)); | BOOST_CHECK(m_processor->registerVotes(it->first, resp, updates)); | ||||
node_round_map.erase(it); | node_round_map.erase(it); | ||||
invs = AvalancheTest::getInvsForNextPoll(p); | invs = getInvsForNextPoll(); | ||||
BOOST_CHECK_EQUAL(invs.size(), 1); | BOOST_CHECK_EQUAL(invs.size(), 1); | ||||
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); | ||||
BOOST_CHECK(invs[0].hash == blockHash); | BOOST_CHECK(invs[0].hash == blockHash); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(quorum_diversity) { | BOOST_AUTO_TEST_CASE(quorum_diversity) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
CBlock block = CreateAndProcessBlock({}, CScript()); | CBlock block = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash = block.GetHash(); | const BlockHash blockHash = block.GetHash(); | ||||
const CBlockIndex *pindex; | const CBlockIndex *pindex; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex = LookupBlockIndex(blockHash); | pindex = LookupBlockIndex(blockHash); | ||||
} | } | ||||
// Create nodes that supports avalanche. | // Create nodes that supports avalanche. | ||||
auto avanodes = ConnectNodes(p); | auto avanodes = ConnectNodes(); | ||||
// Querying for random block returns false. | // Querying for random block returns false. | ||||
BOOST_CHECK(!p.isAccepted(pindex)); | BOOST_CHECK(!m_processor->isAccepted(pindex)); | ||||
// Add a new block. Check it is added to the polls. | // Add a new block. Check it is added to the polls. | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
// Do one valid round of voting. | // Do one valid round of voting. | ||||
uint64_t round = AvalancheTest::getRound(p); | uint64_t round = getRound(); | ||||
Response resp{round, 0, {Vote(0, blockHash)}}; | Response resp{round, 0, {Vote(0, blockHash)}}; | ||||
// Check that all nodes can vote. | // Check that all nodes can vote. | ||||
for (size_t i = 0; i < avanodes.size(); i++) { | for (size_t i = 0; i < avanodes.size(); i++) { | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
BOOST_CHECK(p.registerVotes(avanodes[i]->GetId(), next(resp), updates)); | BOOST_CHECK(m_processor->registerVotes(avanodes[i]->GetId(), next(resp), | ||||
updates)); | |||||
} | } | ||||
// Generate a query for every single node. | // Generate a query for every single node. | ||||
const NodeId firstNodeId = AvalancheTest::getSuitableNodeToQuery(p); | const NodeId firstNodeId = getSuitableNodeToQuery(); | ||||
std::map<NodeId, uint64_t> node_round_map; | std::map<NodeId, uint64_t> node_round_map; | ||||
round = AvalancheTest::getRound(p); | round = getRound(); | ||||
for (size_t i = 0; i < avanodes.size(); i++) { | for (size_t i = 0; i < avanodes.size(); i++) { | ||||
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p); | NodeId nodeid = getSuitableNodeToQuery(); | ||||
BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end()); | BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end()); | ||||
node_round_map[nodeid] = AvalancheTest::getRound(p); | node_round_map[nodeid] = getRound(); | ||||
AvalancheTest::runEventLoop(p); | runEventLoop(); | ||||
} | } | ||||
// Now only tge first node can vote. All others would be duplicate in the | // Now only tge first node can vote. All others would be duplicate in the | ||||
// quorum. | // quorum. | ||||
auto confidence = p.getConfidence(pindex); | auto confidence = m_processor->getConfidence(pindex); | ||||
BOOST_REQUIRE(confidence > 0); | BOOST_REQUIRE(confidence > 0); | ||||
for (auto &pair : node_round_map) { | for (auto &pair : node_round_map) { | ||||
NodeId nodeid = pair.first; | NodeId nodeid = pair.first; | ||||
uint64_t r = pair.second; | uint64_t r = pair.second; | ||||
if (nodeid == firstNodeId) { | if (nodeid == firstNodeId) { | ||||
// Node 0 is the only one which can vote at this stage. | // Node 0 is the only one which can vote at this stage. | ||||
round = r; | round = r; | ||||
continue; | continue; | ||||
} | } | ||||
BOOST_CHECK( | BOOST_CHECK(m_processor->registerVotes( | ||||
p.registerVotes(nodeid, {r, 0, {Vote(0, blockHash)}}, updates)); | nodeid, {r, 0, {Vote(0, blockHash)}}, updates)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), confidence); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), confidence); | ||||
} | } | ||||
BOOST_CHECK(p.registerVotes(firstNodeId, {round, 0, {Vote(0, blockHash)}}, | BOOST_CHECK(m_processor->registerVotes( | ||||
updates)); | firstNodeId, {round, 0, {Vote(0, blockHash)}}, updates)); | ||||
BOOST_CHECK_EQUAL(p.getConfidence(pindex), confidence + 1); | BOOST_CHECK_EQUAL(m_processor->getConfidence(pindex), confidence + 1); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(event_loop) { | BOOST_AUTO_TEST_CASE(event_loop) { | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
CScheduler s; | CScheduler s; | ||||
CBlock block = CreateAndProcessBlock({}, CScript()); | CBlock block = CreateAndProcessBlock({}, CScript()); | ||||
const BlockHash blockHash = block.GetHash(); | const BlockHash blockHash = block.GetHash(); | ||||
const CBlockIndex *pindex; | const CBlockIndex *pindex; | ||||
{ | { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
pindex = LookupBlockIndex(blockHash); | pindex = LookupBlockIndex(blockHash); | ||||
} | } | ||||
// Starting the event loop. | // Starting the event loop. | ||||
BOOST_CHECK(p.startEventLoop(s)); | BOOST_CHECK(m_processor->startEventLoop(s)); | ||||
// There is one task planned in the next hour (our event loop). | // There is one task planned in the next hour (our event loop). | ||||
std::chrono::system_clock::time_point start, stop; | std::chrono::system_clock::time_point start, stop; | ||||
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1); | BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1); | ||||
// Starting twice doesn't start it twice. | // Starting twice doesn't start it twice. | ||||
BOOST_CHECK(!p.startEventLoop(s)); | BOOST_CHECK(!m_processor->startEventLoop(s)); | ||||
// Start the scheduler thread. | // Start the scheduler thread. | ||||
std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s)); | std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s)); | ||||
// Create a node that supports avalanche. | // Create a node that supports avalanche. | ||||
auto avanode = ConnectNode(NODE_AVALANCHE); | auto avanode = ConnectNode(NODE_AVALANCHE); | ||||
NodeId nodeid = avanode->GetId(); | NodeId nodeid = avanode->GetId(); | ||||
BOOST_CHECK(p.addNode(nodeid, GetProof(), CPubKey())); | BOOST_CHECK(m_processor->addNode(nodeid, GetProof(), CPubKey())); | ||||
// There is no query in flight at the moment. | // There is no query in flight at the moment. | ||||
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), nodeid); | BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), nodeid); | ||||
// Add a new block. Check it is added to the polls. | // Add a new block. Check it is added to the polls. | ||||
uint64_t queryRound = AvalancheTest::getRound(p); | uint64_t queryRound = getRound(); | ||||
BOOST_CHECK(p.addBlockToReconcile(pindex)); | BOOST_CHECK(m_processor->addBlockToReconcile(pindex)); | ||||
for (int i = 0; i < 60 * 1000; i++) { | for (int i = 0; i < 60 * 1000; i++) { | ||||
// Technically, this is a race condition, but this should do just fine | // Technically, this is a race condition, but this should do just fine | ||||
// as we wait up to 1 minute for an event that should take 10ms. | // as we wait up to 1 minute for an event that should take 10ms. | ||||
UninterruptibleSleep(std::chrono::milliseconds(1)); | UninterruptibleSleep(std::chrono::milliseconds(1)); | ||||
if (AvalancheTest::getRound(p) != queryRound) { | if (getRound() != queryRound) { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
// Check that we effectively got a request and not timed out. | // Check that we effectively got a request and not timed out. | ||||
BOOST_CHECK(AvalancheTest::getRound(p) > queryRound); | BOOST_CHECK(getRound() > queryRound); | ||||
// Respond and check the cooldown time is respected. | // Respond and check the cooldown time is respected. | ||||
uint64_t responseRound = AvalancheTest::getRound(p); | uint64_t responseRound = getRound(); | ||||
auto queryTime = | auto queryTime = | ||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(100); | std::chrono::steady_clock::now() + std::chrono::milliseconds(100); | ||||
std::vector<BlockUpdate> updates; | std::vector<BlockUpdate> updates; | ||||
p.registerVotes(nodeid, {queryRound, 100, {Vote(0, blockHash)}}, updates); | m_processor->registerVotes(nodeid, {queryRound, 100, {Vote(0, blockHash)}}, | ||||
updates); | |||||
for (int i = 0; i < 10000; i++) { | for (int i = 0; i < 10000; i++) { | ||||
// We make sure that we do not get a request before queryTime. | // We make sure that we do not get a request before queryTime. | ||||
UninterruptibleSleep(std::chrono::milliseconds(1)); | UninterruptibleSleep(std::chrono::milliseconds(1)); | ||||
if (AvalancheTest::getRound(p) != responseRound) { | if (getRound() != responseRound) { | ||||
BOOST_CHECK(std::chrono::steady_clock::now() > queryTime); | BOOST_CHECK(std::chrono::steady_clock::now() > queryTime); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
// But we eventually get one. | // But we eventually get one. | ||||
BOOST_CHECK(AvalancheTest::getRound(p) > responseRound); | BOOST_CHECK(getRound() > responseRound); | ||||
// Stop event loop. | // Stop event loop. | ||||
BOOST_CHECK(p.stopEventLoop()); | BOOST_CHECK(m_processor->stopEventLoop()); | ||||
// We don't have any task scheduled anymore. | // We don't have any task scheduled anymore. | ||||
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0); | BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0); | ||||
// Can't stop the event loop twice. | // Can't stop the event loop twice. | ||||
BOOST_CHECK(!p.stopEventLoop()); | BOOST_CHECK(!m_processor->stopEventLoop()); | ||||
// Wait for the scheduler to stop. | // Wait for the scheduler to stop. | ||||
s.stop(true); | s.stop(true); | ||||
schedulerThread.join(); | schedulerThread.join(); | ||||
} | } | ||||
BOOST_AUTO_TEST_CASE(destructor) { | BOOST_AUTO_TEST_CASE(destructor) { | ||||
CScheduler s; | CScheduler s; | ||||
std::chrono::system_clock::time_point start, stop; | std::chrono::system_clock::time_point start, stop; | ||||
std::thread schedulerThread; | std::thread schedulerThread; | ||||
{ | BOOST_CHECK(m_processor->startEventLoop(s)); | ||||
Processor p(*m_node.chain, m_node.connman.get()); | |||||
BOOST_CHECK(p.startEventLoop(s)); | |||||
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1); | BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1); | ||||
// Start the service thread after the queue size check to prevent a | // Start the service thread after the queue size check to prevent a race | ||||
// race condition where the thread may be processing the event loop | // condition where the thread may be processing the event loop task during | ||||
// task during the check. | // the check. | ||||
schedulerThread = std::thread(std::bind(&CScheduler::serviceQueue, &s)); | schedulerThread = std::thread(std::bind(&CScheduler::serviceQueue, &s)); | ||||
} | |||||
// Destroy the processor. | |||||
m_processor.reset(); | |||||
// Now that avalanche is destroyed, there is no more scheduled tasks. | // Now that avalanche is destroyed, there is no more scheduled tasks. | ||||
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0); | BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0); | ||||
// Wait for the scheduler to stop. | // Wait for the scheduler to stop. | ||||
s.stop(true); | s.stop(true); | ||||
schedulerThread.join(); | schedulerThread.join(); | ||||
} | } | ||||
BOOST_AUTO_TEST_SUITE_END() | BOOST_AUTO_TEST_SUITE_END() |