diff --git a/src/avalanche.h b/src/avalanche.h --- a/src/avalanche.h +++ b/src/avalanche.h @@ -160,9 +160,10 @@ bool addBlockToReconcile(const CBlockIndex *pindex); bool isAccepted(const CBlockIndex *pindex) const; - bool hasFinalized(const CBlockIndex *pindex) const; - bool registerVotes(const AvalancheResponse &response); + bool registerVotes(const AvalancheResponse &response, + std::vector &accepted, + std::vector &rejected); bool startEventLoop(CScheduler &scheduler); bool stopEventLoop(); diff --git a/src/avalanche.cpp b/src/avalanche.cpp --- a/src/avalanche.cpp +++ b/src/avalanche.cpp @@ -35,15 +35,10 @@ return false; } -bool AvalancheProcessor::hasFinalized(const CBlockIndex *pindex) const { - if (auto vr = GetRecord(vote_records, pindex)) { - return vr->hasFinalized(); - } - - return false; -} - -bool AvalancheProcessor::registerVotes(const AvalancheResponse &response) { +bool AvalancheProcessor::registerVotes( + const AvalancheResponse &response, + std::vector &accepted, + std::vector &rejected) { const std::vector &votes = response.GetVotes(); std::map responseIndex; @@ -68,7 +63,29 @@ const CBlockIndex *pindex = p.first; const AvalancheVote &v = p.second; - w[pindex].registerVote(v.IsValid()); + auto it = w->find(pindex); + if (it == w.end()) { + // We are not voting on that item anymore. + continue; + } + + auto &vr = it->second; + vr.registerVote(v.IsValid()); + if (!vr.hasFinalized()) { + // This item has note been finalized, so we have nothing more to + // do. + continue; + } + + // We just finalized a vote. If it is valid, then let the caller + // know. Either way, remove the item from the map. + if (vr.isValid()) { + accepted.push_back(pindex); + } else { + rejected.push_back(pindex); + } + + w->erase(it); } } 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 @@ -74,6 +74,7 @@ BOOST_AUTO_TEST_CASE(block_register) { AvalancheProcessor p; + std::vector accepted, rejected; CBlock block = CreateAndProcessBlock({}, CScript()); const uint256 blockHash = block.GetHash(); @@ -81,7 +82,6 @@ // Querying for random block returns false. BOOST_CHECK(!p.isAccepted(pindex)); - BOOST_CHECK(!p.hasFinalized(pindex)); // Add a new block. Check it is added to the polls. BOOST_CHECK(p.addBlockToReconcile(pindex)); @@ -92,21 +92,22 @@ // Newly added blocks are also considered rejected. BOOST_CHECK(!p.isAccepted(pindex)); - BOOST_CHECK(!p.hasFinalized(pindex)); // Let's vote for this block a few times. AvalancheResponse resp{0, {AvalancheVote(0, blockHash)}}; for (int i = 0; i < 5; i++) { - p.registerVotes(resp); + p.registerVotes(resp, accepted, rejected); BOOST_CHECK(!p.isAccepted(pindex)); - BOOST_CHECK(!p.hasFinalized(pindex)); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 0); } // Now it is accepted, but we can vote for it numerous times. for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { - p.registerVotes(resp); + p.registerVotes(resp, accepted, rejected); BOOST_CHECK(p.isAccepted(pindex)); - BOOST_CHECK(!p.hasFinalized(pindex)); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 0); } // As long as it is not finalized, we poll. @@ -116,27 +117,42 @@ BOOST_CHECK(invs[0].hash == blockHash); // Now finalize the decision. - resp = {0, {AvalancheVote(1, blockHash)}}; - p.registerVotes(resp); - BOOST_CHECK(p.isAccepted(pindex)); - BOOST_CHECK(p.hasFinalized(pindex)); + p.registerVotes(resp, accepted, rejected); + BOOST_CHECK_EQUAL(accepted.size(), 1); + BOOST_CHECK_EQUAL(rejected.size(), 0); + BOOST_CHECK(accepted[0] == pindex); + accepted = {}; + + // Once the decision is finalized, there is no poll for it. + invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 0); // Once the decision is finalized, there is no poll for it. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 0); // Now let's undo this and finalize rejection. - for (int i = 0; i < 5; i++) { - p.registerVotes(resp); - BOOST_CHECK(p.isAccepted(pindex)); - BOOST_CHECK(p.hasFinalized(pindex)); + BOOST_CHECK(p.addBlockToReconcile(pindex)); + invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 1); + BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); + BOOST_CHECK(invs[0].hash == blockHash); + + // Only 3 here as we don't need to flip state. + resp = {0, {AvalancheVote(1, blockHash)}}; + for (int i = 0; i < 3; i++) { + p.registerVotes(resp, accepted, rejected); + BOOST_CHECK(!p.isAccepted(pindex)); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 0); } // Now it is rejected, but we can vote for it numerous times. for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) { - p.registerVotes(resp); + p.registerVotes(resp, accepted, rejected); BOOST_CHECK(!p.isAccepted(pindex)); - BOOST_CHECK(!p.hasFinalized(pindex)); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 0); } // As long as it is not finalized, we poll. @@ -146,18 +162,98 @@ BOOST_CHECK(invs[0].hash == blockHash); // Now finalize the decision. - p.registerVotes(resp); + p.registerVotes(resp, accepted, rejected); BOOST_CHECK(!p.isAccepted(pindex)); - BOOST_CHECK(p.hasFinalized(pindex)); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 1); + BOOST_CHECK(rejected[0] == pindex); + rejected = {}; + + // Once the decision is finalized, there is no poll for it. + invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 0); // Once the decision is finalized, there is no poll for it. invs = AvalancheTest::getInvsForNextPoll(p); BOOST_CHECK_EQUAL(invs.size(), 0); // Adding the block twice does nothing. + BOOST_CHECK(p.addBlockToReconcile(pindex)); BOOST_CHECK(!p.addBlockToReconcile(pindex)); BOOST_CHECK(!p.isAccepted(pindex)); - BOOST_CHECK(p.hasFinalized(pindex)); +} + +BOOST_AUTO_TEST_CASE(multi_block_register) { + AvalancheProcessor p; + CBlockIndex indexA, indexB; + + std::vector accepted, rejected; + + // Make sure the block has a hash. + CBlock blockA = CreateAndProcessBlock({}, CScript()); + const uint256 blockHashA = blockA.GetHash(); + const CBlockIndex *pindexA = mapBlockIndex[blockHashA]; + + CBlock blockB = CreateAndProcessBlock({}, CScript()); + const uint256 blockHashB = blockB.GetHash(); + const CBlockIndex *pindexB = mapBlockIndex[blockHashB]; + + // Querying for random block returns false. + BOOST_CHECK(!p.isAccepted(pindexA)); + BOOST_CHECK(!p.isAccepted(pindexB)); + + // Start voting on block A. + BOOST_CHECK(p.addBlockToReconcile(pindexA)); + auto invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 1); + BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); + BOOST_CHECK(invs[0].hash == blockHashA); + + AvalancheResponse resp{ + 0, {AvalancheVote(0, blockHashA), AvalancheVote(0, blockHashB)}}; + p.registerVotes(resp, accepted, rejected); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 0); + + // Start voing on block B after one vote. + BOOST_CHECK(p.addBlockToReconcile(pindexB)); + invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 2); + BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); + BOOST_CHECK(invs[0].hash == blockHashA); + BOOST_CHECK_EQUAL(invs[1].type, MSG_BLOCK); + BOOST_CHECK(invs[1].hash == blockHashB); + + // Now it is rejected, but we can vote for it numerous times. + for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE + 4; i++) { + p.registerVotes(resp, accepted, rejected); + BOOST_CHECK_EQUAL(accepted.size(), 0); + BOOST_CHECK_EQUAL(rejected.size(), 0); + } + + // Next vote will finalize block A. + p.registerVotes(resp, accepted, rejected); + BOOST_CHECK_EQUAL(accepted.size(), 1); + BOOST_CHECK_EQUAL(rejected.size(), 0); + BOOST_CHECK(accepted[0] == pindexA); + accepted = {}; + + // We do not vote on A anymore. + invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 1); + BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK); + BOOST_CHECK(invs[0].hash == blockHashB); + + // Next vote will finalize block B. + p.registerVotes(resp, accepted, rejected); + BOOST_CHECK_EQUAL(accepted.size(), 1); + BOOST_CHECK_EQUAL(rejected.size(), 0); + BOOST_CHECK(accepted[0] == pindexB); + accepted = {}; + + // There is nothing left to vote on. + invs = AvalancheTest::getInvsForNextPoll(p); + BOOST_CHECK_EQUAL(invs.size(), 0); } BOOST_AUTO_TEST_CASE(event_loop) {