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 all commits
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
19 changes: 19 additions & 0 deletions node/core/chain-selection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,10 @@ where

let _ = tx.send(best_containing);
}
ChainSelectionMessage::RevertBlocks(blocks_to_revert) => {
let write_ops = handle_revert_blocks(backend, blocks_to_revert)?;
backend.write(write_ops)?;
}
}
}
}
Expand Down Expand Up @@ -678,6 +682,21 @@ fn handle_approved_block(backend: &mut impl Backend, approved_block: Hash) -> Re
backend.write(ops)
}

// Here we revert a provided group of blocks. The most common cause for this is that
// the dispute coordinator has notified chain selection of a dispute which concluded
// against a candidate.
fn handle_revert_blocks(
backend: &impl Backend,
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 {
tree::apply_single_reversion(&mut overlay, block_hash, block_number)?;
}

Ok(overlay.into_write_ops().collect())
}

fn detect_stagnant(
backend: &mut impl Backend,
now: Timestamp,
Expand Down
103 changes: 103 additions & 0 deletions node/core/chain-selection/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2014,3 +2014,106 @@ fn stagnant_makes_childless_parent_leaf() {
virtual_overseer
})
}

#[test]
fn revert_blocks_message_triggers_proper_reversion() {
test_harness(|backend, _, mut virtual_overseer| async move {
// Building mini chain with 1 finalized block and 3 unfinalized blocks
let finalized_number = 0;
let finalized_hash = Hash::repeat_byte(0);

let (head_hash, built_chain) =
construct_chain_on_base(vec![1, 2, 3], finalized_number, finalized_hash, |_| {});

import_blocks_into(
&mut virtual_overseer,
&backend,
Some((finalized_number, finalized_hash)),
built_chain.clone(),
)
.await;

// Checking mini chain
assert_backend_contains(&backend, built_chain.iter().map(|&(ref h, _)| h));
assert_leaves(&backend, vec![head_hash]);
assert_leaves_query(&mut virtual_overseer, vec![head_hash]).await;

let block_1_hash = backend.load_blocks_by_number(1).unwrap().get(0).unwrap().clone();
let block_2_hash = backend.load_blocks_by_number(2).unwrap().get(0).unwrap().clone();

// Sending revert blocks message
let (_, write_rx) = backend.await_next_write();
virtual_overseer
.send(FromOrchestra::Communication {
msg: ChainSelectionMessage::RevertBlocks(Vec::from([(2, block_2_hash)])),
})
.await;

write_rx.await.unwrap();

// Checking results:
// Block 2 should be explicitly reverted
assert_eq!(
backend
.load_block_entry(&block_2_hash)
.unwrap()
.unwrap()
.viability
.explicitly_reverted,
true
);
// Block 3 should be non-viable, with 2 as its earliest unviable ancestor
assert_eq!(
backend
.load_block_entry(&head_hash)
.unwrap()
.unwrap()
.viability
.earliest_unviable_ancestor,
Some(block_2_hash)
);
// Block 1 should be left as the only leaf
assert_leaves(&backend, vec![block_1_hash]);

virtual_overseer
})
}

#[test]
fn revert_blocks_against_finalized_is_ignored() {
test_harness(|backend, _, mut virtual_overseer| async move {
// Building mini chain with 1 finalized block and 3 unfinalized blocks
let finalized_number = 0;
let finalized_hash = Hash::repeat_byte(0);

let (head_hash, built_chain) =
construct_chain_on_base(vec![1], finalized_number, finalized_hash, |_| {});

import_blocks_into(
&mut virtual_overseer,
&backend,
Some((finalized_number, finalized_hash)),
built_chain.clone(),
)
.await;

// Checking mini chain
assert_backend_contains(&backend, built_chain.iter().map(|&(ref h, _)| h));

// Sending dispute concluded against message
virtual_overseer
.send(FromOrchestra::Communication {
msg: ChainSelectionMessage::RevertBlocks(Vec::from([(
finalized_number,
finalized_hash,
)])),
})
.await;

// Leaf should be head if reversion of finalized was properly ignored
assert_leaves(&backend, vec![head_hash]);
assert_leaves_query(&mut virtual_overseer, vec![head_hash]).await;

virtual_overseer
})
}
103 changes: 70 additions & 33 deletions node/core/chain-selection/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ pub(crate) fn import_block(
stagnant_at: Timestamp,
) -> Result<(), Error> {
add_block(backend, block_hash, block_number, parent_hash, weight, stagnant_at)?;
apply_reversions(backend, block_hash, block_number, reversion_logs)?;
apply_ancestor_reversions(backend, block_hash, block_number, reversion_logs)?;

Ok(())
}
Expand Down Expand Up @@ -347,9 +347,9 @@ fn add_block(
Ok(())
}

