Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Closed
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
62184b4
BEGIN ASYNC candidate-backing CHANGES
rphmeier Apr 28, 2022
821ed42
rename & document modes
rphmeier Apr 29, 2022
1fc4928
answer prospective validation data requests
rphmeier May 18, 2022
39f2076
GetMinimumRelayParents request is now plural
rphmeier May 19, 2022
80af4be
implement an implicit view utility for backing subsystems
rphmeier May 19, 2022
7c58f7a
implicit-view: get allowed relay parents
rphmeier May 23, 2022
fbcedac
refactorings and improvements to implicit view
rphmeier May 23, 2022
126ed91
add some TODOs for tests
rphmeier May 23, 2022
a5994a7
split implicit view updates into 2 functions
rphmeier May 25, 2022
8944760
backing: define State to prepare for functional refactor
rphmeier May 25, 2022
c34d0e6
add some docs
rphmeier May 25, 2022
20cd422
backing: implement bones of new leaf activation logic
rphmeier May 25, 2022
2b7d883
backing: create per-relay-parent-states
rphmeier May 25, 2022
923b28a
use new handle_active_leaves_update
rphmeier May 25, 2022
b9280d1
begin extracting logic from CandidateBackingJob
rphmeier May 27, 2022
967156e
mostly extract statement import from job logic
rphmeier May 27, 2022
b25acb7
handle statement imports outside of job logic
rphmeier May 27, 2022
df95962
do some TODO planning for prospective parachains integration
rphmeier May 27, 2022
458d24d
finish rewriting backing subsystem in functional style
rphmeier May 27, 2022
22a9d5e
add prospective parachains mode to relay parent entries
rphmeier May 27, 2022
fc0c4e4
fmt
rphmeier May 27, 2022
a4df277
add a RejectedByProspectiveParachains error
rphmeier May 27, 2022
2f202d0
notify prospective parachains of seconded and backed candidates
rphmeier May 28, 2022
910b997
always validate candidates exhaustively in backing.
rphmeier May 28, 2022
19d7a43
return persisted_validation_data from validation
rphmeier May 28, 2022
7f24629
handle rejections by prospective parachains
rphmeier May 28, 2022
22ead26
implement seconding sanity check
rphmeier May 28, 2022
6738ee9
invoke validate_and_second
rphmeier May 28, 2022
c54e1ca
Alter statement table to allow multiple seconded messages per validator
rphmeier May 28, 2022
e855b6c
refactor backing to have statements carry PVD
rphmeier May 30, 2022
eb0ee29
clean up all warnings
rphmeier May 30, 2022
d80c190
Add tests for implicit view
slumber May 31, 2022
21a381b
fix per_relay_parent pruning in backing
rphmeier May 31, 2022
47a9832
BEGIN STATEMENT DISTRIBUTION WORK
rphmeier May 31, 2022
b8f5282
mostly make network bridge amenable to vstaging
rphmeier May 31, 2022
843cb7b
network-bridge: fully adapt to vstaging
rphmeier May 31, 2022
89cf386
add some TODOs for tests
rphmeier May 31, 2022
a320e44
fix fallout in bitfield-distribution
rphmeier May 31, 2022
3cc56d1
add some test TODOs
rphmeier May 31, 2022
00f410d
fix fallout in gossip-support
rphmeier May 31, 2022
1a60a0c
fmt
rphmeier May 31, 2022
6f16adf
collator-protocol: fix message fallout
rphmeier May 31, 2022
7e46d8a
collator-protocol: load PVD from runtime
rphmeier May 31, 2022
86007fb
make things compile
rphmeier Jun 2, 2022
faf00f6
fmt
rphmeier Jun 2, 2022
f43c8f6
begin extracting view logic to separate module
rphmeier Jun 2, 2022
759e1d1
create deeper submodule and add per-peer knowledge
rphmeier Jun 2, 2022
51ca0ef
add ProspectiveParachainsMessage to statement-distribution
rphmeier Jun 2, 2022
778627b
make recv_runtime public
rphmeier Jun 2, 2022
df6f7c0
add ChainApiMessage to outgoing of statement-dist
rphmeier Jun 2, 2022
9af0013
begin new handle_active_leaves_update
rphmeier Jun 2, 2022
ce6e3d8
instantiate relay-parent-info without prospective data
rphmeier Jun 2, 2022
5bdb0ec
add staging-network feature to protocol
rphmeier Jun 5, 2022
8c9158a
rename to network-protocol staging
rphmeier Jun 5, 2022
dc6ee0d
refactor view to better accomodate both protcools
rphmeier Jun 5, 2022
a45cb24
begin fleshing out with_prospective
rphmeier Jun 5, 2022
d5e8e88
extract some tests to without_prospective; comment the rest
rphmeier Jun 5, 2022
3a095e6
begin high-level View API
rphmeier Jun 5, 2022
406d87d
fmt
rphmeier Jun 5, 2022
5c8e048
Merge branch 'rh-async-backing-feature' into rh-async-backing-integra…
slumber Jun 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add tests for implicit view
  • Loading branch information
