diff --git a/core/log/configurator.cpp b/core/log/configurator.cpp index c8a818e8dc..c06584eae0 100644 --- a/core/log/configurator.cpp +++ b/core/log/configurator.cpp @@ -101,14 +101,15 @@ namespace kagome::log { - name: warp_sync_protocol - name: parachain_protocols children: - - name: collation_protocol - - name: validation_protocol + - name: collation_protocol_vstaging + - name: validation_protocol_vstaging - name: req_collation_protocol - name: req_chunk_protocol - name: req_available_data_protocol - name: req_statement_protocol - name: req_pov_protocol - name: dispute_protocol + - name: req_attested_candidate_protocol - name: changes_trie - name: storage children: diff --git a/core/network/impl/peer_manager_impl.cpp b/core/network/impl/peer_manager_impl.cpp index 60d8f0c66c..8f40e3ac62 100644 --- a/core/network/impl/peer_manager_impl.cpp +++ b/core/network/impl/peer_manager_impl.cpp @@ -581,6 +581,15 @@ namespace kagome::network { return it->second; } + std::optional> + PeerManagerImpl::getPeerState(const PeerId &peer_id) const { + auto it = peer_states_.find(peer_id); + if (it == peer_states_.end()) { + return std::nullopt; + } + return it->second; + } + void PeerManagerImpl::processDiscoveredPeer(const PeerId &peer_id) { // Ignore himself if (isSelfPeer(peer_id)) { @@ -726,41 +735,29 @@ namespace kagome::network { log_->trace("Try to open outgoing validation protocol.(peer={})", peer_info.id); - openOutgoing( - stream_engine_, - validation_protocol, - peer_info, - [validation_protocol, peer_info, wptr{weak_from_this()}]( - outcome::result> stream_result) { - auto self = wptr.lock(); - if (not self) { - return; - } - - auto &peer_id = peer_info.id; - if (!stream_result.has_value()) { - SL_TRACE(self->log_, - "Unable to create stream {} with {}: {}", - validation_protocol->protocolName(), - peer_id, - stream_result.error().message()); - auto ps = self->getPeerState(peer_info.id); - if (ps) { - self->tryOpenValidationProtocol( - peer_info, ps->get(), network::CollationVersion::V1); - } else { - SL_TRACE( - self->log_, - "No peer state to open V1 validation protocol {} with {}", - validation_protocol->protocolName(), - peer_id); - } - return; - } - - self->stream_engine_->addOutgoing(stream_result.value(), - validation_protocol); - }); + openOutgoing(stream_engine_, + validation_protocol, + peer_info, + [validation_protocol, peer_info, wptr{weak_from_this()}]( + outcome::result> stream_result) { + auto self = wptr.lock(); + if (not self) { + return; + } + + auto &peer_id = peer_info.id; + if (!stream_result.has_value()) { + SL_TRACE(self->log_, + "Unable to create stream {} with {}: {}", + validation_protocol->protocolName(), + peer_id, + stream_result.error().message()); + return; + } + + self->stream_engine_->addOutgoing(stream_result.value(), + validation_protocol); + }); } } @@ -839,11 +836,14 @@ namespace kagome::network { } void PeerManagerImpl::reserveStatusStreams(const PeerId &peer_id) const { - auto proto_val_vstaging = router_->getValidationProtocolVStaging(); - BOOST_ASSERT_MSG(proto_val_vstaging, - "Router did not provide validation protocol vstaging"); + if (auto ps = getPeerState(peer_id); + ps && ps->get().roles.flags.authority) { + auto proto_val_vstaging = router_->getValidationProtocolVStaging(); + BOOST_ASSERT_MSG(proto_val_vstaging, + "Router did not provide validation protocol vstaging"); - stream_engine_->reserveStreams(peer_id, proto_val_vstaging); + stream_engine_->reserveStreams(peer_id, proto_val_vstaging); + } } void PeerManagerImpl::reserveStreams(const PeerId &peer_id) const { diff --git a/core/network/impl/peer_manager_impl.hpp b/core/network/impl/peer_manager_impl.hpp index ef7b0bdb6d..47e5bd2887 100644 --- a/core/network/impl/peer_manager_impl.hpp +++ b/core/network/impl/peer_manager_impl.hpp @@ -139,6 +139,8 @@ namespace kagome::network { /** @see PeerManager::getPeerState */ std::optional> getPeerState( const PeerId &peer_id) override; + std::optional> getPeerState( + const PeerId &peer_id) const override; private: /// Right way to check self peer as it takes into account dev mode diff --git a/core/network/impl/router_libp2p.cpp b/core/network/impl/router_libp2p.cpp index 5a155c037b..bab9bbf1c1 100644 --- a/core/network/impl/router_libp2p.cpp +++ b/core/network/impl/router_libp2p.cpp @@ -128,8 +128,8 @@ namespace kagome::network { // lazyStart(collation_protocol_); // lazyStart(validation_protocol_); - lazyStart(collation_protocol_); - lazyStart(validation_protocol_); + lazyStart(collation_protocol_vstaging_); + lazyStart(validation_protocol_vstaging_); lazyStart(req_collation_protocol_); lazyStart(req_pov_protocol_); lazyStart(fetch_chunk_protocol_); diff --git a/core/network/peer_manager.hpp b/core/network/peer_manager.hpp index eb4559ef5c..89a5db0b87 100644 --- a/core/network/peer_manager.hpp +++ b/core/network/peer_manager.hpp @@ -193,6 +193,8 @@ namespace kagome::network { */ virtual std::optional> getPeerState( const PeerId &peer_id) = 0; + virtual std::optional> getPeerState( + const PeerId &peer_id) const = 0; /** * @returns number of active peers diff --git a/core/parachain/validator/fragment_tree.hpp b/core/parachain/validator/fragment_tree.hpp index 59dc9831cf..ed7331227e 100644 --- a/core/parachain/validator/fragment_tree.hpp +++ b/core/parachain/validator/fragment_tree.hpp @@ -533,47 +533,125 @@ namespace kagome::parachain::fragment { return res; } + /** + * @brief Select `count` candidates after the given `required_path` which + * pass the predicate and have not already been backed on chain. + * Does an exhaustive search into the tree starting after `required_path`. + * If there are multiple possibilities of size `count`, this will select the + * first one. If there is no chain of size `count` that matches the + * criteria, this will return the largest chain it could find with the + * criteria. If there are no candidates meeting those criteria, + * returns an empty `Vec`. Cycles are accepted, see module docs for the + * `Cycles` section. The intention of the `required_path` is + * to allow queries on the basis of one or more candidates which were + * previously pending availability becoming available and opening up more + * room on the core. + */ + template - std::optional selectChild( - const std::vector &required_path, Func &&pred) const { + std::vector selectChildren( + const std::vector &required_path, + uint32_t count, + Func &&pred) const { NodePointer base_node{NodePointerRoot{}}; for (const CandidateHash &required_step : required_path) { if (auto node = nodeCandidateChild(base_node, required_step)) { base_node = *node; } else { - return std::nullopt; + return {}; } } - return visit_in_place( + std::vector accum; + return selectChildrenInner( + std::move(base_node), count, count, std::forward(pred), accum); + } + + /** + * @brief Try finding a candidate chain starting from `base_node` of length + * `expected_count`. If not possible, return the longest one we + * could find. Does a depth-first search, since we're optimistic that + * there won't be more than one such chains (parachains shouldn't + * usually have forks). So in the usual case, this will conclude in + * `O(expected_count)`. Cycles are accepted, but this doesn't allow for + * infinite execution time, because the maximum depth we'll reach is + * `expected_count`. Worst case performance is `O(num_forks + * ^ expected_count)`. Although an exponential function, this is + * actually a constant that can only be altered via sudo/governance, + * because: 1. `num_forks` at a given level is at most `max_candidate_depth + * * max_validators_per_core` (because each validator in the + * assigned group can second `max_candidate_depth` candidates). The + * prospective-parachains subsystem assumes that the number of para forks is + * limited by collator-protocol and backing subsystems. In practice, this is + * a constant which can only be altered by sudo or governance. 2. + * `expected_count` is equal to the number of cores a para is scheduled on + * (in an elastic scaling scenario). For non-elastic-scaling, this is + * just 1. In practice, this should be a small number (1-3), capped + * by the total number of available cores (a constant alterable only + * via governance/sudo). + */ + template + std::vector selectChildrenInner( + NodePointer base_node, + uint32_t expected_count, + uint32_t remaining_count, + const Func &pred, + std::vector &accumulator) const { + if (remaining_count == 0) { + return accumulator; + } + + auto children = visit_in_place( base_node, - [&](const NodePointerRoot &) -> std::optional { - for (const FragmentNode &n : nodes) { + [&](const NodePointerRoot &) + -> std::vector> { + std::vector> tmp; + for (size_t ptr = 0; ptr < nodes.size(); ++ptr) { + const FragmentNode &n = nodes[ptr]; if (!is_type(n.parent)) { - return std::nullopt; + continue; } if (scope.getPendingAvailability(n.candidate_hash)) { - return std::nullopt; + continue; } if (!pred(n.candidate_hash)) { - return std::nullopt; + continue; } - return n.candidate_hash; + tmp.emplace_back(NodePointerStorage{ptr}, n.candidate_hash); } - return std::nullopt; + return tmp; }, - [&](const NodePointerStorage &ptr) -> std::optional { - for (const auto &[_, h] : nodes[ptr].children) { - if (scope.getPendingAvailability(h)) { - return std::nullopt; + [&](const NodePointerStorage &base_node_ptr) + -> std::vector> { + std::vector> tmp; + const auto &bn = nodes[base_node_ptr]; + for (const auto &[ptr, hash] : bn.children) { + if (scope.getPendingAvailability(hash)) { + continue; } - if (!pred(h)) { - return std::nullopt; + if (!pred(hash)) { + continue; } - return h; + tmp.emplace_back(ptr, hash); } - return std::nullopt; + return tmp; }); + + auto best_result = accumulator; + for (const auto &[child_ptr, child_hash] : children) { + accumulator.emplace_back(child_hash); + auto result = selectChildrenInner( + child_ptr, expected_count, remaining_count - 1, pred, accumulator); + accumulator.pop_back(); + + if (result.size() == size_t(expected_count)) { + return result; + } else if (best_result.size() < result.size()) { + best_result = result; + } + } + + return best_result; } static FragmentTree populate(const std::shared_ptr &hasher, diff --git a/core/parachain/validator/impl/parachain_processor.cpp b/core/parachain/validator/impl/parachain_processor.cpp index 22381ef370..85584aa9a9 100644 --- a/core/parachain/validator/impl/parachain_processor.cpp +++ b/core/parachain/validator/impl/parachain_processor.cpp @@ -387,11 +387,19 @@ namespace kagome::parachain { return; } - [[maybe_unused]] const auto _ = - prospective_parachains_->onActiveLeavesUpdate(network::ExViewRef{ - .new_head = {event.new_head}, - .lost = event.lost, - }); + if (const auto r = + prospective_parachains_->onActiveLeavesUpdate(network::ExViewRef{ + .new_head = {event.new_head}, + .lost = event.lost, + }); + r.has_error()) { + SL_WARN( + logger_, + "Prospective parachains leaf update failed. (relay_parent={}, error={})", + relay_parent, + r.error().message()); + } + backing_store_->onActivateLeaf(relay_parent); createBackingTask(relay_parent); SL_TRACE(logger_, @@ -521,7 +529,7 @@ namespace kagome::parachain { void ParachainProcessorImpl::broadcastViewExcept( const libp2p::peer::PeerId &peer_id, const network::View &view) const { auto msg = std::make_shared< - network::WireMessage>( + network::WireMessage>( network::ViewUpdate{.view = view}); pm_->getStreamEngine()->broadcast( router_->getValidationProtocolVStaging(), @@ -562,7 +570,8 @@ namespace kagome::parachain { BOOST_ASSERT(se); auto message = std::make_shared< - network::WireMessage>(msg); + network::WireMessage>( + msg); SL_TRACE( logger_, "Broadcasting view update to group.(relay_parent={}, group_size={})", @@ -581,7 +590,7 @@ namespace kagome::parachain { void ParachainProcessorImpl::broadcastView(const network::View &view) const { auto msg = std::make_shared< - network::WireMessage>( + network::WireMessage>( network::ViewUpdate{.view = view}); pm_->getStreamEngine()->broadcast(router_->getCollationProtocolVStaging(), msg); @@ -2255,18 +2264,28 @@ namespace kagome::parachain { core, [&](const network::ScheduledCore &scheduled_core) -> std::optional> { - return prospective_parachains_->answerGetBackableCandidate( - relay_parent, scheduled_core.para_id, {}); + if (auto i = prospective_parachains_->answerGetBackableCandidates( + relay_parent, scheduled_core.para_id, 1, {}); + !i.empty()) { + return i[0]; + } + return std::nullopt; }, [&](const runtime::OccupiedCore &occupied_core) -> std::optional> { /// TODO(iceseer): do https://github.com/qdrvm/kagome/issues/1888 /// `bitfields_indicate_availability` check if (occupied_core.next_up_on_available) { - return prospective_parachains_->answerGetBackableCandidate( - relay_parent, - occupied_core.next_up_on_available->para_id, - {occupied_core.candidate_hash}); + if (auto i = + prospective_parachains_->answerGetBackableCandidates( + relay_parent, + occupied_core.next_up_on_available->para_id, + 1, + {occupied_core.candidate_hash}); + !i.empty()) { + return i[0]; + } + return std::nullopt; } return std::nullopt; }, @@ -2810,7 +2829,7 @@ namespace kagome::parachain { peer_id, protocol, std::make_shared< - network::WireMessage>( + network::WireMessage>( network::ViewUpdate{.view = my_view->get().view})); } @@ -3553,6 +3572,7 @@ namespace kagome::parachain { *our_current_state_.implicit_view, our_current_state_.active_leaves, peer_data.collator_state->para_id)) { + SL_TRACE(logger_, "Out of view. (relay_parent={})", on_relay_parent); return Error::OUT_OF_VIEW; } diff --git a/core/parachain/validator/prospective_parachains.hpp b/core/parachain/validator/prospective_parachains.hpp index 5fb5d77796..579dd47db3 100644 --- a/core/parachain/validator/prospective_parachains.hpp +++ b/core/parachain/validator/prospective_parachains.hpp @@ -76,9 +76,19 @@ namespace kagome::parachain { std::vector> answerMinimumRelayParentsRequest(const RelayHash &relay_parent) const { std::vector> v; + SL_TRACE(logger, + "Search for minimum relay parents. (relay_parent={})", + relay_parent); + auto it = view.active_leaves.find(relay_parent); if (it != view.active_leaves.end()) { const RelayBlockViewData &leaf_data = it->second; + SL_TRACE( + logger, + "Found active list. (relay_parent={}, fragment_trees_count={})", + relay_parent, + leaf_data.fragment_trees.size()); + for (const auto &[para_id, fragment_tree] : leaf_data.fragment_trees) { v.emplace_back(para_id, fragment_tree.scope.earliestRelayParent().number); @@ -87,9 +97,10 @@ namespace kagome::parachain { return v; } - std::optional> answerGetBackableCandidate( + std::vector> answerGetBackableCandidates( const RelayHash &relay_parent, ParachainId para, + uint32_t count, const std::vector &required_path) { SL_TRACE(logger, "Search for backable candidates. (para_id={}, " @@ -103,7 +114,7 @@ namespace kagome::parachain { "(relay_parent={}, para_id={})", relay_parent, para); - return std::nullopt; + return {}; } const RelayBlockViewData &data = data_it->second; @@ -114,7 +125,7 @@ namespace kagome::parachain { "(relay_parent={}, para_id={})", relay_parent, para); - return std::nullopt; + return {}; } const fragment::FragmentTree &tree = tree_it->second; @@ -125,40 +136,51 @@ namespace kagome::parachain { "para_id={})", relay_parent, para); - return std::nullopt; + return {}; } const fragment::CandidateStorage &storage = storage_it->second; - auto child_hash = tree.selectChild( - required_path, [&](const CandidateHash &candidate) -> bool { + std::vector> backable_candidates; + const auto children = tree.selectChildren( + required_path, count, [&](const CandidateHash &candidate) -> bool { return storage.isBacked(candidate); }); - if (!child_hash) { - SL_TRACE(logger, - "Child hash is null. (para_id={}, " - "relay_parent={})", - para, - relay_parent); - return std::nullopt; + for (const auto &child_hash : children) { + if (auto parent_hash_opt = + storage.relayParentByCandidateHash(child_hash)) { + backable_candidates.emplace_back(child_hash, *parent_hash_opt); + } else { + SL_ERROR( + logger, + "Candidate is present in fragment tree but not in candidate's storage! (child_hash={}, para_id={})", + child_hash, + para); + } } - auto candidate_relay_parent = - storage.relayParentByCandidateHash(*child_hash); - if (!candidate_relay_parent) { - SL_ERROR(logger, - "Candidate is present in fragment tree but not in candidate's " - "storage! (relay_parent={}, para_id={}, child_hash={})", + if (backable_candidates.empty()) { + SL_TRACE( + logger, + "Could not find any backable candidate. (relay_parent={}, para_id={})", + relay_parent, + para); + } else { + SL_TRACE(logger, + "Found backable candidates. (relay_parent={}, count={})", relay_parent, - para, - *child_hash); - return std::nullopt; + backable_candidates.size()); } - return std::make_pair(*child_hash, *candidate_relay_parent); + return backable_candidates; } fragment::FragmentTreeMembership answerTreeMembershipRequest( ParachainId para, const CandidateHash &candidate) { + SL_TRACE(logger, + "Answer tree membership request. " + "(para_id={}, candidate_hash={})", + para, + candidate); return fragmentTreeMembership(view.active_leaves, para, candidate); } @@ -315,12 +337,32 @@ namespace kagome::parachain { OUTCOME_TRY( hashes, - block_tree_->getDescendingChainToBlock(relay_hash, ancestors)); + block_tree_->getDescendingChainToBlock(relay_hash, ancestors + 1)); + + if (logger->level() >= soralog::Level::TRACE) { + for (const auto &h : hashes) { + SL_TRACE(logger, + "Ancestor hash. " + "(relay_hash={}, ancestor_hash={})", + relay_hash, + h); + } + } + OUTCOME_TRY(required_session, parachain_host_->session_index_for_child(relay_hash)); - - block_info.reserve(hashes.size()); - for (const auto &hash : hashes) { + SL_TRACE(logger, + "Get ancestors. " + "(relay_hash={}, ancestors={}, hashes_len={})", + relay_hash, + ancestors, + hashes.size()); + + if (hashes.size() > 1) { + block_info.reserve(hashes.size() - 1); + } + for (size_t i = 1; i < hashes.size(); ++i) { + const auto &hash = hashes[i]; OUTCOME_TRY(info, fetchBlockInfo(hash)); if (!info) { SL_WARN(logger, @@ -331,8 +373,18 @@ namespace kagome::parachain { } OUTCOME_TRY(session, parachain_host_->session_index_for_child(hash)); if (session == required_session) { + SL_TRACE(logger, + "Add block. " + "(relay_hash={}, hash={})", + relay_hash, + hash); block_info.emplace_back(*info); } else { + SL_TRACE(logger, + "Skipped block. " + "(relay_hash={}, hash={})", + relay_hash, + hash); break; } } @@ -389,6 +441,9 @@ namespace kagome::parachain { outcome::result onActiveLeavesUpdate( const network::ExViewRef &update) { for (const auto &deactivated : update.lost) { + SL_TRACE(logger, + "Remove from active leaves. (relay_parent={})", + deactivated); view.active_leaves.erase(deactivated); } @@ -418,10 +473,12 @@ namespace kagome::parachain { }; OUTCOME_TRY(ancestry, fetchAncestry(hash, mode->allowed_ancestry_len)); + std::unordered_map fragment_trees; for (ParachainId para : scheduled_paras) { auto &candidate_storage = view.candidate_storage[para]; OUTCOME_TRY(backing_state, fetchBackingState(hash, para)); + if (!backing_state) { SL_TRACE(logger, "Failed to get inclusion backing state. (para={}, relay " @@ -472,11 +529,19 @@ namespace kagome::parachain { compact_pending, mode->max_candidate_depth, ancestry)); + + SL_TRACE(logger, + "Create fragment. " + "(relay_parent={}, para={}, min_relay_parent={})", + hash, + para, + scope.earliestRelayParent().number); fragment_trees.emplace(para, fragment::FragmentTree::populate( hasher_, scope, candidate_storage)); } + SL_TRACE(logger, "Insert active leave. (relay parent={})", hash); view.active_leaves.emplace( hash, RelayBlockViewData{fragment_trees, pending_availability}); } diff --git a/test/core/parachain/prospective_parachains.cpp b/test/core/parachain/prospective_parachains.cpp index ad08f58a04..c0d290ae8e 100644 --- a/test/core/parachain/prospective_parachains.cpp +++ b/test/core/parachain/prospective_parachains.cpp @@ -152,6 +152,29 @@ class ProspectiveParachainsTest : public testing::Test { }; } + std::pair + make_and_back_candidate(const TestState &test_state, + const TestLeaf &leaf, + const network::CommittedCandidateReceipt &parent, + uint64_t index) { + auto tmp = make_candidate(leaf.hash, + leaf.number, + 1, + parent.commitments.para_head, + {uint8_t(index)}, + test_state.validation_code_hash); + + tmp.first.descriptor.para_head_hash = fromNumber(index); + const auto &[candidate, pvd] = tmp; + const Hash candidate_hash = network::candidateHash(*hasher_, candidate); + + introduce_candidate(candidate, pvd); + second_candidate(candidate); + back_candidate(candidate, candidate_hash); + + return {candidate, candidate_hash}; + } + std::pair make_candidate(const Hash &relay_parent_hash, @@ -495,13 +518,14 @@ class ProspectiveParachainsTest : public testing::Test { network::candidateHash(*hasher_, candidate)); } - auto get_backable_candidate( + auto get_backable_candidates( const TestLeaf &leaf, ParachainId para_id, std::vector required_path, - const std::optional> &expected_result) { - auto resp = prospective_parachain_->answerGetBackableCandidate( - leaf.hash, para_id, required_path); + uint32_t count, + const std::vector> &expected_result) { + auto resp = prospective_parachain_->answerGetBackableCandidates( + leaf.hash, para_id, count, required_path); ASSERT_EQ(resp, expected_result); } @@ -935,7 +959,8 @@ TEST_F(ProspectiveParachainsTest, FragmentTree_checkCandidateOnMultipleForks) { } } -TEST_F(ProspectiveParachainsTest, FragmentTree_checkBackableQuery) { +TEST_F(ProspectiveParachainsTest, + FragmentTree_checkBackableQuerySingleCandidate) { TestState test_state(hasher_); TestLeaf leaf_a{ .number = 100, @@ -972,24 +997,29 @@ TEST_F(ProspectiveParachainsTest, FragmentTree_checkBackableQuery) { introduce_candidate(candidate_a, pvd_a); introduce_candidate(candidate_b, pvd_b); - get_backable_candidate(leaf_a, 1, {candidate_hash_a}, std::nullopt); + get_backable_candidates(leaf_a, 1, {candidate_hash_a}, 1, {}); + get_backable_candidates(leaf_a, 1, {candidate_hash_a}, 0, {}); + get_backable_candidates(leaf_a, 1, {}, 0, {}); second_candidate(candidate_a); second_candidate(candidate_b); - get_backable_candidate(leaf_a, 1, {candidate_hash_a}, std::nullopt); + get_backable_candidates(leaf_a, 1, {candidate_hash_a}, 1, {}); back_candidate(candidate_a, candidate_hash_a); back_candidate(candidate_b, candidate_hash_b); - get_backable_candidate( - leaf_a, 1, {}, std::make_pair(candidate_hash_a, leaf_a.hash)); - get_backable_candidate(leaf_a, - 1, - {candidate_hash_a}, - std::make_pair(candidate_hash_b, leaf_a.hash)); + // Should not get any backable candidates for the other para. + get_backable_candidates(leaf_a, 2, {}, 1, {}); + get_backable_candidates(leaf_a, 2, {candidate_hash_a}, 1, {}); + + // Get backable candidate. + get_backable_candidates(leaf_a, 1, {}, 1, {{candidate_hash_a, leaf_a.hash}}); + + get_backable_candidates( + leaf_a, 1, {candidate_hash_a}, 1, {{candidate_hash_b, leaf_a.hash}}); - get_backable_candidate(leaf_a, 1, {candidate_hash_b}, std::nullopt); + get_backable_candidates(leaf_a, 1, {candidate_hash_b}, 1, {}); ASSERT_EQ(prospective_parachain_->view.active_leaves.size(), 1); ASSERT_EQ(prospective_parachain_->view.candidate_storage.size(), 2); @@ -1006,6 +1036,351 @@ TEST_F(ProspectiveParachainsTest, FragmentTree_checkBackableQuery) { } } +TEST_F(ProspectiveParachainsTest, + FragmentTree_checkBackableQueryMultipleCandidates_1) { + // Parachain 1 looks like this: + // +---A----+ + // | | + // +----B---+ C + // | | | | + // D E F H + // | | + // G I + // | + // J + TestState test_state(hasher_); + TestLeaf leaf_a{ + .number = 100, + .hash = fromNumber(130), + .para_data = + { + {1, PerParaData(97, {1, 2, 3})}, + {2, PerParaData(100, {2, 3, 4})}, + }, + }; + + fragment::AsyncBackingParams async_backing_params{ + .max_candidate_depth = 4, + .allowed_ancestry_len = ALLOWED_ANCESTRY_LEN, + }; + + activate_leaf(leaf_a, test_state, async_backing_params); + + const auto &[candidate_a, pvd_a] = + make_candidate(leaf_a.hash, + leaf_a.number, + 1, + {1, 2, 3}, + {1}, + test_state.validation_code_hash); + const Hash candidate_hash_a = network::candidateHash(*hasher_, candidate_a); + introduce_candidate(candidate_a, pvd_a); + second_candidate(candidate_a); + back_candidate(candidate_a, candidate_hash_a); + + const auto &[candidate_b, candidate_hash_b] = + make_and_back_candidate(test_state, leaf_a, candidate_a, 2); + const auto &[candidate_c, candidate_hash_c] = + make_and_back_candidate(test_state, leaf_a, candidate_a, 3); + const auto &[_candidate_d, candidate_hash_d] = + make_and_back_candidate(test_state, leaf_a, candidate_b, 4); + const auto &[_candidate_e, candidate_hash_e] = + make_and_back_candidate(test_state, leaf_a, candidate_b, 5); + const auto &[candidate_f, candidate_hash_f] = + make_and_back_candidate(test_state, leaf_a, candidate_b, 6); + const auto &[_candidate_g, candidate_hash_g] = + make_and_back_candidate(test_state, leaf_a, candidate_f, 7); + const auto &[candidate_h, candidate_hash_h] = + make_and_back_candidate(test_state, leaf_a, candidate_c, 8); + const auto &[candidate_i, candidate_hash_i] = + make_and_back_candidate(test_state, leaf_a, candidate_h, 9); + const auto &[_candidate_j, candidate_hash_j] = + make_and_back_candidate(test_state, leaf_a, candidate_i, 10); + + get_backable_candidates(leaf_a, 2, {}, 1, {}); + get_backable_candidates(leaf_a, 2, {}, 5, {}); + get_backable_candidates(leaf_a, 2, {candidate_hash_a}, 1, {}); + + // empty required_path + get_backable_candidates(leaf_a, 1, {}, 1, {{candidate_hash_a, leaf_a.hash}}); + get_backable_candidates(leaf_a, + 1, + {}, + 4, + {{candidate_hash_a, leaf_a.hash}, + {candidate_hash_b, leaf_a.hash}, + {candidate_hash_f, leaf_a.hash}, + {candidate_hash_g, leaf_a.hash}}); + + // required path of 1 + get_backable_candidates( + leaf_a, 1, {candidate_hash_a}, 1, {{candidate_hash_b, leaf_a.hash}}); + get_backable_candidates( + leaf_a, + 1, + {candidate_hash_a}, + 2, + {{candidate_hash_b, leaf_a.hash}, {candidate_hash_d, leaf_a.hash}}); + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a}, + 3, + {{candidate_hash_b, leaf_a.hash}, + {candidate_hash_f, leaf_a.hash}, + {candidate_hash_g, leaf_a.hash}}); + + for (uint32_t count = 5; count < 10; ++count) { + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a}, + count, + {{candidate_hash_c, leaf_a.hash}, + {candidate_hash_h, leaf_a.hash}, + {candidate_hash_i, leaf_a.hash}, + {candidate_hash_j, leaf_a.hash}}); + } + + // required path of 2 + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a, candidate_hash_b}, + 1, + {{candidate_hash_d, leaf_a.hash}}); + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a, candidate_hash_c}, + 1, + {{candidate_hash_h, leaf_a.hash}}); + for (uint32_t count = 4; count < 10; ++count) { + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a, candidate_hash_c}, + count, + {{candidate_hash_h, leaf_a.hash}, + {candidate_hash_i, leaf_a.hash}, + {candidate_hash_j, leaf_a.hash}}); + } + + // No more candidates in any chain. + { + std::vector> required_paths = { + {candidate_hash_a, candidate_hash_b, candidate_hash_e}, + {candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j}}; + + for (const auto &path : required_paths) { + for (uint32_t count = 1; count < 4; ++count) { + get_backable_candidates(leaf_a, 1, path, count, {}); + } + } + } + + // Should not get anything at the wrong path. + get_backable_candidates(leaf_a, 1, {candidate_hash_b}, 1, {}); + get_backable_candidates( + leaf_a, 1, {candidate_hash_b, candidate_hash_a}, 3, {}); + get_backable_candidates( + leaf_a, 1, {candidate_hash_a, candidate_hash_b, candidate_hash_c}, 3, {}); + + ASSERT_EQ(prospective_parachain_->view.active_leaves.size(), 1); + ASSERT_EQ(prospective_parachain_->view.candidate_storage.size(), 2); + + { + auto it = prospective_parachain_->view.candidate_storage.find(1); + ASSERT_TRUE(it != prospective_parachain_->view.candidate_storage.end()); + ASSERT_EQ(it->second.len(), std::make_pair(size_t(7), size_t(10))); + } + { + auto it = prospective_parachain_->view.candidate_storage.find(2); + ASSERT_TRUE(it != prospective_parachain_->view.candidate_storage.end()); + ASSERT_EQ(it->second.len(), std::make_pair(size_t(0), size_t(0))); + } +} + +TEST_F(ProspectiveParachainsTest, + FragmentTree_checkBackableQueryMultipleCandidates_2) { + // A tree with multiple roots. + // Parachain 1 looks like this: + // (imaginary root) + // | | + // +----B---+ A + // | | | | + // | | | C + // D E F | + // | H + // G | + // I + // | + // J + TestState test_state(hasher_); + TestLeaf leaf_a{ + .number = 100, + .hash = fromNumber(130), + .para_data = + { + {1, PerParaData(97, {1, 2, 3})}, + {2, PerParaData(100, {2, 3, 4})}, + }, + }; + + fragment::AsyncBackingParams async_backing_params{ + .max_candidate_depth = 4, + .allowed_ancestry_len = ALLOWED_ANCESTRY_LEN, + }; + + activate_leaf(leaf_a, test_state, async_backing_params); + + const auto &[candidate_b, pvd_b] = + make_candidate(leaf_a.hash, + leaf_a.number, + 1, + {1, 2, 3}, + {2}, + test_state.validation_code_hash); + const Hash candidate_hash_b = network::candidateHash(*hasher_, candidate_b); + introduce_candidate(candidate_b, pvd_b); + second_candidate(candidate_b); + back_candidate(candidate_b, candidate_hash_b); + + const auto &[candidate_a, pvd_a] = + make_candidate(leaf_a.hash, + leaf_a.number, + 1, + {1, 2, 3}, + {1}, + test_state.validation_code_hash); + const Hash candidate_hash_a = network::candidateHash(*hasher_, candidate_a); + introduce_candidate(candidate_a, pvd_a); + second_candidate(candidate_a); + back_candidate(candidate_a, candidate_hash_a); + + const auto &[candidate_c, candidate_hash_c] = + make_and_back_candidate(test_state, leaf_a, candidate_a, 3); + const auto &[_candidate_d, candidate_hash_d] = + make_and_back_candidate(test_state, leaf_a, candidate_b, 4); + const auto &[_candidate_e, candidate_hash_e] = + make_and_back_candidate(test_state, leaf_a, candidate_b, 5); + const auto &[candidate_f, candidate_hash_f] = + make_and_back_candidate(test_state, leaf_a, candidate_b, 6); + const auto &[_candidate_g, candidate_hash_g] = + make_and_back_candidate(test_state, leaf_a, candidate_f, 7); + const auto &[candidate_h, candidate_hash_h] = + make_and_back_candidate(test_state, leaf_a, candidate_c, 8); + const auto &[candidate_i, candidate_hash_i] = + make_and_back_candidate(test_state, leaf_a, candidate_h, 9); + const auto &[_candidate_j, candidate_hash_j] = + make_and_back_candidate(test_state, leaf_a, candidate_i, 10); + + // Should not get any backable candidates for the other para. + get_backable_candidates(leaf_a, 2, {}, 1, {}); + get_backable_candidates(leaf_a, 2, {}, 5, {}); + get_backable_candidates(leaf_a, 2, {candidate_hash_a}, 1, {}); + + // empty required_path + get_backable_candidates(leaf_a, 1, {}, 1, {{candidate_hash_b, leaf_a.hash}}); + get_backable_candidates( + leaf_a, + 1, + {}, + 2, + {{candidate_hash_b, leaf_a.hash}, {candidate_hash_d, leaf_a.hash}}); + get_backable_candidates(leaf_a, + 1, + {}, + 4, + {{candidate_hash_a, leaf_a.hash}, + {candidate_hash_c, leaf_a.hash}, + {candidate_hash_h, leaf_a.hash}, + {candidate_hash_i, leaf_a.hash}}); + + // required path of 1 + get_backable_candidates( + leaf_a, 1, {candidate_hash_a}, 1, {{candidate_hash_c, leaf_a.hash}}); + get_backable_candidates( + leaf_a, 1, {candidate_hash_b}, 1, {{candidate_hash_d, leaf_a.hash}}); + get_backable_candidates( + leaf_a, + 1, + {candidate_hash_a}, + 2, + {{candidate_hash_c, leaf_a.hash}, {candidate_hash_h, leaf_a.hash}}); + + for (uint32_t count = 2; count < 10; ++count) { + get_backable_candidates( + leaf_a, + 1, + {candidate_hash_b}, + count, + {{candidate_hash_f, leaf_a.hash}, {candidate_hash_g, leaf_a.hash}}); + } + + // required path of 2 + get_backable_candidates(leaf_a, + 1, + {candidate_hash_b, candidate_hash_f}, + 1, + {{candidate_hash_g, leaf_a.hash}}); + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a, candidate_hash_c}, + 1, + {{candidate_hash_h, leaf_a.hash}}); + for (uint32_t count = 4; count < 10; ++count) { + get_backable_candidates(leaf_a, + 1, + {candidate_hash_a, candidate_hash_c}, + count, + {{candidate_hash_h, leaf_a.hash}, + {candidate_hash_i, leaf_a.hash}, + {candidate_hash_j, leaf_a.hash}}); + } + + // No more candidates in any chain. + { + std::vector> required_paths = { + {candidate_hash_b, candidate_hash_f, candidate_hash_g}, + {candidate_hash_b, candidate_hash_e}, + {candidate_hash_b, candidate_hash_d}, + { + candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j, + }}; + + for (const auto &path : required_paths) { + for (uint32_t count = 1; count < 4; ++count) { + get_backable_candidates(leaf_a, 1, path, count, {}); + } + } + } + + // Should not get anything at the wrong path. + get_backable_candidates(leaf_a, 1, {candidate_hash_d}, 1, {}); + get_backable_candidates( + leaf_a, 1, {candidate_hash_b, candidate_hash_a}, 3, {}); + get_backable_candidates( + leaf_a, 1, {candidate_hash_a, candidate_hash_c, candidate_hash_d}, 3, {}); + + ASSERT_EQ(prospective_parachain_->view.active_leaves.size(), 1); + ASSERT_EQ(prospective_parachain_->view.candidate_storage.size(), 2); + + { + auto it = prospective_parachain_->view.candidate_storage.find(1); + ASSERT_TRUE(it != prospective_parachain_->view.candidate_storage.end()); + ASSERT_EQ(it->second.len(), std::make_pair(size_t(7), size_t(10))); + } + { + auto it = prospective_parachain_->view.candidate_storage.find(2); + ASSERT_TRUE(it != prospective_parachain_->view.candidate_storage.end()); + ASSERT_EQ(it->second.len(), std::make_pair(size_t(0), size_t(0))); + } +} + TEST_F(ProspectiveParachainsTest, FragmentTree_checkHypotheticalFrontierQuery) { TestState test_state(hasher_); TestLeaf leaf_a{ @@ -1208,10 +1583,11 @@ TEST_F(ProspectiveParachainsTest, second_candidate(candidate_b); back_candidate(candidate_b, candidate_hash_b); - get_backable_candidate(leaf_b, - para_id, - {candidate_hash_a}, - std::make_pair(candidate_hash_b, leaf_b_hash)); + get_backable_candidates(leaf_b, + para_id, + {candidate_hash_a}, + 1, + {{candidate_hash_b, leaf_b_hash}}); } TEST_F(ProspectiveParachainsTest, FragmentTree_backwardsCompatible) { @@ -1253,11 +1629,8 @@ TEST_F(ProspectiveParachainsTest, FragmentTree_backwardsCompatible) { second_candidate(candidate_a); back_candidate(candidate_a, candidate_hash_a); - get_backable_candidate( - leaf_a, - para_id, - {}, - std::make_pair(candidate_hash_a, candidate_relay_parent)); + get_backable_candidates( + leaf_a, para_id, {}, 1, {{candidate_hash_a, candidate_relay_parent}}); TestLeaf leaf_b{ .number = candidate_relay_parent_number + 1, @@ -1276,7 +1649,7 @@ TEST_F(ProspectiveParachainsTest, FragmentTree_backwardsCompatible) { .allowed_ancestry_len = 0, }); - get_backable_candidate(leaf_b, para_id, {}, std::nullopt); + get_backable_candidates(leaf_b, para_id, {}, 1, {}); } TEST_F(ProspectiveParachainsTest, FragmentTree_usesAncestryOnlyWithinSession) { diff --git a/test/mock/core/network/peer_manager_mock.hpp b/test/mock/core/network/peer_manager_mock.hpp index 86e2273a2d..0271d3e7e6 100644 --- a/test/mock/core/network/peer_manager_mock.hpp +++ b/test/mock/core/network/peer_manager_mock.hpp @@ -61,6 +61,11 @@ namespace kagome::network { (const PeerId &), (override)); + MOCK_METHOD(std::optional>, + getPeerState, + (const PeerId &), + (const, override)); + MOCK_METHOD(size_t, activePeersNumber, (), (const, override)); MOCK_METHOD(void,