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

Commit cab7ae5

Browse files
ordianmrcnskibkchrpepoviola
authored
dispute-coordinator: past session dispute slashing (#6811)
* runtime/vstaging: unapplied_slashes runtime API * runtime/vstaging: key_ownership_proof runtime API * runtime/ParachainHost: submit_report_dispute_lost * fix key_ownership_proof API * runtime: submit_report_dispute_lost runtime API * nits * Update node/subsystem-types/src/messages.rs Co-authored-by: Marcin S. <[email protected]> * revert unrelated fmt changes * dispute-coordinator: past session dispute slashing * encapsule runtime api call for submitting report * prettify: extract into a function * do not exit on runtime api error * fix tests * try initial zombienet test * try something * fix a typo * try cumulus-based collator * fix clippy * build polkadot-debug images with fast-runtime enabled * wip * runtime/inclusion: fix availability_threshold * fix wip * fix wip II * revert native provider * propagate tx submission error * DEBUG: sync=trace * print key ownership proof len * panic repro * log validator index in panic message * post merge fixes * replace debug assertion with a log * fix compilation * Let's log the dispatch info in validate block. * fix double encoding * Revert "Let's log the dispatch info in validate block." This reverts commit a70fbc5. * Revert "Let's log the dispatch info in validate block." This reverts commit a70fbc5. * fix compilation * update to latest zombienet and fix test * lower finality lag to 11 * bump zombienet again * add a workaround, but still does not work * Update .gitlab-ci.yml bump zombienet. * add a comment and search logs on all nodes --------- Co-authored-by: Marcin S. <[email protected]> Co-authored-by: Bastian Köcher <[email protected]> Co-authored-by: Javier Viola <[email protected]>
1 parent bfb9e87 commit cab7ae5

File tree

12 files changed

+407
-46
lines changed

12 files changed

+407
-46
lines changed

node/core/dispute-coordinator/src/initialized.rs

Lines changed: 156 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ use polkadot_node_subsystem::{
3939
},
4040
overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal,
4141
};
42-
use polkadot_node_subsystem_util::runtime::RuntimeInfo;
42+
use polkadot_node_subsystem_util::runtime::{
43+
key_ownership_proof, submit_report_dispute_lost, RuntimeInfo,
44+
};
4345
use polkadot_primitives::{
44-
BlockNumber, CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement,
46+
vstaging, BlockNumber, CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement,
4547
DisputeStatementSet, Hash, ScrapedOnChainVotes, SessionIndex, ValidDisputeStatementKind,
4648
ValidatorId, ValidatorIndex,
4749
};
@@ -52,6 +54,7 @@ use crate::{
5254
import::{CandidateEnvironment, CandidateVoteState},
5355
is_potential_spam,
5456
metrics::Metrics,
57+
scraping::ScrapedUpdates,
5558
status::{get_active_with_status, Clock},
5659
DisputeCoordinatorSubsystem, LOG_TARGET,
5760
};
@@ -348,27 +351,167 @@ impl Initialized {
348351
},
349352
}
350353

354+
let ScrapedUpdates { unapplied_slashes, on_chain_votes, .. } = scraped_updates;
355+
356+
self.process_unapplied_slashes(ctx, new_leaf.hash, unapplied_slashes).await;
357+
351358
gum::trace!(
352359
target: LOG_TARGET,
353360
timestamp = now,
354361
"Will process {} onchain votes",
355-
scraped_updates.on_chain_votes.len()
362+
on_chain_votes.len()
356363
);
357364

358-
self.process_chain_import_backlog(
359-
ctx,
360-
overlay_db,
361-
scraped_updates.on_chain_votes,
362-
now,
363-
new_leaf.hash,
364-
)
365-
.await;
365+
self.process_chain_import_backlog(ctx, overlay_db, on_chain_votes, now, new_leaf.hash)
366+
.await;
366367
}
367368

368369
gum::trace!(target: LOG_TARGET, timestamp = now, "Done processing ActiveLeavesUpdate");
369370
Ok(())
370371
}
371372

