diff --git a/essentials/src/collector/mod.rs b/essentials/src/collector/mod.rs index 6f4f0ffc..0defbe4e 100644 --- a/essentials/src/collector/mod.rs +++ b/essentials/src/collector/mod.rs @@ -19,13 +19,14 @@ mod ws; use crate::{ api::{ - subxt_wrapper::{RequestExecutor, SubxtWrapperError}, + subxt_wrapper::{InherentData, RequestExecutor, SubxtWrapperError}, ApiService, }, chain_events::{ decode_chain_event, ChainEvent, SubxtCandidateEvent, SubxtCandidateEventType, SubxtDispute, SubxtDisputeResult, }, chain_subscription::ChainSubscriptionEvent, + metadata::polkadot_primitives::DisputeStatement, storage::{RecordTime, RecordsStorageConfig, StorageEntry}, types::{Timestamp, H256}, utils::RetryOptions, @@ -112,6 +113,8 @@ pub type CollectorStorageApi = ApiService; #[derive(Clone, Debug, Encode, Decode)] pub struct DisputeInfo { pub initiated: ::Index, + pub initiator_indices: Vec, + pub session_index: u32, pub dispute: SubxtDispute, pub parachain_id: u32, pub outcome: Option, @@ -541,7 +544,17 @@ impl Collector { self.state.current_session_index = cur_session; self.broadcast_event(CollectorUpdateEvent::NewSession(cur_session)).await?; } + self.write_parainherent_data(block_hash, block_number, ts).await?; + Ok(()) + } + + async fn write_parainherent_data( + &mut self, + block_hash: H256, + block_number: u32, + ts: Timestamp, + ) -> color_eyre::Result<(), CollectorError> { let inherent_data = self .executor .extract_parainherent_data(self.endpoint.as_str(), Some(block_hash)) @@ -767,6 +780,7 @@ impl Collector { let now = get_unix_time_unwrap(); let para_id = candidate.parachain_id(); candidate.candidate_disputed = Some(CandidateDisputed { disputed: relay_block_number, concluded: None }); + let (initiator_indices, session_index) = self.extract_dispute_initiators(dispute_event).await?; if let Some(to_websocket) = self.to_websocket.as_mut() { to_websocket .send(WebSocketUpdateEvent { @@ -781,7 +795,9 @@ impl Collector { // Fill and write dispute info structure let dispute_info = DisputeInfo { dispute: dispute_event.clone(), - initiated: self.state.current_relay_chain_block_number, + initiated: relay_block_number, + initiator_indices, + session_index, concluded: None, parachain_id: candidate.parachain_id(), outcome: None, @@ -796,10 +812,7 @@ impl Collector { self.storage_write_prefixed( CollectorPrefixType::Dispute(para_id), dispute_event.candidate_hash, - StorageEntry::new_onchain( - RecordTime::with_ts(self.state.current_relay_chain_block_number, now), - dispute_info, - ), + StorageEntry::new_onchain(RecordTime::with_ts(relay_block_number, now), dispute_info), ) .await?; @@ -807,13 +820,47 @@ impl Collector { self.storage_replace_prefixed( CollectorPrefixType::Candidate(para_id), dispute_event.candidate_hash, - StorageEntry::new_onchain(RecordTime::with_ts(self.state.current_relay_chain_block_number, now), candidate), + StorageEntry::new_onchain(RecordTime::with_ts(relay_block_number, now), candidate), ) .await; Ok(()) } + async fn extract_dispute_initiators( + &mut self, + dispute_event: &SubxtDispute, + ) -> color_eyre::Result<(Vec, u32)> { + let default_value = (vec![], self.state.current_session_index); + let entry = match self + .storage_read_prefixed(CollectorPrefixType::InherentData, dispute_event.relay_parent_block) + .await + { + Some(v) => v, + None => return Ok(default_value), + }; + + let data: InherentData = entry.into_inner()?; + let statement_set = match data + .disputes + .iter() + .find(|&d| d.candidate_hash.0 == dispute_event.candidate_hash) + { + Some(v) => v, + None => return Ok(default_value), + }; + + Ok(( + statement_set + .statements + .iter() + .filter(|(statement, _, _)| matches!(statement, DisputeStatement::Invalid(_))) + .map(|(_, idx, _)| idx.0) + .collect(), + statement_set.session, + )) + } + async fn process_dispute_concluded( &mut self, dispute_event: &SubxtDispute, diff --git a/parachain-tracer/src/progress.rs b/parachain-tracer/src/progress.rs index 6e4fc578..cd7e7b49 100644 --- a/parachain-tracer/src/progress.rs +++ b/parachain-tracer/src/progress.rs @@ -245,6 +245,16 @@ impl Display for DisputesTracker { }, } + if !self.initiators.is_empty() { + for (validator_idx, validator_address) in &self.initiators { + writeln!( + f, + "\t\t\t😠 Validator initiated dispute: {}", + format!("idx: {}, address: {}", validator_idx, validator_address).magenta(), + )?; + } + } + if !self.misbehaving_validators.is_empty() { for (validator_idx, validator_address) in &self.misbehaving_validators { writeln!( diff --git a/parachain-tracer/src/tracker.rs b/parachain-tracer/src/tracker.rs index 0386a787..2c048c32 100644 --- a/parachain-tracer/src/tracker.rs +++ b/parachain-tracer/src/tracker.rs @@ -73,6 +73,8 @@ pub struct DisputesTracker { pub voted_for: u32, /// Number of validators voted that a candidate is invalid pub voted_against: u32, + /// A vector of validators initiateds the dispute (index + identify) + pub initiators: Vec<(u32, String)>, /// A vector of validators voted against supermajority (index + identify) pub misbehaving_validators: Vec<(u32, String)>, /// Dispute conclusion time: how many blocks have passed since DisputeInitiated event @@ -581,11 +583,24 @@ impl SubxtTracker { .map(|(_, idx, _)| extract_validator_address(session_info.as_ref(), idx.0)) .collect() }; + + let initiators_session_info = if session_index == stored_dispute.session_index { + session_info + } else { + self.get_session_keys(stored_dispute.session_index).await + }; + let initiators: Vec<_> = stored_dispute + .initiator_indices + .iter() + .map(|idx| extract_validator_address(initiators_session_info.as_ref(), *idx)) + .collect(); + self.disputes.push(DisputesTracker { candidate: dispute_info.candidate_hash.0, voted_for, voted_against, outcome, + initiators, misbehaving_validators, resolve_time: Some( stored_dispute