Runtime: allow backing multiple candidates of same parachain on different cores (#3231)

Fixes https://github.com/paritytech/polkadot-sdk/issues/3144

Builds on top of https://github.com/paritytech/polkadot-sdk/pull/3229

### Summary
Some preparations for Runtime to support elastic scaling, guarded by
config node features bit `FeatureIndex::ElasticScalingMVP`. This PR
introduces a per-candidate `CoreIndex` but does it in a hacky way to
avoid changing `CandidateCommitments`, `CandidateReceipts` primitives
and networking protocols.

#### Including `CoreIndex` in `BackedCandidate`
If the `ElasticScalingMVP` feature bit is enabled then
`BackedCandidate::validator_indices` is extended by 8 bits.
The value stored in these bits represents the assumed core index for the
candidate.

It is temporary solution which works by creating a mapping from
`BackedCandidate` to `CoreIndex` by assuming the `CoreIndex` can be
discovered by checking in which validator group the validator that
signed the statement is.

TODO:
- [x] fix tests
- [x] add new tests
- [x] Bump runtime API for Kusama, so we have that node features thing!
-> https://github.com/polkadot-fellows/runtimes/pull/194

---------

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Signed-off-by: alindima <alin@parity.io>
Co-authored-by: alindima <alin@parity.io>
This commit is contained in:
Andrei Sandu
2024-02-23 23:35:48 +07:00
committed by GitHub
parent 6fc1d41d44
commit 2431001ec0
21 changed files with 1642 additions and 350 deletions
+56 -14
View File
@@ -33,9 +33,10 @@ use polkadot_node_subsystem::{
};
use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_primitives::{
CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData, PvfExecKind,
ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES,
vstaging::node_features, CandidateDescriptor, GroupRotationInfo, HeadData,
PersistedValidationData, PvfExecKind, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES,
};
use rstest::rstest;
use sp_application_crypto::AppCrypto;
use sp_keyring::Sr25519Keyring;
use sp_keystore::Keystore;
@@ -79,6 +80,7 @@ pub(crate) struct TestState {
relay_parent: Hash,
minimum_backing_votes: u32,
disabled_validators: Vec<ValidatorIndex>,
node_features: NodeFeatures,
}
impl TestState {
@@ -157,6 +159,7 @@ impl Default for TestState {
relay_parent,
minimum_backing_votes: LEGACY_MIN_BACKING_VOTES,
disabled_validators: Vec::new(),
node_features: Default::default(),
}
}
}
@@ -298,7 +301,7 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS
AllMessages::RuntimeApi(
RuntimeApiMessage::Request(_parent, RuntimeApiRequest::NodeFeatures(_session_index, tx))
) => {
tx.send(Ok(Default::default())).unwrap();
tx.send(Ok(test_state.node_features.clone())).unwrap();
}
);
@@ -494,9 +497,20 @@ fn backing_second_works() {
}
// Test that the candidate reaches quorum successfully.
#[test]
fn backing_works() {
let test_state = TestState::default();
#[rstest]
#[case(true)]
#[case(false)]
fn backing_works(#[case] elastic_scaling_mvp: bool) {
let mut test_state = TestState::default();
if elastic_scaling_mvp {
test_state
.node_features
.resize((node_features::FeatureIndex::ElasticScalingMVP as u8 + 1) as usize, false);
test_state
.node_features
.set(node_features::FeatureIndex::ElasticScalingMVP as u8 as usize, true);
}
test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move {
test_startup(&mut virtual_overseer, &test_state).await;
@@ -647,6 +661,31 @@ fn backing_works() {
virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await;
let (tx, rx) = oneshot::channel();
let msg = CandidateBackingMessage::GetBackedCandidates(
vec![(candidate_a_hash, test_state.relay_parent)],
tx,
);
virtual_overseer.send(FromOrchestra::Communication { msg }).await;
let candidates = rx.await.unwrap();
assert_eq!(1, candidates.len());
assert_eq!(candidates[0].validity_votes().len(), 3);
let (validator_indices, maybe_core_index) =
candidates[0].validator_indices_and_core_index(elastic_scaling_mvp);
if elastic_scaling_mvp {
assert_eq!(maybe_core_index.unwrap(), CoreIndex(0));
} else {
assert!(maybe_core_index.is_none());
}
assert_eq!(
validator_indices,
bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 1, 0, 1].as_bitslice()
);
virtual_overseer
.send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::stop_work(test_state.relay_parent),
@@ -919,20 +958,20 @@ fn backing_works_while_validation_ongoing() {
let candidates = rx.await.unwrap();
assert_eq!(1, candidates.len());
assert_eq!(candidates[0].validity_votes.len(), 3);
assert_eq!(candidates[0].validity_votes().len(), 3);
assert!(candidates[0]
.validity_votes
.validity_votes()
.contains(&ValidityAttestation::Implicit(signed_a.signature().clone())));
assert!(candidates[0]
.validity_votes
.validity_votes()
.contains(&ValidityAttestation::Explicit(signed_b.signature().clone())));
assert!(candidates[0]
.validity_votes
.validity_votes()
.contains(&ValidityAttestation::Explicit(signed_c.signature().clone())));
assert_eq!(
candidates[0].validator_indices,
bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1],
candidates[0].validator_indices_and_core_index(false),
(bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1].as_bitslice(), None)
);
virtual_overseer
@@ -1604,8 +1643,11 @@ fn candidate_backing_reorders_votes() {
let expected_attestations =
vec![fake_attestation(1).into(), fake_attestation(3).into(), fake_attestation(5).into()];
assert_eq!(backed.validator_indices, expected_bitvec);
assert_eq!(backed.validity_votes, expected_attestations);
assert_eq!(
backed.validator_indices_and_core_index(false),
(expected_bitvec.as_bitslice(), None)
);
assert_eq!(backed.validity_votes(), expected_attestations);
}
// Test whether we retry on failed PoV fetching.