slumber committed May 31, 2022
commit d80c190722af8a3f18e65e99f0928dd76d62807d
320 changes: 308 additions & 12 deletions node/subsystem-util/src/backing_implicit_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ pub struct View {
}

// Minimum relay parents implicitly relative to a particular block.
#[derive(Clone)]
#[derive(Debug, Clone)]
struct AllowedRelayParents {
// minimum relay parents can only be fetched for active leaves,
// so this will be empty for all blocks that haven't ever been
// witnessed as active leaves.
minimum_relay_parents: HashMap<ParaId, BlockNumber>,
// Ancestry, in descending order, starting from the block hash itself down
// to and including the minimum of `minimum_relay_parentes`.
// to and including the minimum of `minimum_relay_parents`.
allowed_relay_parents_contiguous: Vec<Hash>,
}

Expand Down Expand Up @@ -76,14 +76,14 @@ impl AllowedRelayParents {
}
}

#[derive(Clone)]
#[derive(Debug, Clone)]
struct ActiveLeafPruningInfo {
// The mimimum block in the same branch of the relay-chain that should be
// The minimum block in the same branch of the relay-chain that should be
// preserved.
retain_minimum: BlockNumber,
}

#[derive(Clone)]
#[derive(Debug, Clone)]
struct BlockInfo {
block_number: BlockNumber,
// If this was previously an active leaf, this will be `Some`
Expand Down Expand Up @@ -135,7 +135,7 @@ impl View {

match res {
Ok(fetched) => {
let retain_minimum = std::cmp::max(
let retain_minimum = std::cmp::min(
fetched.minimum_ancestor_number,
fetched.leaf_number.saturating_sub(MINIMUM_RETAIN_LENGTH),
);
Expand Down Expand Up @@ -163,7 +163,7 @@ impl View {
let minimum = self.leaves.values().map(|l| l.retain_minimum).min();

self.block_info_storage
.retain(|_, i| minimum.map_or(false, |m| i.block_number < m));
.retain(|_, i| minimum.map_or(false, |m| i.block_number >= m));
}
}

Expand Down Expand Up @@ -207,7 +207,7 @@ impl View {
pub enum FetchError {
/// Leaf was already known.
AlreadyKnown,
/// The prospective parachains subsystem was uavailable.
/// The prospective parachains subsystem was unavailable.
ProspectiveParachainsUnavailable,
/// A block header was unavailable.
BlockHeaderUnavailable(Hash, BlockHeaderUnavailableReason),
Expand Down Expand Up @@ -371,12 +371,308 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::TimeoutExt;
use assert_matches::assert_matches;
use futures::future::{join, FutureExt};
use polkadot_node_subsystem::AllMessages;
use polkadot_node_subsystem_test_helpers::{
make_subsystem_context, TestSubsystemContextHandle,
};
use polkadot_overseer::SubsystemContext;
use polkadot_primitives::v2::Header;
use sp_core::testing::TaskExecutor;
use std::time::Duration;

const PARA_A: ParaId = ParaId::new(0);
const PARA_B: ParaId = ParaId::new(1);
const PARA_C: ParaId = ParaId::new(2);

const GENESIS_HASH: Hash = Hash::repeat_byte(0xFF);
const GENESIS_NUMBER: BlockNumber = 0;

// Chains A and B are forks of genesis.

const CHAIN_A: &[Hash] =
&[Hash::repeat_byte(0x01), Hash::repeat_byte(0x02), Hash::repeat_byte(0x03)];

const CHAIN_B: &[Hash] = &[
Hash::repeat_byte(0x04),
Hash::repeat_byte(0x05),
Hash::repeat_byte(0x06),
Hash::repeat_byte(0x07),
Hash::repeat_byte(0x08),
Hash::repeat_byte(0x09),
];

type VirtualOverseer = TestSubsystemContextHandle<AllMessages>;

const TIMEOUT: Duration = Duration::from_secs(2);

async fn overseer_recv(virtual_overseer: &mut VirtualOverseer) -> AllMessages {
virtual_overseer
.recv()
.timeout(TIMEOUT)
.await
.expect("overseer `recv` timed out")
}

// TODO [now]: test update into fresh view, and that it constructs `AllowedRelayParents` correctly
fn default_header() -> Header {
Header {
parent_hash: Hash::zero(),
number: 0,
state_root: Hash::zero(),
extrinsics_root: Hash::zero(),
digest: Default::default(),
}
}

// TODO [now]: test update that reuses some existing block info
fn get_block_header(chain: &[Hash], hash: &Hash) -> Option<Header> {
let idx = chain.iter().position(|h| h == hash)?;
let parent_hash = idx.checked_sub(1).map(|i| chain[i]).unwrap_or(GENESIS_HASH);
let number =
if *hash == GENESIS_HASH { GENESIS_NUMBER } else { GENESIS_NUMBER + idx as u32 + 1 };
Some(Header { parent_hash, number, ..default_header() })
}

// TODO [now]: test pruning
async fn assert_block_header_requests(
virtual_overseer: &mut VirtualOverseer,
chain: &[Hash],
blocks: &[Hash],
) {
for block in blocks.iter().rev() {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ChainApi(
ChainApiMessage::BlockHeader(hash, tx)
) => {
assert_eq!(*block, hash, "unexpected block header request");
let header = if block == &GENESIS_HASH {
Header {
number: GENESIS_NUMBER,
..default_header()
}
} else {
get_block_header(chain, block).expect("unknown block")
};

tx.send(Ok(Some(header))).unwrap();
}
);
}
}

async fn assert_min_relay_parents_request(
virtual_overseer: &mut VirtualOverseer,
leaf: &Hash,
response: Vec<(ParaId, u32)>,
) {
assert_matches!(
overseer_recv(virtual_overseer).await,
AllMessages::ProspectiveParachains(
ProspectiveParachainsMessage::GetMinimumRelayParents(
leaf_hash,
tx
)
) => {
assert_eq!(*leaf, leaf_hash, "received unexpected leaf hash");
tx.send(response).unwrap();
}
);
}

// TODO [now]: test that former leaves still have `AllowedRelayParents`
#[test]
fn construct_fresh_view() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);

let mut view = View::default();

// Chain B.
const PARA_A_MIN_PARENT: u32 = 4;
const PARA_B_MIN_PARENT: u32 = 3;

let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT), (PARA_B, PARA_B_MIN_PARENT)];

let leaf = CHAIN_B.last().unwrap();
let min_min_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize;

let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_A, PARA_B]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await;
assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[min_min_idx..]).await;
};
futures::executor::block_on(join(fut, overseer_fut));

