Approve multiple candidates with a single signature (#1191)

Initial implementation for the plan discussed here: https://github.com/paritytech/polkadot-sdk/issues/701
Built on top of https://github.com/paritytech/polkadot-sdk/pull/1178
v0: https://github.com/paritytech/polkadot/pull/7554,

## Overall idea

When approval-voting checks a candidate and is ready to advertise the
approval, defer it in a per-relay chain block until we either have
MAX_APPROVAL_COALESCE_COUNT candidates to sign or a candidate has stayed
MAX_APPROVALS_COALESCE_TICKS in the queue, in both cases we sign what
candidates we have available.

This should allow us to reduce the number of approvals messages we have
to create/send/verify. The parameters are configurable, so we should
find some values that balance:

- Security of the network: Delaying broadcasting of an approval
shouldn't but the finality at risk and to make sure that never happens
we won't delay sending a vote if we are past 2/3 from the no-show time.
- Scalability of the network: MAX_APPROVAL_COALESCE_COUNT = 1 &
MAX_APPROVALS_COALESCE_TICKS =0, is what we have now and we know from
the measurements we did on versi, it bottlenecks
approval-distribution/approval-voting when increase significantly the
number of validators and parachains
- Block storage: In case of disputes we have to import this votes on
chain and that increase the necessary storage with
MAX_APPROVAL_COALESCE_COUNT * CandidateHash per vote. Given that
disputes are not the normal way of the network functioning and we will
limit MAX_APPROVAL_COALESCE_COUNT in the single digits numbers, this
should be good enough. Alternatively, we could try to create a better
way to store this on-chain through indirection, if that's needed.

## Other fixes:
- Fixed the fact that we were sending random assignments to
non-validators, that was wrong because those won't do anything with it
and they won't gossip it either because they do not have a grid topology
set, so we would waste the random assignments.
- Added metrics to be able to debug potential no-shows and
mis-processing of approvals/assignments.

## TODO:
- [x] Get feedback, that this is moving in the right direction. @ordian
@sandreim @eskimor @burdges, let me know what you think.
- [x] More and more testing.
- [x]  Test in versi.
- [x] Make MAX_APPROVAL_COALESCE_COUNT &
MAX_APPROVAL_COALESCE_WAIT_MILLIS a parachain host configuration.
- [x] Make sure the backwards compatibility works correctly
- [x] Make sure this direction is compatible with other streams of work:
https://github.com/paritytech/polkadot-sdk/issues/635 &
https://github.com/paritytech/polkadot-sdk/issues/742
- [x] Final versi burn-in before merging

---------

Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
This commit is contained in:
Alexandru Gheorghe
2023-12-13 08:43:15 +02:00
committed by GitHub
parent d18a682bf7
commit a84dd0dba5
82 changed files with 5883 additions and 1483 deletions
+8
View File
@@ -131,6 +131,14 @@ zombienet-polkadot-functional-0008-dispute-old-finalized:
--local-dir="${LOCAL_DIR}/functional"
--test="0008-dispute-old-finalized.zndsl"
zombienet-polkadot-functional-0009-approval-voting-coalescing:
extends:
- .zombienet-polkadot-common
script:
- /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh
--local-dir="${LOCAL_DIR}/functional"
--test="0009-approval-voting-coalescing.zndsl"
zombienet-polkadot-smoke-0001-parachains-smoke-test:
extends:
- .zombienet-polkadot-common
@@ -24,7 +24,7 @@ use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient};
use polkadot_primitives::{
async_backing::{AsyncBackingParams, BackingState},
slashing,
vstaging::NodeFeatures,
vstaging::{ApprovalVotingParams, NodeFeatures},
};
use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError};
use sc_client_api::AuxStore;
@@ -427,6 +427,18 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient {
Ok(self.rpc_client.parachain_host_para_backing_state(at, para_id).await?)
}
/// Approval voting configuration parameters
async fn approval_voting_params(
&self,
at: Hash,
session_index: polkadot_primitives::SessionIndex,
) -> Result<ApprovalVotingParams, ApiError> {
Ok(self
.rpc_client
.parachain_host_staging_approval_voting_params(at, session_index)
.await?)
}
async fn node_features(&self, at: Hash) -> Result<NodeFeatures, ApiError> {
Ok(self.rpc_client.parachain_host_node_features(at).await?)
}
@@ -32,7 +32,7 @@ use cumulus_primitives_core::{
relay_chain::{
async_backing::{AsyncBackingParams, BackingState},
slashing,
vstaging::NodeFeatures,
vstaging::{ApprovalVotingParams, NodeFeatures},
BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo,
Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, OccupiedCoreAssumption,
@@ -625,6 +625,19 @@ impl RelayChainRpcClient {
}
#[allow(missing_docs)]
pub async fn parachain_host_staging_approval_voting_params(
&self,
at: RelayHash,
_session_index: SessionIndex,
) -> Result<ApprovalVotingParams, RelayChainError> {
self.call_remote_runtime_function(
"ParachainHost_staging_approval_voting_params",
at,
None::<()>,
)
.await
}
pub async fn parachain_host_para_backing_state(
&self,
at: RelayHash,
@@ -24,7 +24,7 @@ use emulated_integration_tests_common::{
// Rococo declaration
decl_test_relay_chains! {
#[api_version(9)]
#[api_version(10)]
pub struct Rococo {
genesis = genesis::genesis(),
on_init = (),
@@ -24,7 +24,7 @@ use emulated_integration_tests_common::{
// Westend declaration
decl_test_relay_chains! {
#[api_version(9)]
#[api_version(10)]
pub struct Westend {
genesis = genesis::genesis(),
on_init = (),
-1
View File
@@ -64,7 +64,6 @@ jemalloc-allocator = [
"polkadot-node-core-pvf/jemalloc-allocator",
"polkadot-overseer/jemalloc-allocator",
]
network-protocol-staging = ["polkadot-cli/network-protocol-staging"]
# Enables timeout-based tests supposed to be run only in CI environment as they may be flaky
-2
View File
@@ -75,5 +75,3 @@ runtime-metrics = [
"polkadot-node-metrics/runtime-metrics",
"service/runtime-metrics",
]
network-protocol-staging = ["service/network-protocol-staging"]
@@ -25,6 +25,15 @@ use crate::{
time::Tick,
};
/// Result of counting the necessary tranches needed for approving a block.
#[derive(Debug, PartialEq, Clone)]
pub struct TranchesToApproveResult {
/// The required tranches for approving this block
pub required_tranches: RequiredTranches,
/// The total number of no_shows at the moment we are doing the counting.
pub total_observed_no_shows: usize,
}
/// The required tranches of assignments needed to determine whether a candidate is approved.
#[derive(Debug, PartialEq, Clone)]
pub enum RequiredTranches {
@@ -64,7 +73,7 @@ pub enum RequiredTranches {
}
/// The result of a check.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Check {
/// The candidate is unapproved.
Unapproved,
@@ -178,6 +187,7 @@ struct State {
next_no_show: Option<Tick>,
/// The last tick at which a considered assignment was received.
last_assignment_tick: Option<Tick>,
total_observed_no_shows: usize,
}
impl State {
@@ -187,41 +197,53 @@ impl State {
needed_approvals: usize,
n_validators: usize,
no_show_duration: Tick,
) -> RequiredTranches {
) -> TranchesToApproveResult {
let covering = if self.depth == 0 { 0 } else { self.covering };
if self.depth != 0 && self.assignments + covering + self.uncovered >= n_validators {
return RequiredTranches::All
return TranchesToApproveResult {
required_tranches: RequiredTranches::All,
total_observed_no_shows: self.total_observed_no_shows,
}
}
// If we have enough assignments and all no-shows are covered, we have reached the number
// of tranches that we need to have.
if self.assignments >= needed_approvals && (covering + self.uncovered) == 0 {
return RequiredTranches::Exact {
needed: tranche,
tolerated_missing: self.covered,
next_no_show: self.next_no_show,
last_assignment_tick: self.last_assignment_tick,
return TranchesToApproveResult {
required_tranches: RequiredTranches::Exact {
needed: tranche,
tolerated_missing: self.covered,
next_no_show: self.next_no_show,
last_assignment_tick: self.last_assignment_tick,
},
total_observed_no_shows: self.total_observed_no_shows,
}
}
// We're pending more assignments and should look at more tranches.
let clock_drift = self.clock_drift(no_show_duration);
if self.depth == 0 {
RequiredTranches::Pending {
considered: tranche,
next_no_show: self.next_no_show,
// during the initial assignment-gathering phase, we want to accept assignments
// from any tranche. Note that honest validators will still not broadcast their
// assignment until it is time to do so, regardless of this value.
maximum_broadcast: DelayTranche::max_value(),
clock_drift,
TranchesToApproveResult {
required_tranches: RequiredTranches::Pending {
considered: tranche,
next_no_show: self.next_no_show,
// during the initial assignment-gathering phase, we want to accept assignments
// from any tranche. Note that honest validators will still not broadcast their
// assignment until it is time to do so, regardless of this value.
maximum_broadcast: DelayTranche::max_value(),
clock_drift,
},
total_observed_no_shows: self.total_observed_no_shows,
}
} else {
RequiredTranches::Pending {
considered: tranche,
next_no_show: self.next_no_show,
maximum_broadcast: tranche + (covering + self.uncovered) as DelayTranche,
clock_drift,
TranchesToApproveResult {
required_tranches: RequiredTranches::Pending {
considered: tranche,
next_no_show: self.next_no_show,
maximum_broadcast: tranche + (covering + self.uncovered) as DelayTranche,
clock_drift,
},
total_observed_no_shows: self.total_observed_no_shows,
}
}
}
@@ -276,6 +298,7 @@ impl State {
uncovered,
next_no_show,
last_assignment_tick,
total_observed_no_shows: self.total_observed_no_shows + new_no_shows,
}
}
}
@@ -372,7 +395,7 @@ pub fn tranches_to_approve(
block_tick: Tick,
no_show_duration: Tick,
needed_approvals: usize,
) -> RequiredTranches {
) -> TranchesToApproveResult {
let tick_now = tranche_now as Tick + block_tick;
let n_validators = approval_entry.n_validators();
@@ -384,6 +407,7 @@ pub fn tranches_to_approve(
uncovered: 0,
next_no_show: None,
last_assignment_tick: None,
total_observed_no_shows: 0,
};
// The `ApprovalEntry` doesn't have any data for empty tranches. We still want to iterate over
@@ -434,7 +458,7 @@ pub fn tranches_to_approve(
let s = s.advance(n_assignments, no_shows, next_no_show, last_assignment_tick);
let output = s.output(tranche, needed_approvals, n_validators, no_show_duration);
*state = match output {
*state = match output.required_tranches {
RequiredTranches::Exact { .. } | RequiredTranches::All => {
// Wipe the state clean so the next iteration of this closure will terminate
// the iterator. This guarantees that we can call `last` further down to see
@@ -464,15 +488,17 @@ mod tests {
#[test]
fn pending_is_not_approved() {
let candidate = approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: BitVec::default(),
}
.into();
let candidate = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: BitVec::default(),
},
0,
);
let approval_entry = approval_db::v2::ApprovalEntry {
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: BitVec::default(),
our_assignment: None,
@@ -497,29 +523,31 @@ mod tests {
#[test]
fn exact_takes_only_assignments_up_to() {
let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 10],
}
.into();
let mut candidate: CandidateEntry = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 10],
},
0,
);
for i in 0..3 {
candidate.mark_approval(ValidatorIndex(i));
}
let approval_entry = approval_db::v2::ApprovalEntry {
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: vec![
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 0,
assignments: (0..2).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 1,
assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(),
},
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 2,
assignments: (5..10).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
@@ -569,29 +597,31 @@ mod tests {
#[test]
fn one_honest_node_always_approves() {
let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 10],
}
.into();
let mut candidate: CandidateEntry = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 10],
},
0,
);
for i in 0..3 {
candidate.mark_approval(ValidatorIndex(i));
}
let approval_entry = approval_db::v2::ApprovalEntry {
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: vec![
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 0,
assignments: (0..4).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 1,
assignments: (4..6).map(|i| (ValidatorIndex(i), 1.into())).collect(),
},
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 2,
assignments: (6..10).map(|i| (ValidatorIndex(i), 0.into())).collect(),
},
@@ -647,7 +677,7 @@ mod tests {
let no_show_duration = 10;
let needed_approvals = 4;
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 5],
our_assignment: None,
@@ -675,7 +705,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Exact {
needed: 1,
tolerated_missing: 0,
@@ -691,7 +722,7 @@ mod tests {
let no_show_duration = 10;
let needed_approvals = 4;
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10],
our_assignment: None,
@@ -715,7 +746,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 2,
next_no_show: Some(block_tick + no_show_duration),
@@ -731,7 +763,7 @@ mod tests {
let no_show_duration = 10;
let needed_approvals = 4;
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10],
our_assignment: None,
@@ -759,7 +791,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 11,
next_no_show: None,
@@ -776,7 +809,7 @@ mod tests {
let needed_approvals = 4;
let n_validators = 8;
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators],
our_assignment: None,
@@ -807,7 +840,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 1,
next_no_show: None,
@@ -826,7 +860,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 1,
next_no_show: None,
@@ -843,7 +878,7 @@ mod tests {
let needed_approvals = 4;
let n_validators = 8;
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators],
our_assignment: None,
@@ -879,7 +914,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Exact {
needed: 1,
tolerated_missing: 0,
@@ -898,7 +934,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Exact {
needed: 2,
tolerated_missing: 1,
@@ -917,7 +954,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 2,
next_no_show: None,
@@ -934,7 +972,7 @@ mod tests {
let needed_approvals = 4;
let n_validators = 8;
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators],
our_assignment: None,
@@ -970,7 +1008,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Exact {
needed: 2,
tolerated_missing: 1,
@@ -992,7 +1031,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 2,
next_no_show: None,
@@ -1013,7 +1053,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Exact {
needed: 3,
tolerated_missing: 2,
@@ -1029,22 +1070,24 @@ mod tests {
let no_show_duration = 10;
let needed_approvals = 3;
let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 3],
}
.into();
let mut candidate: CandidateEntry = CandidateEntry::from_v1(
approval_db::v1::CandidateEntry {
candidate: dummy_candidate_receipt(dummy_hash()),
session: 0,
block_assignments: BTreeMap::default(),
approvals: bitvec![u8, BitOrderLsb0; 0; 3],
},
0,
);
for i in 0..3 {
candidate.mark_approval(ValidatorIndex(i));
}
let approval_entry = approval_db::v2::ApprovalEntry {
let approval_entry = approval_db::v3::ApprovalEntry {
tranches: vec![
// Assignments with invalid validator indexes.
approval_db::v2::TrancheEntry {
approval_db::v3::TrancheEntry {
tranche: 1,
assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(),
},
@@ -1068,7 +1111,8 @@ mod tests {
block_tick,
no_show_duration,
needed_approvals,
),
)
.required_tranches,
RequiredTranches::Pending {
considered: 10,
next_no_show: None,
@@ -1094,7 +1138,7 @@ mod tests {
];
for test_tranche in test_tranches {
let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry {
let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry {
tranches: Vec::new(),
backing_group: GroupIndex(0),
our_assignment: None,
@@ -1345,10 +1389,11 @@ mod tests {
uncovered: 0,
next_no_show: None,
last_assignment_tick: None,
total_observed_no_shows: 0,
};
assert_eq!(
state.output(0, 10, 10, 20),
state.output(0, 10, 10, 20).required_tranches,
RequiredTranches::Pending {
considered: 0,
next_no_show: None,
@@ -1368,10 +1413,11 @@ mod tests {
uncovered: 0,
next_no_show: None,
last_assignment_tick: None,
total_observed_no_shows: 0,
};
assert_eq!(
state.output(0, 10, 10, 20),
state.output(0, 10, 10, 20).required_tranches,
RequiredTranches::Exact {
needed: 0,
tolerated_missing: 0,
@@ -0,0 +1,39 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use polkadot_node_primitives::approval::{
v1::{AssignmentCert, AssignmentCertKind, VrfProof, VrfSignature, RELAY_VRF_MODULO_CONTEXT},
v2::VrfPreOutput,
};
pub fn make_bitvec(len: usize) -> BitVec<u8, BitOrderLsb0> {
bitvec::bitvec![u8, BitOrderLsb0; 0; len]
}
pub fn dummy_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert {
let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT);
let msg = b"test-garbage";
let mut prng = rand_core::OsRng;
let keypair = schnorrkel::Keypair::generate_with(&mut prng);
let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg));
let out = inout.to_output();
AssignmentCert {
kind,
vrf: VrfSignature { pre_output: VrfPreOutput(out), proof: VrfProof(proof) },
}
}
@@ -0,0 +1,293 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Common helper functions for all versions of approval-voting database.
use std::sync::Arc;
use parity_scale_codec::{Decode, Encode};
use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
use polkadot_node_subsystem_util::database::{DBTransaction, Database};
use polkadot_primitives::{BlockNumber, CandidateHash, CandidateIndex, Hash};
use crate::{
backend::{Backend, BackendWriteOp, V1ReadBackend, V2ReadBackend},
persisted_entries,
};
use super::{
v2::{load_block_entry_v1, load_candidate_entry_v1},
v3::{load_block_entry_v2, load_candidate_entry_v2, BlockEntry, CandidateEntry},
};
pub mod migration_helpers;
const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks";
/// A range from earliest..last block number stored within the DB.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct StoredBlockRange(pub BlockNumber, pub BlockNumber);
/// The database config.
#[derive(Debug, Clone, Copy)]
pub struct Config {
/// The column family in the database where data is stored.
pub col_approval_data: u32,
}
/// `DbBackend` is a concrete implementation of the higher-level Backend trait
pub struct DbBackend {
inner: Arc<dyn Database>,
config: Config,
}
impl DbBackend {
/// Create a new [`DbBackend`] with the supplied key-value store and
/// config.
pub fn new(db: Arc<dyn Database>, config: Config) -> Self {
DbBackend { inner: db, config }
}
}
/// Errors while accessing things from the DB.
#[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error {
Io(std::io::Error),
InvalidDecoding(parity_scale_codec::Error),
InternalError(SubsystemError),
}
impl std::error::Error for Error {}
/// Result alias for DB errors.
pub type Result<T> = std::result::Result<T, Error>;
impl Backend for DbBackend {
fn load_block_entry(
&self,
block_hash: &Hash,
) -> SubsystemResult<Option<persisted_entries::BlockEntry>> {
load_block_entry(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into))
}
fn load_candidate_entry(
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<persisted_entries::CandidateEntry>> {
load_candidate_entry(&*self.inner, &self.config, candidate_hash).map(|e| e.map(Into::into))
}
fn load_blocks_at_height(&self, block_height: &BlockNumber) -> SubsystemResult<Vec<Hash>> {
load_blocks_at_height(&*self.inner, &self.config, block_height)
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
load_all_blocks(&*self.inner, &self.config)
}
fn load_stored_blocks(&self) -> SubsystemResult<Option<StoredBlockRange>> {
load_stored_blocks(&*self.inner, &self.config)
}
/// Atomically write the list of operations, with later operations taking precedence over prior.
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
where
I: IntoIterator<Item = BackendWriteOp>,
{
let mut tx = DBTransaction::new();
for op in ops {
match op {
BackendWriteOp::WriteStoredBlockRange(stored_block_range) => {
tx.put_vec(
self.config.col_approval_data,
&STORED_BLOCKS_KEY,
stored_block_range.encode(),
);
},
BackendWriteOp::DeleteStoredBlockRange => {
tx.delete(self.config.col_approval_data, &STORED_BLOCKS_KEY);
},
BackendWriteOp::WriteBlocksAtHeight(h, blocks) => {
tx.put_vec(
self.config.col_approval_data,
&blocks_at_height_key(h),
blocks.encode(),
);
},
BackendWriteOp::DeleteBlocksAtHeight(h) => {
tx.delete(self.config.col_approval_data, &blocks_at_height_key(h));
},
BackendWriteOp::WriteBlockEntry(block_entry) => {
let block_entry: BlockEntry = block_entry.into();
tx.put_vec(
self.config.col_approval_data,
&block_entry_key(&block_entry.block_hash),
block_entry.encode(),
);
},
BackendWriteOp::DeleteBlockEntry(hash) => {
tx.delete(self.config.col_approval_data, &block_entry_key(&hash));
},
BackendWriteOp::WriteCandidateEntry(candidate_entry) => {
let candidate_entry: CandidateEntry = candidate_entry.into();
tx.put_vec(
self.config.col_approval_data,
&candidate_entry_key(&candidate_entry.candidate.hash()),
candidate_entry.encode(),
);
},
BackendWriteOp::DeleteCandidateEntry(candidate_hash) => {
tx.delete(self.config.col_approval_data, &candidate_entry_key(&candidate_hash));
},
}
}
self.inner.write(tx).map_err(|e| e.into())
}
}
impl V1ReadBackend for DbBackend {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
candidate_index: CandidateIndex,
) -> SubsystemResult<Option<persisted_entries::CandidateEntry>> {
load_candidate_entry_v1(&*self.inner, &self.config, candidate_hash)
.map(|e| e.map(|e| persisted_entries::CandidateEntry::from_v1(e, candidate_index)))
}
fn load_block_entry_v1(
&self,
block_hash: &Hash,
) -> SubsystemResult<Option<persisted_entries::BlockEntry>> {
load_block_entry_v1(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into))
}
}
impl V2ReadBackend for DbBackend {
fn load_candidate_entry_v2(
&self,
candidate_hash: &CandidateHash,
candidate_index: CandidateIndex,
) -> SubsystemResult<Option<persisted_entries::CandidateEntry>> {
load_candidate_entry_v2(&*self.inner, &self.config, candidate_hash)
.map(|e| e.map(|e| persisted_entries::CandidateEntry::from_v2(e, candidate_index)))
}
fn load_block_entry_v2(
&self,
block_hash: &Hash,
) -> SubsystemResult<Option<persisted_entries::BlockEntry>> {
load_block_entry_v2(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into))
}
}
pub(crate) fn load_decode<D: Decode>(
store: &dyn Database,
col_approval_data: u32,
key: &[u8],
) -> Result<Option<D>> {
match store.get(col_approval_data, key)? {
None => Ok(None),
Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into),
}
}
/// The key a given block entry is stored under.
pub(crate) fn block_entry_key(block_hash: &Hash) -> [u8; 46] {
const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck";
let mut key = [0u8; 14 + 32];
key[0..14].copy_from_slice(&BLOCK_ENTRY_PREFIX);
key[14..][..32].copy_from_slice(block_hash.as_ref());
key
}
/// The key a given candidate entry is stored under.
pub(crate) fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] {
const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand";
let mut key = [0u8; 14 + 32];
key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX);
key[14..][..32].copy_from_slice(candidate_hash.0.as_ref());
key
}
/// The key a set of block hashes corresponding to a block number is stored under.
pub(crate) fn blocks_at_height_key(block_number: BlockNumber) -> [u8; 16] {
const BLOCKS_AT_HEIGHT_PREFIX: [u8; 12] = *b"Approvals_at";
let mut key = [0u8; 12 + 4];
key[0..12].copy_from_slice(&BLOCKS_AT_HEIGHT_PREFIX);
block_number.using_encoded(|s| key[12..16].copy_from_slice(s));
key
}
/// Return all blocks which have entries in the DB, ascending, by height.
pub fn load_all_blocks(store: &dyn Database, config: &Config) -> SubsystemResult<Vec<Hash>> {
let mut hashes = Vec::new();
if let Some(stored_blocks) = load_stored_blocks(store, config)? {
for height in stored_blocks.0..stored_blocks.1 {
let blocks = load_blocks_at_height(store, config, &height)?;
hashes.extend(blocks);
}
}
Ok(hashes)
}
/// Load the stored-blocks key from the state.
pub fn load_stored_blocks(
store: &dyn Database,
config: &Config,
) -> SubsystemResult<Option<StoredBlockRange>> {
load_decode(store, config.col_approval_data, STORED_BLOCKS_KEY)
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a blocks-at-height entry for a given block number.
pub fn load_blocks_at_height(
store: &dyn Database,
config: &Config,
block_number: &BlockNumber,
) -> SubsystemResult<Vec<Hash>> {
load_decode(store, config.col_approval_data, &blocks_at_height_key(*block_number))
.map(|x| x.unwrap_or_default())
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a block entry from the aux store.
pub fn load_block_entry(
store: &dyn Database,
config: &Config,
block_hash: &Hash,
) -> SubsystemResult<Option<BlockEntry>> {
load_decode(store, config.col_approval_data, &block_entry_key(block_hash))
.map(|u: Option<BlockEntry>| u.map(|v| v.into()))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a candidate entry from the aux store in current version format.
pub fn load_candidate_entry(
store: &dyn Database,
config: &Config,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<CandidateEntry>> {
load_decode(store, config.col_approval_data, &candidate_entry_key(candidate_hash))
.map(|u: Option<CandidateEntry>| u.map(|v| v.into()))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
@@ -30,5 +30,7 @@
//! In the future, we may use a temporary DB which doesn't need to be wiped, but for the
//! time being we share the same DB with the rest of Substrate.
pub mod common;
pub mod v1;
pub mod v2;
pub mod v3;
@@ -40,10 +40,6 @@ fn make_db() -> (DbBackend, Arc<dyn Database>) {
(DbBackend::new(db_writer.clone(), TEST_CONFIG), db_writer)
}
fn make_bitvec(len: usize) -> BitVec<u8, BitOrderLsb0> {
bitvec::bitvec![u8, BitOrderLsb0; 0; len]
}
fn make_block_entry(
block_hash: Hash,
parent_hash: Hash,
@@ -16,29 +16,19 @@
//! Approval DB migration helpers.
use super::*;
use crate::backend::Backend;
use polkadot_node_primitives::approval::v1::{
AssignmentCert, AssignmentCertKind, VrfPreOutput, VrfProof, VrfSignature,
RELAY_VRF_MODULO_CONTEXT,
use crate::{
approval_db::common::{
migration_helpers::{dummy_assignment_cert, make_bitvec},
Error, Result, StoredBlockRange,
},
backend::Backend,
};
use polkadot_node_primitives::approval::v1::AssignmentCertKind;
use polkadot_node_subsystem_util::database::Database;
use sp_application_crypto::sp_core::H256;
use std::{collections::HashSet, sync::Arc};
fn dummy_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert {
let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT);
let msg = b"test-garbage";
let mut prng = rand_core::OsRng;
let keypair = schnorrkel::Keypair::generate_with(&mut prng);
let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg));
let preout = inout.to_output();
AssignmentCert {
kind,
vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) },
}
}
fn make_block_entry_v1(
block_hash: Hash,
parent_hash: Hash,
@@ -58,14 +48,10 @@ fn make_block_entry_v1(
}
}
fn make_bitvec(len: usize) -> BitVec<u8, BitOrderLsb0> {
bitvec::bitvec![u8, BitOrderLsb0; 0; len]
}
/// Migrates `OurAssignment`, `CandidateEntry` and `ApprovalEntry` to version 2.
/// Returns on any error.
/// Must only be used in parachains DB migration code - `polkadot-service` crate.
pub fn v1_to_v2(db: Arc<dyn Database>, config: Config) -> Result<()> {
pub fn v1_to_latest(db: Arc<dyn Database>, config: Config) -> Result<()> {
let mut backend = crate::DbBackend::new(db, config);
let all_blocks = backend
.load_all_blocks()
@@ -89,11 +75,13 @@ pub fn v1_to_v2(db: Arc<dyn Database>, config: Config) -> Result<()> {
let mut counter = 0;
// Get all candidate entries, approval entries and convert each of them.
for block in all_blocks {
for (_core_index, candidate_hash) in block.candidates() {
for (candidate_index, (_core_index, candidate_hash)) in
block.candidates().iter().enumerate()
{
// Loading the candidate will also perform the conversion to the updated format and
// return that represantation.
if let Some(candidate_entry) = backend
.load_candidate_entry_v1(&candidate_hash)
.load_candidate_entry_v1(&candidate_hash, candidate_index as CandidateIndex)
.map_err(|e| Error::InternalError(e))?
{
// Write the updated representation.
@@ -113,42 +101,8 @@ pub fn v1_to_v2(db: Arc<dyn Database>, config: Config) -> Result<()> {
Ok(())
}
// Checks if the migration doesn't leave the DB in an unsane state.
// This function is to be used in tests.
pub fn v1_to_v2_sanity_check(
db: Arc<dyn Database>,
config: Config,
expected_candidates: HashSet<CandidateHash>,
) -> Result<()> {
let backend = crate::DbBackend::new(db, config);
let all_blocks = backend
.load_all_blocks()
.unwrap()
.iter()
.map(|block_hash| backend.load_block_entry(block_hash).unwrap().unwrap())
.collect::<Vec<_>>();
let mut candidates = HashSet::new();
// Iterate all blocks and approval entries.
for block in all_blocks {
for (_core_index, candidate_hash) in block.candidates() {
// Loading the candidate will also perform the conversion to the updated format and
// return that represantation.
if let Some(candidate_entry) = backend.load_candidate_entry(&candidate_hash).unwrap() {
candidates.insert(candidate_entry.candidate.hash());
}
}
}
assert_eq!(candidates, expected_candidates);
Ok(())
}
// Fills the db with dummy data in v1 scheme.
pub fn v1_to_v2_fill_test_data<F>(
pub fn v1_fill_test_data<F>(
db: Arc<dyn Database>,
config: Config,
dummy_candidate_create: F,
@@ -21,145 +21,23 @@ use polkadot_node_primitives::approval::{v1::DelayTranche, v2::AssignmentCertV2}
use polkadot_node_subsystem::{SubsystemError, SubsystemResult};
use polkadot_node_subsystem_util::database::{DBTransaction, Database};
use polkadot_primitives::{
BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex,
ValidatorIndex, ValidatorSignature,
BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash,
SessionIndex, ValidatorIndex, ValidatorSignature,
};
use sp_consensus_slots::Slot;
use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
use std::{collections::BTreeMap, sync::Arc};
use std::collections::BTreeMap;
use crate::{
backend::{Backend, BackendWriteOp, V1ReadBackend},
persisted_entries,
};
use crate::backend::V1ReadBackend;
const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks";
use super::common::{block_entry_key, candidate_entry_key, load_decode, Config};
pub mod migration_helpers;
#[cfg(test)]
pub mod tests;
/// `DbBackend` is a concrete implementation of the higher-level Backend trait
pub struct DbBackend {
inner: Arc<dyn Database>,
config: Config,
}
impl DbBackend {
/// Create a new [`DbBackend`] with the supplied key-value store and
/// config.
pub fn new(db: Arc<dyn Database>, config: Config) -> Self {
DbBackend { inner: db, config }
}
}
impl V1ReadBackend for DbBackend {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<persisted_entries::CandidateEntry>> {
load_candidate_entry_v1(&*self.inner, &self.config, candidate_hash)
.map(|e| e.map(Into::into))
}
fn load_block_entry_v1(
&self,
block_hash: &Hash,
) -> SubsystemResult<Option<persisted_entries::BlockEntry>> {
load_block_entry_v1(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into))
}
}
impl Backend for DbBackend {
fn load_block_entry(
&self,
block_hash: &Hash,
) -> SubsystemResult<Option<persisted_entries::BlockEntry>> {
load_block_entry(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into))
}
fn load_candidate_entry(
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<persisted_entries::CandidateEntry>> {
load_candidate_entry(&*self.inner, &self.config, candidate_hash).map(|e| e.map(Into::into))
}
fn load_blocks_at_height(&self, block_height: &BlockNumber) -> SubsystemResult<Vec<Hash>> {
load_blocks_at_height(&*self.inner, &self.config, block_height)
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
load_all_blocks(&*self.inner, &self.config)
}
fn load_stored_blocks(&self) -> SubsystemResult<Option<StoredBlockRange>> {
load_stored_blocks(&*self.inner, &self.config)
}
/// Atomically write the list of operations, with later operations taking precedence over prior.
fn write<I>(&mut self, ops: I) -> SubsystemResult<()>
where
I: IntoIterator<Item = BackendWriteOp>,
{
let mut tx = DBTransaction::new();
for op in ops {
match op {
BackendWriteOp::WriteStoredBlockRange(stored_block_range) => {
tx.put_vec(
self.config.col_approval_data,
&STORED_BLOCKS_KEY,
stored_block_range.encode(),
);
},
BackendWriteOp::DeleteStoredBlockRange => {
tx.delete(self.config.col_approval_data, &STORED_BLOCKS_KEY);
},
BackendWriteOp::WriteBlocksAtHeight(h, blocks) => {
tx.put_vec(
self.config.col_approval_data,
&blocks_at_height_key(h),
blocks.encode(),
);
},
BackendWriteOp::DeleteBlocksAtHeight(h) => {
tx.delete(self.config.col_approval_data, &blocks_at_height_key(h));
},
BackendWriteOp::WriteBlockEntry(block_entry) => {
let block_entry: BlockEntry = block_entry.into();
tx.put_vec(
self.config.col_approval_data,
&block_entry_key(&block_entry.block_hash),
block_entry.encode(),
);
},
BackendWriteOp::DeleteBlockEntry(hash) => {
tx.delete(self.config.col_approval_data, &block_entry_key(&hash));
},
BackendWriteOp::WriteCandidateEntry(candidate_entry) => {
let candidate_entry: CandidateEntry = candidate_entry.into();
tx.put_vec(
self.config.col_approval_data,
&candidate_entry_key(&candidate_entry.candidate.hash()),
candidate_entry.encode(),
);
},
BackendWriteOp::DeleteCandidateEntry(candidate_hash) => {
tx.delete(self.config.col_approval_data, &candidate_entry_key(&candidate_hash));
},
}
}
self.inner.write(tx).map_err(|e| e.into())
}
}
/// A range from earliest..last block number stored within the DB.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct StoredBlockRange(pub BlockNumber, pub BlockNumber);
// slot_duration * 2 + DelayTranche gives the number of delay tranches since the
// unix epoch.
#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq)]
@@ -168,13 +46,6 @@ pub struct Tick(u64);
/// Convenience type definition
pub type Bitfield = BitVec<u8, BitOrderLsb0>;
/// The database config.
#[derive(Debug, Clone, Copy)]
pub struct Config {
/// The column family in the database where data is stored.
pub col_approval_data: u32,
}
/// Details pertaining to our assignment on a block.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct OurAssignment {
@@ -259,118 +130,6 @@ impl From<Tick> for crate::Tick {
}
}
/// Errors while accessing things from the DB.
#[derive(Debug, derive_more::From, derive_more::Display)]
pub enum Error {
Io(std::io::Error),
InvalidDecoding(parity_scale_codec::Error),
InternalError(SubsystemError),
}
impl std::error::Error for Error {}
/// Result alias for DB errors.
pub type Result<T> = std::result::Result<T, Error>;
pub(crate) fn load_decode<D: Decode>(
store: &dyn Database,
col_approval_data: u32,
key: &[u8],
) -> Result<Option<D>> {
match store.get(col_approval_data, key)? {
None => Ok(None),
Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into),
}
}
/// The key a given block entry is stored under.
pub(crate) fn block_entry_key(block_hash: &Hash) -> [u8; 46] {
const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck";
let mut key = [0u8; 14 + 32];
key[0..14].copy_from_slice(&BLOCK_ENTRY_PREFIX);
key[14..][..32].copy_from_slice(block_hash.as_ref());
key
}
/// The key a given candidate entry is stored under.
pub(crate) fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] {
const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand";
let mut key = [0u8; 14 + 32];
key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX);
key[14..][..32].copy_from_slice(candidate_hash.0.as_ref());
key
}
/// The key a set of block hashes corresponding to a block number is stored under.
pub(crate) fn blocks_at_height_key(block_number: BlockNumber) -> [u8; 16] {
const BLOCKS_AT_HEIGHT_PREFIX: [u8; 12] = *b"Approvals_at";
let mut key = [0u8; 12 + 4];
key[0..12].copy_from_slice(&BLOCKS_AT_HEIGHT_PREFIX);
block_number.using_encoded(|s| key[12..16].copy_from_slice(s));
key
}
/// Return all blocks which have entries in the DB, ascending, by height.
pub fn load_all_blocks(store: &dyn Database, config: &Config) -> SubsystemResult<Vec<Hash>> {
let mut hashes = Vec::new();
if let Some(stored_blocks) = load_stored_blocks(store, config)? {
for height in stored_blocks.0..stored_blocks.1 {
let blocks = load_blocks_at_height(store, config, &height)?;
hashes.extend(blocks);
}
}
Ok(hashes)
}
/// Load the stored-blocks key from the state.
pub fn load_stored_blocks(
store: &dyn Database,
config: &Config,
) -> SubsystemResult<Option<StoredBlockRange>> {
load_decode(store, config.col_approval_data, STORED_BLOCKS_KEY)
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a blocks-at-height entry for a given block number.
pub fn load_blocks_at_height(
store: &dyn Database,
config: &Config,
block_number: &BlockNumber,
) -> SubsystemResult<Vec<Hash>> {
load_decode(store, config.col_approval_data, &blocks_at_height_key(*block_number))
.map(|x| x.unwrap_or_default())
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a block entry from the aux store.
pub fn load_block_entry(
store: &dyn Database,
config: &Config,
block_hash: &Hash,
) -> SubsystemResult<Option<BlockEntry>> {
load_decode(store, config.col_approval_data, &block_entry_key(block_hash))
.map(|u: Option<BlockEntry>| u.map(|v| v.into()))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a candidate entry from the aux store in current version format.
pub fn load_candidate_entry(
store: &dyn Database,
config: &Config,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<CandidateEntry>> {
load_decode(store, config.col_approval_data, &candidate_entry_key(candidate_hash))
.map(|u: Option<CandidateEntry>| u.map(|v| v.into()))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a candidate entry from the aux store in v1 format.
pub fn load_candidate_entry_v1(
store: &dyn Database,
@@ -16,13 +16,22 @@
//! Tests for the aux-schema of approval voting.
use super::{DbBackend, StoredBlockRange, *};
use crate::{
approval_db::{
common::{migration_helpers::make_bitvec, DbBackend, StoredBlockRange, *},
v2::*,
v3::{load_block_entry_v2, load_candidate_entry_v2},
},
backend::{Backend, OverlayedBackend},
ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo},
};
use polkadot_primitives::{
BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash,
};
use polkadot_node_subsystem_util::database::Database;
use polkadot_primitives::Id as ParaId;
use sp_consensus_slots::Slot;
use std::{collections::HashMap, sync::Arc};
use ::test_helpers::{dummy_candidate_receipt, dummy_candidate_receipt_bad_sig, dummy_hash};
@@ -60,10 +69,6 @@ fn make_block_entry(
}
}
fn make_bitvec(len: usize) -> BitVec<u8, BitOrderLsb0> {
bitvec::bitvec![u8, BitOrderLsb0; 0; len]
}
fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt {
let mut c = dummy_candidate_receipt(dummy_hash());
@@ -110,7 +115,10 @@ fn read_write() {
overlay_db.write_stored_block_range(range.clone());
overlay_db.write_blocks_at_height(1, at_height.clone());
overlay_db.write_block_entry(block_entry.clone().into());
overlay_db.write_candidate_entry(candidate_entry.clone().into());
overlay_db.write_candidate_entry(crate::persisted_entries::CandidateEntry::from_v2(
candidate_entry.clone(),
0,
));
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
@@ -118,11 +126,11 @@ fn read_write() {
assert_eq!(load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap(), Some(range));
assert_eq!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap(), at_height);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(),
load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(),
Some(block_entry.into())
);
assert_eq!(
load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(),
load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(),
Some(candidate_entry.into()),
);
@@ -134,8 +142,8 @@ fn read_write() {
db.write(write_ops).unwrap();
assert!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap().is_empty());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none());
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash)
assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none());
assert!(load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash)
.unwrap()
.is_none());
}
@@ -196,25 +204,27 @@ fn add_block_entry_works() {
db.write(write_ops).unwrap();
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
Some(block_entry_a.into())
);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
Some(block_entry_b.into())
);
let candidate_entry_a = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_a)
.unwrap()
.unwrap();
let candidate_entry_a =
load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash_a)
.unwrap()
.unwrap();
assert_eq!(
candidate_entry_a.block_assignments.keys().collect::<Vec<_>>(),
vec![&block_hash_a, &block_hash_b]
);
let candidate_entry_b = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_b)
.unwrap()
.unwrap();
let candidate_entry_b =
load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash_b)
.unwrap()
.unwrap();
assert_eq!(candidate_entry_b.block_assignments.keys().collect::<Vec<_>>(), vec![&block_hash_b]);
}
@@ -243,11 +253,11 @@ fn add_block_entry_adds_child() {
block_entry_a.children.push(block_hash_b);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
Some(block_entry_a.into())
);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
Some(block_entry_b.into())
);
}
@@ -365,13 +375,15 @@ fn canonicalize_works() {
for (c_hash, in_blocks) in expected {
let (entry, in_blocks) = match in_blocks {
None => {
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash)
assert!(load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &c_hash)
.unwrap()
.is_none());
continue
},
Some(i) => (
load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash).unwrap().unwrap(),
load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &c_hash)
.unwrap()
.unwrap(),
i,
),
};
@@ -388,13 +400,13 @@ fn canonicalize_works() {
for (hash, with_candidates) in expected {
let (entry, with_candidates) = match with_candidates {
None => {
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash)
assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash)
.unwrap()
.is_none());
continue
},
Some(i) =>
(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i),
(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i),
};
assert_eq!(entry.candidates.len(), with_candidates.len());
@@ -510,22 +522,22 @@ fn force_approve_works() {
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a,)
assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a,)
.unwrap()
.unwrap()
.approved_bitfield
.all());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b,)
assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b,)
.unwrap()
.unwrap()
.approved_bitfield
.all());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_c,)
assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_c,)
.unwrap()
.unwrap()
.approved_bitfield
.not_any());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_d,)
assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_d,)
.unwrap()
.unwrap()
.approved_bitfield
@@ -0,0 +1,237 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Approval DB migration helpers.
use super::*;
use crate::{
approval_db::common::{
block_entry_key, candidate_entry_key,
migration_helpers::{dummy_assignment_cert, make_bitvec},
Config, Error, Result, StoredBlockRange,
},
backend::{Backend, V2ReadBackend},
};
use polkadot_node_primitives::approval::v1::AssignmentCertKind;
use polkadot_node_subsystem_util::database::Database;
use sp_application_crypto::sp_core::H256;
use std::{collections::HashSet, sync::Arc};
/// Migrates `BlockEntry`, `CandidateEntry`, `ApprovalEntry` and `OurApproval` to version 3.
/// Returns on any error.
/// Must only be used in parachains DB migration code - `polkadot-service` crate.
pub fn v2_to_latest(db: Arc<dyn Database>, config: Config) -> Result<()> {
let mut backend = crate::DbBackend::new(db, config);
let all_blocks = backend
.load_all_blocks()
.map_err(|e| Error::InternalError(e))?
.iter()
.filter_map(|block_hash| {
backend
.load_block_entry_v2(block_hash)
.map_err(|e| Error::InternalError(e))
.ok()?
})
.collect::<Vec<_>>();
gum::info!(
target: crate::LOG_TARGET,
"Migrating candidate entries on top of {} blocks",
all_blocks.len()
);
let mut overlay = crate::OverlayedBackend::new(&backend);
let mut counter = 0;
// Get all candidate entries, approval entries and convert each of them.
for block in all_blocks {
for (candidate_index, (_core_index, candidate_hash)) in
block.candidates().iter().enumerate()
{
// Loading the candidate will also perform the conversion to the updated format and
// return that represantation.
if let Some(candidate_entry) = backend
.load_candidate_entry_v2(&candidate_hash, candidate_index as CandidateIndex)
.map_err(|e| Error::InternalError(e))?
{
// Write the updated representation.
overlay.write_candidate_entry(candidate_entry);
counter += 1;
}
}
overlay.write_block_entry(block);
}
gum::info!(target: crate::LOG_TARGET, "Migrated {} entries", counter);
// Commit all changes to DB.
let write_ops = overlay.into_write_ops();
backend.write(write_ops).unwrap();
Ok(())
}
// Checks if the migration doesn't leave the DB in an unsane state.
// This function is to be used in tests.
pub fn v1_to_latest_sanity_check(
db: Arc<dyn Database>,
config: Config,
expected_candidates: HashSet<CandidateHash>,
) -> Result<()> {
let backend = crate::DbBackend::new(db, config);
let all_blocks = backend
.load_all_blocks()
.unwrap()
.iter()
.map(|block_hash| backend.load_block_entry(block_hash).unwrap().unwrap())
.collect::<Vec<_>>();
let mut candidates = HashSet::new();
// Iterate all blocks and approval entries.
for block in all_blocks {
for (_core_index, candidate_hash) in block.candidates() {
// Loading the candidate will also perform the conversion to the updated format and
// return that represantation.
if let Some(candidate_entry) = backend.load_candidate_entry(&candidate_hash).unwrap() {
candidates.insert(candidate_entry.candidate.hash());
}
}
}
assert_eq!(candidates, expected_candidates);
Ok(())
}
// Fills the db with dummy data in v2 scheme.
pub fn v2_fill_test_data<F>(
db: Arc<dyn Database>,
config: Config,
dummy_candidate_create: F,
) -> Result<HashSet<CandidateHash>>
where
F: Fn(H256) -> CandidateReceipt<H256>,
{
let mut backend = crate::DbBackend::new(db.clone(), config);
let mut overlay_db = crate::OverlayedBackend::new(&backend);
let mut expected_candidates = HashSet::new();
const RELAY_BLOCK_COUNT: u32 = 10;
let range = StoredBlockRange(1, 11);
overlay_db.write_stored_block_range(range.clone());
for relay_number in 1..=RELAY_BLOCK_COUNT {
let relay_hash = Hash::repeat_byte(relay_number as u8);
let assignment_core_index = CoreIndex(relay_number);
let candidate = dummy_candidate_create(relay_hash);
let candidate_hash = candidate.hash();
let at_height = vec![relay_hash];
let block_entry = make_block_entry_v2(
relay_hash,
Default::default(),
relay_number,
vec![(assignment_core_index, candidate_hash)],
);
let dummy_assignment = crate::approval_db::v2::OurAssignment {
cert: dummy_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }).into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
};
let candidate_entry = crate::approval_db::v2::CandidateEntry {
candidate,
session: 123,
block_assignments: vec![(
relay_hash,
crate::approval_db::v2::ApprovalEntry {
tranches: Vec::new(),
backing_group: GroupIndex(1),
our_assignment: Some(dummy_assignment),
our_approval_sig: None,
approved: false,
assigned_validators: make_bitvec(1),
},
)]
.into_iter()
.collect(),
approvals: Default::default(),
};
overlay_db.write_blocks_at_height(relay_number, at_height.clone());
expected_candidates.insert(candidate_entry.candidate.hash());
db.write(write_candidate_entry_v2(candidate_entry, config)).unwrap();
db.write(write_block_entry_v2(block_entry, config)).unwrap();
}
let write_ops = overlay_db.into_write_ops();
backend.write(write_ops).unwrap();
Ok(expected_candidates)
}
fn make_block_entry_v2(
block_hash: Hash,
parent_hash: Hash,
block_number: BlockNumber,
candidates: Vec<(CoreIndex, CandidateHash)>,
) -> crate::approval_db::v2::BlockEntry {
crate::approval_db::v2::BlockEntry {
block_hash,
parent_hash,
block_number,
session: 1,
slot: Slot::from(1),
relay_vrf_story: [0u8; 32],
approved_bitfield: make_bitvec(candidates.len()),
distributed_assignments: make_bitvec(candidates.len()),
candidates,
children: Vec::new(),
}
}
// Low level DB helper to write a candidate entry in v1 scheme.
fn write_candidate_entry_v2(
candidate_entry: crate::approval_db::v2::CandidateEntry,
config: Config,
) -> DBTransaction {
let mut tx = DBTransaction::new();
tx.put_vec(
config.col_approval_data,
&candidate_entry_key(&candidate_entry.candidate.hash()),
candidate_entry.encode(),
);
tx
}
// Low level DB helper to write a block entry in v1 scheme.
fn write_block_entry_v2(
block_entry: crate::approval_db::v2::BlockEntry,
config: Config,
) -> DBTransaction {
let mut tx = DBTransaction::new();
tx.put_vec(
config.col_approval_data,
&block_entry_key(&block_entry.block_hash),
block_entry.encode(),
);
tx
}
@@ -0,0 +1,137 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Version 3 of the DB schema.
//!
//! Version 3 modifies the `our_approval` format of `ApprovalEntry`
//! and adds a new field `pending_signatures` for `BlockEntry`
use parity_scale_codec::{Decode, Encode};
use polkadot_node_primitives::approval::v2::CandidateBitfield;
use polkadot_node_subsystem::SubsystemResult;
use polkadot_node_subsystem_util::database::{DBTransaction, Database};
use polkadot_overseer::SubsystemError;
use polkadot_primitives::{
BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash,
SessionIndex, ValidatorIndex, ValidatorSignature,
};
use sp_consensus_slots::Slot;
use std::collections::BTreeMap;
use super::common::{block_entry_key, candidate_entry_key, load_decode, Config};
/// Re-export this structs as v3 since they did not change between v2 and v3.
pub use super::v2::{Bitfield, OurAssignment, Tick, TrancheEntry};
pub mod migration_helpers;
#[cfg(test)]
pub mod tests;
/// Metadata about our approval signature
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct OurApproval {
/// The signature for the candidates hashes pointed by indices.
pub signature: ValidatorSignature,
/// The indices of the candidates signed in this approval.
pub signed_candidates_indices: CandidateBitfield,
}
/// Metadata regarding approval of a particular candidate within the context of some
/// particular block.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct ApprovalEntry {
pub tranches: Vec<TrancheEntry>,
pub backing_group: GroupIndex,
pub our_assignment: Option<OurAssignment>,
pub our_approval_sig: Option<OurApproval>,
// `n_validators` bits.
pub assigned_validators: Bitfield,
pub approved: bool,
}
/// Metadata regarding approval of a particular candidate.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct CandidateEntry {
pub candidate: CandidateReceipt,
pub session: SessionIndex,
// Assignments are based on blocks, so we need to track assignments separately
// based on the block we are looking at.
pub block_assignments: BTreeMap<Hash, ApprovalEntry>,
pub approvals: Bitfield,
}
/// Metadata regarding approval of a particular block, by way of approval of the
/// candidates contained within it.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct BlockEntry {
pub block_hash: Hash,
pub block_number: BlockNumber,
pub parent_hash: Hash,
pub session: SessionIndex,
pub slot: Slot,
/// Random bytes derived from the VRF submitted within the block by the block
/// author as a credential and used as input to approval assignment criteria.
pub relay_vrf_story: [u8; 32],
// The candidates included as-of this block and the index of the core they are
// leaving. Sorted ascending by core index.
pub candidates: Vec<(CoreIndex, CandidateHash)>,
// A bitfield where the i'th bit corresponds to the i'th candidate in `candidates`.
// The i'th bit is `true` iff the candidate has been approved in the context of this
// block. The block can be considered approved if the bitfield has all bits set to `true`.
pub approved_bitfield: Bitfield,
pub children: Vec<Hash>,
// A list of candidates we have checked, but didn't not sign and
// advertise the vote yet.
pub candidates_pending_signature: BTreeMap<CandidateIndex, CandidateSigningContext>,
// Assignments we already distributed. A 1 bit means the candidate index for which
// we already have sent out an assignment. We need this to avoid distributing
// multiple core assignments more than once.
pub distributed_assignments: Bitfield,
}
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
/// Context needed for creating an approval signature for a given candidate.
pub struct CandidateSigningContext {
/// The candidate hash, to be included in the signature.
pub candidate_hash: CandidateHash,
/// The latest tick we have to create and send the approval.
pub sign_no_later_than_tick: Tick,
}
/// Load a candidate entry from the aux store in v2 format.
pub fn load_candidate_entry_v2(
store: &dyn Database,
config: &Config,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<super::v2::CandidateEntry>> {
load_decode(store, config.col_approval_data, &candidate_entry_key(candidate_hash))
.map(|u: Option<super::v2::CandidateEntry>| u.map(|v| v.into()))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
/// Load a block entry from the aux store in v2 format.
pub fn load_block_entry_v2(
store: &dyn Database,
config: &Config,
block_hash: &Hash,
) -> SubsystemResult<Option<super::v2::BlockEntry>> {
load_decode(store, config.col_approval_data, &block_entry_key(block_hash))
.map(|u: Option<super::v2::BlockEntry>| u.map(|v| v.into()))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
@@ -0,0 +1,575 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Tests for the aux-schema of approval voting.
use crate::{
approval_db::{
common::{migration_helpers::make_bitvec, DbBackend, StoredBlockRange, *},
v3::*,
},
backend::{Backend, OverlayedBackend},
ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo},
};
use polkadot_primitives::{
BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash,
};
use polkadot_node_subsystem_util::database::Database;
use polkadot_primitives::Id as ParaId;
use sp_consensus_slots::Slot;
use std::{collections::HashMap, sync::Arc};
use ::test_helpers::{dummy_candidate_receipt, dummy_candidate_receipt_bad_sig, dummy_hash};
const DATA_COL: u32 = 0;
const NUM_COLUMNS: u32 = 1;
const TEST_CONFIG: Config = Config { col_approval_data: DATA_COL };
fn make_db() -> (DbBackend, Arc<dyn Database>) {
let db = kvdb_memorydb::create(NUM_COLUMNS);
let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]);
let db_writer: Arc<dyn Database> = Arc::new(db);
(DbBackend::new(db_writer.clone(), TEST_CONFIG), db_writer)
}
fn make_block_entry(
block_hash: Hash,
parent_hash: Hash,
block_number: BlockNumber,
candidates: Vec<(CoreIndex, CandidateHash)>,
) -> BlockEntry {
BlockEntry {
block_hash,
parent_hash,
block_number,
session: 1,
slot: Slot::from(1),
relay_vrf_story: [0u8; 32],
approved_bitfield: make_bitvec(candidates.len()),
candidates,
children: Vec::new(),
candidates_pending_signature: Default::default(),
distributed_assignments: Default::default(),
}
}
fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt {
let mut c = dummy_candidate_receipt(dummy_hash());
c.descriptor.para_id = para_id;
c.descriptor.relay_parent = relay_parent;
c
}
#[test]
fn read_write() {
let (mut db, store) = make_db();
let hash_a = Hash::repeat_byte(1);
let hash_b = Hash::repeat_byte(2);
let candidate_hash = dummy_candidate_receipt_bad_sig(dummy_hash(), None).hash();
let range = StoredBlockRange(10, 20);
let at_height = vec![hash_a, hash_b];
let block_entry =
make_block_entry(hash_a, Default::default(), 1, vec![(CoreIndex(0), candidate_hash)]);
let candidate_entry = CandidateEntry {
candidate: dummy_candidate_receipt_bad_sig(dummy_hash(), None),
session: 5,
block_assignments: vec![(
hash_a,
ApprovalEntry {
tranches: Vec::new(),
backing_group: GroupIndex(1),
our_assignment: None,
our_approval_sig: None,
assigned_validators: Default::default(),
approved: false,
},
)]
.into_iter()
.collect(),
approvals: Default::default(),
};
let mut overlay_db = OverlayedBackend::new(&db);
overlay_db.write_stored_block_range(range.clone());
overlay_db.write_blocks_at_height(1, at_height.clone());
overlay_db.write_block_entry(block_entry.clone().into());
overlay_db.write_candidate_entry(candidate_entry.clone().into());
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert_eq!(load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap(), Some(range));
assert_eq!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap(), at_height);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(),
Some(block_entry.into())
);
assert_eq!(
load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(),
Some(candidate_entry.into()),
);
let mut overlay_db = OverlayedBackend::new(&db);
overlay_db.delete_blocks_at_height(1);
overlay_db.delete_block_entry(&hash_a);
overlay_db.delete_candidate_entry(&candidate_hash);
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap().is_empty());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none());
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash)
.unwrap()
.is_none());
}
#[test]
fn add_block_entry_works() {
let (mut db, store) = make_db();
let parent_hash = Hash::repeat_byte(1);
let block_hash_a = Hash::repeat_byte(2);
let block_hash_b = Hash::repeat_byte(69);
let candidate_receipt_a = make_candidate(ParaId::from(1_u32), parent_hash);
let candidate_receipt_b = make_candidate(ParaId::from(2_u32), parent_hash);
let candidate_hash_a = candidate_receipt_a.hash();
let candidate_hash_b = candidate_receipt_b.hash();
let block_number = 10;
let block_entry_a = make_block_entry(
block_hash_a,
parent_hash,
block_number,
vec![(CoreIndex(0), candidate_hash_a)],
);
let block_entry_b = make_block_entry(
block_hash_b,
parent_hash,
block_number,
vec![(CoreIndex(0), candidate_hash_a), (CoreIndex(1), candidate_hash_b)],
);
let n_validators = 10;
let mut new_candidate_info = HashMap::new();
new_candidate_info
.insert(candidate_hash_a, NewCandidateInfo::new(candidate_receipt_a, GroupIndex(0), None));
let mut overlay_db = OverlayedBackend::new(&db);
add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |h| {
new_candidate_info.get(h).map(|x| x.clone())
})
.unwrap();
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
new_candidate_info
.insert(candidate_hash_b, NewCandidateInfo::new(candidate_receipt_b, GroupIndex(1), None));
let mut overlay_db = OverlayedBackend::new(&db);
add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |h| {
new_candidate_info.get(h).map(|x| x.clone())
})
.unwrap();
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
Some(block_entry_a.into())
);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
Some(block_entry_b.into())
);
let candidate_entry_a = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_a)
.unwrap()
.unwrap();
assert_eq!(
candidate_entry_a.block_assignments.keys().collect::<Vec<_>>(),
vec![&block_hash_a, &block_hash_b]
);
let candidate_entry_b = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_b)
.unwrap()
.unwrap();
assert_eq!(candidate_entry_b.block_assignments.keys().collect::<Vec<_>>(), vec![&block_hash_b]);
}
#[test]
fn add_block_entry_adds_child() {
let (mut db, store) = make_db();
let parent_hash = Hash::repeat_byte(1);
let block_hash_a = Hash::repeat_byte(2);
let block_hash_b = Hash::repeat_byte(69);
let mut block_entry_a = make_block_entry(block_hash_a, parent_hash, 1, Vec::new());
let block_entry_b = make_block_entry(block_hash_b, block_hash_a, 2, Vec::new());
let n_validators = 10;
let mut overlay_db = OverlayedBackend::new(&db);
add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |_| None).unwrap();
add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |_| None).unwrap();
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
block_entry_a.children.push(block_hash_b);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(),
Some(block_entry_a.into())
);
assert_eq!(
load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(),
Some(block_entry_b.into())
);
}
#[test]
fn canonicalize_works() {
let (mut db, store) = make_db();
// -> B1 -> C1 -> D1
// A -> B2 -> C2 -> D2
//
// We'll canonicalize C1. Everytning except D1 should disappear.
//
// Candidates:
// Cand1 in B2
// Cand2 in C2
// Cand3 in C2 and D1
// Cand4 in D1
// Cand5 in D2
// Only Cand3 and Cand4 should remain after canonicalize.
let n_validators = 10;
let mut overlay_db = OverlayedBackend::new(&db);
overlay_db.write_stored_block_range(StoredBlockRange(1, 5));
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
let genesis = Hash::repeat_byte(0);
let block_hash_a = Hash::repeat_byte(1);
let block_hash_b1 = Hash::repeat_byte(2);
let block_hash_b2 = Hash::repeat_byte(3);
let block_hash_c1 = Hash::repeat_byte(4);
let block_hash_c2 = Hash::repeat_byte(5);
let block_hash_d1 = Hash::repeat_byte(6);
let block_hash_d2 = Hash::repeat_byte(7);
let candidate_receipt_genesis = make_candidate(ParaId::from(1_u32), genesis);
let candidate_receipt_a = make_candidate(ParaId::from(2_u32), block_hash_a);
let candidate_receipt_b = make_candidate(ParaId::from(3_u32), block_hash_a);
let candidate_receipt_b1 = make_candidate(ParaId::from(4_u32), block_hash_b1);
let candidate_receipt_c1 = make_candidate(ParaId::from(5_u32), block_hash_c1);
let cand_hash_1 = candidate_receipt_genesis.hash();
let cand_hash_2 = candidate_receipt_a.hash();
let cand_hash_3 = candidate_receipt_b.hash();
let cand_hash_4 = candidate_receipt_b1.hash();
let cand_hash_5 = candidate_receipt_c1.hash();
let block_entry_a = make_block_entry(block_hash_a, genesis, 1, Vec::new());
let block_entry_b1 = make_block_entry(block_hash_b1, block_hash_a, 2, Vec::new());
let block_entry_b2 =
make_block_entry(block_hash_b2, block_hash_a, 2, vec![(CoreIndex(0), cand_hash_1)]);
let block_entry_c1 = make_block_entry(block_hash_c1, block_hash_b1, 3, Vec::new());
let block_entry_c2 = make_block_entry(
block_hash_c2,
block_hash_b2,
3,
vec![(CoreIndex(0), cand_hash_2), (CoreIndex(1), cand_hash_3)],
);
let block_entry_d1 = make_block_entry(
block_hash_d1,
block_hash_c1,
4,
vec![(CoreIndex(0), cand_hash_3), (CoreIndex(1), cand_hash_4)],
);
let block_entry_d2 =
make_block_entry(block_hash_d2, block_hash_c2, 4, vec![(CoreIndex(0), cand_hash_5)]);
let candidate_info = {
let mut candidate_info = HashMap::new();
candidate_info.insert(
cand_hash_1,
NewCandidateInfo::new(candidate_receipt_genesis, GroupIndex(1), None),
);
candidate_info
.insert(cand_hash_2, NewCandidateInfo::new(candidate_receipt_a, GroupIndex(2), None));
candidate_info
.insert(cand_hash_3, NewCandidateInfo::new(candidate_receipt_b, GroupIndex(3), None));
candidate_info
.insert(cand_hash_4, NewCandidateInfo::new(candidate_receipt_b1, GroupIndex(4), None));
candidate_info
.insert(cand_hash_5, NewCandidateInfo::new(candidate_receipt_c1, GroupIndex(5), None));
candidate_info
};
// now insert all the blocks.
let blocks = vec![
block_entry_a.clone(),
block_entry_b1.clone(),
block_entry_b2.clone(),
block_entry_c1.clone(),
block_entry_c2.clone(),
block_entry_d1.clone(),
block_entry_d2.clone(),
];
let mut overlay_db = OverlayedBackend::new(&db);
for block_entry in blocks {
add_block_entry(&mut overlay_db, block_entry.into(), n_validators, |h| {
candidate_info.get(h).map(|x| x.clone())
})
.unwrap();
}
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
let check_candidates_in_store = |expected: Vec<(CandidateHash, Option<Vec<_>>)>| {
for (c_hash, in_blocks) in expected {
let (entry, in_blocks) = match in_blocks {
None => {
assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash)
.unwrap()
.is_none());
continue
},
Some(i) => (
load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash).unwrap().unwrap(),
i,
),
};
assert_eq!(entry.block_assignments.len(), in_blocks.len());
for x in in_blocks {
assert!(entry.block_assignments.contains_key(&x));
}
}
};
let check_blocks_in_store = |expected: Vec<(Hash, Option<Vec<_>>)>| {
for (hash, with_candidates) in expected {
let (entry, with_candidates) = match with_candidates {
None => {
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash)
.unwrap()
.is_none());
continue
},
Some(i) =>
(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i),
};
assert_eq!(entry.candidates.len(), with_candidates.len());
for x in with_candidates {
assert!(entry.candidates.iter().any(|(_, c)| c == &x));
}
}
};
check_candidates_in_store(vec![
(cand_hash_1, Some(vec![block_hash_b2])),
(cand_hash_2, Some(vec![block_hash_c2])),
(cand_hash_3, Some(vec![block_hash_c2, block_hash_d1])),
(cand_hash_4, Some(vec![block_hash_d1])),
(cand_hash_5, Some(vec![block_hash_d2])),
]);
check_blocks_in_store(vec![
(block_hash_a, Some(vec![])),
(block_hash_b1, Some(vec![])),
(block_hash_b2, Some(vec![cand_hash_1])),
(block_hash_c1, Some(vec![])),
(block_hash_c2, Some(vec![cand_hash_2, cand_hash_3])),
(block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])),
(block_hash_d2, Some(vec![cand_hash_5])),
]);
let mut overlay_db = OverlayedBackend::new(&db);
canonicalize(&mut overlay_db, 3, block_hash_c1).unwrap();
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert_eq!(
load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(),
StoredBlockRange(4, 5)
);
check_candidates_in_store(vec![
(cand_hash_1, None),
(cand_hash_2, None),
(cand_hash_3, Some(vec![block_hash_d1])),
(cand_hash_4, Some(vec![block_hash_d1])),
(cand_hash_5, None),
]);
check_blocks_in_store(vec![
(block_hash_a, None),
(block_hash_b1, None),
(block_hash_b2, None),
(block_hash_c1, None),
(block_hash_c2, None),
(block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])),
(block_hash_d2, None),
]);
}
#[test]
fn force_approve_works() {
let (mut db, store) = make_db();
let n_validators = 10;
let mut overlay_db = OverlayedBackend::new(&db);
overlay_db.write_stored_block_range(StoredBlockRange(1, 4));
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
let candidate_hash = CandidateHash(Hash::repeat_byte(42));
let single_candidate_vec = vec![(CoreIndex(0), candidate_hash)];
let candidate_info = {
let mut candidate_info = HashMap::new();
candidate_info.insert(
candidate_hash,
NewCandidateInfo::new(
make_candidate(ParaId::from(1_u32), Default::default()),
GroupIndex(1),
None,
),
);
candidate_info
};
let block_hash_a = Hash::repeat_byte(1); // 1
let block_hash_b = Hash::repeat_byte(2);
let block_hash_c = Hash::repeat_byte(3);
let block_hash_d = Hash::repeat_byte(4); // 4
let block_entry_a =
make_block_entry(block_hash_a, Default::default(), 1, single_candidate_vec.clone());
let block_entry_b =
make_block_entry(block_hash_b, block_hash_a, 2, single_candidate_vec.clone());
let block_entry_c =
make_block_entry(block_hash_c, block_hash_b, 3, single_candidate_vec.clone());
let block_entry_d =
make_block_entry(block_hash_d, block_hash_c, 4, single_candidate_vec.clone());
let blocks = vec![
block_entry_a.clone(),
block_entry_b.clone(),
block_entry_c.clone(),
block_entry_d.clone(),
];
let mut overlay_db = OverlayedBackend::new(&db);
for block_entry in blocks {
add_block_entry(&mut overlay_db, block_entry.into(), n_validators, |h| {
candidate_info.get(h).map(|x| x.clone())
})
.unwrap();
}
let approved_hashes = force_approve(&mut overlay_db, block_hash_d, 2).unwrap();
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a,)
.unwrap()
.unwrap()
.approved_bitfield
.all());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b,)
.unwrap()
.unwrap()
.approved_bitfield
.all());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_c,)
.unwrap()
.unwrap()
.approved_bitfield
.not_any());
assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_d,)
.unwrap()
.unwrap()
.approved_bitfield
.not_any());
assert_eq!(approved_hashes, vec![block_hash_b, block_hash_a]);
}
#[test]
fn load_all_blocks_works() {
let (mut db, store) = make_db();
let parent_hash = Hash::repeat_byte(1);
let block_hash_a = Hash::repeat_byte(2);
let block_hash_b = Hash::repeat_byte(69);
let block_hash_c = Hash::repeat_byte(42);
let block_number = 10;
let block_entry_a = make_block_entry(block_hash_a, parent_hash, block_number, vec![]);
let block_entry_b = make_block_entry(block_hash_b, parent_hash, block_number, vec![]);
let block_entry_c = make_block_entry(block_hash_c, block_hash_a, block_number + 1, vec![]);
let n_validators = 10;
let mut overlay_db = OverlayedBackend::new(&db);
add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |_| None).unwrap();
// add C before B to test sorting.
add_block_entry(&mut overlay_db, block_entry_c.clone().into(), n_validators, |_| None).unwrap();
add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |_| None).unwrap();
let write_ops = overlay_db.into_write_ops();
db.write(write_ops).unwrap();
assert_eq!(
load_all_blocks(store.as_ref(), &TEST_CONFIG).unwrap(),
vec![block_hash_a, block_hash_b, block_hash_c],
)
}
@@ -22,12 +22,12 @@
//! before any commit to the underlying storage is made.
use polkadot_node_subsystem::SubsystemResult;
use polkadot_primitives::{BlockNumber, CandidateHash, Hash};
use polkadot_primitives::{BlockNumber, CandidateHash, CandidateIndex, Hash};
use std::collections::HashMap;
use super::{
approval_db::v2::StoredBlockRange,
approval_db::common::StoredBlockRange,
persisted_entries::{BlockEntry, CandidateEntry},
};
@@ -72,12 +72,26 @@ pub trait V1ReadBackend: Backend {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
candidate_index: CandidateIndex,
) -> SubsystemResult<Option<CandidateEntry>>;
/// Load a block entry from the DB with scheme version 1.
fn load_block_entry_v1(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>>;
}
/// A read only backend to enable db migration from version 2 of DB.
pub trait V2ReadBackend: Backend {
/// Load a candidate entry from the DB with scheme version 1.
fn load_candidate_entry_v2(
&self,
candidate_hash: &CandidateHash,
candidate_index: CandidateIndex,
) -> SubsystemResult<Option<CandidateEntry>>;
/// Load a block entry from the DB with scheme version 1.
fn load_block_entry_v2(&self, block_hash: &Hash) -> SubsystemResult<Option<BlockEntry>>;
}
// Status of block range in the `OverlayedBackend`.
#[derive(PartialEq)]
enum BlockRangeStatus {
@@ -56,7 +56,7 @@ use futures::{channel::oneshot, prelude::*};
use std::collections::HashMap;
use super::approval_db::v2;
use super::approval_db::v3;
use crate::{
backend::{Backend, OverlayedBackend},
criteria::{AssignmentCriteria, OurAssignment},
@@ -512,7 +512,7 @@ pub(crate) async fn handle_new_head<Context, B: Backend>(
ctx.send_message(ChainSelectionMessage::Approved(block_hash)).await;
}
let block_entry = v2::BlockEntry {
let block_entry = v3::BlockEntry {
block_hash,
parent_hash: block_header.parent_hash,
block_number: block_header.number,
@@ -525,6 +525,7 @@ pub(crate) async fn handle_new_head<Context, B: Backend>(
.collect(),
approved_bitfield,
children: Vec::new(),
candidates_pending_signature: Default::default(),
distributed_assignments: Default::default(),
};
@@ -604,7 +605,10 @@ pub(crate) async fn handle_new_head<Context, B: Backend>(
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{approval_db::v2::DbBackend, RuntimeInfo, RuntimeInfoConfig};
use crate::{
approval_db::common::{load_block_entry, DbBackend},
RuntimeInfo, RuntimeInfoConfig,
};
use ::test_helpers::{dummy_candidate_receipt, dummy_hash};
use assert_matches::assert_matches;
use polkadot_node_primitives::{
@@ -627,7 +631,7 @@ pub(crate) mod tests {
pub(crate) use sp_runtime::{Digest, DigestItem};
use std::{pin::Pin, sync::Arc};
use crate::{approval_db::v2::Config as DatabaseConfig, criteria, BlockEntry};
use crate::{approval_db::common::Config as DatabaseConfig, criteria, BlockEntry};
const DATA_COL: u32 = 0;
@@ -1347,7 +1351,7 @@ pub(crate) mod tests {
let (state, mut session_info_provider) = single_session_state();
overlay_db.write_block_entry(
v2::BlockEntry {
v3::BlockEntry {
block_hash: parent_hash,
parent_hash: Default::default(),
block_number: 4,
@@ -1357,6 +1361,7 @@ pub(crate) mod tests {
candidates: Vec::new(),
approved_bitfield: Default::default(),
children: Vec::new(),
candidates_pending_signature: Default::default(),
distributed_assignments: Default::default(),
}
.into(),
@@ -1389,11 +1394,10 @@ pub(crate) mod tests {
assert_eq!(candidates[1].1.approvals().len(), 6);
// the first candidate should be insta-approved
// the second should not
let entry: BlockEntry =
v2::load_block_entry(db_writer.as_ref(), &TEST_CONFIG, &hash)
.unwrap()
.unwrap()
.into();
let entry: BlockEntry = load_block_entry(db_writer.as_ref(), &TEST_CONFIG, &hash)
.unwrap()
.unwrap()
.into();
assert!(entry.is_candidate_approved(&candidates[0].0));
assert!(!entry.is_candidate_approved(&candidates[1].0));
})
File diff suppressed because it is too large Load Diff
@@ -25,7 +25,7 @@ use polkadot_primitives::{BlockNumber, CandidateHash, CandidateReceipt, GroupInd
use std::collections::{hash_map::Entry, BTreeMap, HashMap};
use super::{
approval_db::v2::{OurAssignment, StoredBlockRange},
approval_db::{common::StoredBlockRange, v2::OurAssignment},
backend::{Backend, OverlayedBackend},
persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry},
LOG_TARGET,
@@ -20,13 +20,14 @@
//! Within that context, things are plain-old-data. Within this module,
//! data and logic are intertwined.
use itertools::Itertools;
use polkadot_node_primitives::approval::{
v1::{DelayTranche, RelayVRFStory},
v2::{AssignmentCertV2, CandidateBitfield},
};
use polkadot_primitives::{
BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex,
ValidatorIndex, ValidatorSignature,
BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash,
SessionIndex, ValidatorIndex, ValidatorSignature,
};
use sp_consensus_slots::Slot;
@@ -76,6 +77,45 @@ impl From<TrancheEntry> for crate::approval_db::v2::TrancheEntry {
}
}
impl From<crate::approval_db::v3::OurApproval> for OurApproval {
fn from(approval: crate::approval_db::v3::OurApproval) -> Self {
Self {
signature: approval.signature,
signed_candidates_indices: approval.signed_candidates_indices,
}
}
}
impl From<OurApproval> for crate::approval_db::v3::OurApproval {
fn from(approval: OurApproval) -> Self {
Self {
signature: approval.signature,
signed_candidates_indices: approval.signed_candidates_indices,
}
}
}
/// Metadata about our approval signature
#[derive(Debug, Clone, PartialEq)]
pub struct OurApproval {
/// The signature for the candidates hashes pointed by indices.
pub signature: ValidatorSignature,
/// The indices of the candidates signed in this approval.
pub signed_candidates_indices: CandidateBitfield,
}
impl OurApproval {
/// Converts a ValidatorSignature to an OurApproval.
/// It used in converting the database from v1 to latest.
pub fn from_v1(value: ValidatorSignature, candidate_index: CandidateIndex) -> Self {
Self { signature: value, signed_candidates_indices: candidate_index.into() }
}
/// Converts a ValidatorSignature to an OurApproval.
/// It used in converting the database from v2 to latest.
pub fn from_v2(value: ValidatorSignature, candidate_index: CandidateIndex) -> Self {
Self::from_v1(value, candidate_index)
}
}
/// Metadata regarding approval of a particular candidate within the context of some
/// particular block.
#[derive(Debug, Clone, PartialEq)]
@@ -83,7 +123,7 @@ pub struct ApprovalEntry {
tranches: Vec<TrancheEntry>,
backing_group: GroupIndex,
our_assignment: Option<OurAssignment>,
our_approval_sig: Option<ValidatorSignature>,
our_approval_sig: Option<OurApproval>,
// `n_validators` bits.
assigned_validators: Bitfield,
approved: bool,
@@ -95,7 +135,7 @@ impl ApprovalEntry {
tranches: Vec<TrancheEntry>,
backing_group: GroupIndex,
our_assignment: Option<OurAssignment>,
our_approval_sig: Option<ValidatorSignature>,
our_approval_sig: Option<OurApproval>,
// `n_validators` bits.
assigned_validators: Bitfield,
approved: bool,
@@ -137,7 +177,7 @@ impl ApprovalEntry {
}
/// Import our local approval vote signature for this candidate.
pub fn import_approval_sig(&mut self, approval_sig: ValidatorSignature) {
pub fn import_approval_sig(&mut self, approval_sig: OurApproval) {
self.our_approval_sig = Some(approval_sig);
}
@@ -224,7 +264,7 @@ impl ApprovalEntry {
/// Get the assignment cert & approval signature.
///
/// The approval signature will only be `Some` if the assignment is too.
pub fn local_statements(&self) -> (Option<OurAssignment>, Option<ValidatorSignature>) {
pub fn local_statements(&self) -> (Option<OurAssignment>, Option<OurApproval>) {
let approval_sig = self.our_approval_sig.clone();
if let Some(our_assignment) = self.our_assignment.as_ref().filter(|a| a.triggered()) {
(Some(our_assignment.clone()), approval_sig)
@@ -232,10 +272,44 @@ impl ApprovalEntry {
(None, None)
}
}
// Convert an ApprovalEntry from v1 version to latest version
pub fn from_v1(
value: crate::approval_db::v1::ApprovalEntry,
candidate_index: CandidateIndex,
) -> Self {
ApprovalEntry {
tranches: value.tranches.into_iter().map(|tranche| tranche.into()).collect(),
backing_group: value.backing_group,
our_assignment: value.our_assignment.map(|assignment| assignment.into()),
our_approval_sig: value
.our_approval_sig
.map(|sig| OurApproval::from_v1(sig, candidate_index)),
assigned_validators: value.assignments,
approved: value.approved,
}
}
// Convert an ApprovalEntry from v1 version to latest version
pub fn from_v2(
value: crate::approval_db::v2::ApprovalEntry,
candidate_index: CandidateIndex,
) -> Self {
ApprovalEntry {
tranches: value.tranches.into_iter().map(|tranche| tranche.into()).collect(),
backing_group: value.backing_group,
our_assignment: value.our_assignment.map(|assignment| assignment.into()),
our_approval_sig: value
.our_approval_sig
.map(|sig| OurApproval::from_v2(sig, candidate_index)),
assigned_validators: value.assigned_validators,
approved: value.approved,
}
}
}
impl From<crate::approval_db::v2::ApprovalEntry> for ApprovalEntry {
fn from(entry: crate::approval_db::v2::ApprovalEntry) -> Self {
impl From<crate::approval_db::v3::ApprovalEntry> for ApprovalEntry {
fn from(entry: crate::approval_db::v3::ApprovalEntry) -> Self {
ApprovalEntry {
tranches: entry.tranches.into_iter().map(Into::into).collect(),
backing_group: entry.backing_group,
@@ -247,7 +321,7 @@ impl From<crate::approval_db::v2::ApprovalEntry> for ApprovalEntry {
}
}
impl From<ApprovalEntry> for crate::approval_db::v2::ApprovalEntry {
impl From<ApprovalEntry> for crate::approval_db::v3::ApprovalEntry {
fn from(entry: ApprovalEntry) -> Self {
Self {
tranches: entry.tranches.into_iter().map(Into::into).collect(),
@@ -303,10 +377,44 @@ impl CandidateEntry {
pub fn approval_entry(&self, block_hash: &Hash) -> Option<&ApprovalEntry> {
self.block_assignments.get(block_hash)
}
/// Convert a CandidateEntry from a v1 to its latest equivalent.
pub fn from_v1(
value: crate::approval_db::v1::CandidateEntry,
candidate_index: CandidateIndex,
) -> Self {
Self {
approvals: value.approvals,
block_assignments: value
.block_assignments
.into_iter()
.map(|(h, ae)| (h, ApprovalEntry::from_v1(ae, candidate_index)))
.collect(),
candidate: value.candidate,
session: value.session,
}
}
/// Convert a CandidateEntry from a v2 to its latest equivalent.
pub fn from_v2(
value: crate::approval_db::v2::CandidateEntry,
candidate_index: CandidateIndex,
) -> Self {
Self {
approvals: value.approvals,
block_assignments: value
.block_assignments
.into_iter()
.map(|(h, ae)| (h, ApprovalEntry::from_v2(ae, candidate_index)))
.collect(),
candidate: value.candidate,
session: value.session,
}
}
}
impl From<crate::approval_db::v2::CandidateEntry> for CandidateEntry {
fn from(entry: crate::approval_db::v2::CandidateEntry) -> Self {
impl From<crate::approval_db::v3::CandidateEntry> for CandidateEntry {
fn from(entry: crate::approval_db::v3::CandidateEntry) -> Self {
CandidateEntry {
candidate: entry.candidate,
session: entry.session,
@@ -320,7 +428,7 @@ impl From<crate::approval_db::v2::CandidateEntry> for CandidateEntry {
}
}
impl From<CandidateEntry> for crate::approval_db::v2::CandidateEntry {
impl From<CandidateEntry> for crate::approval_db::v3::CandidateEntry {
fn from(entry: CandidateEntry) -> Self {
Self {
candidate: entry.candidate,
@@ -353,12 +461,21 @@ pub struct BlockEntry {
// block. The block can be considered approved if the bitfield has all bits set to `true`.
pub approved_bitfield: Bitfield,
pub children: Vec<Hash>,
// A list of candidates we have checked, but didn't not sign and
// advertise the vote yet.
candidates_pending_signature: BTreeMap<CandidateIndex, CandidateSigningContext>,
// A list of assignments for which we already distributed the assignment.
// We use this to ensure we don't distribute multiple core assignments twice as we track
// individual wakeups for each core.
distributed_assignments: Bitfield,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CandidateSigningContext {
pub candidate_hash: CandidateHash,
pub sign_no_later_than_tick: Tick,
}
impl BlockEntry {
/// Mark a candidate as fully approved in the bitfield.
pub fn mark_approved_by_hash(&mut self, candidate_hash: &CandidateHash) {
@@ -447,10 +564,97 @@ impl BlockEntry {
distributed
}
/// Defer signing and issuing an approval for a candidate no later than the specified tick
pub fn defer_candidate_signature(
&mut self,
candidate_index: CandidateIndex,
candidate_hash: CandidateHash,
sign_no_later_than_tick: Tick,
) -> Option<CandidateSigningContext> {
self.candidates_pending_signature.insert(
candidate_index,
CandidateSigningContext { candidate_hash, sign_no_later_than_tick },
)
}
/// Returns the number of candidates waiting for an approval to be issued.
pub fn num_candidates_pending_signature(&self) -> usize {
self.candidates_pending_signature.len()
}
/// Return if we have candidates waiting for signature to be issued
pub fn has_candidates_pending_signature(&self) -> bool {
!self.candidates_pending_signature.is_empty()
}
/// Candidate hashes for candidates pending signatures
fn candidate_hashes_pending_signature(&self) -> Vec<CandidateHash> {
self.candidates_pending_signature
.values()
.map(|unsigned_approval| unsigned_approval.candidate_hash)
.collect()
}
/// Candidate indices for candidates pending signature
fn candidate_indices_pending_signature(&self) -> Option<CandidateBitfield> {
self.candidates_pending_signature
.keys()
.map(|val| *val)
.collect_vec()
.try_into()
.ok()
}
/// Returns a list of candidates hashes that need need signature created at the current tick:
/// This might happen in other of the two reasons:
/// 1. We queued more than max_approval_coalesce_count candidates.
/// 2. We have candidates that waiting in the queue past their `sign_no_later_than_tick`
///
/// Additionally, we also return the first tick when we will have to create a signature,
/// so that the caller can arm the timer if it is not already armed.
pub fn get_candidates_that_need_signature(
&self,
tick_now: Tick,
max_approval_coalesce_count: u32,
) -> (Option<(Vec<CandidateHash>, CandidateBitfield)>, Option<Tick>) {
let sign_no_later_than_tick = self
.candidates_pending_signature
.values()
.min_by(|a, b| a.sign_no_later_than_tick.cmp(&b.sign_no_later_than_tick))
.map(|val| val.sign_no_later_than_tick);
if let Some(sign_no_later_than_tick) = sign_no_later_than_tick {
if sign_no_later_than_tick <= tick_now ||
self.num_candidates_pending_signature() >= max_approval_coalesce_count as usize
{
(
self.candidate_indices_pending_signature().and_then(|candidate_indices| {
Some((self.candidate_hashes_pending_signature(), candidate_indices))
}),
Some(sign_no_later_than_tick),
)
} else {
// We can still wait for other candidates to queue in, so just make sure
// we wake up at the tick we have to sign the longest waiting candidate.
(Default::default(), Some(sign_no_later_than_tick))
}
} else {
// No cached candidates, nothing to do here, this just means the timer fired,
// but the signatures were already sent because we gathered more than
// max_approval_coalesce_count.
(Default::default(), sign_no_later_than_tick)
}
}
/// Clears the candidates pending signature because the approval was issued.
pub fn issued_approval(&mut self) {
self.candidates_pending_signature.clear();
}
}
impl From<crate::approval_db::v2::BlockEntry> for BlockEntry {
fn from(entry: crate::approval_db::v2::BlockEntry) -> Self {
impl From<crate::approval_db::v3::BlockEntry> for BlockEntry {
fn from(entry: crate::approval_db::v3::BlockEntry) -> Self {
BlockEntry {
block_hash: entry.block_hash,
parent_hash: entry.parent_hash,
@@ -461,6 +665,11 @@ impl From<crate::approval_db::v2::BlockEntry> for BlockEntry {
candidates: entry.candidates,
approved_bitfield: entry.approved_bitfield,
children: entry.children,
candidates_pending_signature: entry
.candidates_pending_signature
.into_iter()
.map(|(candidate_index, signing_context)| (candidate_index, signing_context.into()))
.collect(),
distributed_assignments: entry.distributed_assignments,
}
}
@@ -479,11 +688,30 @@ impl From<crate::approval_db::v1::BlockEntry> for BlockEntry {
approved_bitfield: entry.approved_bitfield,
children: entry.children,
distributed_assignments: Default::default(),
candidates_pending_signature: Default::default(),
}
}
}
impl From<BlockEntry> for crate::approval_db::v2::BlockEntry {
impl From<crate::approval_db::v2::BlockEntry> for BlockEntry {
fn from(entry: crate::approval_db::v2::BlockEntry) -> Self {
BlockEntry {
block_hash: entry.block_hash,
parent_hash: entry.parent_hash,
block_number: entry.block_number,
session: entry.session,
slot: entry.slot,
relay_vrf_story: RelayVRFStory(entry.relay_vrf_story),
candidates: entry.candidates,
approved_bitfield: entry.approved_bitfield,
children: entry.children,
distributed_assignments: entry.distributed_assignments,
candidates_pending_signature: Default::default(),
}
}
}
impl From<BlockEntry> for crate::approval_db::v3::BlockEntry {
fn from(entry: BlockEntry) -> Self {
Self {
block_hash: entry.block_hash,
@@ -495,36 +723,30 @@ impl From<BlockEntry> for crate::approval_db::v2::BlockEntry {
candidates: entry.candidates,
approved_bitfield: entry.approved_bitfield,
children: entry.children,
candidates_pending_signature: entry
.candidates_pending_signature
.into_iter()
.map(|(candidate_index, signing_context)| (candidate_index, signing_context.into()))
.collect(),
distributed_assignments: entry.distributed_assignments,
}
}
}
/// Migration helpers.
impl From<crate::approval_db::v1::CandidateEntry> for CandidateEntry {
fn from(value: crate::approval_db::v1::CandidateEntry) -> Self {
impl From<crate::approval_db::v3::CandidateSigningContext> for CandidateSigningContext {
fn from(signing_context: crate::approval_db::v3::CandidateSigningContext) -> Self {
Self {
approvals: value.approvals,
block_assignments: value
.block_assignments
.into_iter()
.map(|(h, ae)| (h, ae.into()))
.collect(),
candidate: value.candidate,
session: value.session,
candidate_hash: signing_context.candidate_hash,
sign_no_later_than_tick: signing_context.sign_no_later_than_tick.into(),
}
}
}
impl From<crate::approval_db::v1::ApprovalEntry> for ApprovalEntry {
fn from(value: crate::approval_db::v1::ApprovalEntry) -> Self {
ApprovalEntry {
tranches: value.tranches.into_iter().map(|tranche| tranche.into()).collect(),
backing_group: value.backing_group,
our_assignment: value.our_assignment.map(|assignment| assignment.into()),
our_approval_sig: value.our_approval_sig,
assigned_validators: value.assignments,
approved: value.approved,
impl From<CandidateSigningContext> for crate::approval_db::v3::CandidateSigningContext {
fn from(signing_context: CandidateSigningContext) -> Self {
Self {
candidate_hash: signing_context.candidate_hash,
sign_no_later_than_tick: signing_context.sign_no_later_than_tick.into(),
}
}
}
+576 -5
View File
@@ -37,8 +37,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers;
use polkadot_node_subsystem_util::TimeoutExt;
use polkadot_overseer::HeadSupportsParachains;
use polkadot_primitives::{
vstaging::NodeFeatures, CandidateCommitments, CandidateEvent, CoreIndex, GroupIndex, Header,
Id as ParaId, IndexedVec, ValidationCode, ValidatorSignature,
vstaging::NodeFeatures, ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex,
GroupIndex, Header, Id as ParaId, IndexedVec, ValidationCode, ValidatorSignature,
};
use std::time::Duration;
@@ -56,7 +56,7 @@ use std::{
};
use super::{
approval_db::v2::StoredBlockRange,
approval_db::common::StoredBlockRange,
backend::BackendWriteOp,
import::tests::{
garbage_vrf_signature, AllowedSlots, BabeEpoch, BabeEpochConfiguration,
@@ -116,7 +116,7 @@ fn make_sync_oracle(val: bool) -> (Box<dyn SyncOracle + Send>, TestSyncOracleHan
#[cfg(test)]
pub mod test_constants {
use crate::approval_db::v2::Config as DatabaseConfig;
use crate::approval_db::common::Config as DatabaseConfig;
const DATA_COL: u32 = 0;
pub(crate) const NUM_COLUMNS: u32 = 1;
@@ -281,6 +281,7 @@ impl V1ReadBackend for TestStoreInner {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
_candidate_index: CandidateIndex,
) -> SubsystemResult<Option<CandidateEntry>> {
self.load_candidate_entry(candidate_hash)
}
@@ -364,6 +365,7 @@ impl V1ReadBackend for TestStore {
fn load_candidate_entry_v1(
&self,
candidate_hash: &CandidateHash,
_candidate_index: CandidateIndex,
) -> SubsystemResult<Option<CandidateEntry>> {
self.load_candidate_entry(candidate_hash)
}
@@ -446,6 +448,15 @@ fn sign_approval(
key.sign(&ApprovalVote(candidate_hash).signing_payload(session_index)).into()
}
fn sign_approval_multiple_candidates(
key: Sr25519Keyring,
candidate_hashes: Vec<CandidateHash>,
session_index: SessionIndex,
) -> ValidatorSignature {
key.sign(&ApprovalVoteMultipleCandidates(&candidate_hashes).signing_payload(session_index))
.into()
}
type VirtualOverseer = test_helpers::TestSubsystemContextHandle<ApprovalVotingMessage>;
#[derive(Default)]
@@ -641,7 +652,12 @@ async fn check_and_import_approval(
overseer,
FromOrchestra::Communication {
msg: ApprovalVotingMessage::CheckAndImportApproval(
IndirectSignedApprovalVote { block_hash, candidate_index, validator, signature },
IndirectSignedApprovalVoteV2 {
block_hash,
candidate_indices: candidate_index.into(),
validator,
signature,
},
tx,
),
},
@@ -2014,6 +2030,91 @@ fn forkful_import_at_same_height_act_on_leaf() {
});
}
#[test]
fn test_signing_a_single_candidate_is_backwards_compatible() {
let session_index = 1;
let block_hash = Hash::repeat_byte(0x01);
let candidate_descriptors = (1..10)
.into_iter()
.map(|val| make_candidate(ParaId::from(val as u32), &block_hash))
.collect::<Vec<CandidateReceipt>>();
let candidate_hashes = candidate_descriptors
.iter()
.map(|candidate_descriptor| candidate_descriptor.hash())
.collect_vec();
let first_descriptor = candidate_descriptors.first().unwrap();
let candidate_hash = first_descriptor.hash();
let sig_a = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index);
let sig_b = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index);
assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
candidate_hash,
session_index,
&sig_a,
)
.is_ok());
assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
candidate_hash,
session_index,
&sig_b,
)
.is_ok());
let sig_c = sign_approval_multiple_candidates(
Sr25519Keyring::Alice,
vec![candidate_hash],
session_index,
);
assert!(DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(vec![candidate_hash])
)
.check_signature(&Sr25519Keyring::Alice.public().into(), candidate_hash, session_index, &sig_c,)
.is_ok());
assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
candidate_hash,
session_index,
&sig_c,
)
.is_ok());
assert!(DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(vec![candidate_hash])
)
.check_signature(&Sr25519Keyring::Alice.public().into(), candidate_hash, session_index, &sig_a,)
.is_ok());
let sig_all = sign_approval_multiple_candidates(
Sr25519Keyring::Alice,
candidate_hashes.clone(),
session_index,
);
assert!(DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes.clone())
)
.check_signature(
&Sr25519Keyring::Alice.public().into(),
*candidate_hashes.first().expect("test"),
session_index,
&sig_all,
)
.is_ok());
}
#[test]
fn import_checked_approval_updates_entries_and_schedules() {
let config = HarnessConfig::default();
@@ -2730,11 +2831,29 @@ async fn handle_double_assignment_import(
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 1,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_))
@@ -3469,3 +3588,455 @@ fn waits_until_approving_assignments_are_old_enough() {
virtual_overseer
});
}
#[test]
fn test_approval_is_sent_on_max_approval_coalesce_count() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let assignments_cert =
garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
});
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(1),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_commitments = CandidateCommitments::default();
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let slot = Slot::from(1);
let candidate_index1 = 0;
let candidate_index2 = 1;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidates = Some(vec![
(candidate_receipt1.clone(), CoreIndex(0), GroupIndex(0)),
(candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)),
]);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: candidates.clone(),
session_info: Some(session_info.clone()),
},
)
.build(&mut virtual_overseer)
.await;
assert!(!clock.inner.lock().current_wakeup_is(1));
clock.inner.lock().wakeup_all(1);
assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot)));
clock.inner.lock().wakeup_all(slot_to_tick(slot));
futures_timer::Delay::new(Duration::from_millis(200)).await;
clock.inner.lock().wakeup_all(slot_to_tick(slot + 2));
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(200)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash1).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
handle_approval_on_max_coalesce_count(
&mut virtual_overseer,
vec![candidate_index1, candidate_index2],
)
.await;
virtual_overseer
});
}
async fn handle_approval_on_max_coalesce_count(
virtual_overseer: &mut VirtualOverseer,
candidate_indicies: Vec<CandidateIndex>,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_indices,
)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies.clone()).unwrap(), c_indices);
}
);
for _ in &candidate_indicies {
recover_available_data(virtual_overseer).await;
fetch_validation_code(virtual_overseer).await;
}
for _ in &candidate_indicies {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive{exec_kind, response_sender, ..}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
}
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 2,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 2,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(vote)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies).unwrap(), vote.candidate_indices);
}
);
// Assert that there are no more messages being sent by the subsystem
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
}
async fn handle_approval_on_max_wait_time(
virtual_overseer: &mut VirtualOverseer,
candidate_indicies: Vec<CandidateIndex>,
clock: Box<MockClock>,
) {
const TICK_NOW_BEGIN: u64 = 1;
const MAX_COALESCE_COUNT: u32 = 3;
clock.inner.lock().set_tick(TICK_NOW_BEGIN);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment(
_,
c_indices,
)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies.clone()).unwrap(), c_indices);
}
);
for _ in &candidate_indicies {
recover_available_data(virtual_overseer).await;
fetch_validation_code(virtual_overseer).await;
}
for _ in &candidate_indicies {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive{exec_kind, response_sender, ..}) if exec_kind == PvfExecKind::Approval => {
response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default())))
.unwrap();
}
);
}
// First time we fetch the configuration when we are ready to approve the first candidate
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: MAX_COALESCE_COUNT,
}));
}
);
// Second time we fetch the configuration when we are ready to approve the second candidate
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: MAX_COALESCE_COUNT,
}));
}
);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
// Move the clock just before we should send the approval
clock
.inner
.lock()
.set_tick(MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick + TICK_NOW_BEGIN - 1);
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
// Move the clock tick, so we can trigger a force sending of the approvals
clock
.inner
.lock()
.set_tick(MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick + TICK_NOW_BEGIN);
// Third time we fetch the configuration when timer expires and we are ready to sent the
// approval
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => {
let _ = sender.send(Ok(ApprovalVotingParams {
max_approval_coalesce_count: 3,
}));
}
);
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(vote)) => {
assert_eq!(TryInto::<CandidateBitfield>::try_into(candidate_indicies).unwrap(), vote.candidate_indices);
}
);
// Assert that there are no more messages being sent by the subsystem
assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none());
}
#[test]
fn test_approval_is_sent_on_max_approval_coalesce_wait() {
let assignment_criteria = Box::new(MockAssignmentCriteria(
|| {
let mut assignments = HashMap::new();
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 })
.into(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let assignments_cert =
garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact {
core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)]
.try_into()
.unwrap(),
});
let _ = assignments.insert(
CoreIndex(0),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
let _ = assignments.insert(
CoreIndex(1),
approval_db::v2::OurAssignment {
cert: assignments_cert.clone(),
tranche: 0,
validator_index: ValidatorIndex(0),
triggered: false,
}
.into(),
);
assignments
},
|_| Ok(0),
));
let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build();
let store = config.backend();
test_harness(config, |test_harness| async move {
let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } =
test_harness;
assert_matches!(
overseer_recv(&mut virtual_overseer).await,
AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => {
rx.send(Ok(0)).unwrap();
}
);
let block_hash = Hash::repeat_byte(0x01);
let candidate_commitments = CandidateCommitments::default();
let candidate_receipt1 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(1_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let candidate_hash1 = candidate_receipt1.hash();
let candidate_receipt2 = {
let mut receipt = dummy_candidate_receipt(block_hash);
receipt.descriptor.para_id = ParaId::from(2_u32);
receipt.commitments_hash = candidate_commitments.hash();
receipt
};
let slot = Slot::from(1);
let candidate_index1 = 0;
let candidate_index2 = 1;
let validators = vec![
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Eve,
];
let session_info = SessionInfo {
validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
vec![ValidatorIndex(0), ValidatorIndex(1)],
vec![ValidatorIndex(2)],
vec![ValidatorIndex(3), ValidatorIndex(4)],
]),
..session_info(&validators)
};
let candidates = Some(vec![
(candidate_receipt1.clone(), CoreIndex(0), GroupIndex(0)),
(candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)),
]);
ChainBuilder::new()
.add_block(
block_hash,
ChainBuilder::GENESIS_HASH,
1,
BlockConfig {
slot,
candidates: candidates.clone(),
session_info: Some(session_info.clone()),
},
)
.build(&mut virtual_overseer)
.await;
assert!(!clock.inner.lock().current_wakeup_is(1));
clock.inner.lock().wakeup_all(1);
assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot)));
clock.inner.lock().wakeup_all(slot_to_tick(slot));
futures_timer::Delay::new(Duration::from_millis(200)).await;
clock.inner.lock().wakeup_all(slot_to_tick(slot + 2));
assert_eq!(clock.inner.lock().wakeups.len(), 0);
futures_timer::Delay::new(Duration::from_millis(200)).await;
let candidate_entry = store.load_candidate_entry(&candidate_hash1).unwrap().unwrap();
let our_assignment =
candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap();
assert!(our_assignment.triggered());
handle_approval_on_max_wait_time(
&mut virtual_overseer,
vec![candidate_index1, candidate_index2],
clock,
)
.await;
virtual_overseer
});
}
+164 -1
View File
@@ -16,14 +16,23 @@
//! Time utilities for approval voting.
use futures::prelude::*;
use futures::{
future::BoxFuture,
prelude::*,
stream::{FusedStream, FuturesUnordered},
Stream, StreamExt,
};
use polkadot_node_primitives::approval::v1::DelayTranche;
use sp_consensus_slots::Slot;
use std::{
collections::HashSet,
pin::Pin,
task::Poll,
time::{Duration, SystemTime},
};
use polkadot_primitives::{Hash, ValidatorIndex};
const TICK_DURATION_MILLIS: u64 = 500;
/// A base unit of time, starting from the Unix epoch, split into half-second intervals.
@@ -88,3 +97,157 @@ pub(crate) fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick
let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS;
u64::from(slot) * ticks_per_slot
}
/// A list of delayed futures that gets triggered when the waiting time has expired and it is
/// time to sign the candidate.
/// We have a timer per relay-chain block.
#[derive(Default)]
pub struct DelayedApprovalTimer {
timers: FuturesUnordered<BoxFuture<'static, (Hash, ValidatorIndex)>>,
blocks: HashSet<Hash>,
}
impl DelayedApprovalTimer {
/// Starts a single timer per block hash
///
/// Guarantees that if a timer already exits for the give block hash,
/// no additional timer is started.
pub(crate) fn maybe_arm_timer(
&mut self,
wait_untill: Tick,
clock: &dyn Clock,
block_hash: Hash,
validator_index: ValidatorIndex,
) {
if self.blocks.insert(block_hash) {
let clock_wait = clock.wait(wait_untill);
self.timers.push(Box::pin(async move {
clock_wait.await;
(block_hash, validator_index)
}));
}
}
}
impl Stream for DelayedApprovalTimer {
type Item = (Hash, ValidatorIndex);
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let poll_result = self.timers.poll_next_unpin(cx);
match poll_result {
Poll::Ready(Some(result)) => {
self.blocks.remove(&result.0);
Poll::Ready(Some(result))
},
_ => poll_result,
}
}
}
impl FusedStream for DelayedApprovalTimer {
fn is_terminated(&self) -> bool {
self.timers.is_terminated()
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use futures::{executor::block_on, FutureExt, StreamExt};
use futures_timer::Delay;
use polkadot_primitives::{Hash, ValidatorIndex};
use crate::time::{Clock, SystemClock};
use super::DelayedApprovalTimer;
#[test]
fn test_select_empty_timer() {
block_on(async move {
let mut timer = DelayedApprovalTimer::default();
for _ in 1..10 {
let result = futures::select!(
_ = timer.select_next_some() => {
0
}
// Only this arm should fire
_ = Delay::new(Duration::from_millis(100)).fuse() => {
1
}
);
assert_eq!(result, 1);
}
});
}
#[test]
fn test_timer_functionality() {
block_on(async move {
let mut timer = DelayedApprovalTimer::default();
let test_hashes =
vec![Hash::repeat_byte(0x01), Hash::repeat_byte(0x02), Hash::repeat_byte(0x03)];
for (index, hash) in test_hashes.iter().enumerate() {
timer.maybe_arm_timer(
SystemClock.tick_now() + index as u64,
&SystemClock,
*hash,
ValidatorIndex::from(2),
);
timer.maybe_arm_timer(
SystemClock.tick_now() + index as u64,
&SystemClock,
*hash,
ValidatorIndex::from(2),
);
}
let timeout_hash = Hash::repeat_byte(0x02);
for i in 0..test_hashes.len() * 2 {
let result = futures::select!(
(hash, _) = timer.select_next_some() => {
hash
}
// Timers should fire only once, so for the rest of the iterations we should timeout through here.
_ = Delay::new(Duration::from_secs(2)).fuse() => {
timeout_hash
}
);
assert_eq!(test_hashes.get(i).cloned().unwrap_or(timeout_hash), result);
}
// Now check timer can be restarted if already fired
for (index, hash) in test_hashes.iter().enumerate() {
timer.maybe_arm_timer(
SystemClock.tick_now() + index as u64,
&SystemClock,
*hash,
ValidatorIndex::from(2),
);
timer.maybe_arm_timer(
SystemClock.tick_now() + index as u64,
&SystemClock,
*hash,
ValidatorIndex::from(2),
);
}
for i in 0..test_hashes.len() * 2 {
let result = futures::select!(
(hash, _) = timer.select_next_some() => {
hash
}
// Timers should fire only once, so for the rest of the iterations we should timeout through here.
_ = Delay::new(Duration::from_secs(2)).fuse() => {
timeout_hash
}
);
assert_eq!(test_hashes.get(i).cloned().unwrap_or(timeout_hash), result);
}
});
}
}
@@ -34,9 +34,9 @@ use polkadot_node_primitives::{
use polkadot_node_subsystem::overseer;
use polkadot_node_subsystem_util::runtime::RuntimeInfo;
use polkadot_primitives::{
CandidateReceipt, DisputeStatement, ExecutorParams, Hash, IndexedVec, SessionIndex,
SessionInfo, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorPair,
ValidatorSignature,
CandidateHash, CandidateReceipt, DisputeStatement, ExecutorParams, Hash, IndexedVec,
SessionIndex, SessionInfo, ValidDisputeStatementKind, ValidatorId, ValidatorIndex,
ValidatorPair, ValidatorSignature,
};
use sc_keystore::LocalKeystore;
@@ -126,7 +126,9 @@ impl OwnVoteState {
let our_valid_votes = controlled_indices
.iter()
.filter_map(|i| votes.valid.raw().get_key_value(i))
.map(|(index, (kind, sig))| (*index, (DisputeStatement::Valid(*kind), sig.clone())));
.map(|(index, (kind, sig))| {
(*index, (DisputeStatement::Valid(kind.clone()), sig.clone()))
});
let our_invalid_votes = controlled_indices
.iter()
.filter_map(|i| votes.invalid.get_key_value(i))
@@ -305,7 +307,7 @@ impl CandidateVoteState<CandidateVotes> {
DisputeStatement::Valid(valid_kind) => {
let fresh = votes.valid.insert_vote(
val_index,
*valid_kind,
valid_kind.clone(),
statement.into_validator_signature(),
);
if fresh {
@@ -511,7 +513,7 @@ impl ImportResult {
pub fn import_approval_votes(
self,
env: &CandidateEnvironment,
approval_votes: HashMap<ValidatorIndex, ValidatorSignature>,
approval_votes: HashMap<ValidatorIndex, (Vec<CandidateHash>, ValidatorSignature)>,
now: Timestamp,
) -> Self {
let Self {
@@ -525,19 +527,33 @@ impl ImportResult {
let (mut votes, _) = new_state.into_old_state();
for (index, sig) in approval_votes.into_iter() {
for (index, (candidate_hashes, sig)) in approval_votes.into_iter() {
debug_assert!(
{
let pub_key = &env.session_info().validators.get(index).expect("indices are validated by approval-voting subsystem; qed");
let candidate_hash = votes.candidate_receipt.hash();
let session_index = env.session_index();
DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking)
.check_signature(pub_key, candidate_hash, session_index, &sig)
candidate_hashes.contains(&votes.candidate_receipt.hash()) && DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes.clone()))
.check_signature(pub_key, *candidate_hashes.first().expect("Valid votes have at least one candidate; qed"), session_index, &sig)
.is_ok()
},
"Signature check for imported approval votes failed! This is a serious bug. Session: {:?}, candidate hash: {:?}, validator index: {:?}", env.session_index(), votes.candidate_receipt.hash(), index
);
if votes.valid.insert_vote(index, ValidDisputeStatementKind::ApprovalChecking, sig) {
if votes.valid.insert_vote(
index,
// There is a hidden dependency here between approval-voting and this subsystem.
// We should be able to start emitting
// ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates only after:
// 1. Runtime have been upgraded to know about the new format.
// 2. All nodes have been upgraded to know about the new format.
// Once those two requirements have been met we should be able to increase
// max_approval_coalesce_count to values greater than 1.
if candidate_hashes.len() > 1 {
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes)
} else {
ValidDisputeStatementKind::ApprovalChecking
},
sig,
) {
imported_valid_votes += 1;
imported_approval_votes += 1;
}
@@ -642,7 +642,7 @@ impl Initialized {
};
debug_assert!(
SignedDisputeStatement::new_checked(
DisputeStatement::Valid(valid_statement_kind),
DisputeStatement::Valid(valid_statement_kind.clone()),
candidate_hash,
session,
validator_public.clone(),
@@ -656,7 +656,7 @@ impl Initialized {
);
let signed_dispute_statement =
SignedDisputeStatement::new_unchecked_from_trusted_source(
DisputeStatement::Valid(valid_statement_kind),
DisputeStatement::Valid(valid_statement_kind.clone()),
candidate_hash,
session,
validator_public,
@@ -576,7 +576,7 @@ pub fn make_dispute_message(
.next()
.ok_or(DisputeMessageCreationError::NoOppositeVote)?;
let other_vote = SignedDisputeStatement::new_checked(
DisputeStatement::Valid(*statement_kind),
DisputeStatement::Valid(statement_kind.clone()),
*our_vote.candidate_hash(),
our_vote.session_index(),
validators
@@ -661,7 +661,7 @@ fn make_candidate_included_event(candidate_receipt: CandidateReceipt) -> Candida
pub async fn handle_approval_vote_request(
ctx_handle: &mut VirtualOverseer,
expected_hash: &CandidateHash,
votes_to_send: HashMap<ValidatorIndex, ValidatorSignature>,
votes_to_send: HashMap<ValidatorIndex, (Vec<CandidateHash>, ValidatorSignature)>,
) {
assert_matches!(
ctx_handle.recv().await,
@@ -868,9 +868,12 @@ fn approval_vote_import_works() {
.await;
gum::trace!("After sending `ImportStatements`");
let approval_votes = [(ValidatorIndex(4), approval_vote.into_validator_signature())]
.into_iter()
.collect();
let approval_votes = [(
ValidatorIndex(4),
(vec![candidate_receipt1.hash()], approval_vote.into_validator_signature()),
)]
.into_iter()
.collect();
handle_approval_vote_request(&mut virtual_overseer, &candidate_hash1, approval_votes)
.await;
@@ -221,7 +221,7 @@ where
votes.valid.retain(|validator_idx, (statement_kind, _)| {
is_vote_worth_to_keep(
validator_idx,
DisputeStatement::Valid(*statement_kind),
DisputeStatement::Valid(statement_kind.clone()),
&onchain_state,
)
});
+25 -6
View File
@@ -20,12 +20,13 @@ use schnellru::{ByLength, LruMap};
use sp_consensus_babe::Epoch;
use polkadot_primitives::{
async_backing, slashing, vstaging, AuthorityDiscoveryId, BlockNumber, CandidateCommitments,
CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState,
ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement,
ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
ValidatorId, ValidatorIndex, ValidatorSignature,
async_backing, slashing,
vstaging::{self, ApprovalVotingParams},
AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption,
PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo,
ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
};
/// For consistency we have the same capacity for all caches. We use 128 as we'll only need that
@@ -68,6 +69,7 @@ pub(crate) struct RequestResultCache {
para_backing_state: LruMap<(Hash, ParaId), Option<async_backing::BackingState>>,
async_backing_params: LruMap<Hash, async_backing::AsyncBackingParams>,
node_features: LruMap<SessionIndex, vstaging::NodeFeatures>,
approval_voting_params: LruMap<SessionIndex, ApprovalVotingParams>,
}
impl Default for RequestResultCache {
@@ -98,6 +100,7 @@ impl Default for RequestResultCache {
unapplied_slashes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
key_ownership_proof: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
minimum_backing_votes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
approval_voting_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
disabled_validators: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
para_backing_state: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)),
@@ -507,6 +510,21 @@ impl RequestResultCache {
) {
self.async_backing_params.insert(key, value);
}
pub(crate) fn approval_voting_params(
&mut self,
key: (Hash, SessionIndex),
) -> Option<&ApprovalVotingParams> {
self.approval_voting_params.get(&key.1).map(|v| &*v)
}
pub(crate) fn cache_approval_voting_params(
&mut self,
session_index: SessionIndex,
value: ApprovalVotingParams,
) {
self.approval_voting_params.insert(session_index, value);
}
}
pub(crate) enum RequestResult {
@@ -554,6 +572,7 @@ pub(crate) enum RequestResult {
slashing::OpaqueKeyOwnershipProof,
Option<()>,
),
ApprovalVotingParams(Hash, SessionIndex, ApprovalVotingParams),
DisabledValidators(Hash, Vec<ValidatorIndex>),
ParaBackingState(Hash, ParaId, Option<async_backing::BackingState>),
AsyncBackingParams(Hash, async_backing::AsyncBackingParams),
+13
View File
@@ -165,6 +165,8 @@ where
KeyOwnershipProof(relay_parent, validator_id, key_ownership_proof) => self
.requests_cache
.cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof),
RequestResult::ApprovalVotingParams(_relay_parent, session_index, params) =>
self.requests_cache.cache_approval_voting_params(session_index, params),
SubmitReportDisputeLost(_, _, _, _) => {},
DisabledValidators(relay_parent, disabled_validators) =>
self.requests_cache.cache_disabled_validators(relay_parent, disabled_validators),
@@ -300,6 +302,9 @@ where
Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender)
},
),
Request::ApprovalVotingParams(session_index, sender) =>
query!(approval_voting_params(session_index), sender)
.map(|sender| Request::ApprovalVotingParams(session_index, sender)),
Request::DisabledValidators(sender) => query!(disabled_validators(), sender)
.map(|sender| Request::DisabledValidators(sender)),
Request::ParaBackingState(para, sender) => query!(para_backing_state(para), sender)
@@ -571,6 +576,14 @@ where
ver = Request::KEY_OWNERSHIP_PROOF_RUNTIME_REQUIREMENT,
sender
),
Request::ApprovalVotingParams(session_index, sender) => {
query!(
ApprovalVotingParams,
approval_voting_params(session_index),
ver = Request::APPROVAL_VOTING_PARAMS_REQUIREMENT,
sender
)
},
Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) => query!(
SubmitReportDisputeLost,
submit_report_dispute_lost(dispute_proof, key_ownership_proof),
+16 -6
View File
@@ -20,12 +20,13 @@ use polkadot_node_primitives::{BabeAllowedSlots, BabeEpoch, BabeEpochConfigurati
use polkadot_node_subsystem::SpawnGlue;
use polkadot_node_subsystem_test_helpers::make_subsystem_context;
use polkadot_primitives::{
async_backing, slashing, vstaging::NodeFeatures, AuthorityDiscoveryId, BlockNumber,
CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState,
DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement,
ScrapedOnChainVotes, SessionIndex, SessionInfo, Slot, ValidationCode, ValidationCodeHash,
ValidatorId, ValidatorIndex, ValidatorSignature,
async_backing, slashing,
vstaging::{ApprovalVotingParams, NodeFeatures},
AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption,
PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo,
Slot, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use sp_api::ApiError;
use sp_core::testing::TaskExecutor;
@@ -242,6 +243,15 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient {
todo!("Not required for tests")
}
/// Approval voting configuration parameters
async fn approval_voting_params(
&self,
_: Hash,
_: SessionIndex,
) -> Result<ApprovalVotingParams, ApiError> {
todo!("Not required for tests")
}
async fn current_epoch(&self, _: Hash) -> Result<sp_consensus_babe::Epoch, ApiError> {
Ok(self.babe_epoch.as_ref().unwrap().clone())
}
File diff suppressed because it is too large Load Diff
@@ -31,6 +31,8 @@ struct MetricsInner {
time_unify_with_peer: prometheus::Histogram,
time_import_pending_now_known: prometheus::Histogram,
time_awaiting_approval_voting: prometheus::Histogram,
assignments_received_result: prometheus::CounterVec<prometheus::U64>,
approvals_received_result: prometheus::CounterVec<prometheus::U64>,
}
trait AsLabel {
@@ -78,6 +80,132 @@ impl Metrics {
.map(|metrics| metrics.time_import_pending_now_known.start_timer())
}
pub fn on_approval_already_known(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["known"]).inc()
}
}
pub fn on_approval_entry_not_found(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["noapprovalentry"]).inc()
}
}
pub fn on_approval_recent_outdated(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["outdated"]).inc()
}
}
pub fn on_approval_invalid_block(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["invalidblock"]).inc()
}
}
pub fn on_approval_unknown_assignment(&self) {
if let Some(metrics) = &self.0 {
metrics
.approvals_received_result
.with_label_values(&["unknownassignment"])
.inc()
}
}
pub fn on_approval_duplicate(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["duplicate"]).inc()
}
}
pub fn on_approval_out_of_view(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["outofview"]).inc()
}
}
pub fn on_approval_good_known(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["goodknown"]).inc()
}
}
pub fn on_approval_bad(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["bad"]).inc()
}
}
pub fn on_approval_unexpected(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["unexpected"]).inc()
}
}
pub fn on_approval_bug(&self) {
if let Some(metrics) = &self.0 {
metrics.approvals_received_result.with_label_values(&["bug"]).inc()
}
}
pub fn on_assignment_already_known(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["known"]).inc()
}
}
pub fn on_assignment_recent_outdated(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["outdated"]).inc()
}
}
pub fn on_assignment_invalid_block(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["invalidblock"]).inc()
}
}
pub fn on_assignment_duplicate(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["duplicate"]).inc()
}
}
pub fn on_assignment_out_of_view(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["outofview"]).inc()
}
}
pub fn on_assignment_good_known(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["goodknown"]).inc()
}
}
pub fn on_assignment_bad(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["bad"]).inc()
}
}
pub fn on_assignment_duplicatevoting(&self) {
if let Some(metrics) = &self.0 {
metrics
.assignments_received_result
.with_label_values(&["duplicatevoting"])
.inc()
}
}
pub fn on_assignment_far(&self) {
if let Some(metrics) = &self.0 {
metrics.assignments_received_result.with_label_values(&["far"]).inc()
}
}
pub(crate) fn time_awaiting_approval_voting(
&self,
) -> Option<prometheus::prometheus::HistogramTimer> {
@@ -167,6 +295,26 @@ impl MetricsTrait for Metrics {
).buckets(vec![0.0001, 0.0004, 0.0016, 0.0064, 0.0256, 0.1024, 0.4096, 1.6384, 3.2768, 4.9152, 6.5536,]))?,
registry,
)?,
assignments_received_result: prometheus::register(
prometheus::CounterVec::new(
prometheus::Opts::new(
"polkadot_parachain_assignments_received_result",
"Result of a processed assignement",
),
&["status"]
)?,
registry,
)?,
approvals_received_result: prometheus::register(
prometheus::CounterVec::new(
prometheus::Opts::new(
"polkadot_parachain_approvals_received_result",
"Result of a processed approval",
),
&["status"]
)?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
File diff suppressed because it is too large Load Diff
@@ -32,7 +32,7 @@ use polkadot_node_network_protocol::{
GridNeighbors, RandomRouting, RequiredRouting, SessionBoundGridTopologyStorage,
},
peer_set::{ProtocolVersion, ValidationVersion},
v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, OurView, PeerId,
v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, OurView, PeerId,
UnifiedReputationChange as Rep, Versioned, View,
};
use polkadot_node_subsystem::{
@@ -102,8 +102,8 @@ impl BitfieldGossipMessage {
self.relay_parent,
self.signed_availability.into(),
)),
Some(ValidationVersion::VStaging) =>
Versioned::VStaging(protocol_vstaging::BitfieldDistributionMessage::Bitfield(
Some(ValidationVersion::V3) =>
Versioned::V3(protocol_v3::BitfieldDistributionMessage::Bitfield(
self.relay_parent,
self.signed_availability.into(),
)),
@@ -503,8 +503,8 @@ async fn relay_message<Context>(
let v2_interested_peers =
filter_by_peer_version(&interested_peers, ValidationVersion::V2.into());
let vstaging_interested_peers =
filter_by_peer_version(&interested_peers, ValidationVersion::VStaging.into());
let v3_interested_peers =
filter_by_peer_version(&interested_peers, ValidationVersion::V3.into());
if !v1_interested_peers.is_empty() {
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
@@ -522,10 +522,10 @@ async fn relay_message<Context>(
.await
}
if !vstaging_interested_peers.is_empty() {
if !v3_interested_peers.is_empty() {
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
vstaging_interested_peers,
message.into_validation_protocol(ValidationVersion::VStaging.into()),
v3_interested_peers,
message.into_validation_protocol(ValidationVersion::V3.into()),
))
.await
}
@@ -551,7 +551,7 @@ async fn process_incoming_peer_message<Context>(
relay_parent,
bitfield,
)) |
Versioned::VStaging(protocol_vstaging::BitfieldDistributionMessage::Bitfield(
Versioned::V3(protocol_v3::BitfieldDistributionMessage::Bitfield(
relay_parent,
bitfield,
)) => (relay_parent, bitfield),
+6 -6
View File
@@ -33,7 +33,7 @@ use sc_network::{
use polkadot_node_network_protocol::{
peer_set::{CollationVersion, PeerSet, ProtocolVersion, ValidationVersion},
request_response::{OutgoingRequest, Recipient, ReqProtocolNames, Requests},
v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, PeerId,
v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, PeerId,
};
use polkadot_primitives::{AuthorityDiscoveryId, Block, Hash};
@@ -62,20 +62,20 @@ pub(crate) fn send_validation_message_v1(
);
}
// Helper function to send a validation vstaging message to a list of peers.
// Helper function to send a validation v3 message to a list of peers.
// Messages are always sent via the main protocol, even legacy protocol messages.
pub(crate) fn send_validation_message_vstaging(
pub(crate) fn send_validation_message_v3(
peers: Vec<PeerId>,
message: WireMessage<protocol_vstaging::ValidationProtocol>,
message: WireMessage<protocol_v3::ValidationProtocol>,
metrics: &Metrics,
notification_sinks: &Arc<Mutex<HashMap<(PeerSet, PeerId), Box<dyn MessageSink>>>>,
) {
gum::trace!(target: LOG_TARGET, ?peers, ?message, "Sending validation vstaging message to peers",);
gum::trace!(target: LOG_TARGET, ?peers, ?message, "Sending validation v3 message to peers",);
send_message(
peers,
PeerSet::Validation,
ValidationVersion::VStaging.into(),
ValidationVersion::V3.into(),
message,
metrics,
notification_sinks,
+48 -49
View File
@@ -37,8 +37,8 @@ use polkadot_node_network_protocol::{
CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion,
ValidationVersion,
},
v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, ObservedRole, OurView,
PeerId, UnifiedReputationChange as Rep, View,
v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, ObservedRole, OurView, PeerId,
UnifiedReputationChange as Rep, View,
};
use polkadot_node_subsystem::{
@@ -70,7 +70,7 @@ use super::validator_discovery;
/// Defines the `Network` trait with an implementation for an `Arc<NetworkService>`.
use crate::network::{
send_collation_message_v1, send_collation_message_v2, send_validation_message_v1,
send_validation_message_v2, send_validation_message_vstaging, Network,
send_validation_message_v2, send_validation_message_v3, Network,
};
use crate::{network::get_peer_id_by_authority_id, WireMessage};
@@ -294,9 +294,9 @@ async fn handle_validation_message<AD>(
metrics,
notification_sinks,
),
ValidationVersion::VStaging => send_validation_message_vstaging(
ValidationVersion::V3 => send_validation_message_v3(
vec![peer],
WireMessage::<protocol_vstaging::ValidationProtocol>::ViewUpdate(local_view),
WireMessage::<protocol_v3::ValidationProtocol>::ViewUpdate(local_view),
metrics,
notification_sinks,
),
@@ -360,48 +360,47 @@ async fn handle_validation_message<AD>(
?peer,
);
let (events, reports) =
if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V1.into()) {
handle_peer_messages::<protocol_v1::ValidationProtocol, _>(
peer,
PeerSet::Validation,
&mut shared.0.lock().validation_peers,
vec![notification.into()],
metrics,
)
} else if expected_versions[PeerSet::Validation] ==
Some(ValidationVersion::V2.into())
{
handle_peer_messages::<protocol_v2::ValidationProtocol, _>(
peer,
PeerSet::Validation,
&mut shared.0.lock().validation_peers,
vec![notification.into()],
metrics,
)
} else if expected_versions[PeerSet::Validation] ==
Some(ValidationVersion::VStaging.into())
{
handle_peer_messages::<protocol_vstaging::ValidationProtocol, _>(
peer,
PeerSet::Validation,
&mut shared.0.lock().validation_peers,
vec![notification.into()],
metrics,
)
} else {
gum::warn!(
target: LOG_TARGET,
version = ?expected_versions[PeerSet::Validation],
"Major logic bug. Peer somehow has unsupported validation protocol version."
);
let (events, reports) = if expected_versions[PeerSet::Validation] ==
Some(ValidationVersion::V1.into())
{
handle_peer_messages::<protocol_v1::ValidationProtocol, _>(
peer,
PeerSet::Validation,
&mut shared.0.lock().validation_peers,
vec![notification.into()],
metrics,
)
} else if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V2.into()) {
handle_peer_messages::<protocol_v2::ValidationProtocol, _>(
peer,
PeerSet::Validation,
&mut shared.0.lock().validation_peers,
vec![notification.into()],
metrics,
)
} else if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V3.into()) {
handle_peer_messages::<protocol_v3::ValidationProtocol, _>(
peer,
PeerSet::Validation,
&mut shared.0.lock().validation_peers,
vec![notification.into()],
metrics,
)
} else {
gum::warn!(
target: LOG_TARGET,
version = ?expected_versions[PeerSet::Validation],
"Major logic bug. Peer somehow has unsupported validation protocol version."
);
never!("Only versions 1 and 2 are supported; peer set connection checked above; qed");
never!(
"Only versions 1 and 2 are supported; peer set connection checked above; qed"
);
// If a peer somehow triggers this, we'll disconnect them
// eventually.
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
};
// If a peer somehow triggers this, we'll disconnect them
// eventually.
(Vec::new(), vec![UNCONNECTED_PEERSET_COST])
};
for report in reports {
network_service.report_peer(peer, report.into());
@@ -980,8 +979,8 @@ fn update_our_view<Context>(
filter_by_peer_version(&validation_peers, ValidationVersion::V2.into());
let v2_collation_peers = filter_by_peer_version(&collation_peers, CollationVersion::V2.into());
let vstaging_validation_peers =
filter_by_peer_version(&validation_peers, ValidationVersion::VStaging.into());
let v3_validation_peers =
filter_by_peer_version(&validation_peers, ValidationVersion::V3.into());
send_validation_message_v1(
v1_validation_peers,
@@ -1011,8 +1010,8 @@ fn update_our_view<Context>(
notification_sinks,
);
send_validation_message_vstaging(
vstaging_validation_peers,
send_validation_message_v3(
v3_validation_peers,
WireMessage::ViewUpdate(new_view.clone()),
metrics,
notification_sinks,
+8 -8
View File
@@ -224,7 +224,7 @@ impl TestNetworkHandle {
PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/1")),
PeerSet::Collation => Some(ProtocolName::from("/polkadot/collation/1")),
},
ValidationVersion::VStaging => match peer_set {
ValidationVersion::V3 => match peer_set {
PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/3")),
PeerSet::Collation => unreachable!(),
},
@@ -1433,8 +1433,8 @@ fn network_protocol_versioning_view_update() {
ValidationVersion::V2 =>
WireMessage::<protocol_v2::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
ValidationVersion::VStaging =>
WireMessage::<protocol_vstaging::ValidationProtocol>::ViewUpdate(view.clone())
ValidationVersion::V3 =>
WireMessage::<protocol_v3::ValidationProtocol>::ViewUpdate(view.clone())
.encode(),
};
assert_network_actions_contains(
@@ -1469,7 +1469,7 @@ fn network_protocol_versioning_subsystem_msg() {
NetworkBridgeEvent::PeerConnected(
peer,
ObservedRole::Full,
ValidationVersion::V2.into(),
ValidationVersion::V3.into(),
None,
),
&mut virtual_overseer,
@@ -1484,9 +1484,9 @@ fn network_protocol_versioning_subsystem_msg() {
}
let approval_distribution_message =
protocol_v2::ApprovalDistributionMessage::Approvals(Vec::new());
protocol_v3::ApprovalDistributionMessage::Approvals(Vec::new());
let msg = protocol_v2::ValidationProtocol::ApprovalDistribution(
let msg = protocol_v3::ValidationProtocol::ApprovalDistribution(
approval_distribution_message.clone(),
);
@@ -1502,7 +1502,7 @@ fn network_protocol_versioning_subsystem_msg() {
virtual_overseer.recv().await,
AllMessages::ApprovalDistribution(
ApprovalDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::V2(m))
NetworkBridgeEvent::PeerMessage(p, Versioned::V3(m))
)
) => {
assert_eq!(p, peer);
@@ -1536,7 +1536,7 @@ fn network_protocol_versioning_subsystem_msg() {
virtual_overseer.recv().await,
AllMessages::StatementDistribution(
StatementDistributionMessage::NetworkBridgeUpdate(
NetworkBridgeEvent::PeerMessage(p, Versioned::V2(m))
NetworkBridgeEvent::PeerMessage(p, Versioned::V3(m))
)
) => {
assert_eq!(p, peer);
+5 -5
View File
@@ -41,7 +41,7 @@ use crate::validator_discovery;
/// Defines the `Network` trait with an implementation for an `Arc<NetworkService>`.
use crate::network::{
send_collation_message_v1, send_collation_message_v2, send_validation_message_v1,
send_validation_message_v2, send_validation_message_vstaging, Network,
send_validation_message_v2, send_validation_message_v3, Network,
};
use crate::metrics::Metrics;
@@ -205,7 +205,7 @@ where
&metrics,
notification_sinks,
),
Versioned::VStaging(msg) => send_validation_message_vstaging(
Versioned::V3(msg) => send_validation_message_v3(
peers,
WireMessage::ProtocolMessage(msg),
&metrics,
@@ -235,7 +235,7 @@ where
&metrics,
notification_sinks,
),
Versioned::VStaging(msg) => send_validation_message_vstaging(
Versioned::V3(msg) => send_validation_message_v3(
peers,
WireMessage::ProtocolMessage(msg),
&metrics,
@@ -264,7 +264,7 @@ where
&metrics,
notification_sinks,
),
Versioned::V2(msg) | Versioned::VStaging(msg) => send_collation_message_v2(
Versioned::V2(msg) | Versioned::V3(msg) => send_collation_message_v2(
peers,
WireMessage::ProtocolMessage(msg),
&metrics,
@@ -287,7 +287,7 @@ where
&metrics,
notification_sinks,
),
Versioned::V2(msg) | Versioned::VStaging(msg) => send_collation_message_v2(
Versioned::V2(msg) | Versioned::V3(msg) => send_collation_message_v2(
peers,
WireMessage::ProtocolMessage(msg),
&metrics,
@@ -882,7 +882,7 @@ async fn handle_incoming_peer_message<Context>(
match msg {
Versioned::V1(V1::Declare(..)) |
Versioned::V2(V2::Declare(..)) |
Versioned::VStaging(V2::Declare(..)) => {
Versioned::V3(V2::Declare(..)) => {
gum::trace!(
target: LOG_TARGET,
?origin,
@@ -895,7 +895,7 @@ async fn handle_incoming_peer_message<Context>(
},
Versioned::V1(V1::AdvertiseCollation(_)) |
Versioned::V2(V2::AdvertiseCollation { .. }) |
Versioned::VStaging(V2::AdvertiseCollation { .. }) => {
Versioned::V3(V2::AdvertiseCollation { .. }) => {
gum::trace!(
target: LOG_TARGET,
?origin,
@@ -911,7 +911,7 @@ async fn handle_incoming_peer_message<Context>(
},
Versioned::V1(V1::CollationSeconded(relay_parent, statement)) |
Versioned::V2(V2::CollationSeconded(relay_parent, statement)) |
Versioned::VStaging(V2::CollationSeconded(relay_parent, statement)) => {
Versioned::V3(V2::CollationSeconded(relay_parent, statement)) => {
if !matches!(statement.unchecked_payload(), Statement::Seconded(_)) {
gum::warn!(
target: LOG_TARGET,
@@ -777,7 +777,7 @@ async fn process_incoming_peer_message<Context>(
match msg {
Versioned::V1(V1::Declare(collator_id, para_id, signature)) |
Versioned::V2(V2::Declare(collator_id, para_id, signature)) |
Versioned::VStaging(V2::Declare(collator_id, para_id, signature)) => {
Versioned::V3(V2::Declare(collator_id, para_id, signature)) => {
if collator_peer_id(&state.peer_data, &collator_id).is_some() {
modify_reputation(
&mut state.reputation,
@@ -894,7 +894,7 @@ async fn process_incoming_peer_message<Context>(
candidate_hash,
parent_head_data_hash,
}) |
Versioned::VStaging(V2::AdvertiseCollation {
Versioned::V3(V2::AdvertiseCollation {
relay_parent,
candidate_hash,
parent_head_data_hash,
@@ -923,7 +923,7 @@ async fn process_incoming_peer_message<Context>(
},
Versioned::V1(V1::CollationSeconded(..)) |
Versioned::V2(V2::CollationSeconded(..)) |
Versioned::VStaging(V2::CollationSeconded(..)) => {
Versioned::V3(V2::CollationSeconded(..)) => {
gum::warn!(
target: LOG_TARGET,
peer_id = ?origin,
@@ -477,7 +477,7 @@ where
match message {
Versioned::V1(m) => match m {},
Versioned::V2(m) => match m {},
Versioned::VStaging(m) => match m {},
Versioned::V3(m) => match m {},
}
},
}
@@ -27,6 +27,3 @@ bitvec = "1"
[dev-dependencies]
rand_chacha = "0.3.1"
[features]
network-protocol-staging = []
@@ -73,12 +73,20 @@ pub struct SessionGridTopology {
shuffled_indices: Vec<usize>,
/// The canonical shuffling of validators for the session.
canonical_shuffling: Vec<TopologyPeerInfo>,
/// The list of peer-ids in an efficient way to search.
peer_ids: HashSet<PeerId>,
}
impl SessionGridTopology {
/// Create a new session grid topology.
pub fn new(shuffled_indices: Vec<usize>, canonical_shuffling: Vec<TopologyPeerInfo>) -> Self {
SessionGridTopology { shuffled_indices, canonical_shuffling }
let mut peer_ids = HashSet::new();
for peer_info in canonical_shuffling.iter() {
for peer_id in peer_info.peer_ids.iter() {
peer_ids.insert(*peer_id);
}
}
SessionGridTopology { shuffled_indices, canonical_shuffling, peer_ids }
}
/// Produces the outgoing routing logic for a particular peer.
@@ -111,6 +119,11 @@ impl SessionGridTopology {
Some(grid_subset)
}
/// Tells if a given peer id is validator in a session
pub fn is_validator(&self, peer: &PeerId) -> bool {
self.peer_ids.contains(peer)
}
}
struct MatrixNeighbors<R, C> {
@@ -273,6 +286,11 @@ impl SessionGridTopologyEntry {
pub fn get(&self) -> &SessionGridTopology {
&self.topology
}
/// Tells if a given peer id is validator in a session
pub fn is_validator(&self, peer: &PeerId) -> bool {
self.topology.is_validator(peer)
}
}
/// A set of topologies indexed by session
@@ -347,6 +365,7 @@ impl Default for SessionBoundGridTopologyStorage {
topology: SessionGridTopology {
shuffled_indices: Vec::new(),
canonical_shuffling: Vec::new(),
peer_ids: Default::default(),
},
local_neighbors: GridNeighbors::empty(),
},
+29 -32
View File
@@ -253,29 +253,29 @@ impl View {
/// A protocol-versioned type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Versioned<V1, V2, VStaging = V2> {
pub enum Versioned<V1, V2, V3 = V2> {
/// V1 type.
V1(V1),
/// V2 type.
V2(V2),
/// VStaging type
VStaging(VStaging),
/// V3 type
V3(V3),
}
impl<V1: Clone, V2: Clone, VStaging: Clone> Versioned<&'_ V1, &'_ V2, &'_ VStaging> {
impl<V1: Clone, V2: Clone, V3: Clone> Versioned<&'_ V1, &'_ V2, &'_ V3> {
/// Convert to a fully-owned version of the message.
pub fn clone_inner(&self) -> Versioned<V1, V2, VStaging> {
pub fn clone_inner(&self) -> Versioned<V1, V2, V3> {
match *self {
Versioned::V1(inner) => Versioned::V1(inner.clone()),
Versioned::V2(inner) => Versioned::V2(inner.clone()),
Versioned::VStaging(inner) => Versioned::VStaging(inner.clone()),
Versioned::V3(inner) => Versioned::V3(inner.clone()),
}
}
}
/// All supported versions of the validation protocol message.
pub type VersionedValidationProtocol =
Versioned<v1::ValidationProtocol, v2::ValidationProtocol, vstaging::ValidationProtocol>;
Versioned<v1::ValidationProtocol, v2::ValidationProtocol, v3::ValidationProtocol>;
impl From<v1::ValidationProtocol> for VersionedValidationProtocol {
fn from(v1: v1::ValidationProtocol) -> Self {
@@ -289,9 +289,9 @@ impl From<v2::ValidationProtocol> for VersionedValidationProtocol {
}
}
impl From<vstaging::ValidationProtocol> for VersionedValidationProtocol {
fn from(vstaging: vstaging::ValidationProtocol) -> Self {
VersionedValidationProtocol::VStaging(vstaging)
impl From<v3::ValidationProtocol> for VersionedValidationProtocol {
fn from(v3: v3::ValidationProtocol) -> Self {
VersionedValidationProtocol::V3(v3)
}
}
@@ -317,7 +317,7 @@ macro_rules! impl_versioned_full_protocol_from {
match versioned_from {
Versioned::V1(x) => Versioned::V1(x.into()),
Versioned::V2(x) => Versioned::V2(x.into()),
Versioned::VStaging(x) => Versioned::VStaging(x.into()),
Versioned::V3(x) => Versioned::V3(x.into()),
}
}
}
@@ -331,7 +331,7 @@ macro_rules! impl_versioned_try_from {
$out:ty,
$v1_pat:pat => $v1_out:expr,
$v2_pat:pat => $v2_out:expr,
$vstaging_pat:pat => $vstaging_out:expr
$v3_pat:pat => $v3_out:expr
) => {
impl TryFrom<$from> for $out {
type Error = crate::WrongVariant;
@@ -341,7 +341,7 @@ macro_rules! impl_versioned_try_from {
match x {
Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out)),
Versioned::V2($v2_pat) => Ok(Versioned::V2($v2_out)),
Versioned::VStaging($vstaging_pat) => Ok(Versioned::VStaging($vstaging_out)),
Versioned::V3($v3_pat) => Ok(Versioned::V3($v3_out)),
_ => Err(crate::WrongVariant),
}
}
@@ -355,8 +355,7 @@ macro_rules! impl_versioned_try_from {
match x {
Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out.clone())),
Versioned::V2($v2_pat) => Ok(Versioned::V2($v2_out.clone())),
Versioned::VStaging($vstaging_pat) =>
Ok(Versioned::VStaging($vstaging_out.clone())),
Versioned::V3($v3_pat) => Ok(Versioned::V3($v3_out.clone())),
_ => Err(crate::WrongVariant),
}
}
@@ -368,7 +367,7 @@ macro_rules! impl_versioned_try_from {
pub type BitfieldDistributionMessage = Versioned<
v1::BitfieldDistributionMessage,
v2::BitfieldDistributionMessage,
vstaging::BitfieldDistributionMessage,
v3::BitfieldDistributionMessage,
>;
impl_versioned_full_protocol_from!(
BitfieldDistributionMessage,
@@ -380,14 +379,14 @@ impl_versioned_try_from!(
BitfieldDistributionMessage,
v1::ValidationProtocol::BitfieldDistribution(x) => x,
v2::ValidationProtocol::BitfieldDistribution(x) => x,
vstaging::ValidationProtocol::BitfieldDistribution(x) => x
v3::ValidationProtocol::BitfieldDistribution(x) => x
);
/// Version-annotated messages used by the statement distribution subsystem.
pub type StatementDistributionMessage = Versioned<
v1::StatementDistributionMessage,
v2::StatementDistributionMessage,
vstaging::StatementDistributionMessage,
v3::StatementDistributionMessage,
>;
impl_versioned_full_protocol_from!(
StatementDistributionMessage,
@@ -399,14 +398,14 @@ impl_versioned_try_from!(
StatementDistributionMessage,
v1::ValidationProtocol::StatementDistribution(x) => x,
v2::ValidationProtocol::StatementDistribution(x) => x,
vstaging::ValidationProtocol::StatementDistribution(x) => x
v3::ValidationProtocol::StatementDistribution(x) => x
);
/// Version-annotated messages used by the approval distribution subsystem.
pub type ApprovalDistributionMessage = Versioned<
v1::ApprovalDistributionMessage,
v2::ApprovalDistributionMessage,
vstaging::ApprovalDistributionMessage,
v3::ApprovalDistributionMessage,
>;
impl_versioned_full_protocol_from!(
ApprovalDistributionMessage,
@@ -418,7 +417,7 @@ impl_versioned_try_from!(
ApprovalDistributionMessage,
v1::ValidationProtocol::ApprovalDistribution(x) => x,
v2::ValidationProtocol::ApprovalDistribution(x) => x,
vstaging::ValidationProtocol::ApprovalDistribution(x) => x
v3::ValidationProtocol::ApprovalDistribution(x) => x
);
@@ -426,7 +425,7 @@ impl_versioned_try_from!(
pub type GossipSupportNetworkMessage = Versioned<
v1::GossipSupportNetworkMessage,
v2::GossipSupportNetworkMessage,
vstaging::GossipSupportNetworkMessage,
v3::GossipSupportNetworkMessage,
>;
// This is a void enum placeholder, so never gets sent over the wire.
@@ -871,19 +870,17 @@ pub mod v2 {
}
}
/// vstaging network protocol types, intended to become v3.
/// Initial purpose is for chaning ApprovalDistributionMessage to
/// include more than one assignment in the message.
pub mod vstaging {
/// v3 network protocol types.
/// Purpose is for chaning ApprovalDistributionMessage to
/// include more than one assignment and approval in a message.
pub mod v3 {
use parity_scale_codec::{Decode, Encode};
use polkadot_node_primitives::approval::{
v1::IndirectSignedApprovalVote,
v2::{CandidateBitfield, IndirectAssignmentCertV2},
use polkadot_node_primitives::approval::v2::{
CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2,
};
/// This parts of the protocol did not change from v2, so just alias them in vstaging,
/// no reason why they can't be change untill vstaging becomes v3 and is released.
/// This parts of the protocol did not change from v2, so just alias them in v3.
pub use super::v2::{
declare_signature_payload, BackedCandidateAcknowledgement, BackedCandidateManifest,
BitfieldDistributionMessage, GossipSupportNetworkMessage, StatementDistributionMessage,
@@ -903,7 +900,7 @@ pub mod vstaging {
Assignments(Vec<(IndirectAssignmentCertV2, CandidateBitfield)>),
/// Approvals for candidates in some recent, unfinalized block.
#[codec(index = 1)]
Approvals(Vec<IndirectSignedApprovalVote>),
Approvals(Vec<IndirectSignedApprovalVoteV2>),
}
/// All network messages on the validation peer-set.
+38 -17
View File
@@ -73,7 +73,11 @@ impl PeerSet {
// Networking layer relies on `get_main_name()` being the main name of the protocol
// for peersets and connection management.
let protocol = peerset_protocol_names.get_main_name(self);
let fallback_names = PeerSetProtocolNames::get_fallback_names(self);
let fallback_names = PeerSetProtocolNames::get_fallback_names(
self,
&peerset_protocol_names.genesis_hash,
peerset_protocol_names.fork_id.as_deref(),
);
let max_notification_size = self.get_max_notification_size(is_authority);
match self {
@@ -127,15 +131,8 @@ impl PeerSet {
/// Networking layer relies on `get_main_version()` being the version
/// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`].
pub fn get_main_version(self) -> ProtocolVersion {
#[cfg(not(feature = "network-protocol-staging"))]
match self {
PeerSet::Validation => ValidationVersion::V2.into(),
PeerSet::Collation => CollationVersion::V2.into(),
}
#[cfg(feature = "network-protocol-staging")]
match self {
PeerSet::Validation => ValidationVersion::VStaging.into(),
PeerSet::Validation => ValidationVersion::V3.into(),
PeerSet::Collation => CollationVersion::V2.into(),
}
}
@@ -163,7 +160,7 @@ impl PeerSet {
Some("validation/1")
} else if version == ValidationVersion::V2.into() {
Some("validation/2")
} else if version == ValidationVersion::VStaging.into() {
} else if version == ValidationVersion::V3.into() {
Some("validation/3")
} else {
None
@@ -236,9 +233,10 @@ pub enum ValidationVersion {
V1 = 1,
/// The second version.
V2 = 2,
/// The staging version to gather changes
/// that before the release become v3.
VStaging = 3,
/// The third version where changes to ApprovalDistributionMessage had been made.
/// The changes are translatable to V2 format untill assignments v2 and approvals
/// coalescing is enabled through a runtime upgrade.
V3 = 3,
}
/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
@@ -299,6 +297,8 @@ impl From<CollationVersion> for ProtocolVersion {
pub struct PeerSetProtocolNames {
protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
genesis_hash: Hash,
fork_id: Option<String>,
}
impl PeerSetProtocolNames {
@@ -333,7 +333,7 @@ impl PeerSetProtocolNames {
}
Self::register_legacy_protocol(&mut protocols, protocol);
}
Self { protocols, names }
Self { protocols, names, genesis_hash, fork_id: fork_id.map(|fork_id| fork_id.into()) }
}
/// Helper function to register main protocol.
@@ -437,9 +437,30 @@ impl PeerSetProtocolNames {
}
/// Get the protocol fallback names. Currently only holds the legacy name
/// for `LEGACY_PROTOCOL_VERSION` = 1.
fn get_fallback_names(protocol: PeerSet) -> Vec<ProtocolName> {
std::iter::once(Self::get_legacy_name(protocol)).collect()
/// for `LEGACY_PROTOCOL_VERSION` = 1 and v2 for validation.
fn get_fallback_names(
protocol: PeerSet,
genesis_hash: &Hash,
fork_id: Option<&str>,
) -> Vec<ProtocolName> {
let mut fallbacks = vec![Self::get_legacy_name(protocol)];
match protocol {
PeerSet::Validation => {
// Fallbacks are tried one by one, till one matches so push v2 at the top, so
// that it is used ahead of the legacy one(v1).
fallbacks.insert(
0,
Self::generate_name(
genesis_hash,
fork_id,
protocol,
ValidationVersion::V2.into(),
),
)
},
PeerSet::Collation => {},
};
fallbacks
}
}
@@ -22,8 +22,8 @@ use polkadot_node_network_protocol::{
grid_topology::{GridNeighbors, RequiredRouting, SessionBoundGridTopologyStorage},
peer_set::{IsAuthority, PeerSet, ValidationVersion},
v1::{self as protocol_v1, StatementMetadata},
v2 as protocol_v2, vstaging as protocol_vstaging, IfDisconnected, PeerId,
UnifiedReputationChange as Rep, Versioned, View,
v2 as protocol_v2, v3 as protocol_v3, IfDisconnected, PeerId, UnifiedReputationChange as Rep,
Versioned, View,
};
use polkadot_node_primitives::{
SignedFullStatement, Statement, StatementWithPVD, UncheckedSignedFullStatement,
@@ -1075,7 +1075,7 @@ async fn circulate_statement<'a, Context>(
})
.partition::<Vec<_>, _>(|(_, _, version)| match version {
ValidationVersion::V1 => true,
ValidationVersion::V2 | ValidationVersion::VStaging => false,
ValidationVersion::V2 | ValidationVersion::V3 => false,
}); // partition is handy here but not if we add more protocol versions
let payload = v1_statement_message(relay_parent, stored.statement.clone(), metrics);
@@ -1108,8 +1108,7 @@ async fn circulate_statement<'a, Context>(
.collect();
let v2_peers_to_send = filter_by_peer_version(&peers_to_send, ValidationVersion::V2.into());
let vstaging_to_send =
filter_by_peer_version(&peers_to_send, ValidationVersion::VStaging.into());
let v3_to_send = filter_by_peer_version(&peers_to_send, ValidationVersion::V3.into());
if !v2_peers_to_send.is_empty() {
gum::trace!(
@@ -1126,17 +1125,17 @@ async fn circulate_statement<'a, Context>(
.await;
}
if !vstaging_to_send.is_empty() {
if !v3_to_send.is_empty() {
gum::trace!(
target: LOG_TARGET,
?vstaging_to_send,
?v3_to_send,
?relay_parent,
statement = ?stored.statement,
"Sending statement to vstaging peers",
"Sending statement to v3 peers",
);
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
vstaging_to_send,
compatible_v1_message(ValidationVersion::VStaging, payload.clone()).into(),
v3_to_send,
compatible_v1_message(ValidationVersion::V3, payload.clone()).into(),
))
.await;
}
@@ -1472,10 +1471,8 @@ async fn handle_incoming_message<'a, Context>(
let message = match message {
Versioned::V1(m) => m,
Versioned::V2(protocol_v2::StatementDistributionMessage::V1Compatibility(m)) |
Versioned::VStaging(protocol_vstaging::StatementDistributionMessage::V1Compatibility(
m,
)) => m,
Versioned::V2(_) | Versioned::VStaging(_) => {
Versioned::V3(protocol_v3::StatementDistributionMessage::V1Compatibility(m)) => m,
Versioned::V2(_) | Versioned::V3(_) => {
// The higher-level subsystem code is supposed to filter out
// all non v1 messages.
gum::debug!(
@@ -2201,8 +2198,7 @@ fn compatible_v1_message(
ValidationVersion::V1 => Versioned::V1(message),
ValidationVersion::V2 =>
Versioned::V2(protocol_v2::StatementDistributionMessage::V1Compatibility(message)),
ValidationVersion::VStaging => Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::V1Compatibility(message),
),
ValidationVersion::V3 =>
Versioned::V3(protocol_v3::StatementDistributionMessage::V1Compatibility(message)),
}
}
@@ -27,7 +27,7 @@ use std::time::Duration;
use polkadot_node_network_protocol::{
request_response::{v1 as request_v1, v2::AttestedCandidateRequest, IncomingRequestReceiver},
v2 as protocol_v2, vstaging as protocol_vstaging, Versioned,
v2 as protocol_v2, v3 as protocol_v3, Versioned,
};
use polkadot_node_primitives::StatementWithPVD;
use polkadot_node_subsystem::{
@@ -400,11 +400,11 @@ impl<R: rand::Rng> StatementDistributionSubsystem<R> {
Versioned::V2(
protocol_v2::StatementDistributionMessage::V1Compatibility(_),
) |
Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::V1Compatibility(_),
Versioned::V3(
protocol_v3::StatementDistributionMessage::V1Compatibility(_),
) => VersionTarget::Legacy,
Versioned::V1(_) => VersionTarget::Legacy,
Versioned::V2(_) | Versioned::VStaging(_) => VersionTarget::Current,
Versioned::V2(_) | Versioned::V3(_) => VersionTarget::Current,
},
_ => VersionTarget::Both,
};
@@ -29,8 +29,7 @@ use polkadot_node_network_protocol::{
MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS,
},
v2::{self as protocol_v2, StatementFilter},
vstaging as protocol_vstaging, IfDisconnected, PeerId, UnifiedReputationChange as Rep,
Versioned, View,
v3 as protocol_v3, IfDisconnected, PeerId, UnifiedReputationChange as Rep, Versioned, View,
};
use polkadot_node_primitives::{
SignedFullStatementWithPVD, StatementWithPVD as FullStatementWithPVD,
@@ -366,7 +365,7 @@ pub(crate) async fn handle_network_update<Context>(
gum::trace!(target: LOG_TARGET, ?peer_id, ?role, ?protocol_version, "Peer connected");
let versioned_protocol = if protocol_version != ValidationVersion::V2.into() &&
protocol_version != ValidationVersion::VStaging.into()
protocol_version != ValidationVersion::V3.into()
{
return
} else {
@@ -432,28 +431,28 @@ pub(crate) async fn handle_network_update<Context>(
net_protocol::StatementDistributionMessage::V2(
protocol_v2::StatementDistributionMessage::V1Compatibility(_),
) |
net_protocol::StatementDistributionMessage::VStaging(
protocol_vstaging::StatementDistributionMessage::V1Compatibility(_),
net_protocol::StatementDistributionMessage::V3(
protocol_v3::StatementDistributionMessage::V1Compatibility(_),
) => return,
net_protocol::StatementDistributionMessage::V2(
protocol_v2::StatementDistributionMessage::Statement(relay_parent, statement),
) |
net_protocol::StatementDistributionMessage::VStaging(
protocol_vstaging::StatementDistributionMessage::Statement(relay_parent, statement),
net_protocol::StatementDistributionMessage::V3(
protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement),
) =>
handle_incoming_statement(ctx, state, peer_id, relay_parent, statement, reputation)
.await,
net_protocol::StatementDistributionMessage::V2(
protocol_v2::StatementDistributionMessage::BackedCandidateManifest(inner),
) |
net_protocol::StatementDistributionMessage::VStaging(
protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(inner),
net_protocol::StatementDistributionMessage::V3(
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(inner),
) => handle_incoming_manifest(ctx, state, peer_id, inner, reputation).await,
net_protocol::StatementDistributionMessage::V2(
protocol_v2::StatementDistributionMessage::BackedCandidateKnown(inner),
) |
net_protocol::StatementDistributionMessage::VStaging(
protocol_vstaging::StatementDistributionMessage::BackedCandidateKnown(inner),
net_protocol::StatementDistributionMessage::V3(
protocol_v3::StatementDistributionMessage::BackedCandidateKnown(inner),
) => handle_incoming_acknowledgement(ctx, state, peer_id, inner, reputation).await,
},
NetworkBridgeEvent::PeerViewChange(peer_id, view) =>
@@ -806,13 +805,13 @@ fn pending_statement_network_message(
protocol_v2::StatementDistributionMessage::Statement(relay_parent, signed)
})
.map(|msg| (vec![peer.0], Versioned::V2(msg).into())),
ValidationVersion::VStaging => statement_store
ValidationVersion::V3 => statement_store
.validator_statement(originator, compact)
.map(|s| s.as_unchecked().clone())
.map(|signed| {
protocol_vstaging::StatementDistributionMessage::Statement(relay_parent, signed)
protocol_v3::StatementDistributionMessage::Statement(relay_parent, signed)
})
.map(|msg| (vec![peer.0], Versioned::VStaging(msg).into())),
.map(|msg| (vec![peer.0], Versioned::V3(msg).into())),
ValidationVersion::V1 => {
gum::error!(
target: LOG_TARGET,
@@ -945,10 +944,10 @@ async fn send_pending_grid_messages<Context>(
)
.into(),
)),
ValidationVersion::VStaging => messages.push((
ValidationVersion::V3 => messages.push((
vec![peer_id.0],
Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(
Versioned::V3(
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest,
),
)
@@ -960,7 +959,7 @@ async fn send_pending_grid_messages<Context>(
"Bug ValidationVersion::V1 should not be used in statement-distribution v2,
legacy should have handled this"
);
}
},
};
},
grid::ManifestKind::Acknowledgement => {
@@ -1308,8 +1307,8 @@ async fn circulate_statement<Context>(
let statement_to_v2_peers =
filter_by_peer_version(&statement_to_peers, ValidationVersion::V2.into());
let statement_to_vstaging_peers =
filter_by_peer_version(&statement_to_peers, ValidationVersion::VStaging.into());
let statement_to_v3_peers =
filter_by_peer_version(&statement_to_peers, ValidationVersion::V3.into());
// ship off the network messages to the network bridge.
if !statement_to_v2_peers.is_empty() {
@@ -1331,17 +1330,17 @@ async fn circulate_statement<Context>(
.await;
}
if !statement_to_vstaging_peers.is_empty() {
if !statement_to_v3_peers.is_empty() {
gum::debug!(
target: LOG_TARGET,
?compact_statement,
n_peers = ?statement_to_peers.len(),
"Sending statement to vstaging peers",
"Sending statement to v3 peers",
);
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
statement_to_vstaging_peers,
Versioned::VStaging(protocol_vstaging::StatementDistributionMessage::Statement(
statement_to_v3_peers,
Versioned::V3(protocol_v3::StatementDistributionMessage::Statement(
relay_parent,
statement.as_unchecked().clone(),
))
@@ -1887,8 +1886,7 @@ async fn provide_candidate_to_grid<Context>(
}
let manifest_peers_v2 = filter_by_peer_version(&manifest_peers, ValidationVersion::V2.into());
let manifest_peers_vstaging =
filter_by_peer_version(&manifest_peers, ValidationVersion::VStaging.into());
let manifest_peers_v3 = filter_by_peer_version(&manifest_peers, ValidationVersion::V3.into());
if !manifest_peers_v2.is_empty() {
gum::debug!(
target: LOG_TARGET,
@@ -1908,27 +1906,27 @@ async fn provide_candidate_to_grid<Context>(
.await;
}
if !manifest_peers_vstaging.is_empty() {
if !manifest_peers_v3.is_empty() {
gum::debug!(
target: LOG_TARGET,
?candidate_hash,
local_validator = ?per_session.local_validator,
n_peers = manifest_peers_vstaging.len(),
"Sending manifest to vstaging peers"
n_peers = manifest_peers_v3.len(),
"Sending manifest to v3 peers"
);
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
manifest_peers_vstaging,
Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(manifest),
)
manifest_peers_v3,
Versioned::V3(protocol_v3::StatementDistributionMessage::BackedCandidateManifest(
manifest,
))
.into(),
))
.await;
}
let ack_peers_v2 = filter_by_peer_version(&ack_peers, ValidationVersion::V2.into());
let ack_peers_vstaging = filter_by_peer_version(&ack_peers, ValidationVersion::VStaging.into());
let ack_peers_v3 = filter_by_peer_version(&ack_peers, ValidationVersion::V3.into());
if !ack_peers_v2.is_empty() {
gum::debug!(
target: LOG_TARGET,
@@ -1948,22 +1946,20 @@ async fn provide_candidate_to_grid<Context>(
.await;
}
if !ack_peers_vstaging.is_empty() {
if !ack_peers_v3.is_empty() {
gum::debug!(
target: LOG_TARGET,
?candidate_hash,
local_validator = ?per_session.local_validator,
n_peers = ack_peers_vstaging.len(),
"Sending acknowledgement to vstaging peers"
n_peers = ack_peers_v3.len(),
"Sending acknowledgement to v3 peers"
);
ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage(
ack_peers_vstaging,
Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::BackedCandidateKnown(
acknowledgement,
),
)
ack_peers_v3,
Versioned::V3(protocol_v3::StatementDistributionMessage::BackedCandidateKnown(
acknowledgement,
))
.into(),
))
.await;
@@ -2293,8 +2289,8 @@ fn post_acknowledgement_statement_messages(
)
.into(),
)),
ValidationVersion::VStaging => messages.push(Versioned::VStaging(
protocol_vstaging::StatementDistributionMessage::Statement(
ValidationVersion::V3 => messages.push(Versioned::V3(
protocol_v3::StatementDistributionMessage::Statement(
relay_parent,
statement.as_unchecked().clone(),
)
@@ -2441,9 +2437,9 @@ fn acknowledgement_and_statement_messages(
let mut messages = match peer.1 {
ValidationVersion::V2 => vec![(vec![peer.0], msg_v2.into())],
ValidationVersion::VStaging => vec![(
ValidationVersion::V3 => vec![(
vec![peer.0],
Versioned::VStaging(protocol_v2::StatementDistributionMessage::BackedCandidateKnown(
Versioned::V3(protocol_v2::StatementDistributionMessage::BackedCandidateKnown(
acknowledgement,
))
.into(),
@@ -2830,7 +2830,7 @@ fn inactive_local_participates_in_grid() {
send_peer_message(
&mut overseer,
peer_a.clone(),
protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(manifest),
protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest),
)
.await;
+56 -1
View File
@@ -219,7 +219,9 @@ pub mod v2 {
use std::ops::BitOr;
use bitvec::{prelude::Lsb0, vec::BitVec};
use polkadot_primitives::{CandidateIndex, CoreIndex, Hash, ValidatorIndex};
use polkadot_primitives::{
CandidateIndex, CoreIndex, Hash, ValidatorIndex, ValidatorSignature,
};
/// A static context associated with producing randomness for a core.
pub const CORE_RANDOMNESS_CONTEXT: &[u8] = b"A&V CORE v2";
@@ -473,6 +475,59 @@ pub mod v2 {
})
}
}
impl From<super::v1::IndirectSignedApprovalVote> for IndirectSignedApprovalVoteV2 {
fn from(value: super::v1::IndirectSignedApprovalVote) -> Self {
Self {
block_hash: value.block_hash,
validator: value.validator,
candidate_indices: value.candidate_index.into(),
signature: value.signature,
}
}
}
/// Errors that can occur when trying to convert to/from approvals v1/v2
#[derive(Debug)]
pub enum ApprovalConversionError {
/// More than one candidate was signed.
MoreThanOneCandidate(usize),
}
impl TryFrom<IndirectSignedApprovalVoteV2> for super::v1::IndirectSignedApprovalVote {
type Error = ApprovalConversionError;
fn try_from(value: IndirectSignedApprovalVoteV2) -> Result<Self, Self::Error> {
if value.candidate_indices.count_ones() != 1 {
return Err(ApprovalConversionError::MoreThanOneCandidate(
value.candidate_indices.count_ones(),
))
}
Ok(Self {
block_hash: value.block_hash,
validator: value.validator,
candidate_index: value.candidate_indices.first_one().expect("Qed we checked above")
as u32,
signature: value.signature,
})
}
}
/// A signed approval vote which references the candidate indirectly via the block.
///
/// In practice, we have a look-up from block hash and candidate index to candidate hash,
/// so this can be transformed into a `SignedApprovalVote`.
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
pub struct IndirectSignedApprovalVoteV2 {
/// A block hash where the candidate appears.
pub block_hash: Hash,
/// The index of the candidate in the list of candidates fully included as-of the block.
pub candidate_indices: CandidateBitfield,
/// The validator index.
pub validator: ValidatorIndex,
/// The signature by the validator.
pub signature: ValidatorSignature,
}
}
#[cfg(test)]
@@ -170,7 +170,7 @@ impl DisputeMessage {
let valid_vote = ValidDisputeVote {
validator_index: valid_index,
signature: valid_statement.validator_signature().clone(),
kind: *valid_kind,
kind: valid_kind.clone(),
};
let invalid_vote = InvalidDisputeVote {
+18 -5
View File
@@ -46,6 +46,15 @@ pub struct SignedDisputeStatement {
session_index: SessionIndex,
}
/// Errors encountered while signing a dispute statement
#[derive(Debug)]
pub enum SignedDisputeStatementError {
/// Encountered a keystore error while signing
KeyStoreError(KeystoreError),
/// Could not generate signing payload
PayloadError,
}
/// Tracked votes on candidates, for the purposes of dispute resolution.
#[derive(Debug, Clone)]
pub struct CandidateVotes {
@@ -107,8 +116,9 @@ impl ValidCandidateVotes {
ValidDisputeStatementKind::BackingValid(_) |
ValidDisputeStatementKind::BackingSeconded(_) => false,
ValidDisputeStatementKind::Explicit |
ValidDisputeStatementKind::ApprovalChecking => {
occupied.insert((kind, sig));
ValidDisputeStatementKind::ApprovalChecking |
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(_) => {
occupied.insert((kind.clone(), sig));
kind != occupied.get().0
},
},
@@ -213,16 +223,19 @@ impl SignedDisputeStatement {
candidate_hash: CandidateHash,
session_index: SessionIndex,
validator_public: ValidatorId,
) -> Result<Option<Self>, KeystoreError> {
) -> Result<Option<Self>, SignedDisputeStatementError> {
let dispute_statement = if valid {
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)
} else {
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit)
};
let data = dispute_statement.payload_data(candidate_hash, session_index);
let data = dispute_statement
.payload_data(candidate_hash, session_index)
.map_err(|_| SignedDisputeStatementError::PayloadError)?;
let signature = keystore
.sr25519_sign(ValidatorId::ID, validator_public.as_ref(), &data)?
.sr25519_sign(ValidatorId::ID, validator_public.as_ref(), &data)
.map_err(SignedDisputeStatementError::KeyStoreError)?
.map(|sig| Self {
dispute_statement,
candidate_hash,
-4
View File
@@ -225,7 +225,3 @@ runtime-metrics = [
"rococo-runtime?/runtime-metrics",
"westend-runtime?/runtime-metrics",
]
network-protocol-staging = [
"polkadot-node-network-protocol/network-protocol-staging",
]
@@ -20,10 +20,16 @@ use std::{
fs, io,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
};
use polkadot_node_core_approval_voting::approval_db::v2::{
migration_helpers::v1_to_v2, Config as ApprovalDbConfig,
use polkadot_node_core_approval_voting::approval_db::{
common::{Config as ApprovalDbConfig, Result as ApprovalDbResult},
v2::migration_helpers::v1_to_latest,
v3::migration_helpers::v2_to_latest,
};
use polkadot_node_subsystem_util::database::{
kvdb_impl::DbAdapter as RocksDbAdapter, paritydb_impl::DbAdapter as ParityDbAdapter, Database,
};
type Version = u32;
@@ -32,7 +38,9 @@ const VERSION_FILE_NAME: &'static str = "parachain_db_version";
/// Current db version.
/// Version 4 changes approval db format for `OurAssignment`.
pub(crate) const CURRENT_VERSION: Version = 4;
/// Version 5 changes approval db format to hold some additional
/// information about delayed approvals.
pub(crate) const CURRENT_VERSION: Version = 5;
#[derive(thiserror::Error, Debug)]
pub enum Error {
@@ -101,7 +109,8 @@ pub(crate) fn try_upgrade_db_to_next_version(
// 2 -> 3 migration
Some(2) => migrate_from_version_2_to_3(db_path, db_kind)?,
// 3 -> 4 migration
Some(3) => migrate_from_version_3_to_4(db_path, db_kind)?,
Some(3) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v1_to_latest)?,
Some(4) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v2_to_latest)?,
// Already at current version, do nothing.
Some(CURRENT_VERSION) => CURRENT_VERSION,
// This is an arbitrary future version, we don't handle it.
@@ -174,14 +183,19 @@ fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result<Ver
})
}
// Migrade approval voting database. `OurAssignment` has been changed to support the v2 assignments.
// Migrade approval voting database.
// In 4 `OurAssignment` has been changed to support the v2 assignments.
// In 5, `BlockEntry` has been changed to store the number of delayed approvals.
// As these are backwards compatible, we'll convert the old entries in the new format.
fn migrate_from_version_3_to_4(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
fn migrate_from_version_3_or_4_to_5<F>(
path: &Path,
db_kind: DatabaseKind,
migration_function: F,
) -> Result<Version, Error>
where
F: Fn(Arc<dyn Database>, ApprovalDbConfig) -> ApprovalDbResult<()>,
{
gum::info!(target: LOG_TARGET, "Migrating parachains db from version 3 to version 4 ...");
use polkadot_node_subsystem_util::database::{
kvdb_impl::DbAdapter as RocksDbAdapter, paritydb_impl::DbAdapter as ParityDbAdapter,
};
use std::sync::Arc;
let approval_db_config =
ApprovalDbConfig { col_approval_data: super::REAL_COLUMNS.col_approval_data };
@@ -194,7 +208,8 @@ fn migrate_from_version_3_to_4(path: &Path, db_kind: DatabaseKind) -> Result<Ver
super::columns::v3::ORDERED_COL,
);
v1_to_v2(Arc::new(db), approval_db_config).map_err(|_| Error::MigrationFailed)?;
migration_function(Arc::new(db), approval_db_config)
.map_err(|_| Error::MigrationFailed)?;
},
DatabaseKind::RocksDB => {
let db_path = path
@@ -207,7 +222,8 @@ fn migrate_from_version_3_to_4(path: &Path, db_kind: DatabaseKind) -> Result<Ver
&super::columns::v3::ORDERED_COL,
);
v1_to_v2(Arc::new(db), approval_db_config).map_err(|_| Error::MigrationFailed)?;
migration_function(Arc::new(db), approval_db_config)
.map_err(|_| Error::MigrationFailed)?;
},
};
@@ -441,7 +457,12 @@ mod tests {
columns::{v2::COL_SESSION_WINDOW_DATA, v4::*},
*,
};
use polkadot_node_core_approval_voting::approval_db::v2::migration_helpers::v1_to_v2_fill_test_data;
use kvdb_rocksdb::{Database, DatabaseConfig};
use polkadot_node_core_approval_voting::approval_db::{
v2::migration_helpers::v1_fill_test_data,
v3::migration_helpers::{v1_to_latest_sanity_check, v2_fill_test_data},
};
use polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter;
use test_helpers::dummy_candidate_receipt;
#[test]
@@ -580,11 +601,7 @@ mod tests {
}
#[test]
fn test_migrate_3_to_4() {
use kvdb_rocksdb::{Database, DatabaseConfig};
use polkadot_node_core_approval_voting::approval_db::v2::migration_helpers::v1_to_v2_sanity_check;
use polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter;
fn test_migrate_3_to_5() {
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
@@ -600,28 +617,60 @@ mod tests {
assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
// Fill the approval voting column with test data.
v1_to_v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt)
v1_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt)
.unwrap()
};
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 4).unwrap();
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
v1_to_v2_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates).unwrap();
v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates)
.unwrap();
}
#[test]
fn test_rocksdb_migrate_0_to_4() {
fn test_migrate_4_to_5() {
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
let approval_cfg = ApprovalDbConfig {
col_approval_data: crate::parachains_db::REAL_COLUMNS.col_approval_data,
};
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(db_dir.path()), "4").expect("Failed to write DB version");
let expected_candidates = {
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
// Fill the approval voting column with test data.
v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt)
.unwrap()
};
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates)
.unwrap();
}
#[test]
fn test_rocksdb_migrate_0_to_5() {
use kvdb_rocksdb::{Database, DatabaseConfig};
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
fs::write(version_file_path(db_dir.path()), "0").expect("Failed to write DB version");
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 4).unwrap();
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
@@ -630,7 +679,7 @@ mod tests {
}
#[test]
fn test_paritydb_migrate_0_to_4() {
fn test_paritydb_migrate_0_to_5() {
use parity_db::Db;
let db_dir = tempfile::tempdir().unwrap();
@@ -644,7 +693,7 @@ mod tests {
assert_eq!(db.num_columns(), columns::v0::NUM_COLUMNS as u8);
}
try_upgrade_db(&path, DatabaseKind::ParityDB, 4).unwrap();
try_upgrade_db(&path, DatabaseKind::ParityDB, 5).unwrap();
let db = Db::open(&paritydb_version_3_config(&path)).unwrap();
assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS as u8);
+21 -14
View File
@@ -33,8 +33,8 @@ use polkadot_node_network_protocol::{
};
use polkadot_node_primitives::{
approval::{
v1::{BlockApprovalMeta, IndirectSignedApprovalVote},
v2::{CandidateBitfield, IndirectAssignmentCertV2},
v1::BlockApprovalMeta,
v2::{CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2},
},
AvailableData, BabeEpoch, BlockWeight, CandidateVotes, CollationGenerationConfig,
CollationSecondedSignal, DisputeMessage, DisputeStatus, ErasureChunk, PoV,
@@ -42,14 +42,15 @@ use polkadot_node_primitives::{
ValidationResult,
};
use polkadot_primitives::{
async_backing, slashing, vstaging::NodeFeatures, AuthorityDiscoveryId, BackedCandidate,
BlockNumber, CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, CollatorId,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupIndex,
GroupRotationInfo, Hash, Header as BlockHeader, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, MultiDisputeStatementSet, OccupiedCoreAssumption, PersistedValidationData,
PvfCheckStatement, PvfExecKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield,
SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
ValidatorSignature,
async_backing, slashing,
vstaging::{ApprovalVotingParams, NodeFeatures},
AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash,
CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState,
DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Header as BlockHeader,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet,
OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, PvfExecKind, SessionIndex,
SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode,
ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use polkadot_statement_table::v2::Misbehavior;
use std::{
@@ -720,6 +721,9 @@ pub enum RuntimeApiRequest {
AsyncBackingParams(RuntimeApiSender<async_backing::AsyncBackingParams>),
/// Get the node features.
NodeFeatures(SessionIndex, RuntimeApiSender<NodeFeatures>),
/// Approval voting params
/// `V10`
ApprovalVotingParams(SessionIndex, RuntimeApiSender<ApprovalVotingParams>),
}
impl RuntimeApiRequest {
@@ -751,6 +755,9 @@ impl RuntimeApiRequest {
/// `Node features`
pub const NODE_FEATURES_RUNTIME_REQUIREMENT: u32 = 9;
/// `approval_voting_params`
pub const APPROVAL_VOTING_PARAMS_REQUIREMENT: u32 = 10;
}
/// A message to the Runtime API subsystem.
@@ -936,7 +943,7 @@ pub enum ApprovalVotingMessage {
/// protocol.
///
/// Should not be sent unless the block hash within the indirect vote is known.
CheckAndImportApproval(IndirectSignedApprovalVote, oneshot::Sender<ApprovalCheckResult>),
CheckAndImportApproval(IndirectSignedApprovalVoteV2, oneshot::Sender<ApprovalCheckResult>),
/// Returns the highest possible ancestor hash of the provided block hash which is
/// acceptable to vote on finality for.
/// The `BlockNumber` provided is the number of the block's ancestor which is the
@@ -952,7 +959,7 @@ pub enum ApprovalVotingMessage {
/// requires calling into `approval-distribution`: Calls should be infrequent and bounded.
GetApprovalSignaturesForCandidate(
CandidateHash,
oneshot::Sender<HashMap<ValidatorIndex, ValidatorSignature>>,
oneshot::Sender<HashMap<ValidatorIndex, (Vec<CandidateHash>, ValidatorSignature)>>,
),
}
@@ -968,7 +975,7 @@ pub enum ApprovalDistributionMessage {
/// Distribute an approval vote for the local validator. The approval vote is assumed to be
/// valid, relevant, and the corresponding approval already issued.
/// If not, the subsystem is free to drop the message.
DistributeApproval(IndirectSignedApprovalVote),
DistributeApproval(IndirectSignedApprovalVoteV2),
/// An update from the network bridge.
#[from]
NetworkBridgeUpdate(NetworkBridgeEvent<net_protocol::ApprovalDistributionMessage>),
@@ -976,7 +983,7 @@ pub enum ApprovalDistributionMessage {
/// Get all approval signatures for all chains a candidate appeared in.
GetApprovalSignatures(
HashSet<(Hash, CandidateIndex)>,
oneshot::Sender<HashMap<ValidatorIndex, ValidatorSignature>>,
oneshot::Sender<HashMap<ValidatorIndex, (Hash, Vec<CandidateIndex>, ValidatorSignature)>>,
),
/// Approval checking lag update measured in blocks.
ApprovalCheckingLagUpdate(BlockNumber),
@@ -16,12 +16,15 @@
use async_trait::async_trait;
use polkadot_primitives::{
async_backing, runtime_api::ParachainHost, slashing, vstaging, Block, BlockNumber,
CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState,
DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage,
InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement,
ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
ValidatorId, ValidatorIndex, ValidatorSignature,
async_backing,
runtime_api::ParachainHost,
slashing,
vstaging::{self, ApprovalVotingParams},
Block, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash,
Header, Id, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption,
PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo,
ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use sc_client_api::HeaderBackend;
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
@@ -316,9 +319,16 @@ pub trait RuntimeApiSubsystemClient {
async fn disabled_validators(&self, at: Hash) -> Result<Vec<ValidatorIndex>, ApiError>;
// === v9 ===
/// Get the node features.
async fn node_features(&self, at: Hash) -> Result<vstaging::NodeFeatures, ApiError>;
// == v10: Approval voting params ==
/// Approval voting configuration parameters
async fn approval_voting_params(
&self,
at: Hash,
session_index: SessionIndex,
) -> Result<ApprovalVotingParams, ApiError>;
}
/// Default implementation of [`RuntimeApiSubsystemClient`] using the client.
@@ -575,4 +585,13 @@ where
async fn disabled_validators(&self, at: Hash) -> Result<Vec<ValidatorIndex>, ApiError> {
self.client.runtime_api().disabled_validators(at)
}
/// Approval voting configuration parameters
async fn approval_voting_params(
&self,
at: Hash,
_session_index: SessionIndex,
) -> Result<ApprovalVotingParams, ApiError> {
self.client.runtime_api().approval_voting_params(at)
}
}
@@ -39,6 +39,3 @@ sc-service = { path = "../../../../../substrate/client/service" }
sp-keyring = { path = "../../../../../substrate/primitives/keyring" }
tokio = { version = "1.24.2", features = ["macros"] }
[features]
network-protocol-staging = ["polkadot-cli/network-protocol-staging"]
+12 -5
View File
@@ -114,12 +114,14 @@
//! separated from the stable primitives.
use crate::{
async_backing, slashing, vstaging, AsyncBackingParams, BlockNumber, CandidateCommitments,
CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState,
ExecutorParams, GroupRotationInfo, Hash, OccupiedCoreAssumption, PersistedValidationData,
PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex,
ValidatorSignature,
async_backing, slashing,
vstaging::{self, ApprovalVotingParams},
AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash,
OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes,
SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use polkadot_core_primitives as pcp;
use polkadot_parachain_primitives::primitives as ppp;
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
@@ -274,5 +276,10 @@ sp_api::decl_runtime_apis! {
/// This is a staging method! Do not use on production runtimes!
#[api_version(9)]
fn node_features() -> vstaging::NodeFeatures;
/***** Added in v10 *****/
/// Approval voting configuration parameters
#[api_version(10)]
fn approval_voting_params() -> ApprovalVotingParams;
}
}
+57 -13
View File
@@ -1070,6 +1070,26 @@ impl ApprovalVote {
}
}
/// A vote of approval for multiple candidates.
#[derive(Clone, RuntimeDebug)]
pub struct ApprovalVoteMultipleCandidates<'a>(pub &'a [CandidateHash]);
impl<'a> ApprovalVoteMultipleCandidates<'a> {
/// Yields the signing payload for this approval vote.
pub fn signing_payload(&self, session_index: SessionIndex) -> Vec<u8> {
const MAGIC: [u8; 4] = *b"APPR";
// Make this backwards compatible with `ApprovalVote` so if we have just on candidate the
// signature will look the same.
// This gives us the nice benefit that old nodes can still check signatures when len is 1
// and the new node can check the signature coming from old nodes.
if self.0.len() == 1 {
(MAGIC, self.0.first().expect("QED: we just checked"), session_index).encode()
} else {
(MAGIC, &self.0, session_index).encode()
}
}
}
/// Custom validity errors used in Polkadot while validating transactions.
#[repr(u8)]
pub enum ValidityError {
@@ -1246,25 +1266,42 @@ pub enum DisputeStatement {
impl DisputeStatement {
/// Get the payload data for this type of dispute statement.
pub fn payload_data(&self, candidate_hash: CandidateHash, session: SessionIndex) -> Vec<u8> {
match *self {
///
/// Returns Error if the candidate_hash is not included in the list of signed
/// candidate from ApprovalCheckingMultipleCandidate.
pub fn payload_data(
&self,
candidate_hash: CandidateHash,
session: SessionIndex,
) -> Result<Vec<u8>, ()> {
match self {
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) =>
ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(),
Ok(ExplicitDisputeStatement { valid: true, candidate_hash, session }
.signing_payload()),
DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(
inclusion_parent,
)) => CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
)) => Ok(CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
session_index: session,
parent_hash: inclusion_parent,
}),
parent_hash: *inclusion_parent,
})),
DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) =>
CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
Ok(CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
session_index: session,
parent_hash: inclusion_parent,
}),
parent_hash: *inclusion_parent,
})),
DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) =>
ApprovalVote(candidate_hash).signing_payload(session),
Ok(ApprovalVote(candidate_hash).signing_payload(session)),
DisputeStatement::Valid(
ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes),
) =>
if candidate_hashes.contains(&candidate_hash) {
Ok(ApprovalVoteMultipleCandidates(candidate_hashes).signing_payload(session))
} else {
Err(())
},
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) =>
ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(),
Ok(ExplicitDisputeStatement { valid: false, candidate_hash, session }
.signing_payload()),
}
}
@@ -1276,7 +1313,7 @@ impl DisputeStatement {
session: SessionIndex,
validator_signature: &ValidatorSignature,
) -> Result<(), ()> {
let payload = self.payload_data(candidate_hash, session);
let payload = self.payload_data(candidate_hash, session)?;
if validator_signature.verify(&payload[..], &validator_public) {
Ok(())
@@ -1308,13 +1345,14 @@ impl DisputeStatement {
Self::Valid(ValidDisputeStatementKind::BackingValid(_)) => true,
Self::Valid(ValidDisputeStatementKind::Explicit) |
Self::Valid(ValidDisputeStatementKind::ApprovalChecking) |
Self::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(_)) |
Self::Invalid(_) => false,
}
}
}
/// Different kinds of statements of validity on a candidate.
#[derive(Encode, Decode, Copy, Clone, PartialEq, RuntimeDebug, TypeInfo)]
#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)]
pub enum ValidDisputeStatementKind {
/// An explicit statement issued as part of a dispute.
#[codec(index = 0)]
@@ -1328,6 +1366,12 @@ pub enum ValidDisputeStatementKind {
/// An approval vote from the approval checking phase.
#[codec(index = 3)]
ApprovalChecking,
/// An approval vote from the new version.
/// We can't create this version untill all nodes
/// have been updated to support it and max_approval_coalesce_count
/// is set to more than 1.
#[codec(index = 4)]
ApprovalCheckingMultipleCandidates(Vec<CandidateHash>),
}
/// Different kinds of statements of invalidity on a candidate.
+32
View File
@@ -17,6 +17,38 @@
//! Staging Primitives.
// Put any primitives used by staging APIs functions here
pub use crate::v6::*;
use sp_std::prelude::*;
use parity_scale_codec::{Decode, Encode};
use primitives::RuntimeDebug;
use scale_info::TypeInfo;
/// Approval voting configuration parameters
#[derive(
RuntimeDebug,
Copy,
Clone,
PartialEq,
Encode,
Decode,
TypeInfo,
serde::Serialize,
serde::Deserialize,
)]
pub struct ApprovalVotingParams {
/// The maximum number of candidates `approval-voting` can vote for with
/// a single signatures.
///
/// Setting it to 1, means we send the approval as soon as we have it available.
pub max_approval_coalesce_count: u32,
}
impl Default for ApprovalVotingParams {
fn default() -> Self {
Self { max_approval_coalesce_count: 1 }
}
}
use bitvec::vec::BitVec;
@@ -4,10 +4,13 @@ Reading the [section on the approval protocol](../../protocol-approval.md) will
aims of this subsystem.
Approval votes are split into two parts: Assignments and Approvals. Validators first broadcast their assignment to
indicate intent to check a candidate. Upon successfully checking, they broadcast an approval vote. If a validator
doesn't broadcast their approval vote shortly after issuing an assignment, this is an indication that they are being
prevented from recovering or validating the block data and that more validators should self-select to check the
candidate. This is known as a "no-show".
indicate intent to check a candidate. Upon successfully checking, they don't immediately send the vote instead
they queue the check for a short period of time `MAX_APPROVAL_COALESCE_WAIT_TICKS` to give the opportunity of the
validator to vote for more than one candidate. Once MAX_APPROVAL_COALESCE_WAIT_TICKS have passed or at least
`MAX_APPROVAL_COALESCE_COUNT` are ready they broadcast an approval vote for all candidates. If a validator
doesn't broadcast their approval vote shortly after issuing an assignment, this is an indication that they are
being prevented from recovering or validating the block data and that more validators should self-select to
check the candidate. This is known as a "no-show".
The core of this subsystem is a Tick-based timer loop, where Ticks are 500ms. We also reason about time in terms of
`DelayTranche`s, which measure the number of ticks elapsed since a block was produced. We track metadata for all
@@ -120,6 +123,13 @@ struct BlockEntry {
// this block. The block can be considered approved has all bits set to 1
approved_bitfield: Bitfield,
children: Vec<Hash>,
// A list of candidates we have checked, but didn't not sign and
// advertise the vote yet.
candidates_pending_signature: BTreeMap<CandidateIndex, CandidateSigningContext>,
// Assignments we already distributed. A 1 bit means the candidate index for which
// we already have sent out an assignment. We need this to avoid distributing
// multiple core assignments more than once.
distributed_assignments: Bitfield,
}
// slot_duration * 2 + DelayTranche gives the number of delay tranches since the
@@ -303,12 +313,12 @@ entry. The cert itself contains information necessary to determine the candidate
On receiving a `CheckAndImportApproval(indirect_approval_vote, response_channel)` message:
* Fetch the `BlockEntry` from the indirect approval vote's `block_hash`. If none, return `ApprovalCheckResult::Bad`.
* Fetch the `CandidateEntry` from the indirect approval vote's `candidate_index`. If the block did not trigger
* Fetch all `CandidateEntry` from the indirect approval vote's `candidate_indices`. If the block did not trigger
inclusion of enough candidates, return `ApprovalCheckResult::Bad`.
* Construct a `SignedApprovalVote` using the candidate hash and check against the validator's approval key, based on
the session info of the block. If invalid or no such validator, return `ApprovalCheckResult::Bad`.
* Construct a `SignedApprovalVote` using the candidates hashes and check against the validator's approval key,
based on the session info of the block. If invalid or no such validator, return `ApprovalCheckResult::Bad`.
* Send `ApprovalCheckResult::Accepted`
* [Import the checked approval vote](#import-checked-approval)
* [Import the checked approval vote](#import-checked-approval) for all candidates
#### `ApprovalVotingMessage::ApprovedAncestor`
@@ -402,10 +412,25 @@ On receiving an `ApprovedAncestor(Hash, BlockNumber, response_channel)`:
#### Issue Approval Vote
* Fetch the block entry and candidate entry. Ignore if `None` - we've probably just lost a race with finality.
* Construct a `SignedApprovalVote` with the validator index for the session.
* [Import the checked approval vote](#import-checked-approval). It is "checked" as we've just issued the signature.
* Construct a `IndirectSignedApprovalVote` using the information about the vote.
* Dispatch `ApprovalDistributionMessage::DistributeApproval`.
* IF `MAX_APPROVAL_COALESCE_COUNT` candidates are in the waiting queue
* Construct a `SignedApprovalVote` with the validator index for the session and all candidate hashes in the waiting queue.
* Construct a `IndirectSignedApprovalVote` using the information about the vote.
* Dispatch `ApprovalDistributionMessage::DistributeApproval`.
* ELSE
* Queue the candidate in the `BlockEntry::candidates_pending_signature`
* Arm a per BlockEntry timer with latest tick we can send the vote.
### Delayed vote distribution
* [Issue Approval Vote](#issue-approval-vote) arms once a per block timer if there are no requirements to send the
vote immediately.
* When the timer wakes up it will either:
* IF there is a candidate in the queue past its sending tick:
* Construct a `SignedApprovalVote` with the validator index for the session and all candidate hashes in the waiting queue.
* Construct a `IndirectSignedApprovalVote` using the information about the vote.
* Dispatch `ApprovalDistributionMessage::DistributeApproval`.
* ELSE
* Re-arm the timer with latest tick we have the send a the vote.
### Determining Approval of Candidate
@@ -296,6 +296,18 @@ provide somewhat more security.
TODO: When? Is this optimal for the network? etc.
## Approval coalescing
To reduce the necessary network bandwidth and cpu time when a validator has more than one candidate to approve we are
doing our best effort to send a single message that approves all available candidates with a single signature.
The implemented heuristic, is that each time we are ready to create a signature and send a vote for a candidate we
delay sending it until one of three things happen:
- We gathered a maximum of `MAX_APPROVAL_COALESCE_COUNT` candidates that we have already checked and we are
ready to sign approval for.
- `MAX_APPROVAL_COALESCE_WAIT_TICKS` have passed since checking oldest candidate and we were ready to sign
and send the approval message.
- We are already in the last third of the no-show period in order to avoid creating accidental no-shows, which in
turn might trigger other assignments.
## On-chain verification
We should verify approval on-chain to reward approval checkers. We therefore require the "no show" timeout to be longer
+1 -1
View File
@@ -636,7 +636,7 @@ impl<T: paras_inherent::Config> BenchBuilder<T> {
} else {
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit)
};
let data = dispute_statement.payload_data(candidate_hash, session);
let data = dispute_statement.payload_data(candidate_hash, session).unwrap();
let statement_sig = validator_public.sign(&data).unwrap();
(dispute_statement, ValidatorIndex(validator_index), statement_sig)
@@ -26,8 +26,9 @@ use polkadot_parachain_primitives::primitives::{
MAX_HORIZONTAL_MESSAGE_NUM, MAX_UPWARD_MESSAGE_NUM,
};
use primitives::{
vstaging::NodeFeatures, AsyncBackingParams, Balance, ExecutorParamError, ExecutorParams,
SessionIndex, LEGACY_MIN_BACKING_VOTES, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE,
vstaging::{ApprovalVotingParams, NodeFeatures},
AsyncBackingParams, Balance, ExecutorParamError, ExecutorParams, SessionIndex,
LEGACY_MIN_BACKING_VOTES, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE,
ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE,
};
use sp_runtime::{traits::Zero, Perbill};
@@ -263,6 +264,8 @@ pub struct HostConfiguration<BlockNumber> {
pub minimum_backing_votes: u32,
/// Node features enablement.
pub node_features: NodeFeatures,
/// Params used by approval-voting
pub approval_voting_params: ApprovalVotingParams,
}
impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber> {
@@ -308,6 +311,7 @@ impl<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber
pvf_voting_ttl: 2u32.into(),
minimum_validation_upgrade_delay: 2.into(),
executor_params: Default::default(),
approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 1 },
on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE,
on_demand_base_fee: 10_000_000u128,
on_demand_fee_variability: Perbill::from_percent(3),
@@ -515,7 +519,8 @@ pub mod pallet {
/// v7-v8: <https://github.com/paritytech/polkadot/pull/6969>
/// v8-v9: <https://github.com/paritytech/polkadot/pull/7577>
/// v9-v10: <https://github.com/paritytech/polkadot-sdk/pull/2177>
const STORAGE_VERSION: StorageVersion = StorageVersion::new(10);
/// v10-11: <https://github.com/paritytech/polkadot-sdk/pull/1191>
const STORAGE_VERSION: StorageVersion = StorageVersion::new(11);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
@@ -1191,6 +1196,7 @@ pub mod pallet {
config.on_demand_ttl = new;
})
}
/// Set the minimum backing votes threshold.
#[pallet::call_index(52)]
#[pallet::weight((
@@ -1203,6 +1209,7 @@ pub mod pallet {
config.minimum_backing_votes = new;
})
}
/// Set/Unset a node feature.
#[pallet::call_index(53)]
#[pallet::weight((
@@ -1220,6 +1227,22 @@ pub mod pallet {
config.node_features.set(index, value);
})
}
/// Set approval-voting-params.
#[pallet::call_index(54)]
#[pallet::weight((
T::WeightInfo::set_config_with_executor_params(),
DispatchClass::Operational,
))]
pub fn set_approval_voting_params(
origin: OriginFor<T>,
new: ApprovalVotingParams,
) -> DispatchResult {
ensure_root(origin)?;
Self::schedule_config_update(|config| {
config.approval_voting_params = new;
})
}
}
#[pallet::hooks]
@@ -17,6 +17,7 @@
//! A module that is responsible for migration of storage.
pub mod v10;
pub mod v11;
pub mod v6;
pub mod v7;
pub mod v8;
@@ -16,17 +16,121 @@
//! A module that is responsible for migration of storage.
use crate::configuration::{self, Config, Pallet};
use crate::configuration::{Config, Pallet};
use frame_support::{pallet_prelude::*, traits::Defensive, weights::Weight};
use frame_system::pallet_prelude::BlockNumberFor;
use primitives::{vstaging::NodeFeatures, SessionIndex};
use primitives::{
vstaging::NodeFeatures, AsyncBackingParams, Balance, ExecutorParams, SessionIndex,
LEGACY_MIN_BACKING_VOTES, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE,
};
use sp_runtime::Perbill;
use sp_std::vec::Vec;
use frame_support::traits::OnRuntimeUpgrade;
use super::v9::V9HostConfiguration;
// All configuration of the runtime with respect to paras.
#[derive(Clone, Encode, PartialEq, Decode, Debug)]
pub struct V10HostConfiguration<BlockNumber> {
pub max_code_size: u32,
pub max_head_data_size: u32,
pub max_upward_queue_count: u32,
pub max_upward_queue_size: u32,
pub max_upward_message_size: u32,
pub max_upward_message_num_per_candidate: u32,
pub hrmp_max_message_num_per_candidate: u32,
pub validation_upgrade_cooldown: BlockNumber,
pub validation_upgrade_delay: BlockNumber,
pub async_backing_params: AsyncBackingParams,
pub max_pov_size: u32,
pub max_downward_message_size: u32,
pub hrmp_max_parachain_outbound_channels: u32,
pub hrmp_sender_deposit: Balance,
pub hrmp_recipient_deposit: Balance,
pub hrmp_channel_max_capacity: u32,
pub hrmp_channel_max_total_size: u32,
pub hrmp_max_parachain_inbound_channels: u32,
pub hrmp_channel_max_message_size: u32,
pub executor_params: ExecutorParams,
pub code_retention_period: BlockNumber,
pub on_demand_cores: u32,
pub on_demand_retries: u32,
pub on_demand_queue_max_size: u32,
pub on_demand_target_queue_utilization: Perbill,
pub on_demand_fee_variability: Perbill,
pub on_demand_base_fee: Balance,
pub on_demand_ttl: BlockNumber,
pub group_rotation_frequency: BlockNumber,
pub paras_availability_period: BlockNumber,
pub scheduling_lookahead: u32,
pub max_validators_per_core: Option<u32>,
pub max_validators: Option<u32>,
pub dispute_period: SessionIndex,
pub dispute_post_conclusion_acceptance_period: BlockNumber,
pub no_show_slots: u32,
pub n_delay_tranches: u32,
pub zeroth_delay_tranche_width: u32,
pub needed_approvals: u32,
pub relay_vrf_modulo_samples: u32,
pub pvf_voting_ttl: SessionIndex,
pub minimum_validation_upgrade_delay: BlockNumber,
pub minimum_backing_votes: u32,
pub node_features: NodeFeatures,
}
type V10HostConfiguration<BlockNumber> = configuration::HostConfiguration<BlockNumber>;
impl<BlockNumber: Default + From<u32>> Default for V10HostConfiguration<BlockNumber> {
fn default() -> Self {
Self {
async_backing_params: AsyncBackingParams {
max_candidate_depth: 0,
allowed_ancestry_len: 0,
},
group_rotation_frequency: 1u32.into(),
paras_availability_period: 1u32.into(),
no_show_slots: 1u32.into(),
validation_upgrade_cooldown: Default::default(),
validation_upgrade_delay: 2u32.into(),
code_retention_period: Default::default(),
max_code_size: Default::default(),
max_pov_size: Default::default(),
max_head_data_size: Default::default(),
on_demand_cores: Default::default(),
on_demand_retries: Default::default(),
scheduling_lookahead: 1,
max_validators_per_core: Default::default(),
max_validators: None,
dispute_period: 6,
dispute_post_conclusion_acceptance_period: 100.into(),
n_delay_tranches: Default::default(),
zeroth_delay_tranche_width: Default::default(),
needed_approvals: Default::default(),
relay_vrf_modulo_samples: Default::default(),
max_upward_queue_count: Default::default(),
max_upward_queue_size: Default::default(),
max_downward_message_size: Default::default(),
max_upward_message_size: Default::default(),
max_upward_message_num_per_candidate: Default::default(),
hrmp_sender_deposit: Default::default(),
hrmp_recipient_deposit: Default::default(),
hrmp_channel_max_capacity: Default::default(),
hrmp_channel_max_total_size: Default::default(),
hrmp_max_parachain_inbound_channels: Default::default(),
hrmp_channel_max_message_size: Default::default(),
hrmp_max_parachain_outbound_channels: Default::default(),
hrmp_max_message_num_per_candidate: Default::default(),
pvf_voting_ttl: 2u32.into(),
minimum_validation_upgrade_delay: 2.into(),
executor_params: Default::default(),
on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE,
on_demand_base_fee: 10_000_000u128,
on_demand_fee_variability: Perbill::from_percent(3),
on_demand_target_queue_utilization: Perbill::from_percent(25),
on_demand_ttl: 5u32.into(),
minimum_backing_votes: LEGACY_MIN_BACKING_VOTES,
node_features: NodeFeatures::EMPTY,
}
}
}
mod v9 {
use super::*;
@@ -0,0 +1,329 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! A module that is responsible for migration of storage.
use crate::configuration::{self, Config, Pallet};
use frame_support::{
migrations::VersionedMigration, pallet_prelude::*, traits::Defensive, weights::Weight,
};
use frame_system::pallet_prelude::BlockNumberFor;
use primitives::{vstaging::ApprovalVotingParams, SessionIndex};
use sp_std::vec::Vec;
use frame_support::traits::OnRuntimeUpgrade;
use super::v10::V10HostConfiguration;
type V11HostConfiguration<BlockNumber> = configuration::HostConfiguration<BlockNumber>;
mod v10 {
use super::*;
#[frame_support::storage_alias]
pub(crate) type ActiveConfig<T: Config> =
StorageValue<Pallet<T>, V10HostConfiguration<BlockNumberFor<T>>, OptionQuery>;
#[frame_support::storage_alias]
pub(crate) type PendingConfigs<T: Config> = StorageValue<
Pallet<T>,
Vec<(SessionIndex, V10HostConfiguration<BlockNumberFor<T>>)>,
OptionQuery,
>;
}
mod v11 {
use super::*;
#[frame_support::storage_alias]
pub(crate) type ActiveConfig<T: Config> =
StorageValue<Pallet<T>, V11HostConfiguration<BlockNumberFor<T>>, OptionQuery>;
#[frame_support::storage_alias]
pub(crate) type PendingConfigs<T: Config> = StorageValue<
Pallet<T>,
Vec<(SessionIndex, V11HostConfiguration<BlockNumberFor<T>>)>,
OptionQuery,
>;
}
pub type MigrateToV11<T> = VersionedMigration<
10,
11,
UncheckedMigrateToV11<T>,
Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
pub struct UncheckedMigrateToV11<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for UncheckedMigrateToV11<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV11");
Ok(Vec::new())
}
fn on_runtime_upgrade() -> Weight {
log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV11 started");
let weight_consumed = migrate_to_v11::<T>();
log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV11 executed successfully");
weight_consumed
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade() for HostConfiguration MigrateToV11");
ensure!(
StorageVersion::get::<Pallet<T>>() >= 11,
"Storage version should be >= 11 after the migration"
);
Ok(())
}
}
fn migrate_to_v11<T: Config>() -> Weight {
// Unusual formatting is justified:
// - make it easier to verify that fields assign what they supposed to assign.
// - this code is transient and will be removed after all migrations are done.
// - this code is important enough to optimize for legibility sacrificing consistency.
#[rustfmt::skip]
let translate =
|pre: V10HostConfiguration<BlockNumberFor<T>>| ->
V11HostConfiguration<BlockNumberFor<T>>
{
V11HostConfiguration {
max_code_size : pre.max_code_size,
max_head_data_size : pre.max_head_data_size,
max_upward_queue_count : pre.max_upward_queue_count,
max_upward_queue_size : pre.max_upward_queue_size,
max_upward_message_size : pre.max_upward_message_size,
max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate,
hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate,
validation_upgrade_cooldown : pre.validation_upgrade_cooldown,
validation_upgrade_delay : pre.validation_upgrade_delay,
max_pov_size : pre.max_pov_size,
max_downward_message_size : pre.max_downward_message_size,
hrmp_sender_deposit : pre.hrmp_sender_deposit,
hrmp_recipient_deposit : pre.hrmp_recipient_deposit,
hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity,
hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size,
hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels,
hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels,
hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size,
code_retention_period : pre.code_retention_period,
on_demand_cores : pre.on_demand_cores,
on_demand_retries : pre.on_demand_retries,
group_rotation_frequency : pre.group_rotation_frequency,
paras_availability_period : pre.paras_availability_period,
scheduling_lookahead : pre.scheduling_lookahead,
max_validators_per_core : pre.max_validators_per_core,
max_validators : pre.max_validators,
dispute_period : pre.dispute_period,
dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period,
no_show_slots : pre.no_show_slots,
n_delay_tranches : pre.n_delay_tranches,
zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width,
needed_approvals : pre.needed_approvals,
relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples,
pvf_voting_ttl : pre.pvf_voting_ttl,
minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay,
async_backing_params : pre.async_backing_params,
executor_params : pre.executor_params,
on_demand_queue_max_size : pre.on_demand_queue_max_size,
on_demand_base_fee : pre.on_demand_base_fee,
on_demand_fee_variability : pre.on_demand_fee_variability,
on_demand_target_queue_utilization : pre.on_demand_target_queue_utilization,
on_demand_ttl : pre.on_demand_ttl,
minimum_backing_votes : pre.minimum_backing_votes,
node_features : pre.node_features,
approval_voting_params : ApprovalVotingParams {
max_approval_coalesce_count: 1,
}
}
};
let v10 = v10::ActiveConfig::<T>::get()
.defensive_proof("Could not decode old config")
.unwrap_or_default();
let v11 = translate(v10);
v11::ActiveConfig::<T>::set(Some(v11));
// Allowed to be empty.
let pending_v9 = v10::PendingConfigs::<T>::get().unwrap_or_default();
let mut pending_v10 = Vec::new();
for (session, v10) in pending_v9.into_iter() {
let v11 = translate(v10);
pending_v10.push((session, v11));
}
v11::PendingConfigs::<T>::set(Some(pending_v10.clone()));
let num_configs = (pending_v10.len() + 1) as u64;
T::DbWeight::get().reads_writes(num_configs, num_configs)
}
#[cfg(test)]
mod tests {
use primitives::LEGACY_MIN_BACKING_VOTES;
use super::*;
use crate::mock::{new_test_ext, Test};
#[test]
fn v11_deserialized_from_actual_data() {
// Example how to get new `raw_config`:
// We'll obtain the raw_config at a specified a block
// Steps:
// 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate
// 2. Set these parameters:
// 2.1. selected state query: configuration; activeConfig():
// PolkadotRuntimeParachainsConfigurationHostConfiguration
// 2.2. blockhash to query at:
// 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of
// the block)
// 2.3. Note the value of encoded storage key ->
// 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the
// referenced block.
// 2.4. You'll also need the decoded values to update the test.
// 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage
// 3.1 Enter the encoded storage key and you get the raw config.
// This exceeds the maximal line width length, but that's fine, since this is not code and
// doesn't need to be read and also leaving it as one line allows to easily copy it.
let raw_config =
hex_literal::hex!["
0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000000000000000001027000080b2e60e80c3c9018096980000000000000000000000000005000000140000000400000001000000010100000000060000006400000002000000190000000000000002000000020000000200000005000000020000000001000000"
];
let v11 =
V11HostConfiguration::<primitives::BlockNumber>::decode(&mut &raw_config[..]).unwrap();
// We check only a sample of the values here. If we missed any fields or messed up data
// types that would skew all the fields coming after.
assert_eq!(v11.max_code_size, 3_145_728);
assert_eq!(v11.validation_upgrade_cooldown, 2);
assert_eq!(v11.max_pov_size, 5_242_880);
assert_eq!(v11.hrmp_channel_max_message_size, 1_048_576);
assert_eq!(v11.n_delay_tranches, 25);
assert_eq!(v11.minimum_validation_upgrade_delay, 5);
assert_eq!(v11.group_rotation_frequency, 20);
assert_eq!(v11.on_demand_cores, 0);
assert_eq!(v11.on_demand_base_fee, 10_000_000);
assert_eq!(v11.minimum_backing_votes, LEGACY_MIN_BACKING_VOTES);
assert_eq!(v11.approval_voting_params.max_approval_coalesce_count, 1);
}
#[test]
fn test_migrate_to_v11() {
// Host configuration has lots of fields. However, in this migration we only add one
// field. The most important part to check are a couple of the last fields. We also pick
// extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and
// also their type.
//
// We specify only the picked fields and the rest should be provided by the `Default`
// implementation. That implementation is copied over between the two types and should work
// fine.
let v10 = V10HostConfiguration::<primitives::BlockNumber> {
needed_approvals: 69,
paras_availability_period: 55,
hrmp_recipient_deposit: 1337,
max_pov_size: 1111,
minimum_validation_upgrade_delay: 20,
..Default::default()
};
let mut pending_configs = Vec::new();
pending_configs.push((100, v10.clone()));
pending_configs.push((300, v10.clone()));
new_test_ext(Default::default()).execute_with(|| {
// Implant the v10 version in the state.
v10::ActiveConfig::<Test>::set(Some(v10));
v10::PendingConfigs::<Test>::set(Some(pending_configs));
migrate_to_v11::<Test>();
let v11 = v11::ActiveConfig::<Test>::get().unwrap();
assert_eq!(v11.approval_voting_params.max_approval_coalesce_count, 1);
let mut configs_to_check = v11::PendingConfigs::<Test>::get().unwrap();
configs_to_check.push((0, v11.clone()));
for (_, v10) in configs_to_check {
#[rustfmt::skip]
{
assert_eq!(v10.max_code_size , v11.max_code_size);
assert_eq!(v10.max_head_data_size , v11.max_head_data_size);
assert_eq!(v10.max_upward_queue_count , v11.max_upward_queue_count);
assert_eq!(v10.max_upward_queue_size , v11.max_upward_queue_size);
assert_eq!(v10.max_upward_message_size , v11.max_upward_message_size);
assert_eq!(v10.max_upward_message_num_per_candidate , v11.max_upward_message_num_per_candidate);
assert_eq!(v10.hrmp_max_message_num_per_candidate , v11.hrmp_max_message_num_per_candidate);
assert_eq!(v10.validation_upgrade_cooldown , v11.validation_upgrade_cooldown);
assert_eq!(v10.validation_upgrade_delay , v11.validation_upgrade_delay);
assert_eq!(v10.max_pov_size , v11.max_pov_size);
assert_eq!(v10.max_downward_message_size , v11.max_downward_message_size);
assert_eq!(v10.hrmp_max_parachain_outbound_channels , v11.hrmp_max_parachain_outbound_channels);
assert_eq!(v10.hrmp_sender_deposit , v11.hrmp_sender_deposit);
assert_eq!(v10.hrmp_recipient_deposit , v11.hrmp_recipient_deposit);
assert_eq!(v10.hrmp_channel_max_capacity , v11.hrmp_channel_max_capacity);
assert_eq!(v10.hrmp_channel_max_total_size , v11.hrmp_channel_max_total_size);
assert_eq!(v10.hrmp_max_parachain_inbound_channels , v11.hrmp_max_parachain_inbound_channels);
assert_eq!(v10.hrmp_channel_max_message_size , v11.hrmp_channel_max_message_size);
assert_eq!(v10.code_retention_period , v11.code_retention_period);
assert_eq!(v10.on_demand_cores , v11.on_demand_cores);
assert_eq!(v10.on_demand_retries , v11.on_demand_retries);
assert_eq!(v10.group_rotation_frequency , v11.group_rotation_frequency);
assert_eq!(v10.paras_availability_period , v11.paras_availability_period);
assert_eq!(v10.scheduling_lookahead , v11.scheduling_lookahead);
assert_eq!(v10.max_validators_per_core , v11.max_validators_per_core);
assert_eq!(v10.max_validators , v11.max_validators);
assert_eq!(v10.dispute_period , v11.dispute_period);
assert_eq!(v10.no_show_slots , v11.no_show_slots);
assert_eq!(v10.n_delay_tranches , v11.n_delay_tranches);
assert_eq!(v10.zeroth_delay_tranche_width , v11.zeroth_delay_tranche_width);
assert_eq!(v10.needed_approvals , v11.needed_approvals);
assert_eq!(v10.relay_vrf_modulo_samples , v11.relay_vrf_modulo_samples);
assert_eq!(v10.pvf_voting_ttl , v11.pvf_voting_ttl);
assert_eq!(v10.minimum_validation_upgrade_delay , v11.minimum_validation_upgrade_delay);
assert_eq!(v10.async_backing_params.allowed_ancestry_len, v11.async_backing_params.allowed_ancestry_len);
assert_eq!(v10.async_backing_params.max_candidate_depth , v11.async_backing_params.max_candidate_depth);
assert_eq!(v10.executor_params , v11.executor_params);
assert_eq!(v10.minimum_backing_votes , v11.minimum_backing_votes);
}; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression.
}
});
}
// Test that migration doesn't panic in case there're no pending configurations upgrades in
// pallet's storage.
#[test]
fn test_migrate_to_v11_no_pending() {
let v10 = V10HostConfiguration::<primitives::BlockNumber>::default();
new_test_ext(Default::default()).execute_with(|| {
// Implant the v10 version in the state.
v10::ActiveConfig::<Test>::set(Some(v10));
// Ensure there're no pending configs.
v11::PendingConfigs::<Test>::set(None);
// Shouldn't fail.
migrate_to_v11::<Test>();
});
}
}
@@ -250,7 +250,7 @@ on_demand_fee_variability : Perbill::from_percent(3),
on_demand_target_queue_utilization : Perbill::from_percent(25),
on_demand_ttl : 5u32.into(),
}
};
};
let v7 = v7::ActiveConfig::<T>::get()
.defensive_proof("Could not decode old config")
@@ -313,6 +313,7 @@ fn setting_pending_config_members() {
pvf_voting_ttl: 3,
minimum_validation_upgrade_delay: 20,
executor_params: Default::default(),
approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 1 },
on_demand_queue_max_size: 10_000u32,
on_demand_base_fee: 10_000_000u128,
on_demand_fee_variability: Perbill::from_percent(3),
+26 -8
View File
@@ -25,11 +25,11 @@ use frame_system::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use polkadot_runtime_metrics::get_current_time;
use primitives::{
byzantine_threshold, supermajority_threshold, ApprovalVote, CandidateHash,
CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CompactStatement, ConsensusLog,
DisputeState, DisputeStatement, DisputeStatementSet, ExplicitDisputeStatement,
InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex, SigningContext,
ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
byzantine_threshold, supermajority_threshold, vstaging::ApprovalVoteMultipleCandidates,
ApprovalVote, CandidateHash, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet,
CompactStatement, ConsensusLog, DisputeState, DisputeStatement, DisputeStatementSet,
ExplicitDisputeStatement, InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex,
SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature,
};
use scale_info::TypeInfo;
use sp_runtime::{
@@ -952,6 +952,8 @@ impl<T: Config> Pallet<T> {
None => return StatementSetFilter::RemoveAll,
};
let config = <configuration::Pallet<T>>::config();
let n_validators = session_info.validators.len();
// Check for ancient.
@@ -1015,7 +1017,14 @@ impl<T: Config> Pallet<T> {
set.session,
statement,
signature,
// This is here to prevent malicious nodes of generating
// `ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates` before that
// is enabled, via setting `max_approval_coalesce_count` in the parachain host
// config.
config.approval_voting_params.max_approval_coalesce_count > 1,
) {
log::warn!("Failed to check dispute signature");
importer.undo(undo);
filter.remove_index(i);
continue
@@ -1260,22 +1269,31 @@ fn check_signature(
session: SessionIndex,
statement: &DisputeStatement,
validator_signature: &ValidatorSignature,
approval_multiple_candidates_enabled: bool,
) -> Result<(), ()> {
let payload = match *statement {
let payload = match statement {
DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) =>
ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(),
DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(inclusion_parent)) =>
CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext {
session_index: session,
parent_hash: inclusion_parent,
parent_hash: *inclusion_parent,
}),
DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) =>
CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext {
session_index: session,
parent_hash: inclusion_parent,
parent_hash: *inclusion_parent,
}),
DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) =>
ApprovalVote(candidate_hash).signing_payload(session),
DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(
candidates,
)) =>
if approval_multiple_candidates_enabled && candidates.contains(&candidate_hash) {
ApprovalVoteMultipleCandidates(candidates).signing_payload(session)
} else {
return Err(())
},
DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) =>
ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(),
};
@@ -1500,7 +1500,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_1,
&signed_1
&signed_1,
true,
)
.is_ok());
assert!(check_signature(
@@ -1508,7 +1509,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_1,
&signed_1
&signed_1,
true
)
.is_err());
assert!(check_signature(
@@ -1516,7 +1518,8 @@ fn test_check_signature() {
wrong_candidate_hash,
session,
&statement_1,
&signed_1
&signed_1,
true,
)
.is_err());
assert!(check_signature(
@@ -1524,7 +1527,8 @@ fn test_check_signature() {
candidate_hash,
wrong_session,
&statement_1,
&signed_1
&signed_1,
true
)
.is_err());
assert!(check_signature(
@@ -1532,7 +1536,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_2,
&signed_1
&signed_1,
true,
)
.is_err());
assert!(check_signature(
@@ -1540,7 +1545,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_3,
&signed_1
&signed_1,
true
)
.is_err());
assert!(check_signature(
@@ -1548,7 +1554,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_4,
&signed_1
&signed_1,
true
)
.is_err());
assert!(check_signature(
@@ -1556,7 +1563,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_5,
&signed_1
&signed_1,
true,
)
.is_err());
@@ -1565,7 +1573,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_2,
&signed_2
&signed_2,
true,
)
.is_ok());
assert!(check_signature(
@@ -1573,7 +1582,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_2,
&signed_2
&signed_2,
true,
)
.is_err());
assert!(check_signature(
@@ -1581,7 +1591,8 @@ fn test_check_signature() {
wrong_candidate_hash,
session,
&statement_2,
&signed_2
&signed_2,
true,
)
.is_err());
assert!(check_signature(
@@ -1589,7 +1600,8 @@ fn test_check_signature() {
candidate_hash,
wrong_session,
&statement_2,
&signed_2
&signed_2,
true
)
.is_err());
assert!(check_signature(
@@ -1597,7 +1609,8 @@ fn test_check_signature() {
candidate_hash,
session,
&wrong_statement_2,
&signed_2
&signed_2,
true,
)
.is_err());
assert!(check_signature(
@@ -1605,7 +1618,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_1,
&signed_2
&signed_2,
true,
)
.is_err());
assert!(check_signature(
@@ -1613,7 +1627,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_3,
&signed_2
&signed_2,
true,
)
.is_err());
assert!(check_signature(
@@ -1621,7 +1636,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_4,
&signed_2
&signed_2,
true,
)
.is_err());
assert!(check_signature(
@@ -1629,7 +1645,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_5,
&signed_2
&signed_2,
true,
)
.is_err());
@@ -1638,7 +1655,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_3,
&signed_3
&signed_3,
true,
)
.is_ok());
assert!(check_signature(
@@ -1646,7 +1664,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_3,
&signed_3
&signed_3,
true,
)
.is_err());
assert!(check_signature(
@@ -1654,7 +1673,8 @@ fn test_check_signature() {
wrong_candidate_hash,
session,
&statement_3,
&signed_3
&signed_3,
true,
)
.is_err());
assert!(check_signature(
@@ -1662,7 +1682,8 @@ fn test_check_signature() {
candidate_hash,
wrong_session,
&statement_3,
&signed_3
&signed_3,
true,
)
.is_err());
assert!(check_signature(
@@ -1670,7 +1691,8 @@ fn test_check_signature() {
candidate_hash,
session,
&wrong_statement_3,
&signed_3
&signed_3,
true,
)
.is_err());
assert!(check_signature(
@@ -1678,7 +1700,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_1,
&signed_3
&signed_3,
true,
)
.is_err());
assert!(check_signature(
@@ -1686,7 +1709,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_2,
&signed_3
&signed_3,
true
)
.is_err());
assert!(check_signature(
@@ -1694,7 +1718,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_4,
&signed_3
&signed_3,
true,
)
.is_err());
assert!(check_signature(
@@ -1702,7 +1727,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_5,
&signed_3
&signed_3,
true,
)
.is_err());
@@ -1711,7 +1737,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_4,
&signed_4
&signed_4,
true,
)
.is_ok());
assert!(check_signature(
@@ -1719,7 +1746,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_4,
&signed_4
&signed_4,
true,
)
.is_err());
assert!(check_signature(
@@ -1727,7 +1755,8 @@ fn test_check_signature() {
wrong_candidate_hash,
session,
&statement_4,
&signed_4
&signed_4,
true,
)
.is_err());
assert!(check_signature(
@@ -1735,7 +1764,8 @@ fn test_check_signature() {
candidate_hash,
wrong_session,
&statement_4,
&signed_4
&signed_4,
true,
)
.is_err());
assert!(check_signature(
@@ -1743,7 +1773,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_1,
&signed_4
&signed_4,
true,
)
.is_err());
assert!(check_signature(
@@ -1751,7 +1782,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_2,
&signed_4
&signed_4,
true,
)
.is_err());
assert!(check_signature(
@@ -1759,7 +1791,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_3,
&signed_4
&signed_4,
true,
)
.is_err());
assert!(check_signature(
@@ -1767,7 +1800,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_5,
&signed_4
&signed_4,
true,
)
.is_err());
@@ -1776,7 +1810,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_5,
&signed_5
&signed_5,
true,
)
.is_ok());
assert!(check_signature(
@@ -1784,7 +1819,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_5,
&signed_5
&signed_5,
true,
)
.is_err());
assert!(check_signature(
@@ -1792,7 +1828,8 @@ fn test_check_signature() {
wrong_candidate_hash,
session,
&statement_5,
&signed_5
&signed_5,
true,
)
.is_err());
assert!(check_signature(
@@ -1800,7 +1837,8 @@ fn test_check_signature() {
candidate_hash,
wrong_session,
&statement_5,
&signed_5
&signed_5,
true,
)
.is_err());
assert!(check_signature(
@@ -1808,7 +1846,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_1,
&signed_5
&signed_5,
true,
)
.is_err());
assert!(check_signature(
@@ -1816,7 +1855,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_2,
&signed_5
&signed_5,
true,
)
.is_err());
assert!(check_signature(
@@ -1824,7 +1864,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_3,
&signed_5
&signed_5,
true,
)
.is_err());
assert!(check_signature(
@@ -1832,7 +1873,8 @@ fn test_check_signature() {
candidate_hash,
session,
&statement_4,
&signed_5
&signed_5,
true,
)
.is_err());
}
@@ -17,7 +17,10 @@
//! Put implementations of functions from staging APIs here.
use crate::{configuration, initializer, shared};
use primitives::{vstaging::NodeFeatures, ValidatorIndex};
use primitives::{
vstaging::{ApprovalVotingParams, NodeFeatures},
ValidatorIndex,
};
use sp_std::{collections::btree_map::BTreeMap, prelude::Vec};
/// Implementation for `DisabledValidators`
@@ -47,3 +50,9 @@ where
pub fn node_features<T: initializer::Config>() -> NodeFeatures {
<configuration::Pallet<T>>::config().node_features
}
/// Approval voting subsystem configuration parameteres
pub fn approval_voting_params<T: initializer::Config>() -> ApprovalVotingParams {
let config = <configuration::Pallet<T>>::config();
config.approval_voting_params
}
+13 -7
View File
@@ -23,12 +23,13 @@
use pallet_nis::WithMaximumOf;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use primitives::{
slashing, vstaging::NodeFeatures, AccountId, AccountIndex, Balance, BlockNumber,
CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState,
ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData,
ScrapedOnChainVotes, SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId,
ValidatorIndex, PARACHAIN_KEY_TYPE_ID,
slashing,
vstaging::{ApprovalVotingParams, NodeFeatures},
AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce,
OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionInfo, Signature,
ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, PARACHAIN_KEY_TYPE_ID,
};
use runtime_common::{
assigned_slots, auctions, claims, crowdloan, identity_migrator, impl_runtime_weights,
@@ -1631,6 +1632,7 @@ pub mod migrations {
// Remove `im-online` pallet on-chain storage
frame_support::migrations::RemovePallet<ImOnlinePalletName, <Runtime as frame_system::Config>::DbWeight>,
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
);
}
@@ -1792,7 +1794,7 @@ sp_api::impl_runtime_apis! {
}
}
#[api_version(9)]
#[api_version(10)]
impl primitives::runtime_api::ParachainHost<Block> for Runtime {
fn validators() -> Vec<ValidatorId> {
parachains_runtime_api_impl::validators::<Runtime>()
@@ -1936,6 +1938,10 @@ sp_api::impl_runtime_apis! {
parachains_runtime_api_impl::async_backing_params::<Runtime>()
}
fn approval_voting_params() -> ApprovalVotingParams {
parachains_staging_runtime_api_impl::approval_voting_params::<Runtime>()
}
fn disabled_validators() -> Vec<ValidatorIndex> {
parachains_staging_runtime_api_impl::disabled_validators::<Runtime>()
}
+14 -7
View File
@@ -45,12 +45,14 @@ use pallet_session::historical as session_historical;
use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use primitives::{
slashing, vstaging::NodeFeatures, AccountId, AccountIndex, Balance, BlockNumber,
CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState,
ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData,
PvfCheckStatement, ScrapedOnChainVotes, SessionInfo, Signature, ValidationCode,
ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, PARACHAIN_KEY_TYPE_ID,
slashing,
vstaging::{ApprovalVotingParams, NodeFeatures},
AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash,
CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash,
Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce,
OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes,
SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
ValidatorSignature, PARACHAIN_KEY_TYPE_ID,
};
use runtime_common::{
assigned_slots, auctions, crowdloan,
@@ -1649,6 +1651,7 @@ pub mod migrations {
ImOnlinePalletName,
<Runtime as frame_system::Config>::DbWeight,
>,
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
);
}
@@ -1789,7 +1792,7 @@ sp_api::impl_runtime_apis! {
}
}
#[api_version(9)]
#[api_version(10)]
impl primitives::runtime_api::ParachainHost<Block> for Runtime {
fn validators() -> Vec<ValidatorId> {
parachains_runtime_api_impl::validators::<Runtime>()
@@ -1933,6 +1936,10 @@ sp_api::impl_runtime_apis! {
parachains_runtime_api_impl::async_backing_params::<Runtime>()
}
fn approval_voting_params() -> ApprovalVotingParams {
parachains_staging_runtime_api_impl::approval_voting_params::<Runtime>()
}
fn disabled_validators() -> Vec<ValidatorIndex> {
parachains_staging_runtime_api_impl::disabled_validators::<Runtime>()
}
@@ -32,6 +32,8 @@ alice: parachain 2005 block height is at least 10 within 300 seconds
alice: parachain 2006 block height is at least 10 within 300 seconds
alice: parachain 2007 block height is at least 10 within 300 seconds
alice: reports substrate_block_height{status="finalized"} is at least 30 within 400 seconds
# Check preparation time is under 10s.
# Check all buckets <= 10.
alice: reports histogram polkadot_pvf_preparation_time has at least 1 samples in buckets ["0.1", "0.5", "1", "2", "3", "10"] within 10 seconds
@@ -5,6 +5,10 @@ timeout = 1000
max_validators_per_core = 5
needed_approvals = 8
[relaychain.genesis.runtime.runtime_genesis_config.configuration.config.approval_voting_params]
max_approval_coalesce_count = 5
[relaychain]
default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}"
chain = "rococo-local"
@@ -0,0 +1,115 @@
[settings]
timeout = 1000
[relaychain]
default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}"
chain = "rococo-local"
[relaychain.genesis.runtimeGenesis.patch.configuration.config]
needed_approvals = 4
relay_vrf_modulo_samples = 6
[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params]
max_approval_coalesce_count = 5
[relaychain.default_resources]
limits = { memory = "4G", cpu = "2" }
requests = { memory = "2G", cpu = "1" }
[[relaychain.node_groups]]
name = "alice"
args = [ "-lparachain=trace,runtime=debug" ]
count = 13
[[parachains]]
id = 2000
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1"
[parachains.collator]
name = "collator01"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"]
[[parachains]]
id = 2001
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=10"
[parachains.collator]
name = "collator02"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2001", "--pvf-complexity=10"]
[[parachains]]
id = 2002
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=100"
[parachains.collator]
name = "collator03"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2002", "--pvf-complexity=100"]
[[parachains]]
id = 2003
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=300"
[parachains.collator]
name = "collator04"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=20000", "--parachain-id=2003", "--pvf-complexity=300"]
[[parachains]]
id = 2004
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300"
[parachains.collator]
name = "collator05"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2004", "--pvf-complexity=300"]
[[parachains]]
id = 2005
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=400"
[parachains.collator]
name = "collator06"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=20000", "--pvf-complexity=400", "--parachain-id=2005"]
[[parachains]]
id = 2006
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300"
[parachains.collator]
name = "collator07"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2006"]
[[parachains]]
id = 2007
addToGenesis = true
genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300"
[parachains.collator]
name = "collator08"
image = "{{COL_IMAGE}}"
command = "undying-collator"
args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2007"]
[types.Header]
number = "u64"
parent_hash = "Hash"
post_state = "Hash"
@@ -0,0 +1,32 @@
Description: Approval voting coalescing does not lag finality
Network: ./0009-approval-voting-coalescing.toml
Creds: config
# Check authority status.
alice: reports node_roles is 4
# Ensure parachains are registered.
alice: parachain 2000 is registered within 60 seconds
alice: parachain 2001 is registered within 60 seconds
alice: parachain 2002 is registered within 60 seconds
alice: parachain 2003 is registered within 60 seconds
alice: parachain 2004 is registered within 60 seconds
alice: parachain 2005 is registered within 60 seconds
alice: parachain 2006 is registered within 60 seconds
alice: parachain 2007 is registered within 60 seconds
# Ensure parachains made progress.
alice: parachain 2000 block height is at least 10 within 300 seconds
alice: parachain 2001 block height is at least 10 within 300 seconds
alice: parachain 2002 block height is at least 10 within 300 seconds
alice: parachain 2003 block height is at least 10 within 300 seconds
alice: parachain 2004 block height is at least 10 within 300 seconds
alice: parachain 2005 block height is at least 10 within 300 seconds
alice: parachain 2006 block height is at least 10 within 300 seconds
alice: parachain 2007 block height is at least 10 within 300 seconds
alice: reports substrate_block_height{status="finalized"} is at least 30 within 400 seconds
alice: reports polkadot_parachain_approval_checking_finality_lag < 3
alice: reports polkadot_parachain_approvals_no_shows_total < 3 within 10 seconds
+21
View File
@@ -0,0 +1,21 @@
title: Approve multiple candidates with a single signature
doc:
- audience: Node Operator
description: |
Changed approval-voting, approval-distribution to approve multiple candidate with a single message, it adds:
* A new parachains_db version.
* A new validation protocol to support the new message types.
The new logic will be disabled and will be enabled at a later date after all validators have upgraded.
migrations:
db:
- name: Parachains database change from v4 to v5.
description: |
Approval-voting column format has been updated with several new fields. All existing data will be automatically
be migrated to the new values.
crates:
- name: "polkadot"
host_functions: []