mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Approval voting failsafe (#2675)
* add consensus log type * origin and issue force_approve * add origin in runtimes * ref API * scrape force_approve digest from header * add parent_hash to BlockEntry * add block_number to block entry and force_approve skeleton * implement and plug in force-approve * test force_approve * test force_approve extraction * westend runtime * Update node/core/approval-voting/src/approval_db/v1/mod.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * rename * Update runtime/parachains/src/initializer.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
dce20644c8
commit
ef816b089d
@@ -92,6 +92,8 @@ pub struct CandidateEntry {
|
||||
#[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
|
||||
@@ -348,14 +350,14 @@ pub(crate) struct NewCandidateInfo {
|
||||
/// no information about new candidates will be referred to by this function.
|
||||
pub(crate) fn add_block_entry(
|
||||
store: &dyn KeyValueDB,
|
||||
parent_hash: Hash,
|
||||
number: BlockNumber,
|
||||
entry: BlockEntry,
|
||||
n_validators: usize,
|
||||
candidate_info: impl Fn(&CandidateHash) -> Option<NewCandidateInfo>,
|
||||
) -> Result<Vec<(CandidateHash, CandidateEntry)>> {
|
||||
let mut transaction = DBTransaction::new();
|
||||
let session = entry.session;
|
||||
let parent_hash = entry.parent_hash;
|
||||
let number = entry.block_number;
|
||||
|
||||
// Update the stored block range.
|
||||
{
|
||||
@@ -439,6 +441,45 @@ pub(crate) fn add_block_entry(
|
||||
Ok(candidate_entries)
|
||||
}
|
||||
|
||||
/// Forcibly approve all candidates included at up to the given relay-chain height in the indicated
|
||||
/// chain.
|
||||
pub fn force_approve(
|
||||
store: &dyn KeyValueDB,
|
||||
chain_head: Hash,
|
||||
up_to: BlockNumber,
|
||||
) -> Result<()> {
|
||||
enum State {
|
||||
WalkTo,
|
||||
Approving,
|
||||
}
|
||||
|
||||
let mut cur_hash = chain_head;
|
||||
let mut state = State::WalkTo;
|
||||
|
||||
let mut tx = Transaction::default();
|
||||
|
||||
// iterate back to the `up_to` block, and then iterate backwards until all blocks
|
||||
// are updated.
|
||||
while let Some(mut entry) = load_block_entry(store, &cur_hash)? {
|
||||
|
||||
if entry.block_number <= up_to {
|
||||
state = State::Approving;
|
||||
}
|
||||
|
||||
cur_hash = entry.parent_hash;
|
||||
|
||||
match state {
|
||||
State::WalkTo => {},
|
||||
State::Approving => {
|
||||
entry.approved_bitfield.iter_mut().for_each(|mut b| *b = true);
|
||||
tx.put_block_entry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.write(store)
|
||||
}
|
||||
|
||||
// An atomic transaction of multiple candidate or block entries.
|
||||
#[derive(Default)]
|
||||
#[must_use = "Transactions do nothing unless written to a DB"]
|
||||
|
||||
@@ -57,10 +57,14 @@ fn make_bitvec(len: usize) -> BitVec<BitOrderLsb0, u8> {
|
||||
|
||||
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],
|
||||
@@ -92,6 +96,8 @@ fn read_write() {
|
||||
|
||||
let block_entry = make_block_entry(
|
||||
hash_a,
|
||||
Default::default(),
|
||||
1,
|
||||
vec![(CoreIndex(0), candidate_hash)],
|
||||
);
|
||||
|
||||
@@ -155,18 +161,23 @@ fn add_block_entry_works() {
|
||||
let candidate_hash_a = CandidateHash(Hash::repeat_byte(3));
|
||||
let candidate_hash_b = CandidateHash(Hash::repeat_byte(4));
|
||||
|
||||
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 block_number = 10;
|
||||
|
||||
let mut new_candidate_info = HashMap::new();
|
||||
new_candidate_info.insert(candidate_hash_a, NewCandidateInfo {
|
||||
@@ -177,8 +188,6 @@ fn add_block_entry_works() {
|
||||
|
||||
add_block_entry(
|
||||
&store,
|
||||
parent_hash,
|
||||
block_number,
|
||||
block_entry_a.clone(),
|
||||
n_validators,
|
||||
|h| new_candidate_info.get(h).map(|x| x.clone()),
|
||||
@@ -192,8 +201,6 @@ fn add_block_entry_works() {
|
||||
|
||||
add_block_entry(
|
||||
&store,
|
||||
parent_hash,
|
||||
block_number,
|
||||
block_entry_b.clone(),
|
||||
n_validators,
|
||||
|h| new_candidate_info.get(h).map(|x| x.clone()),
|
||||
@@ -219,11 +226,15 @@ fn add_block_entry_adds_child() {
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
@@ -231,8 +242,6 @@ fn add_block_entry_adds_child() {
|
||||
|
||||
add_block_entry(
|
||||
&store,
|
||||
parent_hash,
|
||||
1,
|
||||
block_entry_a.clone(),
|
||||
n_validators,
|
||||
|_| None,
|
||||
@@ -240,8 +249,6 @@ fn add_block_entry_adds_child() {
|
||||
|
||||
add_block_entry(
|
||||
&store,
|
||||
block_hash_a,
|
||||
2,
|
||||
block_entry_b.clone(),
|
||||
n_validators,
|
||||
|_| None,
|
||||
@@ -292,19 +299,33 @@ fn canonicalize_works() {
|
||||
let cand_hash_4 = CandidateHash(Hash::repeat_byte(13));
|
||||
let cand_hash_5 = CandidateHash(Hash::repeat_byte(15));
|
||||
|
||||
let block_entry_a = make_block_entry(block_hash_a, Vec::new());
|
||||
let block_entry_b1 = make_block_entry(block_hash_b1, Vec::new());
|
||||
let block_entry_b2 = make_block_entry(block_hash_b2, vec![(CoreIndex(0), cand_hash_1)]);
|
||||
let block_entry_c1 = make_block_entry(block_hash_c1, Vec::new());
|
||||
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, vec![(CoreIndex(0), cand_hash_5)]);
|
||||
let block_entry_d2 = make_block_entry(
|
||||
block_hash_d2,
|
||||
block_hash_c2,
|
||||
4,
|
||||
vec![(CoreIndex(0), cand_hash_5)],
|
||||
);
|
||||
|
||||
|
||||
let candidate_info = {
|
||||
@@ -344,20 +365,18 @@ fn canonicalize_works() {
|
||||
|
||||
// now insert all the blocks.
|
||||
let blocks = vec![
|
||||
(genesis, 1, block_entry_a.clone()),
|
||||
(block_hash_a, 2, block_entry_b1.clone()),
|
||||
(block_hash_a, 2, block_entry_b2.clone()),
|
||||
(block_hash_b1, 3, block_entry_c1.clone()),
|
||||
(block_hash_b2, 3, block_entry_c2.clone()),
|
||||
(block_hash_c1, 4, block_entry_d1.clone()),
|
||||
(block_hash_c2, 4, block_entry_d2.clone()),
|
||||
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(),
|
||||
];
|
||||
|
||||
for (parent_hash, number, block_entry) in blocks {
|
||||
for block_entry in blocks {
|
||||
add_block_entry(
|
||||
&store,
|
||||
parent_hash,
|
||||
number,
|
||||
block_entry,
|
||||
n_validators,
|
||||
|h| candidate_info.get(h).map(|x| x.clone()),
|
||||
@@ -446,3 +465,60 @@ fn canonicalize_works() {
|
||||
(block_hash_d2, None),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn force_approve_works() {
|
||||
let store = kvdb_memorydb::create(1);
|
||||
let n_validators = 10;
|
||||
|
||||
let mut tx = DBTransaction::new();
|
||||
write_stored_blocks(&mut tx, StoredBlockRange(1, 4));
|
||||
store.write(tx).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 {
|
||||
candidate: make_candidate(1.into(), Default::default()),
|
||||
backing_group: GroupIndex(1),
|
||||
our_assignment: 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(),
|
||||
];
|
||||
|
||||
for block_entry in blocks {
|
||||
add_block_entry(
|
||||
&store,
|
||||
block_entry,
|
||||
n_validators,
|
||||
|h| candidate_info.get(h).map(|x| x.clone()),
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
force_approve(&store, block_hash_d, 2).unwrap();
|
||||
|
||||
assert!(load_block_entry(&store, &block_hash_a).unwrap().unwrap().approved_bitfield.all());
|
||||
assert!(load_block_entry(&store, &block_hash_b).unwrap().unwrap().approved_bitfield.all());
|
||||
assert!(load_block_entry(&store, &block_hash_c).unwrap().unwrap().approved_bitfield.not_any());
|
||||
assert!(load_block_entry(&store, &block_hash_d).unwrap().unwrap().approved_bitfield.not_any());
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ use polkadot_node_subsystem::{
|
||||
};
|
||||
use polkadot_primitives::v1::{
|
||||
Hash, SessionIndex, SessionInfo, CandidateEvent, Header, CandidateHash,
|
||||
CandidateReceipt, CoreIndex, GroupIndex, BlockNumber,
|
||||
CandidateReceipt, CoreIndex, GroupIndex, BlockNumber, ConsensusLog,
|
||||
};
|
||||
use polkadot_node_primitives::approval::{
|
||||
self as approval_types, BlockApprovalMeta, RelayVRFStory,
|
||||
@@ -345,6 +345,7 @@ struct ImportedBlockInfo {
|
||||
n_validators: usize,
|
||||
relay_vrf_story: RelayVRFStory,
|
||||
slot: Slot,
|
||||
force_approve: Option<BlockNumber>,
|
||||
}
|
||||
|
||||
struct ImportedBlockInfoEnv<'a> {
|
||||
@@ -494,6 +495,23 @@ async fn imported_block_info(
|
||||
}
|
||||
};
|
||||
|
||||
let force_approve =
|
||||
block_header.digest.convert_first(|l| match ConsensusLog::from_digest_item(l) {
|
||||
Ok(Some(ConsensusLog::ForceApprove(num))) if num < block_header.number => Some(num),
|
||||
Ok(Some(_)) => None,
|
||||
Ok(None) => None,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
target: LOG_TARGET,
|
||||
?err,
|
||||
?block_hash,
|
||||
"Malformed consensus digest in header",
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Some(ImportedBlockInfo {
|
||||
included_candidates,
|
||||
session_index,
|
||||
@@ -501,6 +519,7 @@ async fn imported_block_info(
|
||||
n_validators: session_info.validators.len(),
|
||||
relay_vrf_story,
|
||||
slot,
|
||||
force_approve,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -624,6 +643,7 @@ pub(crate) async fn handle_new_head(
|
||||
n_validators,
|
||||
relay_vrf_story,
|
||||
slot,
|
||||
force_approve,
|
||||
} = imported_block_info;
|
||||
|
||||
let session_info = state.session_window.session_info(session_index)
|
||||
@@ -676,6 +696,8 @@ pub(crate) async fn handle_new_head(
|
||||
|
||||
let block_entry = approval_db::v1::BlockEntry {
|
||||
block_hash,
|
||||
parent_hash: block_header.parent_hash,
|
||||
block_number: block_header.number,
|
||||
session: session_index,
|
||||
slot,
|
||||
relay_vrf_story: relay_vrf_story.0,
|
||||
@@ -685,10 +707,13 @@ pub(crate) async fn handle_new_head(
|
||||
children: Vec::new(),
|
||||
};
|
||||
|
||||
if let Some(up_to) = force_approve {
|
||||
approval_db::v1::force_approve(db_writer, block_hash, up_to)
|
||||
.map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
|
||||
}
|
||||
|
||||
let candidate_entries = approval_db::v1::add_block_entry(
|
||||
db_writer,
|
||||
block_header.parent_hash,
|
||||
block_header.number,
|
||||
block_entry,
|
||||
n_validators,
|
||||
|candidate_hash| {
|
||||
@@ -1026,6 +1051,8 @@ mod tests {
|
||||
known_hash,
|
||||
crate::approval_db::v1::BlockEntry {
|
||||
block_hash: known_hash,
|
||||
parent_hash: Default::default(),
|
||||
block_number: known_number,
|
||||
session: 1,
|
||||
slot: Slot::from(100),
|
||||
relay_vrf_story: Default::default(),
|
||||
@@ -1101,6 +1128,8 @@ mod tests {
|
||||
head_hash,
|
||||
crate::approval_db::v1::BlockEntry {
|
||||
block_hash: head_hash,
|
||||
parent_hash: Default::default(),
|
||||
block_number: 18,
|
||||
session: 1,
|
||||
slot: Slot::from(100),
|
||||
relay_vrf_story: Default::default(),
|
||||
@@ -1149,6 +1178,8 @@ mod tests {
|
||||
parent_hash,
|
||||
crate::approval_db::v1::BlockEntry {
|
||||
block_hash: parent_hash,
|
||||
parent_hash: Default::default(),
|
||||
block_number: 18,
|
||||
session: 1,
|
||||
slot: Slot::from(100),
|
||||
relay_vrf_story: Default::default(),
|
||||
@@ -1195,6 +1226,8 @@ mod tests {
|
||||
parent_hash,
|
||||
crate::approval_db::v1::BlockEntry {
|
||||
block_hash: parent_hash,
|
||||
parent_hash: Default::default(),
|
||||
block_number: 18,
|
||||
session: 1,
|
||||
slot: Slot::from(100),
|
||||
relay_vrf_story: Default::default(),
|
||||
@@ -1346,6 +1379,7 @@ mod tests {
|
||||
assert!(info.assignments.is_empty());
|
||||
assert_eq!(info.n_validators, 0);
|
||||
assert_eq!(info.slot, slot);
|
||||
assert!(info.force_approve.is_none());
|
||||
})
|
||||
};
|
||||
|
||||
@@ -1586,6 +1620,142 @@ mod tests {
|
||||
futures::executor::block_on(futures::future::join(test_fut, aux_fut));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imported_block_info_extracts_force_approve() {
|
||||
let pool = TaskExecutor::new();
|
||||
let (mut ctx, mut handle) = make_subsystem_context::<(), _>(pool.clone());
|
||||
|
||||
let session = 5;
|
||||
let session_info = dummy_session_info(session);
|
||||
|
||||
let slot = Slot::from(10);
|
||||
|
||||
let header = Header {
|
||||
digest: {
|
||||
let mut d = Digest::default();
|
||||
let (vrf_output, vrf_proof) = garbage_vrf();
|
||||
d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
|
||||
SecondaryVRFPreDigest {
|
||||
authority_index: 0,
|
||||
slot,
|
||||
vrf_output,
|
||||
vrf_proof,
|
||||
}
|
||||
)));
|
||||
|
||||
d.push(ConsensusLog::ForceApprove(3).into());
|
||||
|
||||
d
|
||||
},
|
||||
extrinsics_root: Default::default(),
|
||||
number: 5,
|
||||
state_root: Default::default(),
|
||||
parent_hash: Default::default(),
|
||||
};
|
||||
|
||||
let hash = header.hash();
|
||||
let make_candidate = |para_id| {
|
||||
let mut r = CandidateReceipt::default();
|
||||
r.descriptor.para_id = para_id;
|
||||
r.descriptor.relay_parent = hash;
|
||||
r
|
||||
};
|
||||
let candidates = vec![
|
||||
(make_candidate(1.into()), CoreIndex(0), GroupIndex(2)),
|
||||
(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
|
||||
];
|
||||
|
||||
|
||||
let inclusion_events = candidates.iter().cloned()
|
||||
.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let test_fut = {
|
||||
let included_candidates = candidates.iter()
|
||||
.map(|(r, c, g)| (r.hash(), r.clone(), *c, *g))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let session_window = {
|
||||
let mut window = RollingSessionWindow::default();
|
||||
|
||||
window.earliest_session = Some(session);
|
||||
window.session_info.push(session_info);
|
||||
|
||||
window
|
||||
};
|
||||
|
||||
let header = header.clone();
|
||||
Box::pin(async move {
|
||||
let env = ImportedBlockInfoEnv {
|
||||
session_window: &session_window,
|
||||
assignment_criteria: &MockAssignmentCriteria,
|
||||
keystore: &LocalKeystore::in_memory(),
|
||||
};
|
||||
|
||||
let info = imported_block_info(
|
||||
&mut ctx,
|
||||
env,
|
||||
hash,
|
||||
&header,
|
||||
).await.unwrap().unwrap();
|
||||
|
||||
assert_eq!(info.included_candidates, included_candidates);
|
||||
assert_eq!(info.session_index, session);
|
||||
assert!(info.assignments.is_empty());
|
||||
assert_eq!(info.n_validators, 0);
|
||||
assert_eq!(info.slot, slot);
|
||||
assert_eq!(info.force_approve, Some(3));
|
||||
})
|
||||
};
|
||||
|
||||
let aux_fut = Box::pin(async move {
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
h,
|
||||
RuntimeApiRequest::CandidateEvents(c_tx),
|
||||
)) => {
|
||||
assert_eq!(h, hash);
|
||||
let _ = c_tx.send(Ok(inclusion_events));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
h,
|
||||
RuntimeApiRequest::SessionIndexForChild(c_tx),
|
||||
)) => {
|
||||
assert_eq!(h, header.parent_hash);
|
||||
let _ = c_tx.send(Ok(session));
|
||||
}
|
||||
);
|
||||
|
||||
assert_matches!(
|
||||
handle.recv().await,
|
||||
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
|
||||
h,
|
||||
RuntimeApiRequest::CurrentBabeEpoch(c_tx),
|
||||
)) => {
|
||||
assert_eq!(h, hash);
|
||||
let _ = c_tx.send(Ok(BabeEpoch {
|
||||
epoch_index: session as _,
|
||||
start_slot: Slot::from(0),
|
||||
duration: 200,
|
||||
authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)],
|
||||
randomness: [0u8; 32],
|
||||
config: BabeEpochConfiguration {
|
||||
c: (1, 4),
|
||||
allowed_slots: AllowedSlots::PrimarySlots,
|
||||
},
|
||||
}));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
futures::executor::block_on(futures::future::join(test_fut, aux_fut));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insta_approval_works() {
|
||||
let pool = TaskExecutor::new();
|
||||
@@ -1652,6 +1822,8 @@ mod tests {
|
||||
parent_hash.clone(),
|
||||
crate::approval_db::v1::BlockEntry {
|
||||
block_hash: parent_hash.clone(),
|
||||
parent_hash: Default::default(),
|
||||
block_number: 4,
|
||||
session,
|
||||
slot,
|
||||
relay_vrf_story: Default::default(),
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
use polkadot_node_primitives::approval::{DelayTranche, RelayVRFStory, AssignmentCert};
|
||||
use polkadot_primitives::v1::{
|
||||
ValidatorIndex, CandidateReceipt, SessionIndex, GroupIndex, CoreIndex,
|
||||
Hash, CandidateHash,
|
||||
Hash, CandidateHash, BlockNumber,
|
||||
};
|
||||
use sp_consensus_slots::Slot;
|
||||
|
||||
@@ -302,6 +302,8 @@ impl From<CandidateEntry> for crate::approval_db::v1::CandidateEntry {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BlockEntry {
|
||||
block_hash: Hash,
|
||||
parent_hash: Hash,
|
||||
block_number: BlockNumber,
|
||||
session: SessionIndex,
|
||||
slot: Slot,
|
||||
relay_vrf_story: RelayVRFStory,
|
||||
@@ -401,6 +403,8 @@ impl From<crate::approval_db::v1::BlockEntry> for BlockEntry {
|
||||
fn from(entry: crate::approval_db::v1::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),
|
||||
@@ -415,6 +419,8 @@ impl From<BlockEntry> for crate::approval_db::v1::BlockEntry {
|
||||
fn from(entry: BlockEntry) -> Self {
|
||||
Self {
|
||||
block_hash: entry.block_hash,
|
||||
parent_hash: entry.parent_hash,
|
||||
block_number: entry.block_number,
|
||||
session: entry.session,
|
||||
slot: entry.slot,
|
||||
relay_vrf_story: entry.relay_vrf_story.0,
|
||||
|
||||
@@ -314,6 +314,8 @@ fn add_block(
|
||||
block_hash,
|
||||
approval_db::v1::BlockEntry {
|
||||
block_hash,
|
||||
parent_hash: Default::default(),
|
||||
block_number: 0,
|
||||
session,
|
||||
slot,
|
||||
candidates: Vec::new(),
|
||||
|
||||
@@ -1064,6 +1064,43 @@ pub struct AbridgedHrmpChannel {
|
||||
pub mqc_head: Option<Hash>,
|
||||
}
|
||||
|
||||
/// Consensus engine id for polkadot v1 consensus engine.
|
||||
pub const POLKADOT_ENGINE_ID: runtime_primitives::ConsensusEngineId = *b"POL1";
|
||||
|
||||
/// A consensus log item for polkadot validation. To be used with [`POLKADOT_ENGINE_ID`].
|
||||
#[derive(Decode, Encode, Clone, PartialEq, Eq)]
|
||||
pub enum ConsensusLog {
|
||||
/// A parachain or parathread upgraded its code.
|
||||
#[codec(index = 1)]
|
||||
ParaUpgradeCode(Id, Hash),
|
||||
/// A parachain or parathread scheduled a code ugprade.
|
||||
#[codec(index = 2)]
|
||||
ParaScheduleUpgradeCode(Id, Hash, BlockNumber),
|
||||
/// Governance requests to auto-approve every candidate included up to the given block
|
||||
/// number in the current chain, inclusive.
|
||||
#[codec(index = 3)]
|
||||
ForceApprove(BlockNumber),
|
||||
}
|
||||
|
||||
impl ConsensusLog {
|
||||
/// Attempt to convert a reference to a generic digest item into a consensus log.
|
||||
pub fn from_digest_item<H>(digest_item: &runtime_primitives::DigestItem<H>)
|
||||
-> Result<Option<Self>, parity_scale_codec::Error>
|
||||
{
|
||||
match digest_item {
|
||||
runtime_primitives::DigestItem::Consensus(id, encoded) if id == &POLKADOT_ENGINE_ID =>
|
||||
Ok(Some(Self::decode(&mut &encoded[..])?)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> From<ConsensusLog> for runtime_primitives::DigestItem<H> {
|
||||
fn from(c: ConsensusLog) -> runtime_primitives::DigestItem<H> {
|
||||
Self::Consensus(POLKADOT_ENGINE_ID, c.encode())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
//! This module can throw fatal errors if session-change notifications are received after initialization.
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use frame_support::weights::Weight;
|
||||
use primitives::v1::{ValidatorId, SessionIndex};
|
||||
use frame_support::weights::{Weight, DispatchClass};
|
||||
use frame_support::traits::EnsureOrigin;
|
||||
use primitives::v1::{ValidatorId, SessionIndex, ConsensusLog, BlockNumber};
|
||||
use frame_support::{
|
||||
decl_storage, decl_module, decl_error, traits::{OneSessionHandler, Randomness},
|
||||
};
|
||||
@@ -82,6 +83,8 @@ pub trait Config:
|
||||
{
|
||||
/// A randomness beacon.
|
||||
type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
|
||||
/// An origin which is allowed to force updates to parachains.
|
||||
type ForceOrigin: EnsureOrigin<<Self as frame_system::Config>::Origin>;
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
@@ -168,6 +171,16 @@ decl_module! {
|
||||
|
||||
HasInitialized::take();
|
||||
}
|
||||
|
||||
/// Issue a signal to the consensus engine to forcibly act as though all parachain
|
||||
/// blocks in all relay chain blocks up to and including the given number in the current
|
||||
/// chain are valid and should be finalized.
|
||||
#[weight = (0, DispatchClass::Operational)]
|
||||
fn force_approve(origin, up_to: BlockNumber) {
|
||||
T::ForceOrigin::ensure_origin(origin)?;
|
||||
|
||||
frame_system::Pallet::<T>::deposit_log(ConsensusLog::ForceApprove(up_to).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ impl pallet_balances::Config for Test {
|
||||
|
||||
impl crate::initializer::Config for Test {
|
||||
type Randomness = TestRandomness<Self>;
|
||||
type ForceOrigin = frame_system::EnsureRoot<u64>;
|
||||
}
|
||||
|
||||
impl crate::configuration::Config for Test { }
|
||||
|
||||
@@ -589,6 +589,7 @@ impl parachains_scheduler::Config for Runtime {}
|
||||
|
||||
impl parachains_initializer::Config for Runtime {
|
||||
type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
|
||||
type ForceOrigin = EnsureRoot<AccountId>;
|
||||
}
|
||||
|
||||
impl paras_sudo_wrapper::Config for Runtime {}
|
||||
|
||||
@@ -459,6 +459,7 @@ impl parachains_inclusion_inherent::Config for Runtime {}
|
||||
|
||||
impl parachains_initializer::Config for Runtime {
|
||||
type Randomness = pallet_babe::RandomnessFromOneEpochAgo<Runtime>;
|
||||
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
||||
}
|
||||
|
||||
impl parachains_session_info::Config for Runtime {}
|
||||
|
||||
Reference in New Issue
Block a user