From f01605525f448baadf36393c28ea8a014807b78b Mon Sep 17 00:00:00 2001 From: Ruslan Tushov Date: Thu, 28 Nov 2024 10:20:22 +0500 Subject: [PATCH] rfc 103, candidate core and session (#2282) Signed-off-by: turuslan --- core/network/peer_state.hpp | 2 - .../types/collator_messages_vstaging.hpp | 7 +- .../approval/approval_distribution.cpp | 12 +- .../availability/availability_chunk_index.hpp | 21 +-- .../availability/bitfield/signer.cpp | 4 +- .../availability/recovery/recovery_impl.cpp | 3 +- core/parachain/candidate_descriptor_v2.hpp | 169 ++++++++++++++++++ core/parachain/pvf/pvf_error.hpp | 30 ++++ core/parachain/pvf/pvf_impl.cpp | 39 +++- core/parachain/pvf/pvf_impl.hpp | 19 -- core/parachain/transpose_claim_queue.hpp | 34 ++++ core/parachain/types.hpp | 5 +- core/parachain/ump_signal.hpp | 74 ++++++++ .../validator/impl/parachain_processor.cpp | 37 ++-- .../validator/parachain_processor.hpp | 7 +- .../prospective_parachains/fragment.cpp | 8 +- .../per_relay_parent_state.hpp | 3 + .../statement_distribution.cpp | 43 +++++ .../statement_distribution.hpp | 4 + .../runtime_api/impl/parachain_host.cpp | 12 +- .../runtime_api/impl/parachain_host.hpp | 4 +- core/runtime/runtime_api/node_features.hpp | 56 ++++++ core/runtime/runtime_api/parachain_host.hpp | 34 +--- .../parachain/availability/recovery_test.cpp | 12 +- .../core/parachain/parachain_test_harness.hpp | 4 +- .../core/parachain/prospective_parachains.cpp | 4 +- .../mock/core/runtime/parachain_host_mock.hpp | 4 +- 27 files changed, 513 insertions(+), 138 deletions(-) create mode 100644 core/parachain/candidate_descriptor_v2.hpp create mode 100644 core/parachain/pvf/pvf_error.hpp create mode 100644 core/parachain/transpose_claim_queue.hpp create mode 100644 core/parachain/ump_signal.hpp create mode 100644 core/runtime/runtime_api/node_features.hpp diff --git a/core/network/peer_state.hpp b/core/network/peer_state.hpp index 109d478358..427b731035 100644 --- a/core/network/peer_state.hpp +++ b/core/network/peer_state.hpp @@ -99,7 +99,6 @@ namespace kagome::network { template <> struct std::hash { size_t operator()(const kagome::network::FetchedCollation &value) const { - using CollatorId = kagome::parachain::CollatorId; using CandidateHash = kagome::parachain::CandidateHash; using RelayHash = kagome::parachain::RelayHash; using ParachainId = kagome::parachain::ParachainId; @@ -108,7 +107,6 @@ struct std::hash { boost::hash_combine(result, std::hash()(value.para_id)); boost::hash_combine(result, std::hash()(value.candidate_hash)); - boost::hash_combine(result, std::hash()(value.collator_id)); return result; } diff --git a/core/network/types/collator_messages_vstaging.hpp b/core/network/types/collator_messages_vstaging.hpp index 48ea8582a2..424b5b28da 100644 --- a/core/network/types/collator_messages_vstaging.hpp +++ b/core/network/types/collator_messages_vstaging.hpp @@ -447,16 +447,12 @@ namespace kagome::network { }; struct FetchedCollation { - SCALE_TIE(4); - /// Candidate's relay parent. RelayHash relay_parent; /// Parachain id. ParachainId para_id; /// Candidate hash. CandidateHash candidate_hash; - /// Id of the collator the collation was fetched from. - CollatorId collator_id; static FetchedCollation from(const network::CandidateReceipt &receipt, const crypto::Hasher &hasher) { @@ -465,9 +461,10 @@ namespace kagome::network { .relay_parent = descriptor.relay_parent, .para_id = descriptor.para_id, .candidate_hash = receipt.hash(hasher), - .collator_id = descriptor.collator_id, }; } + + bool operator==(const FetchedCollation &) const = default; }; /** diff --git a/core/parachain/approval/approval_distribution.cpp b/core/parachain/approval/approval_distribution.cpp index 10b9de6562..03f701184b 100644 --- a/core/parachain/approval/approval_distribution.cpp +++ b/core/parachain/approval/approval_distribution.cpp @@ -1115,15 +1115,9 @@ namespace kagome::parachain { } bool enable_v2_assignments = false; - if (auto r = parachain_host_->node_features(block_hash, session_index); - r.has_value()) { - if (r.value() - && r.value()->bits.size() > runtime::ParachainHost::NodeFeatureIndex:: - EnableAssignmentsV2) { - enable_v2_assignments = - r.value()->bits - [runtime::ParachainHost::NodeFeatureIndex::EnableAssignmentsV2]; - } + if (auto r = parachain_host_->node_features(block_hash); r.has_value()) { + enable_v2_assignments = + r.value().has(runtime::NodeFeatures::EnableAssignmentsV2); } approval::UnsafeVRFOutput unsafe_vrf{ diff --git a/core/parachain/availability/availability_chunk_index.hpp b/core/parachain/availability/availability_chunk_index.hpp index d71d9eafaa..0f1deaac79 100644 --- a/core/parachain/availability/availability_chunk_index.hpp +++ b/core/parachain/availability/availability_chunk_index.hpp @@ -44,23 +44,8 @@ namespace kagome::parachain { } inline bool availability_chunk_mapping_is_enabled( - std::optional node_features) { - // none if node_features is not defined - [[unlikely]] if (not node_features.has_value()) { // - return false; - } - - const auto &features = node_features.value(); - - static const auto feature = - runtime::ParachainHost::NodeFeatureIndex::AvailabilityChunkMapping; - - // none if needed feature is out of bound - [[unlikely]] if (feature >= features.bits.size()) { // - return false; - } - - return features.bits[feature]; + const runtime::NodeFeatures &node_features) { + return node_features.has(runtime::NodeFeatures::AvailabilityChunkMapping); } /// Compute the per-validator availability chunk index. @@ -68,7 +53,7 @@ namespace kagome::parachain { /// Any modification to the output of the function needs to be coordinated via /// the runtime. It's best to use minimal/no external dependencies. inline outcome::result availability_chunk_index( - std::optional node_features, + const runtime::NodeFeatures &node_features, size_t n_validators, CoreIndex core_index, ValidatorIndex validator_index) { diff --git a/core/parachain/availability/bitfield/signer.cpp b/core/parachain/availability/bitfield/signer.cpp index f138065aed..640556cc5e 100644 --- a/core/parachain/availability/bitfield/signer.cpp +++ b/core/parachain/availability/bitfield/signer.cpp @@ -79,9 +79,7 @@ namespace kagome::parachain { OUTCOME_TRY( session, parachain_api_->session_info(relay_parent, signer->getSessionIndex())); - OUTCOME_TRY( - node_features, - parachain_api_->node_features(relay_parent, signer->getSessionIndex())); + OUTCOME_TRY(node_features, parachain_api_->node_features(relay_parent)); candidates.reserve(cores.size()); for (CoreIndex core_index = 0; core_index < cores.size(); ++core_index) { auto &core = cores[core_index]; diff --git a/core/parachain/availability/recovery/recovery_impl.cpp b/core/parachain/availability/recovery/recovery_impl.cpp index 4da6689c0e..3f8004a43b 100644 --- a/core/parachain/availability/recovery/recovery_impl.cpp +++ b/core/parachain/availability/recovery/recovery_impl.cpp @@ -134,8 +134,7 @@ namespace kagome::parachain { cb(_min.error()); return; } - auto _node_features = - parachain_api_->node_features(block.hash, session_index); + auto _node_features = parachain_api_->node_features(block.hash); if (_node_features.has_error()) { lock.unlock(); cb(_node_features.error()); diff --git a/core/parachain/candidate_descriptor_v2.hpp b/core/parachain/candidate_descriptor_v2.hpp new file mode 100644 index 0000000000..a728adb1d9 --- /dev/null +++ b/core/parachain/candidate_descriptor_v2.hpp @@ -0,0 +1,169 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "crypto/sr25519_provider.hpp" +#include "parachain/pvf/pvf_error.hpp" +#include "parachain/transpose_claim_queue.hpp" +#include "parachain/types.hpp" +#include "parachain/ump_signal.hpp" + +namespace kagome::network { + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L44 + /// The default claim queue offset to be used if it's not + /// configured/accessible in the parachain + /// runtime + constexpr uint8_t kDefaultClaimQueueOffset = 0; + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L532-L534 + inline bool isV1(const CandidateDescriptor &descriptor) { + constexpr auto is_zero = [](BufferView xs) { + return static_cast(std::ranges::count(xs, 0)) == xs.size(); + }; + return not is_zero(std::span{descriptor.reserved_1}.subspan(7)) + or not is_zero(descriptor.reserved_2); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L532-L537 + inline bool isV2(const CandidateDescriptor &descriptor) { + return not isV1(descriptor) and descriptor.reserved_1[0] == 0; + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L179 + /// Check the signature of the collator within this descriptor. + inline outcome::result checkSignature( + const crypto::Sr25519Provider &sr25519, + const CandidateDescriptor &descriptor) { + if (not isV1(descriptor)) { + return outcome::success(); + } + OUTCOME_TRY( + r, + sr25519.verify(crypto::Sr25519Signature{descriptor.reserved_2}, + descriptor.signable(), + crypto::Sr25519PublicKey{descriptor.reserved_1})); + if (not r) { + return PvfError::SIGNATURE; + } + return outcome::success(); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L580 + /// Returns the `core_index` of `V2` candidate descriptors, `None` otherwise. + inline std::optional coreIndex( + const CandidateDescriptor &descriptor) { + if (isV1(descriptor)) { + return std::nullopt; + } + return boost::endian::load_little_u16(&descriptor.reserved_1[1]); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L589 + /// Returns the `core_index` of `V2` candidate descriptors, `None` otherwise. + inline std::optional sessionIndex( + const CandidateDescriptor &descriptor) { + if (isV1(descriptor)) { + return std::nullopt; + } + return boost::endian::load_little_u32(&descriptor.reserved_1[3]); + } + + enum class CheckCoreIndexError : uint8_t { + InvalidCoreIndex, + NoAssignment, + UnknownVersion, + InvalidSession, + }; + Q_ENUM_ERROR_CODE(CheckCoreIndexError) { + using E = decltype(e); + switch (e) { + case E::InvalidCoreIndex: + return "The specified core index is invalid"; + case E::NoAssignment: + return "The parachain is not assigned to any core at specified claim " + "queue offset"; + case E::UnknownVersion: + return "Unknown internal version"; + case E::InvalidSession: + return "Invalid session"; + } + abort(); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L602 + /// Checks if descriptor core index is equal to the committed core index. + /// Input `cores_per_para` is a claim queue snapshot at the candidate's relay + /// parent, stored as a mapping between `ParaId` and the cores assigned per + /// depth. + inline outcome::result checkCoreIndex( + const CommittedCandidateReceipt &receipt, + const TransposedClaimQueue &claims) { + if (isV1(receipt.descriptor)) { + return outcome::success(); + } + if (not isV2(receipt.descriptor)) { + return CheckCoreIndexError::UnknownVersion; + } + OUTCOME_TRY(selector, coreSelector(receipt.commitments)); + auto offset = + selector ? selector->claim_queue_offset : kDefaultClaimQueueOffset; + auto it1 = claims.find(receipt.descriptor.para_id); + if (it1 == claims.end()) { + return CheckCoreIndexError::NoAssignment; + } + auto it2 = it1->second.find(offset); + if (it2 == it1->second.end()) { + return CheckCoreIndexError::NoAssignment; + } + auto &assigned_cores = it2->second; + if (assigned_cores.empty()) { + return CheckCoreIndexError::NoAssignment; + } + auto core = coreIndex(receipt.descriptor).value(); + if (not selector and assigned_cores.size() > 1) { + if (not assigned_cores.contains(core)) { + return CheckCoreIndexError::InvalidCoreIndex; + } + return outcome::success(); + } + auto expected_core = + *std::next(assigned_cores.begin(), + selector ? static_cast(selector->core_selector + % assigned_cores.size()) + : 0); + if (core != expected_core) { + return CheckCoreIndexError::InvalidCoreIndex; + } + return outcome::success(); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L2114 + inline outcome::result descriptorVersionSanityCheck( + const CandidateDescriptor &descriptor, + bool v2_receipts, + CoreIndex expected_core, + SessionIndex expected_session) { + if (isV1(descriptor)) { + return outcome::success(); + } + if (not isV2(descriptor)) { + return CheckCoreIndexError::UnknownVersion; + } + if (not v2_receipts) { + return CheckCoreIndexError::UnknownVersion; + } + if (coreIndex(descriptor) != expected_core) { + return CheckCoreIndexError::InvalidCoreIndex; + } + if (sessionIndex(descriptor) != expected_session) { + return CheckCoreIndexError::InvalidSession; + } + return outcome::success(); + } +} // namespace kagome::network diff --git a/core/parachain/pvf/pvf_error.hpp b/core/parachain/pvf/pvf_error.hpp new file mode 100644 index 0000000000..0cad353ae8 --- /dev/null +++ b/core/parachain/pvf/pvf_error.hpp @@ -0,0 +1,30 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +namespace kagome::parachain { + enum class PvfError : uint8_t { + // NO_DATA conflicted with + NO_PERSISTED_DATA = 1, + POV_SIZE, + POV_HASH, + CODE_HASH, + SIGNATURE, + HEAD_HASH, + COMMITMENTS_HASH, + OUTPUTS, + PERSISTED_DATA_HASH, + NO_CODE, + COMPILATION_ERROR, + }; +} // namespace kagome::parachain + +OUTCOME_HPP_DECLARE_ERROR(kagome::parachain, PvfError) diff --git a/core/parachain/pvf/pvf_impl.cpp b/core/parachain/pvf/pvf_impl.cpp index 06d9bad961..327a8128c6 100644 --- a/core/parachain/pvf/pvf_impl.cpp +++ b/core/parachain/pvf/pvf_impl.cpp @@ -14,8 +14,10 @@ #include "common/visitor.hpp" #include "log/profiling_logger.hpp" #include "metrics/histogram_timer.hpp" +#include "parachain/candidate_descriptor_v2.hpp" #include "parachain/pvf/module_precompiler.hpp" #include "parachain/pvf/pool.hpp" +#include "parachain/pvf/pvf_error.hpp" #include "parachain/pvf/pvf_thread_pool.hpp" #include "parachain/pvf/pvf_worker_types.hpp" #include "parachain/pvf/session_params.hpp" @@ -222,6 +224,18 @@ namespace kagome::parachain { code_zstd, timeout_kind, std::move(cb)); + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/node/core/candidate-validation/src/lib.rs#L763-L782 + auto session = sessionIndex(receipt.descriptor); + if (session and timeout_kind == runtime::PvfExecTimeoutKind::Backing) { + CB_TRY(auto expected_session, + parachain_api_->session_index_for_child( + receipt.descriptor.relay_parent)); + if (sessionIndex(receipt.descriptor) != expected_session) { + cb(network::CheckCoreIndexError::InvalidSession); + return; + } + } + CB_TRY(auto pov_encoded, scale::encode(pov)); if (pov_encoded.size() > data.max_pov_size) { return cb(PvfError::POV_SIZE); @@ -234,13 +248,7 @@ namespace kagome::parachain { if (code_hash != receipt.descriptor.validation_code_hash) { return cb(PvfError::CODE_HASH); } - CB_TRY(auto signature_valid, - sr25519_provider_->verify(receipt.descriptor.signature, - receipt.descriptor.signable(), - receipt.descriptor.collator_id)); - if (!signature_valid) { - return cb(PvfError::SIGNATURE); - } + CB_TRYV(checkSignature(*sr25519_provider_, receipt.descriptor)); auto timer = metric_pvf_execution_time.timer(); ValidationParams params; @@ -257,6 +265,7 @@ namespace kagome::parachain { libp2p::SharedFn{[weak_self{weak_from_this()}, data, receipt, + timeout_kind, cb{std::move(cb)}, timer{std::move(timer)}]( outcome::result r) { @@ -267,6 +276,22 @@ namespace kagome::parachain { CB_TRY(auto result, std::move(r)); CB_TRY(auto commitments, self->fromOutputs(receipt, std::move(result))); + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/node/core/candidate-validation/src/lib.rs#L915-L951 + if (timeout_kind == runtime::PvfExecTimeoutKind::Backing + and coreIndex(receipt.descriptor)) { + CB_TRY(auto claims, + self->parachain_api_->claim_queue( + receipt.descriptor.relay_parent)); + if (not claims) { + claims.emplace(); + } + CB_TRYV(network::checkCoreIndex( + { + .descriptor = receipt.descriptor, + .commitments = commitments, + }, + transposeClaimQueue(*claims))); + } cb(std::make_pair(std::move(commitments), data)); }}); } diff --git a/core/parachain/pvf/pvf_impl.hpp b/core/parachain/pvf/pvf_impl.hpp index 47c2aac7e8..24af532134 100644 --- a/core/parachain/pvf/pvf_impl.hpp +++ b/core/parachain/pvf/pvf_impl.hpp @@ -34,25 +34,6 @@ namespace kagome::runtime { class RuntimeInstancesPool; } // namespace kagome::runtime -namespace kagome::parachain { - enum class PvfError { - // NO_DATA conflicted with - NO_PERSISTED_DATA = 1, - POV_SIZE, - POV_HASH, - CODE_HASH, - SIGNATURE, - HEAD_HASH, - COMMITMENTS_HASH, - OUTPUTS, - PERSISTED_DATA_HASH, - NO_CODE, - COMPILATION_ERROR - }; -} // namespace kagome::parachain - -OUTCOME_HPP_DECLARE_ERROR(kagome::parachain, PvfError) - namespace kagome::parachain { class PvfPool; class PvfThreadPool; diff --git a/core/parachain/transpose_claim_queue.hpp b/core/parachain/transpose_claim_queue.hpp new file mode 100644 index 0000000000..9e487f7b8c --- /dev/null +++ b/core/parachain/transpose_claim_queue.hpp @@ -0,0 +1,34 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include "runtime/runtime_api/parachain_host_types.hpp" + +namespace kagome::parachain { + /// The claim queue mapped by parachain id. + using TransposedClaimQueue = + std::map>>; + + /// Returns a mapping between the para id and the core indices assigned at + /// different + /// depths in the claim queue. + inline TransposedClaimQueue transposeClaimQueue( + const runtime::ClaimQueueSnapshot &claims) { + TransposedClaimQueue r; + for (auto &[core, paras] : claims.claimes) { + size_t depth = 0; + for (auto ¶ : paras) { + r[para][depth].emplace(core); + ++depth; + } + } + return r; + } +} // namespace kagome::parachain diff --git a/core/parachain/types.hpp b/core/parachain/types.hpp index eb7e7cb530..572778dda6 100644 --- a/core/parachain/types.hpp +++ b/core/parachain/types.hpp @@ -156,15 +156,14 @@ namespace kagome::network { primitives::BlockHash relay_parent; /// Hash of the relay chain block the candidate is /// executed in the context of - parachain::CollatorPublicKey collator_id; /// Collators public key. + common::Blob<32> reserved_1; primitives::BlockHash persisted_data_hash; /// Hash of the persisted validation data primitives::BlockHash pov_hash; /// Hash of the PoV block. storage::trie::RootHash erasure_encoding_root; /// Root of the block’s erasure encoding Merkle /// tree. - parachain::Signature - signature; /// Collator signature of the concatenated components + common::Blob<64> reserved_2; primitives::BlockHash para_head_hash; /// Hash of the parachain head data of this candidate. primitives::BlockHash diff --git a/core/parachain/ump_signal.hpp b/core/parachain/ump_signal.hpp new file mode 100644 index 0000000000..a5a0cfda7d --- /dev/null +++ b/core/parachain/ump_signal.hpp @@ -0,0 +1,74 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include + +#include "parachain/types.hpp" + +namespace kagome::parachain { + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L435 + /// Separator between `XCM` and `UMPSignal`. + inline const Buffer kUmpSeparator; + + enum class UmpError : uint8_t { + TooManyUMPSignals, + }; + Q_ENUM_ERROR_CODE(UmpError) { + using E = decltype(e); + switch (e) { + case E::TooManyUMPSignals: + return "Too many UMP signals"; + } + abort(); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L432 + /// A message sent by a parachain to select the core the candidate is + /// committed to. Relay chain validators, in particular backers, use the + /// `CoreSelector` and `ClaimQueueOffset` to compute the index of the core the + /// candidate has committed to. + struct UMPSignalSelectCore { + SCALE_TIE(2); + + uint8_t core_selector; + uint8_t claim_queue_offset; + }; + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L428 + /// Signals that a parachain can send to the relay chain via the UMP queue. + using UMPSignal = boost::variant; + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L438 + /// Utility function for skipping the ump signals. + inline auto skipUmpSignals(std::span messages) { + return messages.first(std::ranges::find(messages, kUmpSeparator) + - messages.begin()); + } + + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/primitives/src/vstaging/mod.rs#L447 + /// Returns the core selector and claim queue offset determined by + /// `UMPSignal::SelectCore` commitment, if present. + inline outcome::result> coreSelector( + const network::CandidateCommitments &commitments) { + auto it = std::ranges::find(commitments.upward_msgs, kUmpSeparator); + if (it == commitments.upward_msgs.end()) { + return std::nullopt; + } + ++it; + if (it == commitments.upward_msgs.end()) { + return std::nullopt; + } + OUTCOME_TRY(signal, scale::decode(*it)); + ++it; + if (it != commitments.upward_msgs.end()) { + return UmpError::TooManyUMPSignals; + } + return boost::get(signal); + } +} // namespace kagome::parachain diff --git a/core/parachain/validator/impl/parachain_processor.cpp b/core/parachain/validator/impl/parachain_processor.cpp index f272db5419..eb819d6e6f 100644 --- a/core/parachain/validator/impl/parachain_processor.cpp +++ b/core/parachain/validator/impl/parachain_processor.cpp @@ -28,6 +28,7 @@ #include "network/router.hpp" #include "parachain/availability/chunks.hpp" #include "parachain/availability/proof.hpp" +#include "parachain/candidate_descriptor_v2.hpp" #include "parachain/candidate_view.hpp" #include "parachain/peer_relay_parent_knowledge.hpp" #include "scale/scale.hpp" @@ -468,17 +469,9 @@ namespace kagome::parachain { return Error::NO_SESSION_INFO; } - bool inject_core_index = false; - if (auto r = parachain_host_->node_features(relay_parent, session_index); - r.has_value()) { - if (r.value() - && r.value()->bits.size() > runtime::ParachainHost::NodeFeatureIndex:: - ElasticScalingMVP) { - inject_core_index = - r.value()->bits - [runtime::ParachainHost::NodeFeatureIndex::ElasticScalingMVP]; - } - } + OUTCOME_TRY(node_features, parachain_host_->node_features(relay_parent)); + auto inject_core_index = + node_features.has(runtime::NodeFeatures::ElasticScalingMVP); uint32_t minimum_backing_votes = 2; /// legacy value if (auto r = @@ -493,8 +486,20 @@ namespace kagome::parachain { } std::optional validator_index; + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L487-L495 + CoreIndex current_core = 0; if (validator) { validator_index = validator->validatorIndex(); + + size_t i_group = 0; + for (auto &group : validator_groups) { + if (group.contains(validator->validatorIndex())) { + current_core = + group_rotation_info.coreForGroup(i_group, cores.size()); + break; + } + ++i_group; + } } OUTCOME_TRY(global_v_index, @@ -592,6 +597,9 @@ namespace kagome::parachain { .fallbacks = {}, .backed_hashes = {}, .inject_core_index = inject_core_index, + .v2_receipts = + node_features.has(runtime::NodeFeatures::CandidateReceiptV2), + .current_core = current_core, .per_session_state = per_session_state, }; } @@ -2679,6 +2687,13 @@ namespace kagome::parachain { auto relay_parent = pending_collation.relay_parent; OUTCOME_TRY(per_relay_parent, getStateByRelayParent(relay_parent)); + + OUTCOME_TRY(descriptorVersionSanityCheck( + pending_collation_fetch.candidate_receipt.descriptor, + per_relay_parent.get().v2_receipts, + per_relay_parent.get().current_core, + per_relay_parent.get().per_session_state->value().session)); + auto &collations = per_relay_parent.get().collations; auto fetched_collation = network::FetchedCollation::from( pending_collation_fetch.candidate_receipt, *hasher_); diff --git a/core/parachain/validator/parachain_processor.hpp b/core/parachain/validator/parachain_processor.hpp index 29a50db570..3a15e5ba24 100644 --- a/core/parachain/validator/parachain_processor.hpp +++ b/core/parachain/validator/parachain_processor.hpp @@ -372,6 +372,8 @@ namespace kagome::parachain { std::unordered_set backed_hashes; bool inject_core_index; + bool v2_receipts; + CoreIndex current_core; std::shared_ptr::RefObj> per_session_state; }; @@ -639,11 +641,6 @@ namespace kagome::parachain { }); } - const network::CollatorPublicKey &collatorIdFromDescriptor( - const network::CandidateDescriptor &descriptor) { - return descriptor.collator_id; - } - /* * Notification */ diff --git a/core/parachain/validator/prospective_parachains/fragment.cpp b/core/parachain/validator/prospective_parachains/fragment.cpp index c04f4d727b..9bdabba5d0 100644 --- a/core/parachain/validator/prospective_parachains/fragment.cpp +++ b/core/parachain/validator/prospective_parachains/fragment.cpp @@ -5,6 +5,8 @@ */ #include "parachain/validator/prospective_parachains/fragment.hpp" + +#include "parachain/ump_signal.hpp" #include "utils/stringify.hpp" #define COMPONENT Fragment @@ -142,7 +144,7 @@ namespace kagome::parachain::fragment { } uint32_t ump_sent_bytes = 0ull; - for (const auto &m : commitments.upward_msgs) { + for (const auto &m : skipUmpSignals(commitments.upward_msgs)) { ump_sent_bytes += uint32_t(m.size()); } @@ -150,9 +152,9 @@ namespace kagome::parachain::fragment { .required_parent = commitments.para_head, .hrmp_watermark = ((commitments.watermark == relay_parent.number) ? HrmpWatermarkUpdate{HrmpWatermarkUpdateHead{ - .v = commitments.watermark}} + .v = commitments.watermark}} : HrmpWatermarkUpdate{HrmpWatermarkUpdateTrunk{ - .v = commitments.watermark}}), + .v = commitments.watermark}}), .outbound_hrmp = outbound_hrmp, .ump_messages_sent = uint32_t(commitments.upward_msgs.size()), .ump_bytes_sent = ump_sent_bytes, diff --git a/core/parachain/validator/statement_distribution/per_relay_parent_state.hpp b/core/parachain/validator/statement_distribution/per_relay_parent_state.hpp index cfe90c8480..78106c7383 100644 --- a/core/parachain/validator/statement_distribution/per_relay_parent_state.hpp +++ b/core/parachain/validator/statement_distribution/per_relay_parent_state.hpp @@ -14,6 +14,7 @@ #include "common/ref_cache.hpp" #include "parachain/backing/cluster.hpp" #include "parachain/backing/grid_tracker.hpp" +#include "parachain/transpose_claim_queue.hpp" #include "parachain/types.hpp" #include "parachain/validator/impl/statements_store.hpp" #include "parachain/validator/statement_distribution/per_session_state.hpp" @@ -47,6 +48,8 @@ namespace kagome::parachain::statement_distribution { SessionIndex session; std::unordered_map> groups_per_para; std::unordered_set disabled_validators; + bool v2_receipts; + TransposedClaimQueue transposed_claim_queue; std::shared_ptr::RefObj> per_session_state; diff --git a/core/parachain/validator/statement_distribution/statement_distribution.cpp b/core/parachain/validator/statement_distribution/statement_distribution.cpp index c177bb9829..52d25e589c 100644 --- a/core/parachain/validator/statement_distribution/statement_distribution.cpp +++ b/core/parachain/validator/statement_distribution/statement_distribution.cpp @@ -8,6 +8,7 @@ #include "network/impl/protocols/fetch_attested_candidate.hpp" #include "network/impl/protocols/parachain.hpp" +#include "parachain/candidate_descriptor_v2.hpp" #include "parachain/validator/parachain_processor.hpp" #include "utils/weak_macro.hpp" @@ -427,6 +428,11 @@ namespace kagome::parachain::statement_distribution { const auto &[_, group_rotation_info] = groups; OUTCOME_TRY(maybe_claim_queue, parachain_host->claim_queue(relay_parent)); + TransposedClaimQueue transposed_claim_queue; + if (maybe_claim_queue) { + transposed_claim_queue = transposeClaimQueue(*maybe_claim_queue); + } + OUTCOME_TRY(node_features, parachain_host->node_features(relay_parent)); auto local_validator = [&]() -> std::optional { if (!per_session_state.value()->value().v_index) { @@ -460,6 +466,9 @@ namespace kagome::parachain::statement_distribution { .session = session_index, .groups_per_para = std::move(groups_per_para), .disabled_validators = std::move(disabled_validators), + .v2_receipts = + node_features.has(runtime::NodeFeatures::CandidateReceiptV2), + .transposed_claim_queue = transposed_claim_queue, .per_session_state = per_session_state.value(), }); } // for @@ -1005,6 +1014,31 @@ namespace kagome::parachain::statement_distribution { }); } + bool StatementDistribution::validate( + const PerRelayParentState &per_relay_parent, + const CandidateHash &candidate_hash, + const network::vstaging::AttestedCandidateResponse &response) const { + if (candidateHash(*hasher, response.candidate_receipt) != candidate_hash) { + return false; + } + // https://github.com/paritytech/polkadot-sdk/blob/1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4/polkadot/node/network/statement-distribution/src/v2/requests.rs#L744-L772 + if (not per_relay_parent.v2_receipts + and isV2(response.candidate_receipt.descriptor)) { + return false; + } + if (auto r = checkCoreIndex(response.candidate_receipt, + per_relay_parent.transposed_claim_queue); + not r) { + return false; + } + if (auto session = sessionIndex(response.candidate_receipt.descriptor)) { + if (session != per_relay_parent.session) { + return false; + } + } + return true; + } + void StatementDistribution::handle_response( outcome::result &&r, const RelayHash &relay_parent, @@ -1043,6 +1077,15 @@ namespace kagome::parachain::statement_distribution { candidate_hash, group_index, response.statements.size()); + + if (not validate(parachain_state->get(), candidate_hash, response)) { + SL_WARN(logger, + "Invalid response receipt relay_parent={} candidate={}", + relay_parent, + candidate_hash); + return; + } + for (const auto &statement : response.statements) { parachain_state->get().statement_store.insert( parachain_state->get().per_session_state->value().groups, diff --git a/core/parachain/validator/statement_distribution/statement_distribution.hpp b/core/parachain/validator/statement_distribution/statement_distribution.hpp index cad21d2bec..29d3e442d3 100644 --- a/core/parachain/validator/statement_distribution/statement_distribution.hpp +++ b/core/parachain/validator/statement_distribution/statement_distribution.hpp @@ -148,6 +148,10 @@ namespace kagome::parachain::statement_distribution { outcome::result> getStateByRelayParent(const primitives::BlockHash &relay_parent); + bool validate( + const PerRelayParentState &per_relay_parent, + const CandidateHash &candidate_hash, + const network::vstaging::AttestedCandidateResponse &response) const; void handle_response( outcome::result &&r, const RelayHash &relay_parent, diff --git a/core/runtime/runtime_api/impl/parachain_host.cpp b/core/runtime/runtime_api/impl/parachain_host.cpp index 03839557f3..a69fb90771 100644 --- a/core/runtime/runtime_api/impl/parachain_host.cpp +++ b/core/runtime/runtime_api/impl/parachain_host.cpp @@ -300,12 +300,12 @@ namespace kagome::runtime { ctx, "ParachainHost_disabled_validators")); } - outcome::result> - ParachainHostImpl::node_features(const primitives::BlockHash &block, - SessionIndex index) { + outcome::result ParachainHostImpl::node_features( + const primitives::BlockHash &block) { OUTCOME_TRY(ctx, executor_->ctx().ephemeralAt(block)); - return ifExport(executor_->call( - ctx, "ParachainHost_node_features")); + OUTCOME_TRY(r, + ifExport(executor_->call( + ctx, "ParachainHost_node_features"))); + return NodeFeatures{std::move(r)}; } - } // namespace kagome::runtime diff --git a/core/runtime/runtime_api/impl/parachain_host.hpp b/core/runtime/runtime_api/impl/parachain_host.hpp index 2d96568bc2..a00b904e7b 100644 --- a/core/runtime/runtime_api/impl/parachain_host.hpp +++ b/core/runtime/runtime_api/impl/parachain_host.hpp @@ -115,8 +115,8 @@ namespace kagome::runtime { outcome::result> disabled_validators( const primitives::BlockHash &block) override; - outcome::result> node_features( - const primitives::BlockHash &block, SessionIndex index) override; + outcome::result node_features( + const primitives::BlockHash &block) override; ClaimQueueResult claim_queue(const primitives::BlockHash &block) override; diff --git a/core/runtime/runtime_api/node_features.hpp b/core/runtime/runtime_api/node_features.hpp new file mode 100644 index 0000000000..cae8d1156f --- /dev/null +++ b/core/runtime/runtime_api/node_features.hpp @@ -0,0 +1,56 @@ +/** + * Copyright Quadrivium LLC + * All Rights Reserved + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include + +namespace kagome::runtime { + struct NodeFeatures { + /// A feature index used to identify a bit into the node_features array + /// stored in the HostConfiguration. + enum Index : uint8_t { + /// Tells if tranch0 assignments could be sent in a single certificate. + /// Reserved for: + /// `` + EnableAssignmentsV2 = 0, + + /// This feature enables the extension of + /// `BackedCandidate::validator_indices` by 8 bits. + /// The value stored there represents the assumed core index where the + /// candidates are backed. This is needed for the elastic scaling MVP. + ElasticScalingMVP = 1, + + /// Tells if the chunk mapping feature is enabled. + /// Enables the implementation of + /// [RFC-47](https://github.com/polkadot-fellows/RFCs/blob/main/text/0047-assignment-of-availability-chunks.md). + /// Must not be enabled unless all validators and collators have stopped + /// using `req_chunk` protocol version 1. If it is enabled, validators can + /// start systematic chunk recovery. + AvailabilityChunkMapping = 2, + + /// Enables node side support of `CoreIndex` committed candidate receipts. + /// See [RFC-103](https://github.com/polkadot-fellows/RFCs/pull/103) for + /// details. + /// Only enable if at least 2/3 of nodes support the feature. + CandidateReceiptV2 = 3, + + /// First unassigned feature bit. + /// Every time a new feature flag is assigned it should take this value. + /// and this should be incremented. + FirstUnassigned = 4, + }; + + bool has(Index index) const { + return bits and index < bits->bits.size() && bits->bits.at(index); + } + + std::optional bits; + }; +} // namespace kagome::runtime diff --git a/core/runtime/runtime_api/parachain_host.hpp b/core/runtime/runtime_api/parachain_host.hpp index f93aac75d0..c008d10bbd 100644 --- a/core/runtime/runtime_api/parachain_host.hpp +++ b/core/runtime/runtime_api/parachain_host.hpp @@ -14,6 +14,7 @@ #include "primitives/block_id.hpp" #include "primitives/common.hpp" #include "primitives/parachain_host.hpp" +#include "runtime/runtime_api/node_features.hpp" #include "runtime/runtime_api/parachain_host_types.hpp" namespace kagome::runtime { @@ -23,35 +24,6 @@ namespace kagome::runtime { public: virtual ~ParachainHost() = default; - using NodeFeatures = scale::BitVec; - /// A feature index used to identify a bit into the node_features array - /// stored in the HostConfiguration. - enum NodeFeatureIndex { - /// Tells if tranch0 assignments could be sent in a single certificate. - /// Reserved for: - /// `` - EnableAssignmentsV2 = 0, - - /// This feature enables the extension of - /// `BackedCandidate::validator_indices` by 8 bits. - /// The value stored there represents the assumed core index where the - /// candidates are backed. This is needed for the elastic scaling MVP. - ElasticScalingMVP = 1, - - /// Tells if the chunk mapping feature is enabled. - /// Enables the implementation of - /// [RFC-47](https://github.com/polkadot-fellows/RFCs/blob/main/text/0047-assignment-of-availability-chunks.md). - /// Must not be enabled unless all validators and collators have stopped - /// using `req_chunk` protocol version 1. If it is enabled, validators can - /// start systematic chunk recovery. - AvailabilityChunkMapping = 2, - - /// First unassigned feature bit. - /// Every time a new feature flag is assigned it should take this value. - /// and this should be incremented. - FirstUnassigned = 3, - }; - /** * @brief Calls the ParachainHost_active_parachains function from wasm code * @return vector of ParachainId items or error if fails @@ -255,8 +227,8 @@ namespace kagome::runtime { virtual outcome::result> disabled_validators( const primitives::BlockHash &block) = 0; - virtual outcome::result> node_features( - const primitives::BlockHash &block, SessionIndex index) = 0; + virtual outcome::result node_features( + const primitives::BlockHash &block) = 0; using ClaimQueueResult = outcome::result>; virtual ClaimQueueResult claim_queue( diff --git a/test/core/parachain/availability/recovery_test.cpp b/test/core/parachain/availability/recovery_test.cpp index a3cfacf10f..5c460a3fd9 100644 --- a/test/core/parachain/availability/recovery_test.cpp +++ b/test/core/parachain/availability/recovery_test.cpp @@ -46,6 +46,7 @@ using kagome::parachain::ValidatorId; using kagome::primitives::AuthorityDiscoveryId; using kagome::primitives::BlockInfo; using kagome::runtime::AvailableData; +using kagome::runtime::NodeFeatures; using kagome::runtime::ParachainHost; using kagome::runtime::ParachainHostMock; using kagome::runtime::SessionInfo; @@ -92,13 +93,12 @@ class RecoveryTest : public testing::Test { parachain_api = std::make_shared(); ON_CALL(*parachain_api, session_info(best_block.hash, session_index)) .WillByDefault(Invoke([&] { return session; })); - ON_CALL(*parachain_api, node_features(best_block.hash, session_index)) + ON_CALL(*parachain_api, node_features(best_block.hash)) .WillByDefault(Invoke([&] { - scale::BitVec nf{}; - nf.bits.resize(ParachainHost::NodeFeatureIndex::FirstUnassigned); - nf.bits[ParachainHost::NodeFeatureIndex::AvailabilityChunkMapping] = - true; - return std::optional{nf}; + scale::BitVec bits; + bits.bits.resize(NodeFeatures::FirstUnassigned); + bits.bits[NodeFeatures::AvailabilityChunkMapping] = true; + return NodeFeatures{.bits = std::move(bits)}; })); av_store = std::make_shared(); diff --git a/test/core/parachain/parachain_test_harness.hpp b/test/core/parachain/parachain_test_harness.hpp index f28745b5ab..7957936b46 100644 --- a/test/core/parachain/parachain_test_harness.hpp +++ b/test/core/parachain/parachain_test_harness.hpp @@ -121,11 +121,11 @@ class ProspectiveParachainsTestHarness : public testing::Test { network::CandidateDescriptor{ .para_id = para_id, .relay_parent = relay_parent, - .collator_id = {}, + .reserved_1 = {}, .persisted_data_hash = persisted_validation_data.getHash(), .pov_hash = fromNumber(1), .erasure_encoding_root = fromNumber(1), - .signature = {}, + .reserved_2 = {}, .para_head_hash = hasher_->blake2b_256(para_head), .validation_code_hash = fromNumber(42), }, diff --git a/test/core/parachain/prospective_parachains.cpp b/test/core/parachain/prospective_parachains.cpp index 5219de061a..c3d74c173c 100644 --- a/test/core/parachain/prospective_parachains.cpp +++ b/test/core/parachain/prospective_parachains.cpp @@ -348,11 +348,11 @@ class ProspectiveParachainsTest : public ProspectiveParachainsTestHarness { return network::CandidateDescriptor{ .para_id = 0, .relay_parent = relay_parent, - .collator_id = {}, + .reserved_1 = {}, .persisted_data_hash = fromNumber(0), .pov_hash = fromNumber(0), .erasure_encoding_root = fromNumber(0), - .signature = {}, + .reserved_2 = {}, .para_head_hash = fromNumber(0), .validation_code_hash = crypto::Hashed>, + MOCK_METHOD(outcome::result, node_features, - (const primitives::BlockHash &, SessionIndex), + (const primitives::BlockHash &), (override)); MOCK_METHOD(ClaimQueueResult,