for i in min_min_idx..(CHAIN_B.len() - 1) {
// No allowed relay parents constructed for ancestry.
assert!(view.known_allowed_relay_parents_under(&CHAIN_B[i], None).is_none());
}

let leaf_info =
view.block_info_storage.get(leaf).expect("block must be present in storage");
assert_matches!(
leaf_info.maybe_allowed_relay_parents,
Some(ref allowed_relay_parents) => {
assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT);
assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_B], PARA_B_MIN_PARENT);
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_min_idx..].iter().rev().copied().collect();
assert_eq!(
allowed_relay_parents.allowed_relay_parents_contiguous,
expected_ancestry
);
}
);

// Suppose the whole test chain A is allowed up to genesis for para C.
const PARA_C_MIN_PARENT: u32 = 0;
let prospective_response = vec![(PARA_C, PARA_C_MIN_PARENT)];
let leaf = CHAIN_A.last().unwrap();
let blocks = [&[GENESIS_HASH], CHAIN_A].concat();

let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_C]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await;
assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks).await;
};
futures::executor::block_on(join(fut, overseer_fut));

assert_eq!(view.leaves.len(), 2);
}

#[test]
fn reuse_block_info_storage() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);

let mut view = View::default();

const PARA_A_MIN_PARENT: u32 = 1;
let leaf_a_number = 3;
let leaf_a = CHAIN_B[leaf_a_number - 1];
let min_min_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize;

