Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Issue 4804: Notify chain selection of concluded disputes directly #6512

Merged
merged 33 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
658f9de
Setting up new ChainSelectionMessage
BradleyOlson64 Dec 28, 2022
c66ea70
Partial first pass
BradleyOlson64 Jan 2, 2023
8ef1202
Got dispute conclusion data to provisioner
BradleyOlson64 Jan 3, 2023
e69a5b4
Finished first draft for 4804 code
BradleyOlson64 Jan 5, 2023
94e231a
Merge branch 'master' of https://github.com/paritytech/polkadot into …
BradleyOlson64 Jan 5, 2023
b370985
A bit of polish and code comments
BradleyOlson64 Jan 5, 2023
043f505
cargo fmt
BradleyOlson64 Jan 5, 2023
85db7c9
Implementers guide and code comments
BradleyOlson64 Jan 5, 2023
161122d
More formatting, and naming issues
BradleyOlson64 Jan 6, 2023
6a72033
Wrote test for ChainSelection side of change
BradleyOlson64 Jan 6, 2023
59e0453
Added dispute coordinator side test
BradleyOlson64 Jan 6, 2023
e12bfca
Merge branch 'master' of https://github.com/paritytech/polkadot into …
BradleyOlson64 Jan 6, 2023
682545d
FMT
BradleyOlson64 Jan 6, 2023
3b026ad
Merge branch 'master' of https://github.com/paritytech/polkadot into …
BradleyOlson64 Jan 9, 2023
87fc1f5
Addressing Marcin's comments
BradleyOlson64 Jan 9, 2023
013c4eb
fmt
BradleyOlson64 Jan 9, 2023
6442838
Addressing further Marcin comment
BradleyOlson64 Jan 9, 2023
bd6fe63
Removing unnecessary test line
BradleyOlson64 Jan 9, 2023
eff981b
Rough draft addressing Robert changes
BradleyOlson64 Jan 11, 2023
428e016
Clean up and test modification
BradleyOlson64 Jan 12, 2023
9f6f2ab
Majorly refactored scraper change
BradleyOlson64 Jan 13, 2023
5b89cf8
Minor fixes for ChainSelection
BradleyOlson64 Jan 13, 2023
d2454d8
Polish and fmt
BradleyOlson64 Jan 13, 2023
32b6f79
Condensing inclusions per candidate logic
BradleyOlson64 Jan 13, 2023
548e11b
Addressing Tsveto's comments
BradleyOlson64 Jan 16, 2023
bd283f0
Addressing Robert's Comments
BradleyOlson64 Jan 17, 2023
a257a1c
Altered inclusions struct to use nested BTreeMaps
BradleyOlson64 Jan 17, 2023
7c1149b
Naming fix
BradleyOlson64 Jan 17, 2023
a75cda3
Fixing inclusions struct comments
BradleyOlson64 Jan 17, 2023
992f240
Update node/core/dispute-coordinator/src/scraping/mod.rs
BradleyOlson64 Jan 17, 2023
492c7ed
Optimizing removal at block height for inclusions
BradleyOlson64 Jan 18, 2023
f6779cd
fmt
BradleyOlson64 Jan 18, 2023
f9b92fa
Using copy trait
BradleyOlson64 Jan 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion node/core/chain-selection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ fn handle_approved_block(backend: &mut impl Backend, approved_block: Hash) -> Re
// the dispute coordinator has notified chain selection
fn handle_revert_blocks(
backend: &impl Backend,
blocks_to_revert: HashSet<(BlockNumber, Hash)>,
blocks_to_revert: Vec<(BlockNumber, Hash)>,
) -> Result<Vec<BackendWriteOp>, Error> {
let mut overlay = OverlayedBackend::new(backend);
for (block_number, block_hash) in blocks_to_revert {
Expand Down
2 changes: 1 addition & 1 deletion node/core/dispute-coordinator/src/initialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@ impl Initialized {
// will need to mark the candidate's relay parent as reverted.
if import_result.is_freshly_concluded_against() {
if let Some(blocks_to_revert) =
self.scraper.get_blocks_including_candidate(candidate_hash)
self.scraper.get_blocks_including_candidate(&candidate_hash)
{
ctx.send_message(ChainSelectionMessage::RevertBlocks(blocks_to_revert.clone()))
.await;
Expand Down
6 changes: 1 addition & 5 deletions node/core/dispute-coordinator/src/scraping/candidates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,15 @@ impl ScrapedCandidates {
}

// Removes all candidates up to a given height. The candidates at the block height are NOT removed.
pub fn remove_up_to_height(&mut self, height: &BlockNumber) -> HashSet<CandidateHash> {
let mut unique_candidates: HashSet<CandidateHash> = HashSet::new();
pub fn remove_up_to_height(&mut self, height: &BlockNumber) {
let not_stale = self.candidates_by_block_number.split_off(&height);
let stale = std::mem::take(&mut self.candidates_by_block_number);
self.candidates_by_block_number = not_stale;
for candidates in stale.values() {
for c in candidates {
self.candidates.remove(c);
unique_candidates.insert(c.clone());
}
}

unique_candidates
}

pub fn insert(&mut self, block_number: BlockNumber, candidate_hash: CandidateHash) {
Expand Down
91 changes: 63 additions & 28 deletions node/core/dispute-coordinator/src/scraping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.

use std::{
collections::{HashMap, HashSet},
num::NonZeroUsize,
};
use std::{collections::HashMap, num::NonZeroUsize};

use futures::channel::oneshot;
use lru::LruCache;
Expand Down Expand Up @@ -72,6 +69,60 @@ impl ScrapedUpdates {
}
}

/// A structure meant to facilitate chain reversions in the event of a dispute
/// concluding against a candidate. Each candidate hash maps to a vector of block
/// numbers and hashes for all blocks which included the candidate. The entries
/// in each vector are ordered by decreasing parent block number to facilitate
/// minimal cost pruning.
pub struct InclusionsPerCandidate {
inclusions_inner: HashMap<CandidateHash, Vec<(BlockNumber, Hash)>>,
BradleyOlson64 marked this conversation as resolved.
Show resolved Hide resolved
}

impl InclusionsPerCandidate {
BradleyOlson64 marked this conversation as resolved.
Show resolved Hide resolved
pub fn new() -> Self {
Self { inclusions_inner: HashMap::new() }
}

// Insert parent block into vector for the candidate hash it is including,
BradleyOlson64 marked this conversation as resolved.
Show resolved Hide resolved
// maintaining ordering by decreasing parent block number.
pub fn insert(
&mut self,
candidate_hash: CandidateHash,
block_number: BlockNumber,
block_hash: Hash,
) {
if let Some(blocks_including) = self.inclusions_inner.get_mut(&candidate_hash) {
for idx in 0..blocks_including.len() {
if blocks_including[idx].0 < block_number {
// Idx is between 0 and blocks_including.len(), therefore in bounds. QED
blocks_including.insert(idx, (block_number, block_hash));
BradleyOlson64 marked this conversation as resolved.
Show resolved Hide resolved
} else if idx == blocks_including.len() - 1 {
blocks_including.push((block_number, block_hash));
}
}
} else {
self.inclusions_inner
.insert(candidate_hash, Vec::from([(block_number, block_hash)]));
}
}

pub fn remove_up_to_height(&mut self, height: &BlockNumber) {
for including_blocks in self.inclusions_inner.values_mut() {
while including_blocks.len() > 0 &&
including_blocks[including_blocks.len() - 1].0 < *height
{
// Since parent_blocks length is positive, parent_blocks.len() - 1 is in bounds. QED
including_blocks.pop();
}
}
self.inclusions_inner.retain(|_, including_blocks| including_blocks.len() > 0);
}

pub fn get(&mut self, candidate: &CandidateHash) -> Option<&Vec<(BlockNumber, Hash)>> {
self.inclusions_inner.get(candidate)
}
}

/// Chain scraper
///
/// Scrapes unfinalized chain in order to collect information from blocks. Chain scraping
Expand Down Expand Up @@ -105,7 +156,7 @@ pub struct ChainScraper {
/// These correspond to all the relay blocks which marked a candidate as included,
/// and are needed to apply reversions in case a dispute is concluded against the
/// candidate.
inclusions_per_candidate: HashMap<CandidateHash, HashSet<(BlockNumber, Hash)>>,
inclusions_per_candidate: InclusionsPerCandidate,
}

impl ChainScraper {
Expand All @@ -130,7 +181,7 @@ impl ChainScraper {
included_candidates: candidates::ScrapedCandidates::new(),
backed_candidates: candidates::ScrapedCandidates::new(),
last_observed_blocks: LruCache::new(LRU_OBSERVED_BLOCKS_CAPACITY),
inclusions_per_candidate: HashMap::new(),
inclusions_per_candidate: InclusionsPerCandidate::new(),
};
let update =
ActiveLeavesUpdate { activated: Some(initial_head), deactivated: Default::default() };
Expand Down Expand Up @@ -207,19 +258,8 @@ impl ChainScraper {
{
Some(key_to_prune) => {
self.backed_candidates.remove_up_to_height(&key_to_prune);
let candidates_pruned = self.included_candidates.remove_up_to_height(&key_to_prune);
// Removing entries from inclusions per candidate referring to blocks before
// key_to_prune
for candidate_hash in candidates_pruned {
self.inclusions_per_candidate.entry(candidate_hash).and_modify(|inclusions| {
inclusions.retain(|inclusion| inclusion.0 >= key_to_prune);
});
if let Some(inclusions) = self.inclusions_per_candidate.get(&candidate_hash) {
if inclusions.is_empty() {
self.inclusions_per_candidate.remove(&candidate_hash);
}
}
}
self.included_candidates.remove_up_to_height(&key_to_prune);
self.inclusions_per_candidate.remove_up_to_height(&key_to_prune)
},
None => {
// Nothing to prune. We are still in the beginning of the chain and there are not
Expand Down Expand Up @@ -257,12 +297,7 @@ impl ChainScraper {
"Processing included event"
);
self.included_candidates.insert(block_number, candidate_hash);
self.inclusions_per_candidate
.entry(candidate_hash)
.and_modify(|blocks_including| {
blocks_including.insert((block_number, block_hash));
})
.or_insert(HashSet::from([(block_number, block_hash)]));
self.inclusions_per_candidate.insert(candidate_hash, block_number, block_hash);
included_receipts.push(receipt);
},
CandidateEvent::CandidateBacked(receipt, _, _, _) => {
Expand Down Expand Up @@ -351,9 +386,9 @@ impl ChainScraper {

pub fn get_blocks_including_candidate(
&mut self,
candidate: CandidateHash,
) -> Option<&HashSet<(BlockNumber, Hash)>> {
self.inclusions_per_candidate.get(&candidate)
candidate: &CandidateHash,
) -> Option<&Vec<(BlockNumber, Hash)>> {
self.inclusions_per_candidate.get(candidate)
}
}

Expand Down
70 changes: 70 additions & 0 deletions node/core/dispute-coordinator/src/scraping/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,3 +578,73 @@ fn scraper_handles_the_same_candidate_incuded_in_two_different_block_heights() {
assert!(!scraper.is_candidate_included(&magic_candidate.hash()));
});
}

#[test]
fn inclusions_per_candidate_properly_adds_and_prunes() {
const TEST_TARGET_BLOCK_NUMBER: BlockNumber = 2;
const TEST_TARGET_BLOCK_NUMBER_2: BlockNumber = 3;

// How many blocks should we skip before sending a leaf update.
const BLOCKS_TO_SKIP: usize = 4;

futures::executor::block_on(async {
let (state, mut virtual_overseer) = TestState::new().await;

let TestState { mut chain, mut scraper, mut ctx } = state;

// 1 because `TestState` starts at leaf 1.
let next_update = (1..BLOCKS_TO_SKIP).map(|_| next_leaf(&mut chain)).last().unwrap();

let mut finalized_block_number = 1;
let expected_ancestry_len = BLOCKS_TO_SKIP - finalized_block_number as usize;
let overseer_fut = overseer_process_active_leaves_update(
&mut virtual_overseer,
&chain,
finalized_block_number,
expected_ancestry_len,
|block_num| {
if block_num == TEST_TARGET_BLOCK_NUMBER || block_num == TEST_TARGET_BLOCK_NUMBER_2
{
get_backed_and_included_candidate_events(TEST_TARGET_BLOCK_NUMBER)
} else {
vec![]
}
},
);
join(process_active_leaves_update(ctx.sender(), &mut scraper, next_update), overseer_fut)
.await;

let candidate = make_candidate_receipt(get_block_number_hash(TEST_TARGET_BLOCK_NUMBER));

// We included the same candidate at two different block heights. So both blocks in which
// the candidate is included are recorded
assert_eq!(
scraper.get_blocks_including_candidate(&candidate.hash()),
Some(&Vec::from([
(TEST_TARGET_BLOCK_NUMBER_2, get_block_number_hash(TEST_TARGET_BLOCK_NUMBER_2)),
(TEST_TARGET_BLOCK_NUMBER, get_block_number_hash(TEST_TARGET_BLOCK_NUMBER))
]))
);

// After `DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION` blocks the earlier inclusion should be removed
finalized_block_number =
TEST_TARGET_BLOCK_NUMBER + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);

// The later inclusion should still be present, as we haven't exceeded its lifetime
assert_eq!(
scraper.get_blocks_including_candidate(&candidate.hash()),
Some(&Vec::from([(
TEST_TARGET_BLOCK_NUMBER_2,
get_block_number_hash(TEST_TARGET_BLOCK_NUMBER_2)
)]))
);

finalized_block_number =
TEST_TARGET_BLOCK_NUMBER_2 + DISPUTE_CANDIDATE_LIFETIME_AFTER_FINALIZATION;
process_finalized_block(&mut scraper, &finalized_block_number);

// Now both inclusions have exceeded their lifetimes after finalization and should be purged
assert_eq!(scraper.get_blocks_including_candidate(&candidate.hash()), None);
});
}
2 changes: 1 addition & 1 deletion node/subsystem-types/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ pub enum ChainSelectionMessage {
BestLeafContaining(Hash, oneshot::Sender<Option<Hash>>),
/// The passed blocks must be marked as reverted, and their children must be marked
/// as non-viable.
RevertBlocks(HashSet<(BlockNumber, Hash)>),
RevertBlocks(Vec<(BlockNumber, Hash)>),
}

/// A sender for the result of a runtime API request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Gets all leaves of the chain, i.e. block hashes that are suitable to build upon
If the required block is unknown or not viable, then return `None`. Iterate over all leaves in order of descending weight, returning the first leaf containing the required block in its chain, and `None` otherwise.

### `ChainSelectionMessage::RevertBlocks`
This message indicates that a dispute has concluded against a parachain block candidate. The message passes along the block number and hash of the disputed candidate's relay parent. The relay parent will be marked as reverted, and its descendants will be marked as non-viable.
This message indicates that a dispute has concluded against a parachain block candidate. The message passes along a vector containing the block number and block hash of each block where the disputed candidate was included. The passed blocks will be marked as reverted, and their descendants will be marked as non-viable.


### Periodically
Expand Down