Approval Voting improvements (#2781)

* extract database from av-store itself

* generalize approval-voting over database type

* modes (without handling) and pruning old wakeups

* rework approval importing

* add our_approval_sig to ApprovalEntry

* import assignment

* guide updates for check-full-approval changes

* some aux functions

* send messages when becoming active.

* guide: network bridge sends view updates only when done syncing

* network bridge: send view updates only when done syncing

* tests for new network-bridge behavior

* add a test for updating approval entry with sig

* fix some warnings

* test load-all-blocks

* instantiate new parachains DB

* fix network-bridge empty view updates

* tweak

* fix wasm build, i think

* Update node/core/approval-voting/src/lib.rs

Co-authored-by: Andronik Ordian <write@reusable.software>

* add some versioning to parachains_db

* warnings

* fix merge changes

* remove versioning again

Co-authored-by: Andronik Ordian <write@reusable.software>
This commit is contained in:
Robert Habermeier
2021-04-01 19:33:52 +02:00
committed by GitHub
parent 01badafba6
commit 57b56770e0
20 changed files with 1593 additions and 701 deletions
+5 -3
View File
@@ -5650,6 +5650,7 @@ dependencies = [
"assert_matches",
"async-trait",
"futures 0.3.13",
"futures-timer 3.0.2",
"parity-scale-codec",
"parking_lot 0.11.1",
"polkadot-node-network-protocol",
@@ -5659,6 +5660,7 @@ dependencies = [
"polkadot-primitives",
"sc-authority-discovery",
"sc-network",
"sp-consensus",
"sp-core",
"sp-keyring",
"strum",
@@ -5692,7 +5694,6 @@ dependencies = [
"futures-timer 3.0.2",
"kvdb",
"kvdb-memorydb",
"kvdb-rocksdb",
"maplit",
"merlin",
"parity-scale-codec",
@@ -5710,6 +5711,7 @@ dependencies = [
"schnorrkel",
"sp-application-crypto",
"sp-blockchain",
"sp-consensus",
"sp-consensus-babe",
"sp-consensus-slots",
"sp-core",
@@ -5730,7 +5732,6 @@ dependencies = [
"futures-timer 3.0.2",
"kvdb",
"kvdb-memorydb",
"kvdb-rocksdb",
"log",
"parity-scale-codec",
"parking_lot 0.11.1",
@@ -5741,7 +5742,6 @@ dependencies = [
"polkadot-node-subsystem-util",
"polkadot-overseer",
"polkadot-primitives",
"sc-service",
"sp-core",
"sp-keyring",
"thiserror",
@@ -6345,6 +6345,8 @@ dependencies = [
"futures 0.3.13",
"hex-literal",
"kusama-runtime",
"kvdb",
"kvdb-rocksdb",
"pallet-babe",
"pallet-im-online",
"pallet-mmr-primitives",
@@ -13,7 +13,6 @@ bitvec = { version = "0.20.1", default-features = false, features = ["alloc"] }
merlin = "2.0"
schnorrkel = "0.9.1"
kvdb = "0.9.0"
kvdb-rocksdb = "0.11.0"
derive_more = "0.99.1"
polkadot-node-subsystem = { path = "../../subsystem" }
@@ -25,6 +24,7 @@ polkadot-node-jaeger = { path = "../../jaeger" }
sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sc-keystore = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, features = ["full_crypto"] }
@@ -406,6 +406,7 @@ mod tests {
tranches: Vec::new(),
assignments: Default::default(),
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -452,6 +453,7 @@ mod tests {
],
assignments: bitvec![BitOrderLsb0, u8; 1; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -515,6 +517,7 @@ mod tests {
],
assignments: bitvec![BitOrderLsb0, u8; 1; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -582,6 +585,7 @@ mod tests {
tranches: Vec::new(),
assignments: bitvec![BitOrderLsb0, u8; 0; 5],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -619,6 +623,7 @@ mod tests {
tranches: Vec::new(),
assignments: bitvec![BitOrderLsb0, u8; 0; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -657,6 +662,7 @@ mod tests {
tranches: Vec::new(),
assignments: bitvec![BitOrderLsb0, u8; 0; 10],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -700,6 +706,7 @@ mod tests {
tranches: Vec::new(),
assignments: bitvec![BitOrderLsb0, u8; 0; n_validators],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -765,6 +772,7 @@ mod tests {
tranches: Vec::new(),
assignments: bitvec![BitOrderLsb0, u8; 0; n_validators],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -852,6 +860,7 @@ mod tests {
tranches: Vec::new(),
assignments: bitvec![BitOrderLsb0, u8; 0; n_validators],
our_assignment: None,
our_approval_sig: None,
backing_group: GroupIndex(0),
approved: false,
}.into();
@@ -20,14 +20,13 @@ use kvdb::{DBTransaction, KeyValueDB};
use polkadot_node_primitives::approval::{DelayTranche, AssignmentCert};
use polkadot_primitives::v1::{
ValidatorIndex, GroupIndex, CandidateReceipt, SessionIndex, CoreIndex,
BlockNumber, Hash, CandidateHash,
BlockNumber, Hash, CandidateHash, ValidatorSignature,
};
use sp_consensus_slots::Slot;
use parity_scale_codec::{Encode, Decode};
use std::collections::{BTreeMap, HashMap};
use std::collections::hash_map::Entry;
use std::sync::Arc;
use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0};
#[cfg(test)]
@@ -40,11 +39,15 @@ pub struct Tick(u64);
pub type Bitfield = BitVec<BitOrderLsb0, u8>;
const NUM_COLUMNS: u32 = 1;
const DATA_COL: u32 = 0;
const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks";
/// The database config.
#[derive(Debug, Clone, Copy)]
pub struct Config {
/// The column family in the database where data is stored.
pub col_data: u32,
}
/// Details pertaining to our assignment on a block.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct OurAssignment {
@@ -71,6 +74,7 @@ pub struct ApprovalEntry {
pub tranches: Vec<TrancheEntry>,
pub backing_group: GroupIndex,
pub our_assignment: Option<OurAssignment>,
pub our_approval_sig: Option<ValidatorSignature>,
// `n_validators` bits.
pub assignments: Bitfield,
pub approved: bool,
@@ -109,33 +113,6 @@ pub struct BlockEntry {
pub children: Vec<Hash>,
}
/// Clear the given directory and create a RocksDB instance there.
pub fn clear_and_recreate(path: &std::path::Path, cache_size: usize)
-> std::io::Result<Arc<dyn KeyValueDB>>
{
use kvdb_rocksdb::{DatabaseConfig, Database as RocksDB};
tracing::info!("Recreating approval-checking DB at {:?}", path);
if let Err(e) = std::fs::remove_dir_all(path) {
if e.kind() != std::io::ErrorKind::NotFound {
return Err(e);
}
}
std::fs::create_dir_all(path)?;
let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS);
db_config.memory_budget.insert(DATA_COL, cache_size);
let path = path.to_str().ok_or_else(|| std::io::Error::new(
std::io::ErrorKind::Other,
format!("Non-UTF-8 database path {:?}", path),
))?;
Ok(Arc::new(RocksDB::open(&db_config, path)?))
}
/// A range from earliest..last block number stored within the DB.
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
pub struct StoredBlockRange(BlockNumber, BlockNumber);
@@ -168,12 +145,13 @@ pub type Result<T> = std::result::Result<T, Error>;
/// pruning any competing branches at the same height.
pub(crate) fn canonicalize(
store: &dyn KeyValueDB,
config: &Config,
canon_number: BlockNumber,
canon_hash: Hash,
)
-> Result<()>
{
let range = match load_stored_blocks(store)? {
let range = match load_stored_blocks(store, config)? {
None => return Ok(()),
Some(range) => if range.0 >= canon_number {
return Ok(())
@@ -197,17 +175,17 @@ pub(crate) fn canonicalize(
transaction: &mut DBTransaction,
visited_candidates: &mut HashMap<CandidateHash, CandidateEntry>,
| -> Result<Vec<Hash>> {
let block_entry = match load_block_entry(store, &block_hash)? {
let block_entry = match load_block_entry(store, config, &block_hash)? {
None => return Ok(Vec::new()),
Some(b) => b,
};
transaction.delete(DATA_COL, &block_entry_key(&block_hash)[..]);
transaction.delete(config.col_data, &block_entry_key(&block_hash)[..]);
for &(_, ref candidate_hash) in &block_entry.candidates {
let candidate = match visited_candidates.entry(*candidate_hash) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
e.insert(match load_candidate_entry(store, candidate_hash)? {
e.insert(match load_candidate_entry(store, config, candidate_hash)? {
None => continue, // Should not happen except for corrupt DB
Some(c) => c,
})
@@ -222,8 +200,8 @@ pub(crate) fn canonicalize(
// First visit everything before the height.
for i in range.0..canon_number {
let at_height = load_blocks_at_height(store, i)?;
transaction.delete(DATA_COL, &blocks_at_height_key(i)[..]);
let at_height = load_blocks_at_height(store, config, i)?;
transaction.delete(config.col_data, &blocks_at_height_key(i)[..]);
for b in at_height {
let _ = visit_and_remove_block_entry(
@@ -236,8 +214,8 @@ pub(crate) fn canonicalize(
// Then visit everything at the height.
let pruned_branches = {
let at_height = load_blocks_at_height(store, canon_number)?;
transaction.delete(DATA_COL, &blocks_at_height_key(canon_number));
let at_height = load_blocks_at_height(store, config, canon_number)?;
transaction.delete(config.col_data, &blocks_at_height_key(canon_number));
// Note that while there may be branches descending from blocks at earlier heights,
// we have already covered them by removing everything at earlier heights.
@@ -274,7 +252,7 @@ pub(crate) fn canonicalize(
// visit the at-height key for this deleted block's height.
let at_height = match visited_heights.entry(height) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => e.insert(load_blocks_at_height(store, height)?),
Entry::Vacant(e) => e.insert(load_blocks_at_height(store, config, height)?),
};
if let Some(i) = at_height.iter().position(|x| x == &next_child) {
@@ -286,10 +264,10 @@ pub(crate) fn canonicalize(
// Update all `CandidateEntry`s, deleting all those which now have empty `block_assignments`.
for (candidate_hash, candidate) in visited_candidates {
if candidate.block_assignments.is_empty() {
transaction.delete(DATA_COL, &candidate_entry_key(&candidate_hash)[..]);
transaction.delete(config.col_data, &candidate_entry_key(&candidate_hash)[..]);
} else {
transaction.put_vec(
DATA_COL,
config.col_data,
&candidate_entry_key(&candidate_hash)[..],
candidate.encode(),
);
@@ -299,9 +277,9 @@ pub(crate) fn canonicalize(
// Update all blocks-at-height keys, deleting all those which now have empty `block_assignments`.
for (h, at) in visited_heights {
if at.is_empty() {
transaction.delete(DATA_COL, &blocks_at_height_key(h)[..]);
transaction.delete(config.col_data, &blocks_at_height_key(h)[..]);
} else {
transaction.put_vec(DATA_COL, &blocks_at_height_key(h), at.encode());
transaction.put_vec(config.col_data, &blocks_at_height_key(h), at.encode());
}
}
@@ -312,16 +290,16 @@ pub(crate) fn canonicalize(
std::cmp::max(range.1, canon_number + 2),
).encode();
transaction.put_vec(DATA_COL, &STORED_BLOCKS_KEY[..], new_range);
transaction.put_vec(config.col_data, &STORED_BLOCKS_KEY[..], new_range);
// Update the values on-disk.
store.write(transaction).map_err(Into::into)
}
fn load_decode<D: Decode>(store: &dyn KeyValueDB, key: &[u8])
fn load_decode<D: Decode>(store: &dyn KeyValueDB, col_data: u32, key: &[u8])
-> Result<Option<D>>
{
match store.get(DATA_COL, key)? {
match store.get(col_data, key)? {
None => Ok(None),
Some(raw) => D::decode(&mut &raw[..])
.map(Some)
@@ -350,6 +328,7 @@ 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,
config: &Config,
entry: BlockEntry,
n_validators: usize,
candidate_info: impl Fn(&CandidateHash) -> Option<NewCandidateInfo>,
@@ -361,7 +340,7 @@ pub(crate) fn add_block_entry(
// Update the stored block range.
{
let new_range = match load_stored_blocks(store)? {
let new_range = match load_stored_blocks(store, config)? {
None => Some(StoredBlockRange(number, number + 1)),
Some(range) => if range.1 <= number {
Some(StoredBlockRange(range.0, number + 1))
@@ -370,19 +349,19 @@ pub(crate) fn add_block_entry(
}
};
new_range.map(|n| transaction.put_vec(DATA_COL, &STORED_BLOCKS_KEY[..], n.encode()))
new_range.map(|n| transaction.put_vec(config.col_data, &STORED_BLOCKS_KEY[..], n.encode()))
};
// Update the blocks at height meta key.
{
let mut blocks_at_height = load_blocks_at_height(store, number)?;
let mut blocks_at_height = load_blocks_at_height(store, config, number)?;
if blocks_at_height.contains(&entry.block_hash) {
// seems we already have a block entry for this block. nothing to do here.
return Ok(Vec::new())
}
blocks_at_height.push(entry.block_hash);
transaction.put_vec(DATA_COL, &blocks_at_height_key(number)[..], blocks_at_height.encode())
transaction.put_vec(config.col_data, &blocks_at_height_key(number)[..], blocks_at_height.encode())
};
let mut candidate_entries = Vec::with_capacity(entry.candidates.len());
@@ -399,7 +378,7 @@ pub(crate) fn add_block_entry(
Some(info) => info,
};
let mut candidate_entry = load_candidate_entry(store, &candidate_hash)?
let mut candidate_entry = load_candidate_entry(store, config, &candidate_hash)?
.unwrap_or_else(move || CandidateEntry {
candidate,
session,
@@ -413,13 +392,14 @@ pub(crate) fn add_block_entry(
tranches: Vec::new(),
backing_group,
our_assignment,
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
approved: false,
}
);
transaction.put_vec(
DATA_COL,
config.col_data,
&candidate_entry_key(&candidate_hash)[..],
candidate_entry.encode(),
);
@@ -429,13 +409,13 @@ pub(crate) fn add_block_entry(
};
// Update the child index for the parent.
load_block_entry(store, &parent_hash)?.map(|mut e| {
load_block_entry(store, config, &parent_hash)?.map(|mut e| {
e.children.push(entry.block_hash);
transaction.put_vec(DATA_COL, &block_entry_key(&parent_hash)[..], e.encode())
transaction.put_vec(config.col_data, &block_entry_key(&parent_hash)[..], e.encode())
});
// Put the new block entry in.
transaction.put_vec(DATA_COL, &block_entry_key(&entry.block_hash)[..], entry.encode());
transaction.put_vec(config.col_data, &block_entry_key(&entry.block_hash)[..], entry.encode());
store.write(transaction)?;
Ok(candidate_entries)
@@ -445,6 +425,7 @@ pub(crate) fn add_block_entry(
/// chain.
pub fn force_approve(
store: &dyn KeyValueDB,
db_config: Config,
chain_head: Hash,
up_to: BlockNumber,
) -> Result<()> {
@@ -456,11 +437,11 @@ pub fn force_approve(
let mut cur_hash = chain_head;
let mut state = State::WalkTo;
let mut tx = Transaction::default();
let mut tx = Transaction::new(db_config);
// 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)? {
while let Some(mut entry) = load_block_entry(store, &db_config, &cur_hash)? {
if entry.block_number <= up_to {
state = State::Approving;
@@ -480,15 +461,35 @@ pub fn force_approve(
tx.write(store)
}
/// Return all blocks which have entries in the DB, ascending, by height.
pub(crate) fn load_all_blocks(store: &dyn KeyValueDB, config: &Config) -> Result<Vec<Hash>> {
let stored_blocks = load_stored_blocks(store, config)?;
let mut hashes = Vec::new();
for height in stored_blocks.into_iter().flat_map(|s| s.0..s.1) {
hashes.extend(load_blocks_at_height(store, config, height)?);
}
Ok(hashes)
}
// An atomic transaction of multiple candidate or block entries.
#[derive(Default)]
#[must_use = "Transactions do nothing unless written to a DB"]
pub struct Transaction {
block_entries: HashMap<Hash, BlockEntry>,
candidate_entries: HashMap<CandidateHash, CandidateEntry>,
config: Config,
}
impl Transaction {
pub(crate) fn new(config: Config) -> Self {
Transaction {
block_entries: HashMap::default(),
candidate_entries: HashMap::default(),
config,
}
}
/// Put a block entry in the transaction, overwriting any other with the
/// same hash.
pub(crate) fn put_block_entry(&mut self, entry: BlockEntry) {
@@ -519,14 +520,14 @@ impl Transaction {
let k = block_entry_key(&hash);
let v = entry.encode();
db_transaction.put_vec(DATA_COL, &k, v);
db_transaction.put_vec(self.config.col_data, &k, v);
}
for (hash, entry) in self.candidate_entries {
let k = candidate_entry_key(&hash);
let v = entry.encode();
db_transaction.put_vec(DATA_COL, &k, v);
db_transaction.put_vec(self.config.col_data, &k, v);
}
db.write(db_transaction).map_err(Into::into)
@@ -534,31 +535,39 @@ impl Transaction {
}
/// Load the stored-blocks key from the state.
fn load_stored_blocks(store: &dyn KeyValueDB)
fn load_stored_blocks(store: &dyn KeyValueDB, config: &Config)
-> Result<Option<StoredBlockRange>>
{
load_decode(store, STORED_BLOCKS_KEY)
load_decode(store, config.col_data, STORED_BLOCKS_KEY)
}
/// Load a blocks-at-height entry for a given block number.
pub(crate) fn load_blocks_at_height(store: &dyn KeyValueDB, block_number: BlockNumber)
pub(crate) fn load_blocks_at_height(
store: &dyn KeyValueDB,
config: &Config,
block_number: BlockNumber,
)
-> Result<Vec<Hash>> {
load_decode(store, &blocks_at_height_key(block_number))
load_decode(store, config.col_data, &blocks_at_height_key(block_number))
.map(|x| x.unwrap_or_default())
}
/// Load a block entry from the aux store.
pub(crate) fn load_block_entry(store: &dyn KeyValueDB, block_hash: &Hash)
pub(crate) fn load_block_entry(store: &dyn KeyValueDB, config: &Config, block_hash: &Hash)
-> Result<Option<BlockEntry>>
{
load_decode(store, &block_entry_key(block_hash))
load_decode(store, config.col_data, &block_entry_key(block_hash))
}
/// Load a candidate entry from the aux store.
pub(crate) fn load_candidate_entry(store: &dyn KeyValueDB, candidate_hash: &CandidateHash)
pub(crate) fn load_candidate_entry(
store: &dyn KeyValueDB,
config: &Config,
candidate_hash: &CandidateHash,
)
-> Result<Option<CandidateEntry>>
{
load_decode(store, &candidate_entry_key(candidate_hash))
load_decode(store, config.col_data, &candidate_entry_key(candidate_hash))
}
/// The key a given block entry is stored under.
@@ -19,6 +19,13 @@
use super::*;
use polkadot_primitives::v1::Id as ParaId;
const DATA_COL: u32 = 0;
const NUM_COLUMNS: u32 = 1;
const TEST_CONFIG: Config = Config {
col_data: DATA_COL,
};
pub(crate) fn write_stored_blocks(tx: &mut DBTransaction, range: StoredBlockRange) {
tx.put_vec(
DATA_COL,
@@ -85,7 +92,7 @@ fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt {
#[test]
fn read_write() {
let store = kvdb_memorydb::create(1);
let store = kvdb_memorydb::create(NUM_COLUMNS);
let hash_a = Hash::repeat_byte(1);
let hash_b = Hash::repeat_byte(2);
@@ -109,6 +116,7 @@ fn read_write() {
tranches: Vec::new(),
backing_group: GroupIndex(1),
our_assignment: None,
our_approval_sig: None,
assignments: Default::default(),
approved: false,
})
@@ -125,10 +133,13 @@ fn read_write() {
store.write(tx).unwrap();
assert_eq!(load_stored_blocks(&store).unwrap(), Some(range));
assert_eq!(load_blocks_at_height(&store, 1).unwrap(), at_height);
assert_eq!(load_block_entry(&store, &hash_a).unwrap(), Some(block_entry));
assert_eq!(load_candidate_entry(&store, &candidate_hash).unwrap(), Some(candidate_entry));
assert_eq!(load_stored_blocks(&store, &TEST_CONFIG).unwrap(), Some(range));
assert_eq!(load_blocks_at_height(&store, &TEST_CONFIG, 1).unwrap(), at_height);
assert_eq!(load_block_entry(&store, &TEST_CONFIG, &hash_a).unwrap(), Some(block_entry));
assert_eq!(
load_candidate_entry(&store, &TEST_CONFIG, &candidate_hash).unwrap(),
Some(candidate_entry),
);
let delete_keys = vec![
STORED_BLOCKS_KEY.to_vec(),
@@ -144,15 +155,15 @@ fn read_write() {
store.write(tx).unwrap();
assert!(load_stored_blocks(&store).unwrap().is_none());
assert!(load_blocks_at_height(&store, 1).unwrap().is_empty());
assert!(load_block_entry(&store, &hash_a).unwrap().is_none());
assert!(load_candidate_entry(&store, &candidate_hash).unwrap().is_none());
assert!(load_stored_blocks(&store, &TEST_CONFIG).unwrap().is_none());
assert!(load_blocks_at_height(&store, &TEST_CONFIG, 1).unwrap().is_empty());
assert!(load_block_entry(&store, &TEST_CONFIG, &hash_a).unwrap().is_none());
assert!(load_candidate_entry(&store, &TEST_CONFIG, &candidate_hash).unwrap().is_none());
}
#[test]
fn add_block_entry_works() {
let store = kvdb_memorydb::create(1);
let store = kvdb_memorydb::create(NUM_COLUMNS);
let parent_hash = Hash::repeat_byte(1);
let block_hash_a = Hash::repeat_byte(2);
@@ -188,6 +199,7 @@ fn add_block_entry_works() {
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_a.clone(),
n_validators,
|h| new_candidate_info.get(h).map(|x| x.clone()),
@@ -201,24 +213,27 @@ fn add_block_entry_works() {
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_b.clone(),
n_validators,
|h| new_candidate_info.get(h).map(|x| x.clone()),
).unwrap();
assert_eq!(load_block_entry(&store, &block_hash_a).unwrap(), Some(block_entry_a));
assert_eq!(load_block_entry(&store, &block_hash_b).unwrap(), Some(block_entry_b));
assert_eq!(load_block_entry(&store, &TEST_CONFIG, &block_hash_a).unwrap(), Some(block_entry_a));
assert_eq!(load_block_entry(&store, &TEST_CONFIG, &block_hash_b).unwrap(), Some(block_entry_b));
let candidate_entry_a = load_candidate_entry(&store, &candidate_hash_a).unwrap().unwrap();
let candidate_entry_a = load_candidate_entry(&store, &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, &candidate_hash_b).unwrap().unwrap();
let candidate_entry_b = load_candidate_entry(&store, &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 store = kvdb_memorydb::create(1);
let store = kvdb_memorydb::create(NUM_COLUMNS);
let parent_hash = Hash::repeat_byte(1);
let block_hash_a = Hash::repeat_byte(2);
@@ -242,6 +257,7 @@ fn add_block_entry_adds_child() {
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_a.clone(),
n_validators,
|_| None,
@@ -249,6 +265,7 @@ fn add_block_entry_adds_child() {
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_b.clone(),
n_validators,
|_| None,
@@ -256,13 +273,13 @@ fn add_block_entry_adds_child() {
block_entry_a.children.push(block_hash_b);
assert_eq!(load_block_entry(&store, &block_hash_a).unwrap(), Some(block_entry_a));
assert_eq!(load_block_entry(&store, &block_hash_b).unwrap(), Some(block_entry_b));
assert_eq!(load_block_entry(&store, &TEST_CONFIG, &block_hash_a).unwrap(), Some(block_entry_a));
assert_eq!(load_block_entry(&store, &TEST_CONFIG, &block_hash_b).unwrap(), Some(block_entry_b));
}
#[test]
fn canonicalize_works() {
let store = kvdb_memorydb::create(1);
let store = kvdb_memorydb::create(NUM_COLUMNS);
// -> B1 -> C1 -> D1
// A -> B2 -> C2 -> D2
@@ -377,6 +394,7 @@ fn canonicalize_works() {
for block_entry in blocks {
add_block_entry(
&store,
&TEST_CONFIG,
block_entry,
n_validators,
|h| candidate_info.get(h).map(|x| x.clone()),
@@ -387,11 +405,11 @@ fn canonicalize_works() {
for (c_hash, in_blocks) in expected {
let (entry, in_blocks) = match in_blocks {
None => {
assert!(load_candidate_entry(&store, &c_hash).unwrap().is_none());
assert!(load_candidate_entry(&store, &TEST_CONFIG, &c_hash).unwrap().is_none());
continue
}
Some(i) => (
load_candidate_entry(&store, &c_hash).unwrap().unwrap(),
load_candidate_entry(&store, &TEST_CONFIG, &c_hash).unwrap().unwrap(),
i,
),
};
@@ -408,11 +426,11 @@ fn canonicalize_works() {
for (hash, with_candidates) in expected {
let (entry, with_candidates) = match with_candidates {
None => {
assert!(load_block_entry(&store, &hash).unwrap().is_none());
assert!(load_block_entry(&store, &TEST_CONFIG, &hash).unwrap().is_none());
continue
}
Some(i) => (
load_block_entry(&store, &hash).unwrap().unwrap(),
load_block_entry(&store, &TEST_CONFIG, &hash).unwrap().unwrap(),
i,
),
};
@@ -443,9 +461,9 @@ fn canonicalize_works() {
(block_hash_d2, Some(vec![cand_hash_5])),
]);
canonicalize(&store, 3, block_hash_c1).unwrap();
canonicalize(&store, &TEST_CONFIG, 3, block_hash_c1).unwrap();
assert_eq!(load_stored_blocks(&store).unwrap().unwrap(), StoredBlockRange(4, 5));
assert_eq!(load_stored_blocks(&store, &TEST_CONFIG).unwrap().unwrap(), StoredBlockRange(4, 5));
check_candidates_in_store(vec![
(cand_hash_1, None),
@@ -468,7 +486,7 @@ fn canonicalize_works() {
#[test]
fn force_approve_works() {
let store = kvdb_memorydb::create(1);
let store = kvdb_memorydb::create(NUM_COLUMNS);
let n_validators = 10;
let mut tx = DBTransaction::new();
@@ -509,16 +527,101 @@ fn force_approve_works() {
for block_entry in blocks {
add_block_entry(
&store,
&TEST_CONFIG,
block_entry,
n_validators,
|h| candidate_info.get(h).map(|x| x.clone()),
).unwrap();
}
force_approve(&store, block_hash_d, 2).unwrap();
force_approve(&store, TEST_CONFIG, 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());
assert!(load_block_entry(
&store,
&TEST_CONFIG,
&block_hash_a,
).unwrap().unwrap().approved_bitfield.all());
assert!(load_block_entry(
&store,
&TEST_CONFIG,
&block_hash_b,
).unwrap().unwrap().approved_bitfield.all());
assert!(load_block_entry(
&store,
&TEST_CONFIG,
&block_hash_c,
).unwrap().unwrap().approved_bitfield.not_any());
assert!(load_block_entry(
&store,
&TEST_CONFIG,
&block_hash_d,
).unwrap().unwrap().approved_bitfield.not_any());
}
#[test]
fn load_all_blocks_works() {
let store = kvdb_memorydb::create(NUM_COLUMNS);
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;
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_a.clone(),
n_validators,
|_| None
).unwrap();
// add C before B to test sorting.
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_c.clone(),
n_validators,
|_| None
).unwrap();
add_block_entry(
&store,
&TEST_CONFIG,
block_entry_b.clone(),
n_validators,
|_| None
).unwrap();
assert_eq!(
load_all_blocks(
&store,
&TEST_CONFIG
).unwrap(),
vec![block_hash_a, block_hash_b, block_hash_c],
)
}
@@ -53,7 +53,7 @@ use bitvec::order::Lsb0 as BitOrderLsb0;
use std::collections::HashMap;
use std::convert::TryFrom;
use crate::approval_db;
use crate::approval_db::{self, v1::Config as DatabaseConfig};
use crate::persisted_entries::CandidateEntry;
use crate::criteria::{AssignmentCriteria, OurAssignment};
use crate::time::{slot_number_to_tick, Tick};
@@ -561,6 +561,7 @@ pub(crate) async fn handle_new_head(
ctx: &mut impl SubsystemContext,
state: &mut State<impl DBReader>,
db_writer: &dyn KeyValueDB,
db_config: DatabaseConfig,
head: Hash,
finalized_number: &Option<BlockNumber>,
) -> SubsystemResult<Vec<BlockImportedCandidates>> {
@@ -737,7 +738,7 @@ pub(crate) async fn handle_new_head(
"Enacting force-approve",
);
approval_db::v1::force_approve(db_writer, block_hash, up_to)
approval_db::v1::force_approve(db_writer, db_config, block_hash, up_to)
.map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
}
@@ -750,6 +751,7 @@ pub(crate) async fn handle_new_head(
let candidate_entries = approval_db::v1::add_block_entry(
db_writer,
&db_config,
block_entry,
n_validators,
|candidate_hash| {
@@ -815,6 +817,13 @@ mod tests {
use crate::{criteria, BlockEntry};
const DATA_COL: u32 = 0;
const NUM_COLUMNS: u32 = 1;
const TEST_CONFIG: DatabaseConfig = DatabaseConfig {
col_data: DATA_COL,
};
#[derive(Default)]
struct TestDB {
block_entries: HashMap<Hash, BlockEntry>,
@@ -835,6 +844,14 @@ mod tests {
) -> SubsystemResult<Option<CandidateEntry>> {
Ok(self.candidate_entries.get(candidate_hash).map(|c| c.clone()))
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
let mut hashes: Vec<_> = self.block_entries.keys().cloned().collect();
hashes.sort_by_key(|k| self.block_entries.get(k).unwrap().block_number());
Ok(hashes)
}
}
#[derive(Default)]
@@ -1876,7 +1893,7 @@ mod tests {
}.into(),
);
let db_writer = kvdb_memorydb::create(1);
let db_writer = kvdb_memorydb::create(NUM_COLUMNS);
let test_fut = {
Box::pin(async move {
@@ -1884,6 +1901,7 @@ mod tests {
&mut ctx,
&mut state,
&db_writer,
TEST_CONFIG,
hash,
&Some(1),
).await.unwrap();
@@ -1895,7 +1913,11 @@ mod tests {
assert_eq!(candidates[1].1.approvals().len(), 6);
// the first candidate should be insta-approved
// the second should not
let entry: BlockEntry = crate::approval_db::v1::load_block_entry(&db_writer, &hash)
let entry: BlockEntry = crate::approval_db::v1::load_block_entry(
&db_writer,
&TEST_CONFIG,
&hash,
)
.unwrap()
.unwrap()
.into();
+430 -166
View File
@@ -43,10 +43,12 @@ use polkadot_primitives::v1::{
use polkadot_node_primitives::{ValidationResult, PoV};
use polkadot_node_primitives::approval::{
IndirectAssignmentCert, IndirectSignedApprovalVote, ApprovalVote, DelayTranche,
BlockApprovalMeta,
};
use polkadot_node_jaeger as jaeger;
use parity_scale_codec::Encode;
use sc_keystore::LocalKeystore;
use sp_consensus::SyncOracle;
use sp_consensus_slots::Slot;
use sp_runtime::traits::AppVerify;
use sp_application_crypto::Pair;
@@ -56,7 +58,7 @@ use futures::prelude::*;
use futures::future::RemoteHandle;
use futures::channel::{mpsc, oneshot};
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::collections::btree_map::Entry;
use std::sync::Arc;
@@ -72,6 +74,8 @@ mod import;
mod time;
mod persisted_entries;
use crate::approval_db::v1::Config as DatabaseConfig;
#[cfg(test)]
mod tests;
@@ -79,25 +83,39 @@ const APPROVAL_SESSIONS: SessionIndex = 6;
const LOG_TARGET: &str = "parachain::approval-voting";
/// Configuration for the approval voting subsystem
#[derive(Debug, Clone)]
pub struct Config {
/// The path where the approval-voting DB should be kept. This directory is completely removed when starting
/// the service.
pub path: std::path::PathBuf,
/// The cache size, in bytes, to spend on approval checking metadata.
pub cache_size: Option<usize>,
/// The column family in the DB where approval-voting data is stored.
pub col_data: u32,
/// The slot duration of the consensus algorithm, in milliseconds. Should be evenly
/// divisible by 500.
pub slot_duration_millis: u64,
}
// The mode of the approval voting subsystem. It should start in a `Syncing` mode when it first
// starts, and then once it's reached the head of the chain it should move into the `Active` mode.
//
// In `Active` mode, the node is an active participant in the approvals protocol. When syncing,
// the node follows the new incoming blocks and finalized number, but does not yet participate.
//
// When transitioning from `Syncing` to `Active`, the node notifies the `ApprovalDistribution`
// subsystem of all unfinalized blocks and the candidates included within them, as well as all
// votes that the local node itself has cast on candidates within those blocks.
enum Mode {
Active,
Syncing(Box<dyn SyncOracle + Send>),
}
/// The approval voting subsystem.
pub struct ApprovalVotingSubsystem {
/// LocalKeystore is needed for assignment keys, but not necessarily approval keys.
///
/// We do a lot of VRF signing and need the keys to have low latency.
keystore: Arc<LocalKeystore>,
db_config: DatabaseConfig,
slot_duration_millis: u64,
db: Arc<dyn KeyValueDB>,
mode: Mode,
metrics: Metrics,
}
@@ -239,27 +257,24 @@ impl metrics::Metrics for Metrics {
}
impl ApprovalVotingSubsystem {
/// Create a new approval voting subsystem with the given keystore and config,
/// which creates a DB at the given path. This function will delete the directory
/// at the given path if it already exists.
/// Create a new approval voting subsystem with the given keystore, config, and database.
pub fn with_config(
config: Config,
db: Arc<dyn KeyValueDB>,
keystore: Arc<LocalKeystore>,
sync_oracle: Box<dyn SyncOracle + Send>,
metrics: Metrics,
) -> std::io::Result<Self> {
const DEFAULT_CACHE_SIZE: usize = 100 * 1024 * 1024; // 100MiB default should be fine unless finality stalls.
let db = approval_db::v1::clear_and_recreate(
&config.path,
config.cache_size.unwrap_or(DEFAULT_CACHE_SIZE),
)?;
Ok(ApprovalVotingSubsystem {
) -> Self {
ApprovalVotingSubsystem {
keystore,
slot_duration_millis: config.slot_duration_millis,
db,
db_config: DatabaseConfig {
col_data: config.col_data,
},
mode: Mode::Syncing(sync_oracle),
metrics,
})
}
}
}
@@ -305,6 +320,7 @@ struct Wakeups {
// Tick -> [(Relay Block, Candidate Hash)]
wakeups: BTreeMap<Tick, Vec<(Hash, CandidateHash)>>,
reverse_wakeups: HashMap<(Hash, CandidateHash), Tick>,
block_numbers: BTreeMap<BlockNumber, HashSet<Hash>>,
}
impl Wakeups {
@@ -313,9 +329,19 @@ impl Wakeups {
self.wakeups.keys().next().map(|t| *t)
}
fn note_block(&mut self, block_hash: Hash, block_number: BlockNumber) {
self.block_numbers.entry(block_number).or_default().insert(block_hash);
}
// Schedules a wakeup at the given tick. no-op if there is already an earlier or equal wake-up
// for these values. replaces any later wakeup.
fn schedule(&mut self, block_hash: Hash, candidate_hash: CandidateHash, tick: Tick) {
fn schedule(
&mut self,
block_hash: Hash,
block_number: BlockNumber,
candidate_hash: CandidateHash,
tick: Tick,
) {
if let Some(prev) = self.reverse_wakeups.get(&(block_hash, candidate_hash)) {
if prev <= &tick { return }
@@ -331,12 +357,42 @@ impl Wakeups {
let _ = entry.remove_entry();
}
}
} else {
self.note_block(block_hash, block_number);
}
self.reverse_wakeups.insert((block_hash, candidate_hash), tick);
self.wakeups.entry(tick).or_default().push((block_hash, candidate_hash));
}
fn prune_finalized_wakeups(&mut self, finalized_number: BlockNumber) {
let after = self.block_numbers.split_off(&(finalized_number + 1));
let pruned_blocks: HashSet<_> = std::mem::replace(&mut self.block_numbers, after)
.into_iter()
.flat_map(|(_number, hashes)| hashes)
.collect();
let mut pruned_wakeups = BTreeMap::new();
self.reverse_wakeups.retain(|&(ref h, ref c_h), tick| {
let live = !pruned_blocks.contains(h);
if !live {
pruned_wakeups.entry(*tick)
.or_insert_with(HashSet::new)
.insert((*h, *c_h));
}
live
});
for (tick, pruned) in pruned_wakeups {
if let Entry::Occupied(mut entry) = self.wakeups.entry(tick) {
entry.get_mut().retain(|wakeup| !pruned.contains(wakeup));
if entry.get().is_empty() {
let _ = entry.remove();
}
}
}
}
// Get the wakeup for a particular block/candidate combo, if any.
fn wakeup_for(&self, block_hash: Hash, candidate_hash: CandidateHash) -> Option<Tick> {
self.reverse_wakeups.get(&(block_hash, candidate_hash)).map(|t| *t)
@@ -379,30 +435,40 @@ trait DBReader {
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<CandidateEntry>>;
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>>;
}
// This is a submodule to enforce opacity of the inner DB type.
mod approval_db_v1_reader {
use super::{
DBReader, KeyValueDB, Hash, CandidateHash, BlockEntry, CandidateEntry,
Arc, SubsystemResult, SubsystemError, approval_db,
SubsystemResult, SubsystemError, DatabaseConfig, approval_db,
};
/// A DB reader that uses the approval-db V1 under the hood.
pub(super) struct ApprovalDBV1Reader<T: ?Sized>(Arc<T>);
pub(super) struct ApprovalDBV1Reader<T> {
inner: T,
config: DatabaseConfig,
}
impl<T: ?Sized> From<Arc<T>> for ApprovalDBV1Reader<T> {
fn from(a: Arc<T>) -> Self {
ApprovalDBV1Reader(a)
impl<T> ApprovalDBV1Reader<T> {
pub(super) fn new(inner: T, config: DatabaseConfig) -> Self {
ApprovalDBV1Reader {
inner,
config,
}
}
}
impl DBReader for ApprovalDBV1Reader<dyn KeyValueDB> {
impl<'a, T: 'a> DBReader for ApprovalDBV1Reader<T>
where T: std::ops::Deref<Target=(dyn KeyValueDB + 'a)>
{
fn load_block_entry(
&self,
block_hash: &Hash,
) -> SubsystemResult<Option<BlockEntry>> {
approval_db::v1::load_block_entry(&*self.0, block_hash)
approval_db::v1::load_block_entry(&*self.inner, &self.config, block_hash)
.map(|e| e.map(Into::into))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
@@ -411,14 +477,25 @@ mod approval_db_v1_reader {
&self,
candidate_hash: &CandidateHash,
) -> SubsystemResult<Option<CandidateEntry>> {
approval_db::v1::load_candidate_entry(&*self.0, candidate_hash)
approval_db::v1::load_candidate_entry(&*self.inner, &self.config, candidate_hash)
.map(|e| e.map(Into::into))
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
approval_db::v1::load_all_blocks(&*self.inner, &self.config)
.map_err(|e| SubsystemError::with_origin("approval-voting", e))
}
}
}
use approval_db_v1_reader::ApprovalDBV1Reader;
struct ApprovalStatus {
required_tranches: RequiredTranches,
tranche_now: DelayTranche,
block_tick: Tick,
}
struct State<T> {
session_window: import::RollingSessionWindow,
keystore: Arc<LocalKeystore>,
@@ -432,12 +509,59 @@ impl<T> State<T> {
fn session_info(&self, i: SessionIndex) -> Option<&SessionInfo> {
self.session_window.session_info(i)
}
// Compute the required tranches for approval for this block and candidate combo.
// Fails if there is no approval entry for the block under the candidate or no candidate entry
// under the block, or if the session is out of bounds.
fn approval_status<'a, 'b>(
&'a self,
block_entry: &'a BlockEntry,
candidate_entry: &'b CandidateEntry,
) -> Option<(&'b ApprovalEntry, ApprovalStatus)> {
let session_info = match self.session_info(block_entry.session()) {
Some(s) => s,
None => {
tracing::warn!(target: LOG_TARGET, "Unknown session info for {}", block_entry.session());
return None;
}
};
let block_hash = block_entry.block_hash();
let tranche_now = self.clock.tranche_now(self.slot_duration_millis, block_entry.slot());
let block_tick = slot_number_to_tick(self.slot_duration_millis, block_entry.slot());
let no_show_duration = slot_number_to_tick(
self.slot_duration_millis,
Slot::from(u64::from(session_info.no_show_slots)),
);
if let Some(approval_entry) = candidate_entry.approval_entry(&block_hash) {
let required_tranches = approval_checking::tranches_to_approve(
approval_entry,
candidate_entry.approvals(),
tranche_now,
block_tick,
no_show_duration,
session_info.needed_approvals as _
);
let status = ApprovalStatus {
required_tranches,
block_tick,
tranche_now,
};
Some((approval_entry, status))
} else {
None
}
}
}
#[derive(Debug)]
enum Action {
ScheduleWakeup {
block_hash: Hash,
block_number: BlockNumber,
candidate_hash: CandidateHash,
tick: Tick,
},
@@ -451,6 +575,7 @@ enum Action {
candidate: CandidateReceipt,
backing_group: GroupIndex,
},
BecomeActive,
Conclude,
}
@@ -458,7 +583,7 @@ type BackgroundTaskMap = BTreeMap<BlockNumber, Vec<RemoteHandle<()>>>;
async fn run<C>(
mut ctx: C,
subsystem: ApprovalVotingSubsystem,
mut subsystem: ApprovalVotingSubsystem,
clock: Box<dyn Clock + Send + Sync>,
assignment_criteria: Box<dyn AssignmentCriteria + Send + Sync>,
) -> SubsystemResult<()>
@@ -469,7 +594,7 @@ async fn run<C>(
session_window: Default::default(),
keystore: subsystem.keystore,
slot_duration_millis: subsystem.slot_duration_millis,
db: ApprovalDBV1Reader::from(subsystem.db.clone()),
db: ApprovalDBV1Reader::new(subsystem.db.clone(), subsystem.db_config.clone()),
clock,
assignment_criteria,
};
@@ -496,20 +621,28 @@ async fn run<C>(
)?
}
next_msg = ctx.recv().fuse() => {
let actions = handle_from_overseer(
let mut actions = handle_from_overseer(
&mut ctx,
&mut state,
&subsystem.metrics,
db_writer,
subsystem.db_config,
next_msg?,
&mut last_finalized_height,
&wakeups,
&mut wakeups,
).await?;
if let Some(finalized_height) = last_finalized_height {
cleanup_background_tasks(finalized_height, &mut background_tasks);
}
if let Mode::Syncing(ref mut oracle) = subsystem.mode {
if !oracle.is_major_syncing() {
// note that we're active before processing other actions.
actions.insert(0, Action::BecomeActive)
}
}
actions
}
background_request = background_rx.next().fuse() => {
@@ -531,8 +664,10 @@ async fn run<C>(
&subsystem.metrics,
&mut wakeups,
db_writer,
subsystem.db_config,
&background_tx,
&mut background_tasks,
&mut subsystem.mode,
actions,
).await? {
break;
@@ -548,20 +683,25 @@ async fn handle_actions(
metrics: &Metrics,
wakeups: &mut Wakeups,
db: &dyn KeyValueDB,
db_config: DatabaseConfig,
background_tx: &mpsc::Sender<BackgroundRequest>,
background_tasks: &mut BackgroundTaskMap,
mode: &mut Mode,
actions: impl IntoIterator<Item = Action>,
) -> SubsystemResult<bool> {
let mut transaction = approval_db::v1::Transaction::default();
let mut transaction = approval_db::v1::Transaction::new(db_config);
let mut conclude = false;
for action in actions {
match action {
Action::ScheduleWakeup {
block_hash,
block_number,
candidate_hash,
tick,
} => wakeups.schedule(block_hash, candidate_hash, tick),
} => {
wakeups.schedule(block_hash, block_number, candidate_hash, tick)
}
Action::WriteBlockEntry(block_entry) => {
transaction.put_block_entry(block_entry.into());
}
@@ -576,6 +716,9 @@ async fn handle_actions(
candidate,
backing_group,
} => {
// Don't launch approval work if the node is syncing.
if let Mode::Syncing(_) = *mode { continue }
metrics.on_assignment_produced();
let block_hash = indirect_cert.block_hash;
let validator_index = indirect_cert.validator;
@@ -600,6 +743,15 @@ async fn handle_actions(
background_tasks.entry(relay_block_number).or_default().push(handle);
}
}
Action::BecomeActive => {
*mode = Mode::Active;
let messages = distribution_messages_for_activation(
ApprovalDBV1Reader::new(db, db_config)
)?;
ctx.send_messages(messages.into_iter().map(Into::into)).await;
}
Action::Conclude => { conclude = true; }
}
}
@@ -627,15 +779,113 @@ fn cleanup_background_tasks(
// the task on drop.
}
fn distribution_messages_for_activation<'a>(
db: impl DBReader + 'a,
) -> SubsystemResult<Vec<ApprovalDistributionMessage>> {
let all_blocks = db.load_all_blocks()?;
let mut approval_meta = Vec::with_capacity(all_blocks.len());
let mut messages = Vec::new();
messages.push(ApprovalDistributionMessage::NewBlocks(Vec::new())); // dummy value.
for block_hash in all_blocks {
let block_entry = match db.load_block_entry(&block_hash)? {
Some(b) => b,
None => {
tracing::warn!(
target: LOG_TARGET,
?block_hash,
"Missing block entry",
);
continue
}
};
approval_meta.push(BlockApprovalMeta {
hash: block_hash,
number: block_entry.block_number(),
parent_hash: block_entry.parent_hash(),
candidates: block_entry.candidates().iter().map(|(_, c_hash)| *c_hash).collect(),
slot: block_entry.slot(),
});
for (i, (_, candidate_hash)) in block_entry.candidates().iter().enumerate() {
let candidate_entry = match db.load_candidate_entry(&candidate_hash)? {
Some(c) => c,
None => {
tracing::warn!(
target: LOG_TARGET,
?block_hash,
?candidate_hash,
"Missing candidate entry",
);
continue
}
};
match candidate_entry.approval_entry(&block_hash) {
Some(approval_entry) => {
match approval_entry.local_statements() {
(None, None) | (None, Some(_)) => {}, // second is impossible case.
(Some(assignment), None) => {
messages.push(ApprovalDistributionMessage::DistributeAssignment(
IndirectAssignmentCert {
block_hash,
validator: assignment.validator_index(),
cert: assignment.cert().clone(),
},
i as _,
));
}
(Some(assignment), Some(approval_sig)) => {
messages.push(ApprovalDistributionMessage::DistributeAssignment(
IndirectAssignmentCert {
block_hash,
validator: assignment.validator_index(),
cert: assignment.cert().clone(),
},
i as _,
));
messages.push(ApprovalDistributionMessage::DistributeApproval(
IndirectSignedApprovalVote {
block_hash,
candidate_index: i as _,
validator: assignment.validator_index(),
signature: approval_sig,
}
))
}
}
}
None => {
tracing::warn!(
target: LOG_TARGET,
?block_hash,
?candidate_hash,
"Missing approval entry",
);
}
}
}
}
messages[0] = ApprovalDistributionMessage::NewBlocks(approval_meta);
Ok(messages)
}
// Handle an incoming signal from the overseer. Returns true if execution should conclude.
async fn handle_from_overseer(
ctx: &mut impl SubsystemContext,
state: &mut State<impl DBReader>,
metrics: &Metrics,
db_writer: &dyn KeyValueDB,
db_config: DatabaseConfig,
x: FromOverseer<ApprovalVotingMessage>,
last_finalized_height: &mut Option<BlockNumber>,
wakeups: &Wakeups,
wakeups: &mut Wakeups,
) -> SubsystemResult<Vec<Action>> {
let actions = match x {
@@ -648,6 +898,7 @@ async fn handle_from_overseer(
ctx,
state,
db_writer,
db_config,
head,
&*last_finalized_height,
).await {
@@ -685,6 +936,7 @@ async fn handle_from_overseer(
// and approvals which trigger rescheduling.
actions.push(Action::ScheduleWakeup {
block_hash: block_batch.block_hash,
block_number: block_batch.block_number,
candidate_hash: c_hash,
tick,
});
@@ -700,9 +952,11 @@ async fn handle_from_overseer(
FromOverseer::Signal(OverseerSignal::BlockFinalized(block_hash, block_number)) => {
*last_finalized_height = Some(block_number);
approval_db::v1::canonicalize(db_writer, block_number, block_hash)
approval_db::v1::canonicalize(db_writer, &db_config, block_number, block_hash)
.map_err(|e| SubsystemError::with_origin("db", e))?;
wakeups.prune_finalized_wakeups(block_number);
Vec::new()
}
FromOverseer::Signal(OverseerSignal::Conclude) => {
@@ -711,7 +965,7 @@ async fn handle_from_overseer(
FromOverseer::Communication { msg } => match msg {
ApprovalVotingMessage::CheckAndImportAssignment(a, claimed_core, res) => {
let (check_outcome, actions)
= check_and_import_assignment(state, metrics, a, claimed_core)?;
= check_and_import_assignment(state, a, claimed_core)?;
let _ = res.send(check_outcome);
actions
}
@@ -996,6 +1250,7 @@ fn min_prefer_some<T: std::cmp::Ord>(
fn schedule_wakeup_action(
approval_entry: &ApprovalEntry,
block_hash: Hash,
block_number: BlockNumber,
candidate_hash: CandidateHash,
block_tick: Tick,
required_tranches: RequiredTranches,
@@ -1005,6 +1260,7 @@ fn schedule_wakeup_action(
RequiredTranches::All => None,
RequiredTranches::Exact { next_no_show, .. } => next_no_show.map(|tick| Action::ScheduleWakeup {
block_hash,
block_number,
candidate_hash,
tick,
}),
@@ -1032,7 +1288,12 @@ fn schedule_wakeup_action(
};
min_prefer_some(next_non_empty_tranche, next_no_show)
.map(|tick| Action::ScheduleWakeup { block_hash, candidate_hash, tick })
.map(|tick| Action::ScheduleWakeup {
block_hash,
block_number,
candidate_hash,
tick,
})
}
};
@@ -1060,7 +1321,6 @@ fn schedule_wakeup_action(
fn check_and_import_assignment(
state: &State<impl DBReader>,
metrics: &Metrics,
assignment: IndirectAssignmentCert,
candidate_index: CandidateIndex,
) -> SubsystemResult<(AssignmentCheckResult, Vec<Action>)> {
@@ -1155,24 +1415,22 @@ fn check_and_import_assignment(
}
};
// We check for approvals here because we may be late in seeing a block containing a
// candidate for which we have already seen approvals by the same validator.
//
// For these candidates, we will receive the assignments potentially after a corresponding
// approval, and so we must check for approval here.
//
// Note that this already produces actions for writing
// the candidate entry and any modified block entries to disk.
//
// It also produces actions to schedule wakeups for the candidate.
let actions = check_and_apply_full_approval(
state,
&metrics,
Some((assignment.block_hash, block_entry)),
assigned_candidate_hash,
candidate_entry,
|h, _| h == &assignment.block_hash,
)?;
let mut actions = Vec::new();
// We've imported a new approval, so we need to schedule a wake-up for when that might no-show.
if let Some((approval_entry, status)) = state.approval_status(&block_entry, &candidate_entry) {
actions.extend(schedule_wakeup_action(
approval_entry,
block_entry.block_hash(),
block_entry.block_number(),
assigned_candidate_hash,
status.block_tick,
status.required_tranches,
));
}
// We also write the candidate entry as it now contains the new candidate.
actions.push(Action::WriteCandidateEntry(assigned_candidate_hash, candidate_entry));
Ok((res, actions))
}
@@ -1259,116 +1517,89 @@ fn check_and_import_approval<T>(
let actions = import_checked_approval(
state,
&metrics,
Some((approval.block_hash, block_entry)),
block_entry,
approved_candidate_hash,
candidate_entry,
approval.validator,
)?;
ApprovalSource::Remote(approval.validator),
);
Ok((actions, t))
}
enum ApprovalSource {
Remote(ValidatorIndex),
Local(ValidatorIndex, ValidatorSignature),
}
impl ApprovalSource {
fn validator_index(&self) -> ValidatorIndex {
match *self {
ApprovalSource::Remote(v) | ApprovalSource::Local(v, _) => v,
}
}
fn is_remote(&self) -> bool {
match *self {
ApprovalSource::Remote(_) => true,
ApprovalSource::Local(_, _) => false,
}
}
}
// Import an approval vote which is already checked to be valid and corresponding to an assigned
// validator on the candidate and block. This updates the block entry and candidate entry as
// necessary and schedules any further wakeups.
fn import_checked_approval(
state: &State<impl DBReader>,
metrics: &Metrics,
already_loaded: Option<(Hash, BlockEntry)>,
mut block_entry: BlockEntry,
candidate_hash: CandidateHash,
mut candidate_entry: CandidateEntry,
validator: ValidatorIndex,
) -> SubsystemResult<Vec<Action>> {
if candidate_entry.mark_approval(validator) {
// already approved - nothing to do here.
return Ok(Vec::new());
source: ApprovalSource,
) -> Vec<Action> {
let validator_index = source.validator_index();
let already_approved_by = candidate_entry.mark_approval(validator_index);
let candidate_approved_in_block = block_entry.is_candidate_approved(&candidate_hash);
// Check for early exits.
//
// If the candidate was approved
// but not the block, it means that we still need more approvals for the candidate under the
// block.
//
// If the block was approved, but the validator hadn't approved it yet, we should still hold
// onto the approval vote on-disk in case we restart and rebroadcast votes. Otherwise, our
// assignment might manifest as a no-show.
match source {
ApprovalSource::Remote(_) => {
// We don't store remote votes, so we can early exit as long at the candidate is
// already concluded under the block i.e. we don't need more approvals.
if candidate_approved_in_block {
return Vec::new();
}
}
ApprovalSource::Local(_, _) => {
// We never early return on the local validator.
}
}
// Check if this approval vote alters the approval state of any blocks.
//
// This may include blocks beyond the already loaded block.
let actions = check_and_apply_full_approval(
state,
metrics,
already_loaded,
candidate_hash,
candidate_entry,
|_, a| a.is_assigned(validator),
)?;
Ok(actions)
}
// Checks the candidate for full approval under all blocks matching the given filter.
//
// If returning without error, is guaranteed to have produced actions
// to write all modified block entries. It also schedules wakeups for
// the candidate under any blocks filtered.
fn check_and_apply_full_approval(
state: &State<impl DBReader>,
metrics: &Metrics,
mut already_loaded: Option<(Hash, BlockEntry)>,
candidate_hash: CandidateHash,
mut candidate_entry: CandidateEntry,
filter: impl Fn(&Hash, &ApprovalEntry) -> bool,
) -> SubsystemResult<Vec<Action>> {
// We only query this max once per hash.
let db = &state.db;
let mut load_block_entry = move |block_hash| -> SubsystemResult<Option<BlockEntry>> {
if already_loaded.as_ref().map_or(false, |(h, _)| h == block_hash) {
Ok(already_loaded.take().map(|(_, c)| c))
} else {
db.load_block_entry(block_hash)
}
};
let mut newly_approved = Vec::new();
let mut actions = Vec::new();
for (block_hash, approval_entry) in candidate_entry.iter_approval_entries()
.into_iter()
.filter(|(h, a)| !a.is_approved() && filter(h, a))
let block_hash = block_entry.block_hash();
let block_number = block_entry.block_number();
let (is_approved, status) = if let Some((approval_entry, status))
= state.approval_status(&block_entry, &candidate_entry)
{
let mut block_entry = match load_block_entry(block_hash)? {
None => {
tracing::warn!(
target: LOG_TARGET,
"Missing block entry {} referenced by candidate {}",
block_hash,
candidate_hash,
);
continue
}
Some(b) => b,
};
let session_info = match state.session_info(block_entry.session()) {
Some(s) => s,
None => {
tracing::warn!(target: LOG_TARGET, "Unknown session info for {}", block_entry.session());
continue
}
};
let tranche_now = state.clock.tranche_now(state.slot_duration_millis, block_entry.slot());
let block_tick = slot_number_to_tick(state.slot_duration_millis, block_entry.slot());
let no_show_duration = slot_number_to_tick(
state.slot_duration_millis,
Slot::from(u64::from(session_info.no_show_slots)),
);
let required_tranches = approval_checking::tranches_to_approve(
approval_entry,
candidate_entry.approvals(),
tranche_now,
block_tick,
no_show_duration,
session_info.needed_approvals as _
);
let check = approval_checking::check_approval(
&candidate_entry,
approval_entry,
required_tranches.clone(),
status.required_tranches.clone(),
);
if check.is_approved() {
let is_approved = check.is_approved();
if is_approved {
tracing::trace!(
target: LOG_TARGET,
?candidate_hash,
@@ -1378,42 +1609,74 @@ fn check_and_apply_full_approval(
let no_shows = check.known_no_shows();
let was_approved = block_entry.is_fully_approved();
newly_approved.push(*block_hash);
let was_block_approved = block_entry.is_fully_approved();
block_entry.mark_approved_by_hash(&candidate_hash);
let is_approved = block_entry.is_fully_approved();
let is_block_approved = block_entry.is_fully_approved();
if no_shows != 0 {
metrics.on_no_shows(no_shows);
}
metrics.on_candidate_approved(tranche_now as _);
metrics.on_candidate_approved(status.tranche_now as _);
if is_approved && !was_approved {
metrics.on_block_approved(tranche_now as _)
if is_block_approved && !was_block_approved {
metrics.on_block_approved(status.tranche_now as _);
}
actions.push(Action::WriteBlockEntry(block_entry));
}
(is_approved, status)
} else {
tracing::warn!(
target: LOG_TARGET,
?candidate_hash,
?block_hash,
?validator_index,
"No approval entry for approval under block",
);
return Vec::new();
};
{
let approval_entry = candidate_entry.approval_entry_mut(&block_hash)
.expect("Approval entry just fetched; qed");
let was_approved = approval_entry.is_approved();
let newly_approved = is_approved && !was_approved;
if is_approved {
approval_entry.mark_approved();
}
if let ApprovalSource::Local(_, ref sig) = source {
approval_entry.import_approval_sig(sig.clone());
}
actions.extend(schedule_wakeup_action(
&approval_entry,
*block_hash,
block_hash,
block_number,
candidate_hash,
block_tick,
required_tranches,
status.block_tick,
status.required_tranches,
));
}
for b in &newly_approved {
if let Some(a) = candidate_entry.approval_entry_mut(b) {
a.mark_approved();
// We have no need to write the candidate entry if
//
// 1. The source is remote, as we don't store anything new in the approval entry.
// 2. The candidate is not newly approved, as we haven't altered the approval entry's
// approved flag with `mark_approved` above.
// 3. The source had already approved the candidate, as we haven't altered the bitfield.
if !source.is_remote() || newly_approved || !already_approved_by {
// In all other cases, we need to write the candidate entry.
actions.push(Action::WriteCandidateEntry(candidate_hash, candidate_entry));
}
}
actions.push(Action::WriteCandidateEntry(candidate_hash, candidate_entry));
Ok(actions)
actions
}
fn should_trigger_assignment(
@@ -1592,6 +1855,7 @@ fn process_wakeup(
actions.extend(schedule_wakeup_action(
&approval_entry,
relay_block,
block_entry.block_number(),
candidate_hash,
block_tick,
tranches_to_approve,
@@ -1863,11 +2127,11 @@ fn issue_approval(
let actions = import_checked_approval(
state,
metrics,
Some((block_hash, block_entry)),
block_entry,
candidate_hash,
candidate_entry,
validator_index as _,
)?;
ApprovalSource::Local(validator_index as _, sig.clone()),
);
metrics.on_approval_produced();
@@ -23,7 +23,7 @@
use polkadot_node_primitives::approval::{DelayTranche, RelayVRFStory, AssignmentCert};
use polkadot_primitives::v1::{
ValidatorIndex, CandidateReceipt, SessionIndex, GroupIndex, CoreIndex,
Hash, CandidateHash, BlockNumber,
Hash, CandidateHash, BlockNumber, ValidatorSignature,
};
use sp_consensus_slots::Slot;
@@ -79,6 +79,7 @@ pub struct ApprovalEntry {
tranches: Vec<TrancheEntry>,
backing_group: GroupIndex,
our_assignment: Option<OurAssignment>,
our_approval_sig: Option<ValidatorSignature>,
// `n_validators` bits.
assignments: BitVec<BitOrderLsb0, u8>,
approved: bool,
@@ -108,6 +109,11 @@ impl ApprovalEntry {
})
}
/// Import our local approval vote signature for this candidate.
pub fn import_approval_sig(&mut self, approval_sig: ValidatorSignature) {
self.our_approval_sig = Some(approval_sig);
}
/// Whether a validator is already assigned.
pub fn is_assigned(&self, validator_index: ValidatorIndex) -> bool {
self.assignments.get(validator_index.0 as usize).map(|b| *b).unwrap_or(false)
@@ -190,6 +196,18 @@ impl ApprovalEntry {
self.backing_group
}
/// 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>) {
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)
} else {
(None, None)
}
}
/// For tests: set our assignment.
#[cfg(test)]
pub fn set_our_assignment(&mut self, our_assignment: OurAssignment) {
@@ -203,6 +221,7 @@ impl From<crate::approval_db::v1::ApprovalEntry> for ApprovalEntry {
tranches: entry.tranches.into_iter().map(Into::into).collect(),
backing_group: entry.backing_group,
our_assignment: entry.our_assignment.map(Into::into),
our_approval_sig: entry.our_approval_sig.map(Into::into),
assignments: entry.assignments,
approved: entry.approved,
}
@@ -215,6 +234,7 @@ impl From<ApprovalEntry> for crate::approval_db::v1::ApprovalEntry {
tranches: entry.tranches.into_iter().map(Into::into).collect(),
backing_group: entry.backing_group,
our_assignment: entry.our_assignment.map(Into::into),
our_approval_sig: entry.our_approval_sig.map(Into::into),
assignments: entry.assignments,
approved: entry.approved,
}
@@ -260,11 +280,6 @@ impl CandidateEntry {
self.block_assignments.get(block_hash)
}
/// Iterate over approval entries.
pub fn iter_approval_entries(&self) -> impl IntoIterator<Item = (&Hash, &ApprovalEntry)> {
self.block_assignments.iter()
}
#[cfg(test)]
pub fn add_approval_entry(
&mut self,
@@ -325,6 +340,13 @@ impl BlockEntry {
}
}
/// Whether a candidate is approved in the bitfield.
pub fn is_candidate_approved(&self, candidate_hash: &CandidateHash) -> bool {
self.candidates.iter().position(|(_, h)| h == candidate_hash)
.and_then(|p| self.approved_bitfield.get(p).map(|b| *b))
.unwrap_or(false)
}
/// Whether the block entry is fully approved.
pub fn is_fully_approved(&self) -> bool {
self.approved_bitfield.all()
@@ -339,18 +361,6 @@ impl BlockEntry {
})
}
#[cfg(test)]
pub fn block_hash(&self) -> Hash {
self.block_hash
}
#[cfg(test)]
pub fn is_candidate_approved(&self, candidate_hash: &CandidateHash) -> bool {
self.candidates.iter().position(|(_, h)| h == candidate_hash)
.and_then(|p| self.approved_bitfield.get(p).map(|b| *b))
.unwrap_or(false)
}
/// For tests: Add a candidate to the block entry. Returns the
/// index where the candidate was added.
///
@@ -402,6 +412,16 @@ impl BlockEntry {
pub fn block_number(&self) -> BlockNumber {
self.block_number
}
/// Access the block hash of the block entry.
pub fn block_hash(&self) -> Hash {
self.block_hash
}
/// Access the parent hash of the block entry.
pub fn parent_hash(&self) -> Hash {
self.parent_hash
}
}
impl From<crate::approval_db::v1::BlockEntry> for BlockEntry {
+294 -227
View File
@@ -179,6 +179,14 @@ impl DBReader for TestStore {
) -> SubsystemResult<Option<CandidateEntry>> {
Ok(self.candidate_entries.get(candidate_hash).cloned())
}
fn load_all_blocks(&self) -> SubsystemResult<Vec<Hash>> {
let mut hashes: Vec<_> = self.block_entries.keys().cloned().collect();
hashes.sort_by_key(|k| self.block_entries.get(k).unwrap().block_number());
Ok(hashes)
}
}
fn blank_state() -> State<TestStore> {
@@ -226,6 +234,7 @@ fn sign_approval(
key.sign(&super::approval_signing_payload(ApprovalVote(candidate_hash), session_index)).into()
}
#[derive(Clone)]
struct StateConfig {
session_index: SessionIndex,
slot: Slot,
@@ -353,6 +362,7 @@ fn add_candidate_to_block(
tranches: Vec::new(),
backing_group,
our_assignment: None,
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
approved: false,
}.into(),
@@ -378,7 +388,6 @@ fn rejects_bad_assignment() {
let res = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment_good.clone(),
candidate_index,
).unwrap();
@@ -399,7 +408,6 @@ fn rejects_bad_assignment() {
let res = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment,
candidate_index,
).unwrap();
@@ -415,7 +423,6 @@ fn rejects_bad_assignment() {
// same assignment, but this time rejected
let res = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment_good,
candidate_index,
).unwrap();
@@ -446,7 +453,6 @@ fn rejects_assignment_in_future() {
let res = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment.clone(),
candidate_index,
).unwrap();
@@ -461,7 +467,6 @@ fn rejects_assignment_in_future() {
let res = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment.clone(),
candidate_index,
).unwrap();
@@ -486,7 +491,6 @@ fn rejects_assignment_with_unknown_candidate() {
let res = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment.clone(),
candidate_index,
).unwrap();
@@ -518,7 +522,6 @@ fn assignment_import_updates_candidate_entry_and_schedules_wakeup() {
let (res, actions) = check_and_import_assignment(
&mut state,
&Metrics(None),
assignment.clone(),
candidate_index,
).unwrap();
@@ -532,6 +535,7 @@ fn assignment_import_updates_candidate_entry_and_schedules_wakeup() {
block_hash: b,
candidate_hash: c,
tick,
..
} => {
assert_eq!(b, &block_hash);
assert_eq!(c, &candidate_hash);
@@ -700,10 +704,11 @@ fn accepts_and_imports_approval_after_assignment() {
}
#[test]
fn second_approval_import_is_no_op() {
fn second_approval_import_only_schedules_wakeups() {
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC));
let validator_index = ValidatorIndex(0);
let validator_index_b = ValidatorIndex(1);
let candidate_index = 0;
let mut state = State {
@@ -733,6 +738,25 @@ fn second_approval_import_is_no_op() {
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index));
// There is only one assignment, so nothing to schedule if we double-import.
let (actions, res) = check_and_import_approval(
&state,
&Metrics(None),
vote.clone(),
|r| r
).unwrap();
assert_eq!(res, ApprovalCheckResult::Accepted);
assert!(actions.is_empty());
// After adding a second assignment, there should be a schedule wakeup action.
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap()
.import_assignment(0, validator_index_b, 0);
let (actions, res) = check_and_import_approval(
&state,
&Metrics(None),
@@ -741,11 +765,16 @@ fn second_approval_import_is_no_op() {
).unwrap();
assert_eq!(res, ApprovalCheckResult::Accepted);
assert!(actions.is_empty())
assert_eq!(actions.len(), 1);
assert_matches!(
actions.get(0).unwrap(),
Action::ScheduleWakeup { .. } => {}
);
}
#[test]
fn check_and_apply_full_approval_sets_flag_and_bit() {
fn import_checked_approval_updates_entries_and_schedules() {
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC));
let validator_index_a = ValidatorIndex(0);
@@ -773,101 +802,67 @@ fn check_and_apply_full_approval_sets_flag_and_bit() {
.unwrap()
.import_assignment(0, validator_index_b, 0);
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index_a));
{
let mut actions = import_checked_approval(
&state,
&Metrics(None),
state.db.block_entries.get(&block_hash).unwrap().clone(),
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
ApprovalSource::Remote(validator_index_a),
);
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index_b));
assert_eq!(actions.len(), 2);
assert_matches!(
actions.get(0).unwrap(),
Action::ScheduleWakeup {
block_hash: b_hash,
candidate_hash: c_hash,
..
} => {
assert_eq!(b_hash, &block_hash);
assert_eq!(c_hash, &candidate_hash);
}
);
assert_matches!(
actions.get_mut(1).unwrap(),
Action::WriteCandidateEntry(c_hash, ref mut c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert!(!c_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(c_entry.mark_approval(validator_index_a));
let actions = check_and_apply_full_approval(
&state,
&Metrics(None),
None,
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
|b_hash, _a| b_hash == &block_hash,
).unwrap();
state.db.candidate_entries.insert(candidate_hash, c_entry.clone());
}
);
}
assert_eq!(actions.len(), 2);
assert_matches!(
actions.get(0).unwrap(),
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash);
assert!(b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
}
);
assert_matches!(
actions.get(1).unwrap(),
Action::WriteCandidateEntry(c_hash, c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert!(c_entry.approval_entry(&block_hash).unwrap().is_approved());
}
);
}
{
let mut actions = import_checked_approval(
&state,
&Metrics(None),
state.db.block_entries.get(&block_hash).unwrap().clone(),
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
ApprovalSource::Remote(validator_index_b),
);
#[test]
fn check_and_apply_full_approval_does_not_load_cached_block_from_db() {
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC));
let validator_index_a = ValidatorIndex(0);
let validator_index_b = ValidatorIndex(1);
let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| {
Ok(0)
})),
..some_state(StateConfig {
validators: vec![Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie],
validator_groups: vec![vec![ValidatorIndex(0), ValidatorIndex(1)], vec![ValidatorIndex(2)]],
needed_approvals: 2,
..Default::default()
})
};
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap()
.import_assignment(0, validator_index_a, 0);
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap()
.import_assignment(0, validator_index_b, 0);
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index_a));
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index_b));
let block_entry = state.db.block_entries.remove(&block_hash).unwrap();
let actions = check_and_apply_full_approval(
&state,
&Metrics(None),
Some((block_hash, block_entry)),
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
|b_hash, _a| b_hash == &block_hash,
).unwrap();
assert_eq!(actions.len(), 2);
assert_matches!(
actions.get(0).unwrap(),
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash);
assert!(b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
}
);
assert_matches!(
actions.get(1).unwrap(),
Action::WriteCandidateEntry(c_hash, c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert!(c_entry.approval_entry(&block_hash).unwrap().is_approved());
}
);
assert_matches!(
actions.get(0).unwrap(),
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash);
assert!(b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
}
);
assert_matches!(
actions.get_mut(1).unwrap(),
Action::WriteCandidateEntry(c_hash, ref mut c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert!(c_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(c_entry.mark_approval(validator_index_b));
}
);
}
}
#[test]
@@ -886,6 +881,7 @@ fn assignment_triggered_by_all_with_less_than_threshold() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -931,6 +927,7 @@ fn assignment_not_triggered_by_all_with_threshold() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -982,6 +979,7 @@ fn assignment_not_triggered_if_already_triggered() {
validator_index: ValidatorIndex(4),
triggered: true,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -1019,6 +1017,7 @@ fn assignment_not_triggered_by_exact() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -1057,6 +1056,7 @@ fn assignment_not_triggered_more_than_maximum() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -1100,6 +1100,7 @@ fn assignment_triggered_if_at_maximum() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -1143,6 +1144,7 @@ fn assignment_not_triggered_if_at_maximum_but_clock_is_before() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -1186,6 +1188,7 @@ fn assignment_not_triggered_if_at_maximum_but_clock_is_before_with_drift() {
validator_index: ValidatorIndex(4),
triggered: false,
}),
our_approval_sig: None,
assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; 4],
approved: false,
};
@@ -1222,9 +1225,9 @@ fn wakeups_next() {
let c_a = CandidateHash(Hash::repeat_byte(2));
let c_b = CandidateHash(Hash::repeat_byte(3));
wakeups.schedule(b_a, c_a, 1);
wakeups.schedule(b_a, c_b, 4);
wakeups.schedule(b_b, c_b, 3);
wakeups.schedule(b_a, 0, c_a, 1);
wakeups.schedule(b_a, 0, c_b, 4);
wakeups.schedule(b_b, 1, c_b, 3);
assert_eq!(wakeups.first().unwrap(), 1);
@@ -1237,6 +1240,28 @@ fn wakeups_next() {
assert_eq!(wakeups.next(&clock).await, (4, b_a, c_b));
assert!(wakeups.first().is_none());
assert!(wakeups.wakeups.is_empty());
assert_eq!(
wakeups.block_numbers.get(&0).unwrap(),
&vec![b_a].into_iter().collect::<HashSet<_>>(),
);
assert_eq!(
wakeups.block_numbers.get(&1).unwrap(),
&vec![b_b].into_iter().collect::<HashSet<_>>(),
);
wakeups.prune_finalized_wakeups(0);
assert!(wakeups.block_numbers.get(&0).is_none());
assert_eq!(
wakeups.block_numbers.get(&1).unwrap(),
&vec![b_b].into_iter().collect::<HashSet<_>>(),
);
wakeups.prune_finalized_wakeups(1);
assert!(wakeups.block_numbers.get(&0).is_none());
assert!(wakeups.block_numbers.get(&1).is_none());
});
let aux_fut = Box::pin(async move {
@@ -1255,9 +1280,9 @@ fn wakeup_earlier_supersedes_later() {
let b_a = Hash::repeat_byte(0);
let c_a = CandidateHash(Hash::repeat_byte(2));
wakeups.schedule(b_a, c_a, 4);
wakeups.schedule(b_a, c_a, 2);
wakeups.schedule(b_a, c_a, 3);
wakeups.schedule(b_a, 0, c_a, 4);
wakeups.schedule(b_a, 0, c_a, 2);
wakeups.schedule(b_a, 0, c_a, 3);
let clock = MockClock::new(0);
let clock_aux = clock.clone();
@@ -1276,7 +1301,7 @@ fn wakeup_earlier_supersedes_later() {
}
#[test]
fn block_not_approved_until_all_candidates_approved() {
fn import_checked_approval_sets_one_block_bit_at_a_time() {
let block_hash = Hash::repeat_byte(0x01);
let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC));
let candidate_hash_2 = CandidateHash(Hash::repeat_byte(0xDD));
@@ -1305,7 +1330,7 @@ fn block_not_approved_until_all_candidates_approved() {
GroupIndex(1),
);
let approve_candidate = |db: &mut TestStore, c_hash| {
let setup_candidate = |db: &mut TestStore, c_hash| {
db.candidate_entries.get_mut(&c_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap()
@@ -1318,27 +1343,51 @@ fn block_not_approved_until_all_candidates_approved() {
assert!(!db.candidate_entries.get_mut(&c_hash).unwrap()
.mark_approval(validator_index_a));
assert!(!db.candidate_entries.get_mut(&c_hash).unwrap()
.mark_approval(validator_index_b));
};
approve_candidate(&mut state.db, candidate_hash_2);
setup_candidate(&mut state.db, candidate_hash);
setup_candidate(&mut state.db, candidate_hash_2);
{
let b = state.db.block_entries.get_mut(&block_hash).unwrap();
b.mark_approved_by_hash(&candidate_hash);
assert!(!b.is_fully_approved());
}
let actions = check_and_apply_full_approval(
let actions = import_checked_approval(
&state,
&Metrics(None),
None,
state.db.block_entries.get(&block_hash).unwrap().clone(),
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
ApprovalSource::Remote(validator_index_b),
);
assert_eq!(actions.len(), 2);
assert_matches!(
actions.get(0).unwrap(),
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash);
assert!(!b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
assert!(!b_entry.is_candidate_approved(&candidate_hash_2));
state.db.block_entries.insert(block_hash, b_entry.clone());
}
);
assert_matches!(
actions.get(1).unwrap(),
Action::WriteCandidateEntry(c_h, c_entry) => {
assert_eq!(c_h, &candidate_hash);
assert!(c_entry.approval_entry(&block_hash).unwrap().is_approved());
state.db.candidate_entries.insert(*c_h, c_entry.clone());
}
);
let actions = import_checked_approval(
&state,
&Metrics(None),
state.db.block_entries.get(&block_hash).unwrap().clone(),
candidate_hash_2,
state.db.candidate_entries.get(&candidate_hash_2).unwrap().clone(),
|b_hash, _a| b_hash == &block_hash,
).unwrap();
ApprovalSource::Remote(validator_index_b),
);
assert_eq!(actions.len(), 2);
assert_matches!(
@@ -1346,6 +1395,7 @@ fn block_not_approved_until_all_candidates_approved() {
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash);
assert!(b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
assert!(b_entry.is_candidate_approved(&candidate_hash_2));
}
);
@@ -1359,109 +1409,6 @@ fn block_not_approved_until_all_candidates_approved() {
);
}
#[test]
fn candidate_approval_applied_to_all_blocks() {
let block_hash = Hash::repeat_byte(0x01);
let block_hash_2 = Hash::repeat_byte(0x02);
let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC));
let validator_index_a = ValidatorIndex(0);
let validator_index_b = ValidatorIndex(1);
let slot = Slot::from(1);
let session_index = 1;
let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| {
Ok(0)
})),
..some_state(StateConfig {
validators: vec![Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie],
validator_groups: vec![vec![ValidatorIndex(0), ValidatorIndex(1)], vec![ValidatorIndex(2)]],
needed_approvals: 2,
session_index,
slot,
..Default::default()
})
};
add_block(
&mut state.db,
block_hash_2,
session_index,
slot,
);
add_candidate_to_block(
&mut state.db,
block_hash_2,
candidate_hash,
3,
CoreIndex(1),
GroupIndex(1),
);
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap()
.import_assignment(0, validator_index_a, 0);
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap()
.import_assignment(0, validator_index_b, 0);
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash_2)
.unwrap()
.import_assignment(0, validator_index_a, 0);
state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash_2)
.unwrap()
.import_assignment(0, validator_index_b, 0);
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index_a));
assert!(!state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.mark_approval(validator_index_b));
let actions = check_and_apply_full_approval(
&state,
&Metrics(None),
None,
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
|_b_hash, _a| true,
).unwrap();
assert_eq!(actions.len(), 3);
assert_matches!(
actions.get(0).unwrap(),
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash);
assert!(b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
}
);
assert_matches!(
actions.get(1).unwrap(),
Action::WriteBlockEntry(b_entry) => {
assert_eq!(b_entry.block_hash(), block_hash_2);
assert!(b_entry.is_fully_approved());
assert!(b_entry.is_candidate_approved(&candidate_hash));
}
);
assert_matches!(
actions.get(2).unwrap(),
Action::WriteCandidateEntry(c_hash, c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert!(c_entry.approval_entry(&block_hash).unwrap().is_approved());
assert!(c_entry.approval_entry(&block_hash_2).unwrap().is_approved());
}
);
}
#[test]
fn approved_ancestor_all_approved() {
let block_hash_1 = Hash::repeat_byte(0x01);
@@ -1760,7 +1707,7 @@ fn process_wakeup_schedules_wakeup() {
assert_eq!(actions.len(), 1);
assert_matches!(
actions.get(0).unwrap(),
Action::ScheduleWakeup { block_hash: b, candidate_hash: c, tick } => {
Action::ScheduleWakeup { block_hash: b, candidate_hash: c, tick, .. } => {
assert_eq!(b, &block_hash);
assert_eq!(c, &candidate_hash);
assert_eq!(tick, &(slot_to_tick(slot) + 10));
@@ -1777,3 +1724,123 @@ fn triggered_assignment_leads_to_recovery_and_validation() {
fn finalization_event_prunes() {
}
#[test]
fn local_approval_import_always_updates_approval_entry() {
let block_hash = Hash::repeat_byte(0x01);
let block_hash_2 = Hash::repeat_byte(0x02);
let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC));
let validator_index = ValidatorIndex(0);
let state_config = StateConfig {
validators: vec![Sr25519Keyring::Alice, Sr25519Keyring::Bob, Sr25519Keyring::Charlie],
validator_groups: vec![vec![ValidatorIndex(0), ValidatorIndex(1)], vec![ValidatorIndex(2)]],
needed_approvals: 2,
..Default::default()
};
let mut state = State {
assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|| {
Ok(0)
})),
..some_state(state_config.clone())
};
add_block(
&mut state.db,
block_hash_2,
state_config.session_index,
state_config.slot,
);
add_candidate_to_block(
&mut state.db,
block_hash_2,
candidate_hash,
state_config.validators.len(),
1.into(),
GroupIndex(1),
);
let sig_a = sign_approval(Sr25519Keyring::Alice, candidate_hash, 1);
let sig_b = sign_approval(Sr25519Keyring::Alice, candidate_hash, 1);
{
let mut import_local_assignment = |block_hash: Hash| {
let approval_entry = state.db.candidate_entries.get_mut(&candidate_hash).unwrap()
.approval_entry_mut(&block_hash)
.unwrap();
approval_entry.set_our_assignment(approval_db::v1::OurAssignment {
cert: garbage_assignment_cert(
AssignmentCertKind::RelayVRFModulo { sample: 0 }
),
tranche: 0,
validator_index,
triggered: false,
}.into());
assert!(approval_entry.trigger_our_assignment(0).is_some());
assert!(approval_entry.local_statements().0.is_some());
};
import_local_assignment(block_hash);
import_local_assignment(block_hash_2);
}
{
let mut actions = import_checked_approval(
&state,
&Metrics(None),
state.db.block_entries.get(&block_hash).unwrap().clone(),
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
ApprovalSource::Local(validator_index, sig_a.clone()),
);
assert_eq!(actions.len(), 1);
assert_matches!(
actions.get_mut(0).unwrap(),
Action::WriteCandidateEntry(c_hash, ref mut c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert_eq!(
c_entry.approval_entry(&block_hash).unwrap().local_statements().1,
Some(sig_a),
);
assert!(c_entry.mark_approval(validator_index));
state.db.candidate_entries.insert(candidate_hash, c_entry.clone());
}
);
}
{
let mut actions = import_checked_approval(
&state,
&Metrics(None),
state.db.block_entries.get(&block_hash_2).unwrap().clone(),
candidate_hash,
state.db.candidate_entries.get(&candidate_hash).unwrap().clone(),
ApprovalSource::Local(validator_index, sig_b.clone()),
);
assert_eq!(actions.len(), 1);
assert_matches!(
actions.get_mut(0).unwrap(),
Action::WriteCandidateEntry(c_hash, ref mut c_entry) => {
assert_eq!(c_hash, &candidate_hash);
assert_eq!(
c_entry.approval_entry(&block_hash_2).unwrap().local_statements().1,
Some(sig_b),
);
assert!(c_entry.mark_approval(validator_index));
state.db.candidate_entries.insert(candidate_hash, c_entry.clone());
}
);
}
}
// TODO [now]: handling `BecomeActive` action broadcasts everything.
-3
View File
@@ -8,7 +8,6 @@ edition = "2018"
futures = "0.3.12"
futures-timer = "3.0.2"
kvdb = "0.9.0"
kvdb-rocksdb = "0.11.0"
thiserror = "1.0.23"
tracing = "0.1.25"
bitvec = "0.20.1"
@@ -21,8 +20,6 @@ polkadot-overseer = { path = "../../overseer" }
polkadot-primitives = { path = "../../../primitives" }
polkadot-node-primitives = { path = "../../primitives" }
sc-service = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
[dev-dependencies]
log = "0.4.13"
env_logger = "0.8.2"
+123 -112
View File
@@ -21,14 +21,12 @@
use std::collections::HashMap;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
use parity_scale_codec::{Encode, Decode, Input, Error as CodecError};
use futures::{select, channel::oneshot, future, FutureExt};
use futures_timer::Delay;
use kvdb_rocksdb::{Database, DatabaseConfig};
use kvdb::{KeyValueDB, DBTransaction};
use polkadot_primitives::v1::{
@@ -54,12 +52,6 @@ mod tests;
const LOG_TARGET: &str = "parachain::availability";
mod columns {
pub const DATA: u32 = 0;
pub const META: u32 = 1;
pub const NUM_COLUMNS: u32 = 2;
}
/// The following constants are used under normal conditions:
const AVAILABLE_PREFIX: &[u8; 9] = b"available";
@@ -177,97 +169,107 @@ fn query_inner<D: Decode>(
fn write_available_data(
tx: &mut DBTransaction,
config: &Config,
hash: &CandidateHash,
available_data: &AvailableData,
) {
let key = (AVAILABLE_PREFIX, hash).encode();
tx.put_vec(columns::DATA, &key[..], available_data.encode());
tx.put_vec(config.col_data, &key[..], available_data.encode());
}
fn load_available_data(
db: &Arc<dyn KeyValueDB>,
config: &Config,
hash: &CandidateHash,
) -> Result<Option<AvailableData>, Error> {
let key = (AVAILABLE_PREFIX, hash).encode();
query_inner(db, columns::DATA, &key)
query_inner(db, config.col_data, &key)
}
fn delete_available_data(
tx: &mut DBTransaction,
config: &Config,
hash: &CandidateHash,
) {
let key = (AVAILABLE_PREFIX, hash).encode();
tx.delete(columns::DATA, &key[..])
tx.delete(config.col_data, &key[..])
}
fn load_chunk(
db: &Arc<dyn KeyValueDB>,
config: &Config,
candidate_hash: &CandidateHash,
chunk_index: ValidatorIndex,
) -> Result<Option<ErasureChunk>, Error> {
let key = (CHUNK_PREFIX, candidate_hash, chunk_index).encode();
query_inner(db, columns::DATA, &key)
query_inner(db, config.col_data, &key)
}
fn write_chunk(
tx: &mut DBTransaction,
config: &Config,
candidate_hash: &CandidateHash,
chunk_index: ValidatorIndex,
erasure_chunk: &ErasureChunk,
) {
let key = (CHUNK_PREFIX, candidate_hash, chunk_index).encode();
tx.put_vec(columns::DATA, &key, erasure_chunk.encode());
tx.put_vec(config.col_data, &key, erasure_chunk.encode());
}
fn delete_chunk(
tx: &mut DBTransaction,
config: &Config,
candidate_hash: &CandidateHash,
chunk_index: ValidatorIndex,
) {
let key = (CHUNK_PREFIX, candidate_hash, chunk_index).encode();
tx.delete(columns::DATA, &key[..]);
tx.delete(config.col_data, &key[..]);
}
fn load_meta(
db: &Arc<dyn KeyValueDB>,
config: &Config,
hash: &CandidateHash,
) -> Result<Option<CandidateMeta>, Error> {
let key = (META_PREFIX, hash).encode();
query_inner(db, columns::META, &key)
query_inner(db, config.col_meta, &key)
}
fn write_meta(
tx: &mut DBTransaction,
config: &Config,
hash: &CandidateHash,
meta: &CandidateMeta,
) {
let key = (META_PREFIX, hash).encode();
tx.put_vec(columns::META, &key, meta.encode());
tx.put_vec(config.col_meta, &key, meta.encode());
}
fn delete_meta(tx: &mut DBTransaction, hash: &CandidateHash) {
fn delete_meta(tx: &mut DBTransaction, config: &Config, hash: &CandidateHash) {
let key = (META_PREFIX, hash).encode();
tx.delete(columns::META, &key[..])
tx.delete(config.col_meta, &key[..])
}
fn delete_unfinalized_height(
tx: &mut DBTransaction,
config: &Config,
block_number: BlockNumber,
) {
let prefix = (UNFINALIZED_PREFIX, BEBlockNumber(block_number)).encode();
tx.delete_prefix(columns::META, &prefix);
tx.delete_prefix(config.col_meta, &prefix);
}
fn delete_unfinalized_inclusion(
tx: &mut DBTransaction,
config: &Config,
block_number: BlockNumber,
block_hash: &Hash,
candidate_hash: &CandidateHash,
@@ -279,18 +281,28 @@ fn delete_unfinalized_inclusion(
candidate_hash,
).encode();
tx.delete(columns::META, &key[..]);
tx.delete(config.col_meta, &key[..]);
}
fn delete_pruning_key(tx: &mut DBTransaction, t: impl Into<BETimestamp>, h: &CandidateHash) {
fn delete_pruning_key(
tx: &mut DBTransaction,
config: &Config,
t: impl Into<BETimestamp>,
h: &CandidateHash,
) {
let key = (PRUNE_BY_TIME_PREFIX, t.into(), h).encode();
tx.delete(columns::META, &key);
tx.delete(config.col_meta, &key);
}
fn write_pruning_key(tx: &mut DBTransaction, t: impl Into<BETimestamp>, h: &CandidateHash) {
fn write_pruning_key(
tx: &mut DBTransaction,
config: &Config,
t: impl Into<BETimestamp>,
h: &CandidateHash,
) {
let t = t.into();
let key = (PRUNE_BY_TIME_PREFIX, t, h).encode();
tx.put(columns::META, &key, TOMBSTONE_VALUE);
tx.put(config.col_meta, &key, TOMBSTONE_VALUE);
}
fn finalized_block_range(finalized: BlockNumber) -> (Vec<u8>, Vec<u8>) {
@@ -303,12 +315,13 @@ fn finalized_block_range(finalized: BlockNumber) -> (Vec<u8>, Vec<u8>) {
fn write_unfinalized_block_contains(
tx: &mut DBTransaction,
config: &Config,
n: BlockNumber,
h: &Hash,
ch: &CandidateHash,
) {
let key = (UNFINALIZED_PREFIX, BEBlockNumber(n), h, ch).encode();
tx.put(columns::META, &key, TOMBSTONE_VALUE);
tx.put(config.col_meta, &key, TOMBSTONE_VALUE);
}
fn pruning_range(now: impl Into<BETimestamp>) -> (Vec<u8>, Vec<u8>) {
@@ -405,28 +418,12 @@ impl Default for PruningConfig {
}
/// Configuration for the availability store.
#[derive(Debug, Clone, Copy)]
pub struct Config {
/// Total cache size in megabytes. If `None` the default (128 MiB per column) is used.
pub cache_size: Option<usize>,
/// Path to the database.
pub path: PathBuf,
}
impl std::convert::TryFrom<sc_service::config::DatabaseConfig> for Config {
type Error = Error;
fn try_from(config: sc_service::config::DatabaseConfig) -> Result<Self, Self::Error> {
let path = config.path().ok_or(Error::CustomDatabase)?;
Ok(Self {
// substrate cache size is improper here; just use the default
cache_size: None,
// DB path is a sub-directory of substrate db path to give two properties:
// 1: column numbers don't conflict with substrate
// 2: commands like purge-chain work without further changes
path: path.join("parachains").join("av-store"),
})
}
/// The column family for availability data and chunks.
pub col_data: u32,
/// The column family for availability store meta information.
pub col_meta: u32,
}
trait Clock: Send + Sync {
@@ -445,6 +442,7 @@ impl Clock for SystemClock {
/// An implementation of the Availability Store subsystem.
pub struct AvailabilityStoreSubsystem {
pruning_config: PruningConfig,
config: Config,
db: Arc<dyn KeyValueDB>,
metrics: Metrics,
clock: Box<dyn Clock>,
@@ -452,44 +450,33 @@ pub struct AvailabilityStoreSubsystem {
impl AvailabilityStoreSubsystem {
/// Create a new `AvailabilityStoreSubsystem` with a given config on disk.
pub fn new_on_disk(config: Config, metrics: Metrics) -> io::Result<Self> {
let mut db_config = DatabaseConfig::with_columns(columns::NUM_COLUMNS);
if let Some(cache_size) = config.cache_size {
let mut memory_budget = HashMap::new();
for i in 0..columns::NUM_COLUMNS {
memory_budget.insert(i, cache_size / columns::NUM_COLUMNS as usize);
}
db_config.memory_budget = memory_budget;
}
let path = config.path.to_str().ok_or_else(|| io::Error::new(
io::ErrorKind::Other,
format!("Bad database path: {:?}", config.path),
))?;
std::fs::create_dir_all(&path)?;
let db = Database::open(&db_config, &path)?;
Ok(Self {
pruning_config: PruningConfig::default(),
db: Arc::new(db),
pub fn new(
db: Arc<dyn KeyValueDB>,
config: Config,
metrics: Metrics,
) -> Self {
Self::with_pruning_config_and_clock(
db,
config,
PruningConfig::default(),
Box::new(SystemClock),
metrics,
clock: Box::new(SystemClock),
})
)
}
#[cfg(test)]
fn new_in_memory(
/// Create a new `AvailabilityStoreSubsystem` with a given config on disk.
fn with_pruning_config_and_clock(
db: Arc<dyn KeyValueDB>,
config: Config,
pruning_config: PruningConfig,
clock: Box<dyn Clock>,
metrics: Metrics,
) -> Self {
Self {
pruning_config,
config,
db,
metrics: Metrics(None),
metrics,
clock,
}
}
@@ -581,7 +568,7 @@ where
*next_pruning = Delay::new(subsystem.pruning_config.pruning_interval).fuse();
let _timer = subsystem.metrics.time_pruning();
prune_all(&subsystem.db, &*subsystem.clock)?;
prune_all(&subsystem.db, &subsystem.config, &*subsystem.clock)?;
}
}
@@ -648,6 +635,7 @@ async fn process_block_activated(
note_block_backed(
&subsystem.db,
&mut tx,
&subsystem.config,
&subsystem.pruning_config,
now,
n_validators,
@@ -658,6 +646,7 @@ async fn process_block_activated(
note_block_included(
&subsystem.db,
&mut tx,
&subsystem.config,
&subsystem.pruning_config,
(block_number, activated),
receipt,
@@ -675,6 +664,7 @@ async fn process_block_activated(
fn note_block_backed(
db: &Arc<dyn KeyValueDB>,
db_transaction: &mut DBTransaction,
config: &Config,
pruning_config: &PruningConfig,
now: Duration,
n_validators: usize,
@@ -688,7 +678,7 @@ fn note_block_backed(
"Candidate backed",
);
if load_meta(db, &candidate_hash)?.is_none() {
if load_meta(db, config, &candidate_hash)?.is_none() {
let meta = CandidateMeta {
state: State::Unavailable(now.into()),
data_available: false,
@@ -697,8 +687,8 @@ fn note_block_backed(
let prune_at = now + pruning_config.keep_unavailable_for;
write_pruning_key(db_transaction, prune_at, &candidate_hash);
write_meta(db_transaction, &candidate_hash, &meta);
write_pruning_key(db_transaction, config, prune_at, &candidate_hash);
write_meta(db_transaction, config, &candidate_hash, &meta);
}
Ok(())
@@ -707,13 +697,14 @@ fn note_block_backed(
fn note_block_included(
db: &Arc<dyn KeyValueDB>,
db_transaction: &mut DBTransaction,
config: &Config,
pruning_config:&PruningConfig,
block: (BlockNumber, Hash),
candidate: CandidateReceipt,
) -> Result<(), Error> {
let candidate_hash = candidate.hash();
match load_meta(db, &candidate_hash)? {
match load_meta(db, config, &candidate_hash)? {
None => {
// This is alarming. We've observed a block being included without ever seeing it backed.
// Warn and ignore.
@@ -736,7 +727,7 @@ fn note_block_included(
State::Unavailable(at) => {
let at_d: Duration = at.into();
let prune_at = at_d + pruning_config.keep_unavailable_for;
delete_pruning_key(db_transaction, prune_at, &candidate_hash);
delete_pruning_key(db_transaction, config, prune_at, &candidate_hash);
State::Unfinalized(at, vec![be_block])
}
@@ -754,8 +745,14 @@ fn note_block_included(
}
};
write_unfinalized_block_contains(db_transaction, block.0, &block.1, &candidate_hash);
write_meta(db_transaction, &candidate_hash, &meta);
write_unfinalized_block_contains(
db_transaction,
config,
block.0,
&block.1,
&candidate_hash,
);
write_meta(db_transaction, config, &candidate_hash, &meta);
}
}
@@ -788,7 +785,7 @@ async fn process_block_finalized(
// as it is not `Send`. That is why we create the iterator once within this loop, drop it,
// do an asynchronous request, and then instantiate the exact same iterator again.
let batch_num = {
let mut iter = subsystem.db.iter_with_prefix(columns::META, &start_prefix)
let mut iter = subsystem.db.iter_with_prefix(subsystem.config.col_meta, &start_prefix)
.take_while(|(k, _)| &k[..] < &end_prefix[..])
.peekable();
@@ -821,7 +818,7 @@ async fn process_block_finalized(
}
};
let iter = subsystem.db.iter_with_prefix(columns::META, &start_prefix)
let iter = subsystem.db.iter_with_prefix(subsystem.config.col_meta, &start_prefix)
.take_while(|(k, _)| &k[..] < &end_prefix[..])
.peekable();
@@ -830,7 +827,7 @@ async fn process_block_finalized(
// Now that we've iterated over the entire batch at this finalized height,
// update the meta.
delete_unfinalized_height(&mut db_transaction, batch_num);
delete_unfinalized_height(&mut db_transaction, &subsystem.config, batch_num);
update_blocks_at_finalized_height(
&subsystem,
@@ -888,7 +885,7 @@ fn update_blocks_at_finalized_height(
now: Duration,
) -> Result<(), Error> {
for (candidate_hash, is_finalized) in candidates {
let mut meta = match load_meta(&subsystem.db, &candidate_hash)? {
let mut meta = match load_meta(&subsystem.db, &subsystem.config, &candidate_hash)? {
None => {
tracing::warn!(
target: LOG_TARGET,
@@ -909,7 +906,7 @@ fn update_blocks_at_finalized_height(
// This is also not going to happen; the very fact that we are
// iterating over the candidate here indicates that `State` should
// be `Unfinalized`.
delete_pruning_key(db_transaction, at, &candidate_hash);
delete_pruning_key(db_transaction, &subsystem.config, at, &candidate_hash);
}
State::Unfinalized(_, blocks) => {
for (block_num, block_hash) in blocks.iter().cloned() {
@@ -917,6 +914,7 @@ fn update_blocks_at_finalized_height(
if block_num.0 != block_number {
delete_unfinalized_inclusion(
db_transaction,
&subsystem.config,
block_num.0,
&block_hash,
&candidate_hash,
@@ -929,9 +927,10 @@ fn update_blocks_at_finalized_height(
meta.state = State::Finalized(now.into());
// Write the meta and a pruning record.
write_meta(db_transaction, &candidate_hash, &meta);
write_meta(db_transaction, &subsystem.config, &candidate_hash, &meta);
write_pruning_key(
db_transaction,
&subsystem.config,
now + subsystem.pruning_config.keep_finalized_for,
&candidate_hash,
);
@@ -948,7 +947,12 @@ fn update_blocks_at_finalized_height(
if blocks.is_empty() {
let at_d: Duration = at.into();
let prune_at = at_d + subsystem.pruning_config.keep_unavailable_for;
write_pruning_key(db_transaction, prune_at, &candidate_hash);
write_pruning_key(
db_transaction,
&subsystem.config,
prune_at,
&candidate_hash,
);
State::Unavailable(at)
} else {
State::Unfinalized(at, blocks)
@@ -957,7 +961,7 @@ fn update_blocks_at_finalized_height(
};
// Update the meta entry.
write_meta(db_transaction, &candidate_hash, &meta)
write_meta(db_transaction, &subsystem.config, &candidate_hash, &meta)
}
}
@@ -970,18 +974,18 @@ fn process_message(
) -> Result<(), Error> {
match msg {
AvailabilityStoreMessage::QueryAvailableData(candidate, tx) => {
let _ = tx.send(load_available_data(&subsystem.db, &candidate)?);
let _ = tx.send(load_available_data(&subsystem.db, &subsystem.config, &candidate)?);
}
AvailabilityStoreMessage::QueryDataAvailability(candidate, tx) => {
let a = load_meta(&subsystem.db, &candidate)?.map_or(false, |m| m.data_available);
let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?.map_or(false, |m| m.data_available);
let _ = tx.send(a);
}
AvailabilityStoreMessage::QueryChunk(candidate, validator_index, tx) => {
let _timer = subsystem.metrics.time_get_chunk();
let _ = tx.send(load_chunk(&subsystem.db, &candidate, validator_index)?);
let _ = tx.send(load_chunk(&subsystem.db, &subsystem.config, &candidate, validator_index)?);
}
AvailabilityStoreMessage::QueryAllChunks(candidate, tx) => {
match load_meta(&subsystem.db, &candidate)? {
match load_meta(&subsystem.db, &subsystem.config, &candidate)? {
None => {
let _ = tx.send(Vec::new());
}
@@ -990,7 +994,12 @@ fn process_message(
for (index, _) in meta.chunks_stored.iter().enumerate().filter(|(_, b)| **b) {
let _timer = subsystem.metrics.time_get_chunk();
match load_chunk(&subsystem.db, &candidate, ValidatorIndex(index as _))? {
match load_chunk(
&subsystem.db,
&subsystem.config,
&candidate,
ValidatorIndex(index as _),
)? {
Some(c) => chunks.push(c),
None => {
tracing::warn!(
@@ -1008,7 +1017,7 @@ fn process_message(
}
}
AvailabilityStoreMessage::QueryChunkAvailability(candidate, validator_index, tx) => {
let a = load_meta(&subsystem.db, &candidate)?
let a = load_meta(&subsystem.db, &subsystem.config, &candidate)?
.map_or(false, |m|
*m.chunks_stored.get(validator_index.0 as usize).as_deref().unwrap_or(&false)
);
@@ -1022,7 +1031,7 @@ fn process_message(
subsystem.metrics.on_chunks_received(1);
let _timer = subsystem.metrics.time_store_chunk();
match store_chunk(&subsystem.db, candidate_hash, chunk) {
match store_chunk(&subsystem.db, &subsystem.config, candidate_hash, chunk) {
Ok(true) => {
let _ = tx.send(Ok(()));
}
@@ -1065,12 +1074,13 @@ fn process_message(
// Ok(true) on success, Ok(false) on failure, and Err on internal error.
fn store_chunk(
db: &Arc<dyn KeyValueDB>,
config: &Config,
candidate_hash: CandidateHash,
chunk: ErasureChunk,
) -> Result<bool, Error> {
let mut tx = DBTransaction::new();
let mut meta = match load_meta(db, &candidate_hash)? {
let mut meta = match load_meta(db, config, &candidate_hash)? {
Some(m) => m,
None => return Ok(false), // we weren't informed of this candidate by import events.
};
@@ -1080,8 +1090,8 @@ fn store_chunk(
Some(false) => {
meta.chunks_stored.set(chunk.index.0 as usize, true);
write_chunk(&mut tx, &candidate_hash, chunk.index, &chunk);
write_meta(&mut tx, &candidate_hash, &meta);
write_chunk(&mut tx, config, &candidate_hash, chunk.index, &chunk);
write_meta(&mut tx, config, &candidate_hash, &meta);
}
None => return Ok(false), // out of bounds.
}
@@ -1106,7 +1116,7 @@ fn store_available_data(
) -> Result<(), Error> {
let mut tx = DBTransaction::new();
let mut meta = match load_meta(&subsystem.db, &candidate_hash)? {
let mut meta = match load_meta(&subsystem.db, &subsystem.config, &candidate_hash)? {
Some(m) => {
if m.data_available {
return Ok(()); // already stored.
@@ -1119,7 +1129,7 @@ fn store_available_data(
// Write a pruning record.
let prune_at = now + subsystem.pruning_config.keep_unavailable_for;
write_pruning_key(&mut tx, prune_at, &candidate_hash);
write_pruning_key(&mut tx, &subsystem.config, prune_at, &candidate_hash);
CandidateMeta {
state: State::Unavailable(now.into()),
@@ -1142,14 +1152,14 @@ fn store_available_data(
});
for chunk in erasure_chunks {
write_chunk(&mut tx, &candidate_hash, chunk.index, &chunk);
write_chunk(&mut tx, &subsystem.config, &candidate_hash, chunk.index, &chunk);
}
meta.data_available = true;
meta.chunks_stored = bitvec::bitvec![BitOrderLsb0, u8; 1; n_validators];
write_meta(&mut tx, &candidate_hash, &meta);
write_available_data(&mut tx, &candidate_hash, &available_data);
write_meta(&mut tx, &subsystem.config, &candidate_hash, &meta);
write_available_data(&mut tx, &subsystem.config, &candidate_hash, &available_data);
subsystem.db.write(tx)?;
@@ -1162,35 +1172,35 @@ fn store_available_data(
Ok(())
}
fn prune_all(db: &Arc<dyn KeyValueDB>, clock: &dyn Clock) -> Result<(), Error> {
fn prune_all(db: &Arc<dyn KeyValueDB>, config: &Config, clock: &dyn Clock) -> Result<(), Error> {
let now = clock.now()?;
let (range_start, range_end) = pruning_range(now);
let mut tx = DBTransaction::new();
let iter = db.iter_with_prefix(columns::META, &range_start[..])
let iter = db.iter_with_prefix(config.col_meta, &range_start[..])
.take_while(|(k, _)| &k[..] < &range_end[..]);
for (k, _v) in iter {
tx.delete(columns::META, &k[..]);
tx.delete(config.col_meta, &k[..]);
let (_, candidate_hash) = match decode_pruning_key(&k[..]) {
Ok(m) => m,
Err(_) => continue, // sanity
};
delete_meta(&mut tx, &candidate_hash);
delete_meta(&mut tx, config, &candidate_hash);
// Clean up all attached data of the candidate.
if let Some(meta) = load_meta(db, &candidate_hash)? {
if let Some(meta) = load_meta(db, config, &candidate_hash)? {
// delete available data.
if meta.data_available {
delete_available_data(&mut tx, &candidate_hash)
delete_available_data(&mut tx, config, &candidate_hash)
}
// delete chunks.
for (i, b) in meta.chunks_stored.iter().enumerate() {
if *b {
delete_chunk(&mut tx, &candidate_hash, ValidatorIndex(i as _));
delete_chunk(&mut tx, config, &candidate_hash, ValidatorIndex(i as _));
}
}
@@ -1200,6 +1210,7 @@ fn prune_all(db: &Arc<dyn KeyValueDB>, clock: &dyn Clock) -> Result<(), Error> {
for (block_number, block_hash) in blocks {
delete_unfinalized_inclusion(
&mut tx,
config,
block_number.0,
&block_hash,
&candidate_hash,
+17 -4
View File
@@ -38,6 +38,17 @@ use polkadot_node_subsystem_test_helpers as test_helpers;
use sp_keyring::Sr25519Keyring;
use parking_lot::Mutex;
mod columns {
pub const DATA: u32 = 0;
pub const META: u32 = 1;
pub const NUM_COLUMNS: u32 = 2;
}
const TEST_CONFIG: Config = Config {
col_data: columns::DATA,
col_meta: columns::META,
};
struct TestHarness {
virtual_overseer: test_helpers::TestSubsystemContextHandle<AvailabilityStoreMessage>,
}
@@ -149,10 +160,12 @@ fn test_harness<T: Future<Output=()>>(
let pool = sp_core::testing::TaskExecutor::new();
let (context, virtual_overseer) = test_helpers::make_subsystem_context(pool.clone());
let subsystem = AvailabilityStoreSubsystem::new_in_memory(
let subsystem = AvailabilityStoreSubsystem::with_pruning_config_and_clock(
store,
TEST_CONFIG,
state.pruning_config.clone(),
Box::new(state.clock),
Metrics::default(),
);
let subsystem = run(subsystem, context);
@@ -297,7 +310,7 @@ fn store_chunk_works() {
// Ensure an entry already exists. In reality this would come from watching
// chain events.
with_tx(&store, |tx| {
super::write_meta(tx, &candidate_hash, &CandidateMeta {
super::write_meta(tx, &TEST_CONFIG, &candidate_hash, &CandidateMeta {
data_available: false,
chunks_stored: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators],
state: State::Unavailable(BETimestamp(0)),
@@ -379,7 +392,7 @@ fn query_chunk_checks_meta() {
// Ensure an entry already exists. In reality this would come from watching
// chain events.
with_tx(&store, |tx| {
super::write_meta(tx, &candidate_hash, &CandidateMeta {
super::write_meta(tx, &TEST_CONFIG, &candidate_hash, &CandidateMeta {
data_available: false,
chunks_stored: {
let mut v = bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators];
@@ -548,7 +561,7 @@ fn query_all_chunks_works() {
{
with_tx(&store, |tx| {
super::write_meta(tx, &candidate_hash_2, &CandidateMeta {
super::write_meta(tx, &TEST_CONFIG, &candidate_hash_2, &CandidateMeta {
data_available: false,
chunks_stored: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators as _],
state: State::Unavailable(BETimestamp(0)),
@@ -244,10 +244,13 @@ impl State {
candidates,
});
new_hashes.insert(meta.hash.clone());
// In case there are duplicates, we should only set this if the entry
// was vacant.
self.blocks_by_number.entry(meta.number).or_default().push(meta.hash);
}
_ => continue,
}
self.blocks_by_number.entry(meta.number).or_default().push(meta.hash);
}
tracing::debug!(
+2
View File
@@ -12,6 +12,7 @@ polkadot-primitives = { path = "../../../primitives" }
parity-scale-codec = { version = "2.0.0", default-features = false, features = ["derive"] }
sc-authority-discovery = { git = "https://github.com/paritytech/substrate", branch = "master" }
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" }
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" }
polkadot-node-network-protocol = { path = "../protocol" }
strum = "0.20.0"
@@ -23,3 +24,4 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
polkadot-node-subsystem-util = { path = "../../subsystem-util"}
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" }
futures-timer = "3"
+281 -32
View File
@@ -25,6 +25,7 @@ use parking_lot::Mutex;
use futures::prelude::*;
use futures::channel::mpsc;
use sc_network::Event as NetworkEvent;
use sp_consensus::SyncOracle;
use polkadot_subsystem::{
ActiveLeavesUpdate, ActivatedLeaf, Subsystem, SubsystemContext, SpawnedSubsystem, SubsystemError,
@@ -96,6 +97,7 @@ pub struct NetworkBridge<N, AD> {
network_service: N,
authority_discovery_service: AD,
request_multiplexer: RequestMultiplexer,
sync_oracle: Box<dyn SyncOracle + Send>,
}
impl<N, AD> NetworkBridge<N, AD> {
@@ -103,11 +105,17 @@ impl<N, AD> NetworkBridge<N, AD> {
///
/// This assumes that the network service has had the notifications protocol for the network
/// bridge already registered. See [`peers_sets_info`](peers_sets_info).
pub fn new(network_service: N, authority_discovery_service: AD, request_multiplexer: RequestMultiplexer) -> Self {
pub fn new(
network_service: N,
authority_discovery_service: AD,
request_multiplexer: RequestMultiplexer,
sync_oracle: Box<dyn SyncOracle + Send>,
) -> Self {
NetworkBridge {
network_service,
authority_discovery_service,
request_multiplexer,
sync_oracle,
}
}
}
@@ -165,17 +173,23 @@ struct Shared(Arc<Mutex<SharedInner>>);
#[derive(Default)]
struct SharedInner {
local_view: View,
local_view: Option<View>,
validation_peers: HashMap<PeerId, PeerData>,
collation_peers: HashMap<PeerId, PeerData>,
}
enum Mode {
Syncing(Box<dyn SyncOracle + Send>),
Active,
}
async fn handle_subsystem_messages<Context, N, AD>(
mut ctx: Context,
mut network_service: N,
mut authority_discovery_service: AD,
validator_discovery_notifications: mpsc::Receiver<ValidatorDiscoveryNotification>,
shared: Shared,
sync_oracle: Box<dyn SyncOracle + Send>,
) -> Result<(), UnexpectedAbort>
where
Context: SubsystemContext<Message = NetworkBridgeMessage>,
@@ -187,6 +201,8 @@ where
let mut finalized_number = 0;
let mut validator_discovery = validator_discovery::Service::<N, AD>::new();
let mut mode = Mode::Syncing(sync_oracle);
let mut validator_discovery_notifications = validator_discovery_notifications.fuse();
loop {
@@ -210,13 +226,26 @@ where
}
live_heads.retain(|h| !deactivated.contains(&h.hash));
update_our_view(
&mut network_service,
&mut ctx,
&live_heads,
&shared,
finalized_number,
).await?;
// if we're done syncing, set the mode to `Mode::Active`.
// Otherwise, we don't need to send view updates.
{
let is_done_syncing = match mode {
Mode::Active => true,
Mode::Syncing(ref mut sync_oracle) => !sync_oracle.is_major_syncing(),
};
if is_done_syncing {
mode = Mode::Active;
update_our_view(
&mut network_service,
&mut ctx,
&live_heads,
&shared,
finalized_number,
).await?;
}
}
}
Ok(FromOverseer::Signal(OverseerSignal::BlockFinalized(_hash, number))) => {
tracing::trace!(
@@ -413,7 +442,7 @@ async fn handle_network_messages(
}
}
shared.local_view.clone()
shared.local_view.clone().unwrap_or(View::default())
};
// Failure here means that the other side of the network bridge
@@ -637,6 +666,7 @@ where
network_service,
request_multiplexer,
authority_discovery_service,
sync_oracle,
} = bridge;
let (validation_worker_tx, validation_worker_rx) = mpsc::channel(1024);
@@ -657,6 +687,7 @@ where
authority_discovery_service,
validation_worker_rx,
shared,
sync_oracle,
);
futures::pin_mut!(subsystem_event_handler);
@@ -722,11 +753,22 @@ async fn update_our_view(
// We only want to send a view update when the heads changed.
// A change in finalized block number only is _not_ sufficient.
if shared.local_view.check_heads_eq(&new_view) {
return Ok(())
}
//
// If this is the first view update since becoming active, but our view is empty,
// there is no need to send anything.
match shared.local_view {
Some(ref v) if v.check_heads_eq(&new_view) => {
return Ok(())
}
None if live_heads.is_empty() => {
shared.local_view = Some(new_view);
return Ok(())
}
_ => {
shared.local_view = Some(new_view.clone());
}
shared.local_view = new_view.clone();
}
(
shared.validation_peers.keys().cloned().collect::<Vec<_>>(),
@@ -910,11 +952,12 @@ mod tests {
use super::*;
use futures::executor;
use futures::stream::BoxStream;
use std::pin::Pin;
use std::sync::Arc;
use futures::channel::oneshot;
use std::borrow::Cow;
use std::collections::HashSet;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use async_trait::async_trait;
use parking_lot::Mutex;
use assert_matches::assert_matches;
@@ -1071,12 +1114,76 @@ mod tests {
}
}
#[derive(Clone)]
struct TestSyncOracle {
flag: Arc<AtomicBool>,
done_syncing_sender: Arc<Mutex<Option<oneshot::Sender<()>>>>,
}
struct TestSyncOracleHandle {
done_syncing_receiver: oneshot::Receiver<()>,
flag: Arc<AtomicBool>,
}
impl TestSyncOracleHandle {
fn set_done(&self) {
self.flag.store(false, Ordering::SeqCst);
}
async fn await_mode_switch(self) {
let _ = self.done_syncing_receiver.await;
}
}
impl SyncOracle for TestSyncOracle {
fn is_major_syncing(&mut self) -> bool {
let is_major_syncing = self.flag.load(Ordering::SeqCst);
if !is_major_syncing {
if let Some(sender) = self.done_syncing_sender.lock().take() {
let _ = sender.send(());
}
}
is_major_syncing
}
fn is_offline(&mut self) -> bool {
unimplemented!("not used in network bridge")
}
}
// val - result of `is_major_syncing`.
fn make_sync_oracle(val: bool) -> (TestSyncOracle, TestSyncOracleHandle) {
let (tx, rx) = oneshot::channel();
let flag = Arc::new(AtomicBool::new(val));
(
TestSyncOracle {
flag: flag.clone(),
done_syncing_sender: Arc::new(Mutex::new(Some(tx))),
},
TestSyncOracleHandle {
flag,
done_syncing_receiver: rx,
}
)
}
fn done_syncing_oracle() -> Box<dyn SyncOracle + Send> {
let (oracle, _) = make_sync_oracle(false);
Box::new(oracle)
}
struct TestHarness {
network_handle: TestNetworkHandle,
virtual_overseer: TestSubsystemContextHandle<NetworkBridgeMessage>,
}
fn test_harness<T: Future<Output=()>>(test: impl FnOnce(TestHarness) -> T) {
fn test_harness<T: Future<Output=()>>(
sync_oracle: Box<dyn SyncOracle + Send>,
test: impl FnOnce(TestHarness) -> T,
) {
let pool = sp_core::testing::TaskExecutor::new();
let (request_multiplexer, req_configs) = RequestMultiplexer::new();
let (network, network_handle, discovery) = new_test_network(req_configs);
@@ -1086,6 +1193,7 @@ mod tests {
network_service: network,
authority_discovery_service: discovery,
request_multiplexer,
sync_oracle,
};
let network_bridge = run_network(
@@ -1148,7 +1256,8 @@ mod tests {
#[test]
fn send_our_view_upon_connection() {
test_harness(|test_harness| async move {
let (oracle, handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1167,6 +1276,8 @@ mod tests {
))
).await;
handle.await_mode_switch().await;
network_handle.connect_peer(peer.clone(), PeerSet::Validation, ObservedRole::Full).await;
network_handle.connect_peer(peer.clone(), PeerSet::Collation, ObservedRole::Full).await;
@@ -1197,12 +1308,24 @@ mod tests {
#[test]
fn sends_view_updates_to_peers() {
test_harness(|test_harness| async move {
let (oracle, handle) = make_sync_oracle(false);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer } = test_harness;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
virtual_overseer.send(
FromOverseer::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate {
activated: Default::default(),
deactivated: Default::default(),
}
))
).await;
handle.await_mode_switch().await;
network_handle.connect_peer(
peer_a.clone(),
PeerSet::Validation,
@@ -1210,10 +1333,33 @@ mod tests {
).await;
network_handle.connect_peer(
peer_b.clone(),
PeerSet::Validation,
PeerSet::Collation,
ObservedRole::Full,
).await;
let actions = network_handle.next_network_actions(2).await;
let wire_message = WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(
View::default(),
).encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_a,
PeerSet::Validation,
wire_message.clone(),
),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_b,
PeerSet::Collation,
wire_message.clone(),
),
);
let hash_a = Hash::repeat_byte(1);
virtual_overseer.send(
@@ -1226,7 +1372,7 @@ mod tests {
))
).await;
let actions = network_handle.next_network_actions(4).await;
let actions = network_handle.next_network_actions(2).await;
let wire_message = WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(
view![hash_a]
).encode();
@@ -1244,16 +1390,119 @@ mod tests {
&actions,
&NetworkAction::WriteNotification(
peer_b,
PeerSet::Validation,
PeerSet::Collation,
wire_message.clone(),
),
);
});
}
#[test]
fn do_not_send_view_update_until_synced() {
let (oracle, handle) = make_sync_oracle(true);
test_harness(Box::new(oracle), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer } = test_harness;
let peer_a = PeerId::random();
let peer_b = PeerId::random();
network_handle.connect_peer(
peer_a.clone(),
PeerSet::Validation,
ObservedRole::Full,
).await;
network_handle.connect_peer(
peer_b.clone(),
PeerSet::Collation,
ObservedRole::Full,
).await;
{
let actions = network_handle.next_network_actions(2).await;
let wire_message = WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(
View::default(),
).encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_a,
PeerSet::Validation,
wire_message.clone(),
),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_b,
PeerSet::Collation,
wire_message.clone(),
),
);
}
let hash_a = Hash::repeat_byte(1);
let hash_b = Hash::repeat_byte(1);
virtual_overseer.send(
FromOverseer::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(ActivatedLeaf {
hash: hash_a,
number: 1,
span: Arc::new(jaeger::Span::Disabled),
})
))
).await;
// delay until the previous update has certainly been processed.
futures_timer::Delay::new(std::time::Duration::from_millis(100)).await;
handle.set_done();
virtual_overseer.send(
FromOverseer::Signal(OverseerSignal::ActiveLeaves(
ActiveLeavesUpdate::start_work(ActivatedLeaf {
hash: hash_b,
number: 1,
span: Arc::new(jaeger::Span::Disabled),
})
))
).await;
handle.await_mode_switch().await;
// There should be a mode switch only for the second view update.
{
let actions = network_handle.next_network_actions(2).await;
let wire_message = WireMessage::<protocol_v1::ValidationProtocol>::ViewUpdate(
view![hash_a, hash_b]
).encode();
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_a,
PeerSet::Validation,
wire_message.clone(),
),
);
assert_network_actions_contains(
&actions,
&NetworkAction::WriteNotification(
peer_b,
PeerSet::Collation,
wire_message.clone(),
),
);
}
});
}
#[test]
fn do_not_send_view_update_when_only_finalized_block_changed() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer } = test_harness;
let peer_a = PeerId::random();
@@ -1319,7 +1568,7 @@ mod tests {
#[test]
fn peer_view_updates_sent_via_overseer() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1361,7 +1610,7 @@ mod tests {
#[test]
fn peer_messages_sent_via_overseer() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1428,7 +1677,7 @@ mod tests {
#[test]
fn peer_disconnect_from_just_one_peerset() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1503,7 +1752,7 @@ mod tests {
#[test]
fn relays_collation_protocol_messages() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1590,7 +1839,7 @@ mod tests {
#[test]
fn different_views_on_different_peer_sets() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1655,7 +1904,7 @@ mod tests {
#[test]
fn sent_views_include_finalized_number_update() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, mut virtual_overseer } = test_harness;
let peer_a = PeerId::random();
@@ -1700,7 +1949,7 @@ mod tests {
#[test]
fn view_finalized_number_can_not_go_down() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness { mut network_handle, .. } = test_harness;
let peer_a = PeerId::random();
@@ -1740,7 +1989,7 @@ mod tests {
#[test]
fn send_messages_to_peers() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut network_handle,
mut virtual_overseer,
@@ -1876,7 +2125,7 @@ mod tests {
#[test]
fn our_view_updates_decreasing_order_and_limited_to_max() {
test_harness(|test_harness| async move {
test_harness(done_syncing_oracle(), |test_harness| async move {
let TestHarness {
mut virtual_overseer,
..
+5 -1
View File
@@ -63,6 +63,8 @@ hex-literal = "0.3.1"
tracing = "0.1.25"
serde = { version = "1.0.123", features = ["derive"] }
thiserror = "1.0.23"
kvdb = "0.9.0"
kvdb-rocksdb = { version = "0.11.0", optional = true }
# Polkadot
polkadot-node-core-proposer = { path = "../core/proposer" }
@@ -111,7 +113,8 @@ db = ["service/db"]
full-node = [
"polkadot-node-core-av-store",
"polkadot-node-core-approval-voting",
"sc-finality-grandpa-warp-sync"
"sc-finality-grandpa-warp-sync",
"kvdb-rocksdb"
]
runtime-benchmarks = [
@@ -128,6 +131,7 @@ try-runtime = [
]
real-overseer = [
"full-node",
"polkadot-availability-bitfield-distribution",
"polkadot-availability-distribution",
"polkadot-availability-recovery",
+32 -16
View File
@@ -21,10 +21,10 @@
pub mod chain_spec;
mod grandpa_support;
mod client;
mod parachains_db;
#[cfg(feature = "full-node")]
use {
std::convert::TryInto,
std::time::Duration,
tracing::info,
polkadot_node_core_av_store::Config as AvailabilityConfig,
@@ -418,7 +418,9 @@ fn real_overseer<Spawner, RuntimeClient>(
leaves: impl IntoIterator<Item = BlockInfo>,
_: Arc<LocalKeystore>,
_: Arc<RuntimeClient>,
_parachains_db: (),
_: AvailabilityConfig,
_: ApprovalVotingConfig,
_: Arc<sc_network::NetworkService<Block, Hash>>,
_: AuthorityDiscoveryService,
_request_multiplexer: (),
@@ -426,7 +428,6 @@ fn real_overseer<Spawner, RuntimeClient>(
spawner: Spawner,
_: IsCollator,
_: IsolationStrategy,
_: ApprovalVotingConfig,
) -> Result<(Overseer<Spawner>, OverseerHandler), Error>
where
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
@@ -446,7 +447,9 @@ fn real_overseer<Spawner, RuntimeClient>(
leaves: impl IntoIterator<Item = BlockInfo>,
keystore: Arc<LocalKeystore>,
runtime_client: Arc<RuntimeClient>,
parachains_db: Arc<dyn kvdb::KeyValueDB>,
availability_config: AvailabilityConfig,
approval_voting_config: ApprovalVotingConfig,
network_service: Arc<sc_network::NetworkService<Block, Hash>>,
authority_discovery: AuthorityDiscoveryService,
request_multiplexer: RequestMultiplexer,
@@ -454,7 +457,6 @@ fn real_overseer<Spawner, RuntimeClient>(
spawner: Spawner,
is_collator: IsCollator,
isolation_strategy: IsolationStrategy,
approval_voting_config: ApprovalVotingConfig,
) -> Result<(Overseer<Spawner>, OverseerHandler), Error>
where
RuntimeClient: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block> + AuxStore,
@@ -489,10 +491,11 @@ where
),
availability_recovery: AvailabilityRecoverySubsystem::with_chunks_only(
),
availability_store: AvailabilityStoreSubsystem::new_on_disk(
availability_store: AvailabilityStoreSubsystem::new(
parachains_db.clone(),
availability_config,
Metrics::register(registry)?,
)?,
),
bitfield_distribution: BitfieldDistributionSubsystem::new(
Metrics::register(registry)?,
),
@@ -537,9 +540,10 @@ where
)
},
network_bridge: NetworkBridgeSubsystem::new(
network_service,
network_service.clone(),
authority_discovery,
request_multiplexer,
Box::new(network_service.clone()),
),
provisioner: ProvisionerSubsystem::new(
spawner.clone(),
@@ -559,9 +563,11 @@ where
),
approval_voting: ApprovalVotingSubsystem::with_config(
approval_voting_config,
parachains_db,
keystore.clone(),
Box::new(network_service.clone()),
Metrics::register(registry)?,
)?,
),
gossip_support: GossipSupportSubsystem::new(
keystore.clone(),
runtime_client.clone(),
@@ -843,17 +849,26 @@ pub fn new_full<RuntimeApi, Executor>(
);
}
let availability_config = config.database.clone().try_into().map_err(Error::Availability)?;
let chain_spec = config.chain_spec.cloned_box();
#[cfg(feature = "real-overseer")]
let parachains_db = crate::parachains_db::open_creating(
config.database.path().ok_or(Error::DatabasePathRequired)?.into(),
crate::parachains_db::CacheSizes::default(),
)?;
let approval_voting_config = ApprovalVotingConfig {
path: config.database.path()
.ok_or(Error::DatabasePathRequired)?
.join("parachains").join("approval-voting"),
slot_duration_millis: slot_duration.as_millis() as u64,
cache_size: None, // default is fine.
#[cfg(not(feature = "real-overseer"))]
let parachains_db = ();
let availability_config = AvailabilityConfig {
col_data: crate::parachains_db::REAL_COLUMNS.col_availability_data,
col_meta: crate::parachains_db::REAL_COLUMNS.col_availability_meta,
};
let approval_voting_config = ApprovalVotingConfig {
col_data: crate::parachains_db::REAL_COLUMNS.col_approval_data,
slot_duration_millis: slot_duration.as_millis() as u64,
};
let chain_spec = config.chain_spec.cloned_box();
let rpc_handlers = service::spawn_tasks(service::SpawnTasksParams {
config,
backend: backend.clone(),
@@ -922,7 +937,9 @@ pub fn new_full<RuntimeApi, Executor>(
active_leaves,
keystore,
overseer_client.clone(),
parachains_db,
availability_config,
approval_voting_config,
network.clone(),
authority_discovery_service,
request_multiplexer,
@@ -930,7 +947,6 @@ pub fn new_full<RuntimeApi, Executor>(
spawner,
is_collator,
isolation_strategy,
approval_voting_config,
)?;
let overseer_handler_clone = overseer_handler.clone();
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2021 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.
//! A RocksDB instance for storing parachain data; availability data, and approvals.
#[cfg(feature = "real-overseer")]
use {
std::io,
std::path::PathBuf,
std::sync::Arc,
kvdb::KeyValueDB,
};
mod columns {
#[cfg(feature = "real-overseer")]
pub const NUM_COLUMNS: u32 = 3;
pub const COL_AVAILABILITY_DATA: u32 = 0;
pub const COL_AVAILABILITY_META: u32 = 1;
pub const COL_APPROVAL_DATA: u32 = 2;
}
/// Columns used by different subsystems.
#[derive(Debug, Clone)]
pub struct ColumnsConfig {
/// The column used by the av-store for data.
pub col_availability_data: u32,
/// The column used by the av-store for meta information.
pub col_availability_meta: u32,
/// The column used by approval voting for data.
pub col_approval_data: u32,
}
/// The real columns used by the parachains DB.
pub const REAL_COLUMNS: ColumnsConfig = ColumnsConfig {
col_availability_data: columns::COL_AVAILABILITY_DATA,
col_availability_meta: columns::COL_AVAILABILITY_META,
col_approval_data: columns::COL_APPROVAL_DATA,
};
/// The cache size for each column, in bytes.
#[derive(Debug, Clone)]
pub struct CacheSizes {
/// Cache used by availability data.
pub availability_data: usize,
/// Cache used by availability meta.
pub availability_meta: usize,
/// Cache used by approval data.
pub approval_data: usize,
}
impl Default for CacheSizes {
fn default() -> Self {
CacheSizes {
availability_data: 25 * 1024 * 1024,
availability_meta: 512 * 1024,
approval_data: 5 * 1024 * 1024,
}
}
}
/// Open the database on disk, creating it if it doesn't exist.
#[cfg(feature = "real-overseer")]
pub fn open_creating(
root: PathBuf,
cache_sizes: CacheSizes,
) -> io::Result<Arc<dyn KeyValueDB>> {
use kvdb_rocksdb::{DatabaseConfig, Database};
let path = root.join("parachains").join("db");
let mut db_config = DatabaseConfig::with_columns(columns::NUM_COLUMNS);
let _ = db_config.memory_budget
.insert(columns::COL_AVAILABILITY_DATA, cache_sizes.availability_data);
let _ = db_config.memory_budget
.insert(columns::COL_AVAILABILITY_META, cache_sizes.availability_meta);
let _ = db_config.memory_budget
.insert(columns::COL_APPROVAL_DATA, cache_sizes.approval_data);
let path = path.to_str().ok_or_else(|| io::Error::new(
io::ErrorKind::Other,
format!("Bad database path: {:?}", path),
))?;
std::fs::create_dir_all(&path)?;
let db = Database::open(&db_config, &path)?;
Ok(Arc::new(db))
}
@@ -60,6 +60,7 @@ struct ApprovalEntry {
tranches: Vec<TrancheEntry>, // sorted ascending by tranche number.
backing_group: GroupIndex,
our_assignment: Option<OurAssignment>,
our_approval_sig: Option<ValidatorSignature>,
assignments: Bitfield, // n_validators bits
approved: bool,
}
@@ -205,7 +206,7 @@ On receiving a `ApprovalVotingMessage::CheckAndImportAssignment` message, we che
* Ensure the validator index is not present in the approval entry already.
* Create a tranche entry for the delay tranche in the approval entry and note the assignment within it.
* Note the candidate index within the approval entry.
* [Check for full approval of the candidate entry](#check-full-approval) of the candidate_entry, filtering by this specific approval entry.
* [Schedule a wakeup](#schedule-wakeup) for this block, candidate pair.
* return the appropriate `AssignmentCheckResult` on the response channel.
#### `ApprovalVotingMessage::CheckAndImportApproval`
@@ -232,20 +233,15 @@ On receiving an `ApprovedAncestor(Hash, BlockNumber, response_channel)`:
### Updates and Auxiliary Logic
#### Import Checked Approval
* Import an approval vote which we can assume to have passed signature checks.
* Import an approval vote which we can assume to have passed signature checks and correspond to an imported assignment.
* Requires `(BlockEntry, CandidateEntry, ValidatorIndex)`
* Set the corresponding bit of the `approvals` bitfield in the `CandidateEntry` to `1`. If already `1`, return.
* [Check full approval of the candidate](#check-full-approval)
#### Check Full Approval
* Checks the approval state of the candidate under every block it is included by, and updates the block entries accordingly.
* Requires `(CandidateEntry, filter)`, where filter is used to limit which approval entries are inspected.
* Checks every `ApprovalEntry` that is not yet `approved` for whether it is now approved.
* For each `ApprovalEntry` in the `CandidateEntry` that is not `approved` and passes the `filter`
* Load the block entry for the `ApprovalEntry`.
* If so, [determine the tranches to inspect](#determine-required-tranches) of the candidate,
* If [the candidate is approved under the block](#check-approval), set the corresponding bit in the `block_entry.approved_bitfield`.
* Checks the approval state of a candidate under a specific block, and updates the block and candidate entries accordingly.
* Checks the `ApprovalEntry` for the block.
* [determine the tranches to inspect](#determine-required-tranches) of the candidate,
* [the candidate is approved under the block](#check-approval), set the corresponding bit in the `block_entry.approved_bitfield`.
* Otherwise, [schedule a wakeup of the candidate](#schedule-wakeup)
* If the approval vote originates locally, set the `our_approval_sig` in the candidate entry.
#### Handling Wakeup
* Handle a previously-scheduled wakeup of a candidate under a specific block.
@@ -59,6 +59,8 @@ Each network event is associated with a particular peer-set.
The `activated` and `deactivated` lists determine the evolution of our local view over time. A `ProtocolMessage::ViewUpdate` is issued to each connected peer on each peer-set, and a `NetworkBridgeEvent::OurViewChange` is issued to each event handler for each protocol.
We only send view updates if the node has indicated that it has finished major blockchain synchronization.
If we are connected to the same peer on both peer-sets, we will send the peer two view updates as a result.
### Overseer Signal: BlockFinalized
@@ -67,7 +69,7 @@ We update our view's `finalized_number` to the provided one and delay `ProtocolM
### Network Event: Peer Connected
Issue a `NetworkBridgeEvent::PeerConnected` for each [Event Handler](#event-handlers) of the peer-set and negotiated protocol version of the peer. Also issue a `NetworkBridgeEvent::PeerViewChange` and send the peer our current view.
Issue a `NetworkBridgeEvent::PeerConnected` for each [Event Handler](#event-handlers) of the peer-set and negotiated protocol version of the peer. Also issue a `NetworkBridgeEvent::PeerViewChange` and send the peer our current view, but only if the node has indicated that it has finished major blockchain synchronization. Otherwise, we only send the peer an empty view.
### Network Event: Peer Disconnected