Skip to content

Commit

Permalink
Runtime: allow backing multiple candidates of same parachain on diffe…
Browse files Browse the repository at this point in the history
…rent cores (#3231)

Fixes #3144

Builds on top of #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!
-> polkadot-fellows/runtimes#194

---------

Signed-off-by: Andrei Sandu <[email protected]>
Signed-off-by: alindima <[email protected]>
Co-authored-by: alindima <[email protected]>
  • Loading branch information
sandreim and alindima authored Feb 23, 2024
1 parent 6fc1d41 commit 2431001
Show file tree
Hide file tree
Showing 21 changed files with 1,640 additions and 348 deletions.
8 changes: 8 additions & 0 deletions .gitlab/pipeline/zombienet/polkadot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ zombienet-polkadot-functional-0011-async-backing-6-seconds-rate:
--local-dir="${LOCAL_DIR}/functional"
--test="0011-async-backing-6-seconds-rate.zndsl"

zombienet-polkadot-functional-0012-elastic-scaling-mvp:
extends:
- .zombienet-polkadot-common
script:
- /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh
--local-dir="${LOCAL_DIR}/functional"
--test="0012-elastic-scaling-mvp.zndsl"

zombienet-polkadot-smoke-0001-parachains-smoke-test:
extends:
- .zombienet-polkadot-common
Expand Down
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions polkadot/node/core/backing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ sc-keystore = { path = "../../../../substrate/client/keystore" }
sp-tracing = { path = "../../../../substrate/primitives/tracing" }
futures = { version = "0.3.21", features = ["thread-pool"] }
assert_matches = "1.4.0"
rstest = "0.18.2"
polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" }
19 changes: 7 additions & 12 deletions polkadot/node/core/backing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ use std::{
sync::Arc,
};

use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use bitvec::vec::BitVec;
use futures::{
channel::{mpsc, oneshot},
future::BoxFuture,
Expand Down Expand Up @@ -494,20 +494,15 @@ fn table_attested_to_backed(
}
vote_positions.sort_by_key(|(_orig, pos_in_group)| *pos_in_group);

if inject_core_index {
let core_index_to_inject: BitVec<u8, BitOrderLsb0> =
BitVec::from_vec(vec![core_index.0 as u8]);
validator_indices.extend(core_index_to_inject);
}

Some(BackedCandidate {
Some(BackedCandidate::new(
candidate,
validity_votes: vote_positions
vote_positions
.into_iter()
.map(|(pos_in_votes, _pos_in_group)| validity_votes[pos_in_votes].clone())
.collect(),
validator_indices,
})
inject_core_index.then_some(core_index),
))
}

async fn store_available_data(
Expand Down Expand Up @@ -1775,7 +1770,7 @@ async fn post_import_statement_actions<Context>(
&rp_state.table_context,
rp_state.inject_core_index,
) {
let para_id = backed.candidate.descriptor.para_id;
let para_id = backed.candidate().descriptor.para_id;
gum::debug!(
target: LOG_TARGET,
candidate_hash = ?candidate_hash,
Expand All @@ -1796,7 +1791,7 @@ async fn post_import_statement_actions<Context>(
// notify collator protocol.
ctx.send_message(CollatorProtocolMessage::Backed {
para_id,
para_head: backed.candidate.descriptor.para_head,
para_head: backed.candidate().descriptor.para_head,
})
.await;
// Notify statement distribution of backed candidate.
Expand Down
70 changes: 56 additions & 14 deletions polkadot/node/core/backing/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,6 +80,7 @@ pub(crate) struct TestState {
relay_parent: Hash,
minimum_backing_votes: u32,
disabled_validators: Vec<ValidatorIndex>,
node_features: NodeFeatures,
}

impl TestState {
Expand Down Expand Up @@ -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(),
}
}
}
Expand Down Expand Up @@ -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();
}
);

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion polkadot/node/core/provisioner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ async fn select_candidates(
// keep only one candidate with validation code.
let mut with_validation_code = false;
candidates.retain(|c| {
if c.candidate.commitments.new_validation_code.is_some() {
if c.candidate().commitments.new_validation_code.is_some() {
if with_validation_code {
return false
}
Expand Down
Loading

0 comments on commit 2431001

Please sign in to comment.