use crate::{error::Error, keystore::BeefyKeystore, round::Rounds, LOG_TARGET};
use log::{debug, error, warn};
use sc_client_api::Backend;
use sp_api::ProvideRuntimeApi;
use sp_application_crypto::RuntimeAppPublic;
use sp_blockchain::HeaderBackend;
use sp_consensus_beefy::{
check_double_voting_proof, AuthorityIdBound, BeefyApi, BeefySignatureHasher, DoubleVotingProof,
OpaqueKeyOwnershipProof, ValidatorSetId,
};
use sp_runtime::{
generic::BlockId,
traits::{Block, NumberFor},
};
use std::{marker::PhantomData, sync::Arc};
pub struct ProvedValidator {
pub key_owner_proof: OpaqueKeyOwnershipProof,
}
pub struct Fisherman<B, BE, RuntimeApi, AuthorityId: AuthorityIdBound> {
backend: Arc<BE>,
runtime: Arc<RuntimeApi>,
key_store: Arc<BeefyKeystore<AuthorityId>>,
_phantom: PhantomData<B>,
}
impl<B: Block, BE: Backend<B>, RuntimeApi: ProvideRuntimeApi<B>, AuthorityId>
Fisherman<B, BE, RuntimeApi, AuthorityId>
where
RuntimeApi::Api: BeefyApi<B, AuthorityId>,
AuthorityId: AuthorityIdBound,
{
pub fn new(
backend: Arc<BE>,
runtime: Arc<RuntimeApi>,
keystore: Arc<BeefyKeystore<AuthorityId>>,
) -> Self {
Self { backend, runtime, key_store: keystore, _phantom: Default::default() }
}
fn prove_offenders<'a>(
&self,
at: BlockId<B>,
offender_ids: impl Iterator<Item = &'a AuthorityId>,
validator_set_id: ValidatorSetId,
) -> Result<Vec<ProvedValidator>, Error> {
let hash = match at {
BlockId::Hash(hash) => hash,
BlockId::Number(number) => self
.backend
.blockchain()
.expect_block_hash_from_id(&BlockId::Number(number))
.map_err(|err| {
Error::Backend(format!(
"Couldn't get hash for block #{:?} (error: {:?}). \
Skipping report for equivocation",
at, err
))
})?,
};
let runtime_api = self.runtime.runtime_api();
let mut proved_offenders = vec![];
for offender_id in offender_ids {
match runtime_api.generate_key_ownership_proof(
hash,
validator_set_id,
offender_id.clone(),
) {
Ok(Some(key_owner_proof)) => {
proved_offenders.push(ProvedValidator { key_owner_proof });
},
Ok(None) => {
debug!(
target: LOG_TARGET,
"🥩 Equivocation offender {} not part of the authority set {}.",
offender_id, validator_set_id
);
},
Err(e) => {
error!(
target: LOG_TARGET,
"🥩 Error generating key ownership proof for equivocation offender {} \
in authority set {}: {}",
offender_id, validator_set_id, e
);
},
};
}
Ok(proved_offenders)
}
pub fn report_double_voting(
&self,
proof: DoubleVotingProof<
NumberFor<B>,
AuthorityId,
<AuthorityId as RuntimeAppPublic>::Signature,
>,
active_rounds: &Rounds<B, AuthorityId>,
) -> Result<(), Error> {
let (validators, validator_set_id) =
(active_rounds.validators(), active_rounds.validator_set_id());
let offender_id = proof.offender_id();
if !check_double_voting_proof::<_, _, BeefySignatureHasher>(&proof) {
debug!(target: LOG_TARGET, "🥩 Skipping report for bad equivocation {:?}", proof);
return Ok(());
}
if let Some(local_id) = self.key_store.authority_id(validators) {
if offender_id == &local_id {
warn!(target: LOG_TARGET, "🥩 Skipping report for own equivocation");
return Ok(());
}
}
let key_owner_proofs = self.prove_offenders(
BlockId::Number(*proof.round_number()),
vec![offender_id].into_iter(),
validator_set_id,
)?;
let best_block_hash = self.backend.blockchain().info().best_hash;
for ProvedValidator { key_owner_proof, .. } in key_owner_proofs {
self.runtime
.runtime_api()
.submit_report_double_voting_unsigned_extrinsic(
best_block_hash,
proof.clone(),
key_owner_proof,
)
.map_err(Error::RuntimeApi)?;
}
Ok(())
}
}