// Assuming that a block is already imported, accepts the number of the block
// as well as a list of reversions triggered by the block in ascending order.
fn apply_reversions(
/// Assuming that a block is already imported, accepts the number of the block
/// as well as a list of reversions triggered by the block in ascending order.
fn apply_ancestor_reversions(
backend: &mut OverlayedBackend<impl Backend>,
block_hash: Hash,
block_number: BlockNumber,
Expand All @@ -358,39 +358,76 @@ fn apply_reversions(
// Note: since revert numbers are in ascending order, the expensive propagation
// of unviability is only heavy on the first log.
for revert_number in reversions {
let mut ancestor_entry =
match load_ancestor(backend, block_hash, block_number, revert_number)? {
None => {
gum::warn!(
target: LOG_TARGET,
?block_hash,
block_number,
revert_target = revert_number,
"The hammer has dropped. \
A block has indicated that its finalized ancestor be reverted. \
Please inform an adult.",
);
let maybe_block_entry = load_ancestor(backend, block_hash, block_number, revert_number)?;
revert_single_block_entry_if_present(
backend,
maybe_block_entry,
None,
revert_number,
Some(block_hash),
Some(block_number),
)?;
}

continue
},
Some(ancestor_entry) => {
gum::info!(
target: LOG_TARGET,
?block_hash,
block_number,
revert_target = revert_number,
revert_hash = ?ancestor_entry.block_hash,
"A block has signaled that its ancestor be reverted due to a bad parachain block.",
);
Ok(())
}

ancestor_entry
},
};
/// Marks a single block as explicitly reverted, then propagates viability updates
/// to all its children. This is triggered when the disputes subsystem signals that
/// a dispute has concluded against a candidate.
pub(crate) fn apply_single_reversion(
eskimor marked this conversation as resolved.
Show resolved Hide resolved
backend: &mut OverlayedBackend<impl Backend>,
revert_hash: Hash,
revert_number: BlockNumber,
) -> Result<(), Error> {
let maybe_block_entry = backend.load_block_entry(&revert_hash)?;
revert_single_block_entry_if_present(
backend,
maybe_block_entry,
Some(revert_hash),
revert_number,
None,
None,
)?;
Ok(())
}

ancestor_entry.viability.explicitly_reverted = true;
propagate_viability_update(backend, ancestor_entry)?;
}
fn revert_single_block_entry_if_present(
backend: &mut OverlayedBackend<impl Backend>,
maybe_block_entry: Option<BlockEntry>,
maybe_revert_hash: Option<Hash>,
revert_number: BlockNumber,
maybe_reporting_hash: Option<Hash>,
maybe_reporting_number: Option<BlockNumber>,
) -> Result<(), Error> {
match maybe_block_entry {
None => {
gum::warn!(
target: LOG_TARGET,
?maybe_revert_hash,
revert_target = revert_number,
?maybe_reporting_hash,
?maybe_reporting_number,
"The hammer has dropped. \
The protocol has indicated that a finalized block be reverted. \
Please inform an adult.",
);
},
Some(mut block_entry) => {
gum::info!(
target: LOG_TARGET,
?maybe_revert_hash,
revert_target = revert_number,
?maybe_reporting_hash,
?maybe_reporting_number,
"Unfinalized block reverted due to a bad parachain block.",
);

block_entry.viability.explicitly_reverted = true;
// Marks children of reverted block as non-viable
propagate_viability_update(backend, block_entry)?;
},
}
Ok(())
}

Expand Down
18 changes: 17 additions & 1 deletion node/core/dispute-coordinator/src/initialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use polkadot_node_primitives::{
};
use polkadot_node_subsystem::{
messages::{
ApprovalVotingMessage, BlockDescription, DisputeCoordinatorMessage,
ApprovalVotingMessage, BlockDescription, ChainSelectionMessage, DisputeCoordinatorMessage,
DisputeDistributionMessage, ImportStatementsResult,
},
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal,
Expand Down Expand Up @@ -1027,6 +1027,22 @@ impl Initialized {
}
}

// Notify ChainSelection if a dispute has concluded against a candidate. ChainSelection
// will need to mark the candidate's relay parent as reverted.
if import_result.is_freshly_concluded_against() {
let blocks_including = self.scraper.get_blocks_including_candidate(&candidate_hash);
if blocks_including.len() > 0 {
ctx.send_message(ChainSelectionMessage::RevertBlocks(blocks_including)).await;
} else {
gum::debug!(
target: LOG_TARGET,
?candidate_hash,
?session,
"Could not find an including block for candidate against which a dispute has concluded."
);
}
}

// Update metrics:
if import_result.is_freshly_disputed() {
self.metrics.on_open();
Expand Down
5 changes: 4 additions & 1 deletion node/core/dispute-coordinator/src/scraping/candidates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,18 @@ 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) {
pub fn remove_up_to_height(&mut self, height: &BlockNumber) -> HashSet<CandidateHash> {
let mut candidates_modified: HashSet<CandidateHash> = HashSet::new();
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);
candidates_modified.insert(*c);
}
}
candidates_modified
}

pub fn insert(&mut self, block_number: BlockNumber, candidate_hash: CandidateHash) {
Expand Down
Loading