referrerpolicy=no-referrer-when-downgrade

sc_consensus_beefy/
fisherman.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::{error::Error, keystore::BeefyKeystore, round::Rounds, LOG_TARGET};
20use log::{debug, error, warn};
21use sc_client_api::Backend;
22use sp_api::ProvideRuntimeApi;
23use sp_application_crypto::RuntimeAppPublic;
24use sp_blockchain::HeaderBackend;
25use sp_consensus_beefy::{
26	check_double_voting_proof, AuthorityIdBound, BeefyApi, BeefySignatureHasher, DoubleVotingProof,
27	OpaqueKeyOwnershipProof, ValidatorSetId,
28};
29use sp_runtime::{
30	generic::BlockId,
31	traits::{Block, NumberFor},
32};
33use std::{marker::PhantomData, sync::Arc};
34
35/// Helper struct containing the key ownership proof for a validator.
36pub struct ProvedValidator {
37	pub key_owner_proof: OpaqueKeyOwnershipProof,
38}
39
40/// Helper used to check and report equivocations.
41pub struct Fisherman<B, BE, RuntimeApi, AuthorityId: AuthorityIdBound> {
42	backend: Arc<BE>,
43	runtime: Arc<RuntimeApi>,
44	key_store: Arc<BeefyKeystore<AuthorityId>>,
45
46	_phantom: PhantomData<B>,
47}
48
49impl<B: Block, BE: Backend<B>, RuntimeApi: ProvideRuntimeApi<B>, AuthorityId>
50	Fisherman<B, BE, RuntimeApi, AuthorityId>
51where
52	RuntimeApi::Api: BeefyApi<B, AuthorityId>,
53	AuthorityId: AuthorityIdBound,
54{
55	pub fn new(
56		backend: Arc<BE>,
57		runtime: Arc<RuntimeApi>,
58		keystore: Arc<BeefyKeystore<AuthorityId>>,
59	) -> Self {
60		Self { backend, runtime, key_store: keystore, _phantom: Default::default() }
61	}
62
63	fn prove_offenders<'a>(
64		&self,
65		at: BlockId<B>,
66		offender_ids: impl Iterator<Item = &'a AuthorityId>,
67		validator_set_id: ValidatorSetId,
68	) -> Result<Vec<ProvedValidator>, Error> {
69		let hash = match at {
70			BlockId::Hash(hash) => hash,
71			BlockId::Number(number) => self
72				.backend
73				.blockchain()
74				.expect_block_hash_from_id(&BlockId::Number(number))
75				.map_err(|err| {
76					Error::Backend(format!(
77						"Couldn't get hash for block #{:?} (error: {:?}). \
78						Skipping report for equivocation",
79						at, err
80					))
81				})?,
82		};
83
84		let runtime_api = self.runtime.runtime_api();
85		let mut proved_offenders = vec![];
86		for offender_id in offender_ids {
87			match runtime_api.generate_key_ownership_proof(
88				hash,
89				validator_set_id,
90				offender_id.clone(),
91			) {
92				Ok(Some(key_owner_proof)) => {
93					proved_offenders.push(ProvedValidator { key_owner_proof });
94				},
95				Ok(None) => {
96					debug!(
97						target: LOG_TARGET,
98						"🥩 Equivocation offender {} not part of the authority set {}.",
99						offender_id, validator_set_id
100					);
101				},
102				Err(e) => {
103					error!(
104						target: LOG_TARGET,
105						"🥩 Error generating key ownership proof for equivocation offender {} \
106						in authority set {}: {}",
107						offender_id, validator_set_id, e
108					);
109				},
110			};
111		}
112
113		Ok(proved_offenders)
114	}
115
116	/// Report the given equivocation to the BEEFY runtime module. This method
117	/// generates a session membership proof of the offender and then submits an
118	/// extrinsic to report the equivocation. In particular, the session membership
119	/// proof must be generated at the block at which the given set was active which
120	/// isn't necessarily the best block if there are pending authority set changes.
121	pub fn report_double_voting(
122		&self,
123		proof: DoubleVotingProof<
124			NumberFor<B>,
125			AuthorityId,
126			<AuthorityId as RuntimeAppPublic>::Signature,
127		>,
128		active_rounds: &Rounds<B, AuthorityId>,
129	) -> Result<(), Error> {
130		let (validators, validator_set_id) =
131			(active_rounds.validators(), active_rounds.validator_set_id());
132		let offender_id = proof.offender_id();
133
134		if !check_double_voting_proof::<_, _, BeefySignatureHasher>(&proof) {
135			debug!(target: LOG_TARGET, "🥩 Skipping report for bad equivocation {:?}", proof);
136			return Ok(());
137		}
138
139		if let Some(local_id) = self.key_store.authority_id(validators) {
140			if offender_id == &local_id {
141				warn!(target: LOG_TARGET, "🥩 Skipping report for own equivocation");
142				return Ok(());
143			}
144		}
145
146		let key_owner_proofs = self.prove_offenders(
147			BlockId::Number(*proof.round_number()),
148			vec![offender_id].into_iter(),
149			validator_set_id,
150		)?;
151
152		// submit equivocation report at **best** block
153		let best_block_hash = self.backend.blockchain().info().best_hash;
154		for ProvedValidator { key_owner_proof, .. } in key_owner_proofs {
155			self.runtime
156				.runtime_api()
157				.submit_report_double_voting_unsigned_extrinsic(
158					best_block_hash,
159					proof.clone(),
160					key_owner_proof,
161				)
162				.map_err(Error::RuntimeApi)?;
163		}
164
165		Ok(())
166	}
167}