let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)];

let fut = view.activate_leaf(ctx.sender(), leaf_a).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_A]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await;
assert_block_header_requests(
&mut ctx_handle,
CHAIN_B,
&CHAIN_B[min_min_idx..leaf_a_number],
)
.await;
};
futures::executor::block_on(join(fut, overseer_fut));

// Blocks up to the 3rd are present in storage.
const PARA_B_MIN_PARENT: u32 = 2;
let leaf_b_number = 5;
let leaf_b = CHAIN_B[leaf_b_number - 1];

let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)];

let fut = view.activate_leaf(ctx.sender(), leaf_b).timeout(TIMEOUT).map(|res| {
let paras = res.expect("`activate_leaf` timed out").unwrap();
assert_eq!(paras, vec![PARA_B]);
});
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await;
assert_block_header_requests(
&mut ctx_handle,
CHAIN_B,
&CHAIN_B[leaf_a_number..leaf_b_number], // Note the expected range.
)
.await;
};
futures::executor::block_on(join(fut, overseer_fut));

// Allowed relay parents for leaf A are preserved.
let leaf_a_info =
view.block_info_storage.get(&leaf_a).expect("block must be present in storage");
assert_matches!(
leaf_a_info.maybe_allowed_relay_parents,
Some(ref allowed_relay_parents) => {
assert_eq!(allowed_relay_parents.minimum_relay_parents[&PARA_A], PARA_A_MIN_PARENT);
let expected_ancestry: Vec<Hash> =
CHAIN_B[min_min_idx..leaf_a_number].iter().rev().copied().collect();
let ancestry = view.known_allowed_relay_parents_under(&leaf_a, Some(PARA_A)).unwrap().to_vec();
assert_eq!(ancestry, expected_ancestry);
}
);
}

#[test]
fn pruning() {
let pool = TaskExecutor::new();
let (mut ctx, mut ctx_handle) = make_subsystem_context::<AllMessages, _>(pool);

let mut view = View::default();

const PARA_A_MIN_PARENT: u32 = 3;
let leaf_a = CHAIN_B.iter().rev().nth(1).unwrap();
let leaf_a_idx = CHAIN_B.len() - 2;
let min_a_idx = (PARA_A_MIN_PARENT - GENESIS_NUMBER - 1) as usize;

let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)];

let fut = view
.activate_leaf(ctx.sender(), *leaf_a)
.timeout(TIMEOUT)
.map(|res| res.unwrap().unwrap());
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await;
assert_block_header_requests(
&mut ctx_handle,
CHAIN_B,
&CHAIN_B[min_a_idx..=leaf_a_idx],
)
.await;
};
futures::executor::block_on(join(fut, overseer_fut));

// Also activate a leaf with a lesser minimum relay parent.
const PARA_B_MIN_PARENT: u32 = 2;
let leaf_b = CHAIN_B.last().unwrap();
let min_b_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize;

let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)];
// Headers will be requested for the minimum block and the leaf.
let blocks = &[CHAIN_B[min_b_idx], *leaf_b];

let fut = view
.activate_leaf(ctx.sender(), *leaf_b)
.timeout(TIMEOUT)
.map(|res| res.expect("`activate_leaf` timed out").unwrap());
let overseer_fut = async {
assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await;
assert_block_header_requests(&mut ctx_handle, CHAIN_B, blocks).await;
};
futures::executor::block_on(join(fut, overseer_fut));

// Prune implicit ancestor (no-op).
let block_info_len = view.block_info_storage.len();
view.deactivate_leaf(CHAIN_B[leaf_a_idx - 1]);
assert_eq!(block_info_len, view.block_info_storage.len());

// Prune a leaf with a greater minimum relay parent.
view.deactivate_leaf(*leaf_b);
for hash in CHAIN_B.iter().take(PARA_B_MIN_PARENT as usize) {
assert!(!view.block_info_storage.contains_key(hash));
}

// Prune the last leaf.
view.deactivate_leaf(*leaf_a);
assert!(view.block_info_storage.is_empty());
}
}