373+
/// For each unapplied (past-session) slash, report an unsigned extrinsic
374+
/// to the runtime.
375+
async fn process_unapplied_slashes<Context>(
376+
&mut self,
377+
ctx: &mut Context,
378+
relay_parent: Hash,
379+
unapplied_slashes: Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>,
380+
) {
381+
for (session_index, candidate_hash, pending) in unapplied_slashes {
382+
gum::info!(
383+
target: LOG_TARGET,
384+
?session_index,
385+
?candidate_hash,
386+
n_slashes = pending.keys.len(),
387+
"Processing unapplied validator slashes",
388+
);
389+
390+
let inclusions = self.scraper.get_blocks_including_candidate(&candidate_hash);
391+
if inclusions.is_empty() {
392+
gum::info!(
393+
target: LOG_TARGET,
394+
"Couldn't find inclusion parent for an unapplied slash",
395+
);
396+
return
397+
}
398+
399+
// Find the first inclusion parent that we can use
400+
// to generate key ownership proof on.
401+
// We use inclusion parents because of the proper session index.
402+
let mut key_ownership_proofs = Vec::new();
403+
let mut dispute_proofs = Vec::new();
404+
405+
for (_height, inclusion_parent) in inclusions {
406+
for (validator_index, validator_id) in pending.keys.iter() {
407+
let res =
408+
key_ownership_proof(ctx.sender(), inclusion_parent, validator_id.clone())
409+
.await;
410+
411+
match res {
412+
Ok(Some(key_ownership_proof)) => {
413+
key_ownership_proofs.push(key_ownership_proof);
414+
let time_slot = vstaging::slashing::DisputesTimeSlot::new(
415+
session_index,
416+
candidate_hash,
417+
);
418+
let dispute_proof = vstaging::slashing::DisputeProof {
419+
time_slot,
420+
kind: pending.kind,
421+
validator_index: *validator_index,
422+
validator_id: validator_id.clone(),
423+
};
424+
dispute_proofs.push(dispute_proof);
425+
},
426+
Ok(None) => {},
427+
Err(error) => {
428+
gum::debug!(
429+
target: LOG_TARGET,
430+
?error,
431+
?session_index,
432+
?candidate_hash,
433+
?validator_id,
434+
"Could not generate key ownership proof",
435+
);
436+
},
437+
}
438+
}
439+
440+
if !key_ownership_proofs.is_empty() {
441+
// If we found a parent that we can use, stop searching.
442+
// If one key ownership was resolved successfully, all of them should be.
443+
debug_assert_eq!(key_ownership_proofs.len(), pending.keys.len());
444+
break
445+
}
446+
}
447+
448+
let expected_keys = pending.keys.len();
449+
let resolved_keys = key_ownership_proofs.len();
450+
if resolved_keys < expected_keys {
451+
gum::warn!(
452+
target: LOG_TARGET,
453+
?session_index,
454+
?candidate_hash,
455+
"Could not generate key ownership proofs for {} keys",
456+
expected_keys - resolved_keys,
457+
);
458+
}
459+
debug_assert_eq!(resolved_keys, dispute_proofs.len());
460+
461+
for (key_ownership_proof, dispute_proof) in
462+
key_ownership_proofs.into_iter().zip(dispute_proofs.into_iter())
463+
{
464+
let validator_id = dispute_proof.validator_id.clone();
465+
466+
gum::info!(
467+
target: LOG_TARGET,
468+
?session_index,
469+
?candidate_hash,
470+
key_ownership_proof_len = key_ownership_proof.len(),
471+
"Trying to submit a slashing report",
472+
);
473+
474+
let res = submit_report_dispute_lost(
475+
ctx.sender(),
476+
relay_parent,
477+
dispute_proof,
478+
key_ownership_proof,
479+
)
480+
.await;
481+
482+
match res {
483+
Err(error) => {
484+
gum::warn!(
485+
target: LOG_TARGET,
486+
?error,
487+
?session_index,
488+
?candidate_hash,
489+
"Error reporting pending slash",
490+
);
491+
},
492+
Ok(Some(())) => {
493+
gum::info!(
494+
target: LOG_TARGET,
495+
?session_index,
496+
?candidate_hash,
497+
?validator_id,
498+
"Successfully reported pending slash",
499+
);
500+
},
501+
Ok(None) => {
502+
gum::debug!(
503+
target: LOG_TARGET,
504+
?session_index,
505+
?candidate_hash,
506+
?validator_id,
507+
"Duplicate pending slash report",
508+
);
509+
},
510+
}
511+
}
512+
}
513+
}
514+
372515
/// Process one batch of our `chain_import_backlog`.
373516
///
374517
/// `new_votes` will be appended beforehand.
@@ -475,10 +618,11 @@ impl Initialized {
475618
validator_public.clone(),
476619
validator_signature.clone(),
477620
).is_ok(),
478-
"Scraped backing votes had invalid signature! candidate: {:?}, session: {:?}, validator_public: {:?}",
621+
"Scraped backing votes had invalid signature! candidate: {:?}, session: {:?}, validator_public: {:?}, validator_index: {}",
479622
candidate_hash,
480623
session,
481624
validator_public,
625+
validator_index.0,
482626
);
483627
let signed_dispute_statement =
484628
SignedDisputeStatement::new_unchecked_from_trusted_source(

node/core/dispute-coordinator/src/scraping/mod.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ use polkadot_node_subsystem::{
2727
messages::ChainApiMessage, overseer, ActivatedLeaf, ActiveLeavesUpdate, ChainApiError,
2828
SubsystemSender,
2929
};
30-
use polkadot_node_subsystem_util::runtime::{get_candidate_events, get_on_chain_votes};
30+
use polkadot_node_subsystem_util::runtime::{
31+
get_candidate_events, get_on_chain_votes, get_unapplied_slashes,
32+
};
3133
use polkadot_primitives::{
32-
BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, Hash, ScrapedOnChainVotes,
34+
vstaging, BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, Hash,
35+
ScrapedOnChainVotes, SessionIndex,
3336
};
3437

3538
use crate::{
@@ -64,11 +67,16 @@ const LRU_OBSERVED_BLOCKS_CAPACITY: NonZeroUsize = match NonZeroUsize::new(20) {
6467
pub struct ScrapedUpdates {
6568
pub on_chain_votes: Vec<ScrapedOnChainVotes>,
6669
pub included_receipts: Vec<CandidateReceipt>,
70+
pub unapplied_slashes: Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>,
6771
}
6872

6973
impl ScrapedUpdates {
7074
pub fn new() -> Self {
71-
Self { on_chain_votes: Vec::new(), included_receipts: Vec::new() }
75+
Self {
76+
on_chain_votes: Vec::new(),
77+
included_receipts: Vec::new(),
78+
unapplied_slashes: Vec::new(),
79+
}
7280
}
7381
}
7482

@@ -120,7 +128,7 @@ impl Inclusions {
120128
.retain(|_, blocks_including| blocks_including.keys().len() > 0);
121129
}
122130

123-
pub fn get(&mut self, candidate: &CandidateHash) -> Vec<(BlockNumber, Hash)> {
131+
pub fn get(&self, candidate: &CandidateHash) -> Vec<(BlockNumber, Hash)> {
124132
let mut inclusions_as_vec: Vec<(BlockNumber, Hash)> = Vec::new();
125133
if let Some(blocks_including) = self.inclusions_inner.get(candidate) {
126134
for (height, blocks_at_height) in blocks_including.iter() {
@@ -256,6 +264,22 @@ impl ChainScraper {
256264
}
257265
}
258266

267+
// for unapplied slashes, we only look at the latest activated hash,
268+
// it should accumulate them all
269+
match get_unapplied_slashes(sender, activated.hash).await {
270+
Ok(unapplied_slashes) => {
271+
scraped_updates.unapplied_slashes = unapplied_slashes;
272+
},
273+
Err(error) => {
274+
gum::debug!(
275+
target: LOG_TARGET,
276+
block_hash = ?activated.hash,
277+
?error,
278+
"Error fetching unapplied slashes.",
279+
);
280+
},
281+
}
282+
259283
self.last_observed_blocks.put(activated.hash, ());
260284

261285
Ok(scraped_updates)
@@ -403,7 +427,7 @@ impl ChainScraper {
403427
}
404428

405429
pub fn get_blocks_including_candidate(
406-
&mut self,
430+
&self,
407431
candidate: &CandidateHash,
408432
) -> Vec<(BlockNumber, Hash)> {
409433
self.inclusions.get(candidate)

node/core/dispute-coordinator/src/scraping/tests.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ impl TestState {
8181
)
8282
.await;
8383
assert_chain_vote_request(&mut ctx_handle, &chain).await;
84+
assert_unapplied_slashes_request(&mut ctx_handle, &chain).await;
8485
};
8586

8687
let (scraper, _) = join(ChainScraper::new(ctx.sender(), leaf.clone()), overseer_fut)
@@ -242,6 +243,18 @@ async fn assert_chain_vote_request(virtual_overseer: &mut VirtualOverseer, _chai
242243
);
243244
}
244245

246+
async fn assert_unapplied_slashes_request(virtual_overseer: &mut VirtualOverseer, _chain: &[Hash]) {
247+
assert_matches!(
248+
overseer_recv(virtual_overseer).await,
249+
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
250+
_hash,
251+
RuntimeApiRequest::UnappliedSlashes(tx),
252+
)) => {
253+
tx.send(Ok(Vec::new())).unwrap();
254+
}
255+
);
256+
}
257+
245258
async fn assert_finalized_block_number_request(
246259
virtual_overseer: &mut VirtualOverseer,
247260
response: BlockNumber,
@@ -287,6 +300,7 @@ async fn overseer_process_active_leaves_update<F>(
287300
assert_candidate_events_request(virtual_overseer, chain, event_generator.clone()).await;
288301
assert_chain_vote_request(virtual_overseer, chain).await;
289302
}
303+
assert_unapplied_slashes_request(virtual_overseer, chain).await;
290304
}
291305

292306
#[test]

node/core/dispute-coordinator/src/tests.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,12 @@ impl TestState {
385385
})))
386386
.unwrap();
387387
},
388+
AllMessages::RuntimeApi(RuntimeApiMessage::Request(
389+
_new_leaf,
390+
RuntimeApiRequest::UnappliedSlashes(tx),
391+
)) => {
392+
tx.send(Ok(Vec::new())).unwrap();
393+
},
388394
AllMessages::ChainApi(ChainApiMessage::Ancestors { hash, k, response_channel }) => {
389395
let target_header = self
390396
.headers

node/subsystem-util/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ use futures::channel::{mpsc, oneshot};
4242
use parity_scale_codec::Encode;
4343

4444
use polkadot_primitives::{
45-
AuthorityDiscoveryId, CandidateEvent, CommittedCandidateReceipt, CoreState, EncodeAs,
46-
GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption,
45+
vstaging, AuthorityDiscoveryId, CandidateEvent, CandidateHash, CommittedCandidateReceipt,
46+
CoreState, EncodeAs, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption,
4747
PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed,
4848
SigningContext, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex,
4949
ValidatorSignature,
@@ -211,7 +211,10 @@ specialize_requests! {
211211
fn request_validation_code_hash(para_id: ParaId, assumption: OccupiedCoreAssumption)
212212
-> Option<ValidationCodeHash>; ValidationCodeHash;
213213
fn request_on_chain_votes() -> Option<ScrapedOnChainVotes>; FetchOnChainVotes;
214-
fn request_session_executor_params(session_index: SessionIndex) -> Option<ExecutorParams>; SessionExecutorParams;
214+
fn request_session_executor_params(session_index: SessionIndex) -> Option<ExecutorParams>;SessionExecutorParams;
215+
fn request_unapplied_slashes() -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>; UnappliedSlashes;
216+
fn request_key_ownership_proof(validator_id: ValidatorId) -> Option<vstaging::slashing::OpaqueKeyOwnershipProof>; KeyOwnershipProof;
217+
fn request_submit_report_dispute_lost(dp: vstaging::slashing::DisputeProof, okop: vstaging::slashing::OpaqueKeyOwnershipProof) -> Option<()>; SubmitReportDisputeLost;
215218
}
216219

217220
/// Requests executor parameters from the runtime effective at given relay-parent. First obtains

0 commit comments

Comments
 (0)