Skip to content

Commit b6f1823

Browse files
serban300acatangiu
andauthored
[BEEFY] Add runtime support for reporting fork voting (#4522)
Related to #4523 Extracting part of #1903 (credits to @Lederstrumpf for the high-level strategy), but also introducing significant adjustments both to the approach and to the code. The main adjustment is the fact that the `ForkVotingProof` accepts only one vote, compared to the original version which accepted a `vec![]`. With this approach more calls are needed in order to report multiple equivocated votes on the same commit, but it simplifies a lot the checking logic. We can add support for reporting multiple signatures at once in the future. There are 2 things that are missing in order to consider this issue done, but I would propose to do them in a separate PR since this one is already pretty big: - benchmarks/computing a weight for the new extrinsic (this wasn't present in #1903 either) - exposing an API for generating the ancestry proof. I'm not sure if we should do this in the Mmr pallet or in the Beefy pallet Co-authored-by: Robert Hambrock <roberthambrock@gmail.com> --------- Co-authored-by: Adrian Catangiu <adrian@parity.io>
1 parent e5791a5 commit b6f1823

25 files changed

Lines changed: 1378 additions & 400 deletions

File tree

polkadot/node/service/src/fake_runtime_api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ sp_api::impl_runtime_apis! {
241241
unimplemented!()
242242
}
243243

244-
fn submit_report_equivocation_unsigned_extrinsic(
244+
fn submit_report_double_voting_unsigned_extrinsic(
245245
_: sp_consensus_beefy::DoubleVotingProof<
246246
BlockNumber,
247247
BeefyId,

polkadot/runtime/rococo/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,7 @@ impl pallet_beefy::Config for Runtime {
12831283
type MaxNominators = ConstU32<0>;
12841284
type MaxSetIdSessionEntries = BeefySetIdSessionEntries;
12851285
type OnNewValidatorSet = MmrLeaf;
1286+
type AncestryHelper = MmrLeaf;
12861287
type WeightInfo = ();
12871288
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
12881289
type EquivocationReportSystem =
@@ -2052,7 +2053,7 @@ sp_api::impl_runtime_apis! {
20522053
}
20532054
}
20542055

2055-
#[api_version(3)]
2056+
#[api_version(4)]
20562057
impl sp_consensus_beefy::BeefyApi<Block, BeefyId> for Runtime {
20572058
fn beefy_genesis() -> Option<BlockNumber> {
20582059
pallet_beefy::GenesisBlock::<Runtime>::get()
@@ -2062,7 +2063,7 @@ sp_api::impl_runtime_apis! {
20622063
Beefy::validator_set()
20632064
}
20642065

2065-
fn submit_report_equivocation_unsigned_extrinsic(
2066+
fn submit_report_double_voting_unsigned_extrinsic(
20662067
equivocation_proof: sp_consensus_beefy::DoubleVotingProof<
20672068
BlockNumber,
20682069
BeefyId,
@@ -2072,7 +2073,7 @@ sp_api::impl_runtime_apis! {
20722073
) -> Option<()> {
20732074
let key_owner_proof = key_owner_proof.decode()?;
20742075

2075-
Beefy::submit_unsigned_equivocation_report(
2076+
Beefy::submit_unsigned_double_voting_report(
20762077
equivocation_proof,
20772078
key_owner_proof,
20782079
)

polkadot/runtime/test-runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ sp_api::impl_runtime_apis! {
10151015
None
10161016
}
10171017

1018-
fn submit_report_equivocation_unsigned_extrinsic(
1018+
fn submit_report_double_voting_unsigned_extrinsic(
10191019
_equivocation_proof: sp_consensus_beefy::DoubleVotingProof<
10201020
BlockNumber,
10211021
BeefyId,

polkadot/runtime/westend/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ impl pallet_beefy::Config for Runtime {
328328
type MaxNominators = MaxNominators;
329329
type MaxSetIdSessionEntries = BeefySetIdSessionEntries;
330330
type OnNewValidatorSet = BeefyMmrLeaf;
331+
type AncestryHelper = BeefyMmrLeaf;
331332
type WeightInfo = ();
332333
type KeyOwnerProof = sp_session::MembershipProof;
333334
type EquivocationReportSystem =
@@ -2009,6 +2010,7 @@ sp_api::impl_runtime_apis! {
20092010
}
20102011
}
20112012

2013+
#[api_version(4)]
20122014
impl sp_consensus_beefy::BeefyApi<Block, BeefyId> for Runtime {
20132015
fn beefy_genesis() -> Option<BlockNumber> {
20142016
pallet_beefy::GenesisBlock::<Runtime>::get()
@@ -2018,7 +2020,7 @@ sp_api::impl_runtime_apis! {
20182020
Beefy::validator_set()
20192021
}
20202022

2021-
fn submit_report_equivocation_unsigned_extrinsic(
2023+
fn submit_report_double_voting_unsigned_extrinsic(
20222024
equivocation_proof: sp_consensus_beefy::DoubleVotingProof<
20232025
BlockNumber,
20242026
BeefyId,
@@ -2028,7 +2030,7 @@ sp_api::impl_runtime_apis! {
20282030
) -> Option<()> {
20292031
let key_owner_proof = key_owner_proof.decode()?;
20302032

2031-
Beefy::submit_unsigned_equivocation_report(
2033+
Beefy::submit_unsigned_double_voting_report(
20322034
equivocation_proof,
20332035
key_owner_proof,
20342036
)

prdoc/pr_4522.prdoc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
2+
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
3+
4+
title: Added runtime support for reporting BEEFY fork voting
5+
6+
doc:
7+
- audience:
8+
- Runtime Dev
9+
- Runtime User
10+
description: |
11+
This PR adds the `report_fork_voting`, `report_future_voting` extrinsics to `pallet-beefy`
12+
and renames the `report_equivocation` extrinsic to `report_double_voting`.
13+
`report_fork_voting` can't be called yet, since it uses `Weight::MAX` weight. We will
14+
add benchmarks for it and set the proper weight in a future PR.
15+
Also a new `AncestryHelper` associated trait was added to `pallet_beefy::Config`.
16+
- audience: Node Dev
17+
description: |
18+
This PR renames the `submit_report_equivocation_unsigned_extrinsic` in `BeefyApi` to
19+
`submit_report_double_voting_unsigned_extrinsic`and bumps the `BeefyApi` version from 3 to 4.
20+
21+
crates:
22+
- name: pallet-beefy
23+
bump: major
24+
- name: pallet-beefy-mmr
25+
bump: minor
26+
- name: pallet-mmr
27+
bump: major
28+
- name: sc-consensus-beefy
29+
bump: patch
30+
- name: kitchensink-runtime
31+
bump: major
32+
- name: rococo-runtime
33+
bump: major
34+
- name: westend-runtime
35+
bump: major
36+
- name: sp-consensus-beefy
37+
bump: major
38+
- name: polkadot-service
39+
bump: patch

substrate/bin/node/runtime/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2548,6 +2548,7 @@ impl pallet_beefy::Config for Runtime {
25482548
type MaxNominators = ConstU32<0>;
25492549
type MaxSetIdSessionEntries = BeefySetIdSessionEntries;
25502550
type OnNewValidatorSet = MmrLeaf;
2551+
type AncestryHelper = MmrLeaf;
25512552
type WeightInfo = ();
25522553
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
25532554
type EquivocationReportSystem =
@@ -3032,7 +3033,7 @@ impl_runtime_apis! {
30323033
}
30333034
}
30343035

3035-
#[api_version(3)]
3036+
#[api_version(4)]
30363037
impl sp_consensus_beefy::BeefyApi<Block, BeefyId> for Runtime {
30373038
fn beefy_genesis() -> Option<BlockNumber> {
30383039
pallet_beefy::GenesisBlock::<Runtime>::get()
@@ -3042,7 +3043,7 @@ impl_runtime_apis! {
30423043
Beefy::validator_set()
30433044
}
30443045

3045-
fn submit_report_equivocation_unsigned_extrinsic(
3046+
fn submit_report_double_voting_unsigned_extrinsic(
30463047
equivocation_proof: sp_consensus_beefy::DoubleVotingProof<
30473048
BlockNumber,
30483049
BeefyId,
@@ -3052,7 +3053,7 @@ impl_runtime_apis! {
30523053
) -> Option<()> {
30533054
let key_owner_proof = key_owner_proof.decode()?;
30543055

3055-
Beefy::submit_unsigned_equivocation_report(
3056+
Beefy::submit_unsigned_double_voting_report(
30563057
equivocation_proof,
30573058
key_owner_proof,
30583059
)

substrate/client/consensus/beefy/src/fisherman.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use sp_api::ProvideRuntimeApi;
2323
use sp_application_crypto::RuntimeAppPublic;
2424
use sp_blockchain::HeaderBackend;
2525
use sp_consensus_beefy::{
26-
check_equivocation_proof, AuthorityIdBound, BeefyApi, BeefySignatureHasher, DoubleVotingProof,
26+
check_double_voting_proof, AuthorityIdBound, BeefyApi, BeefySignatureHasher, DoubleVotingProof,
2727
OpaqueKeyOwnershipProof, ValidatorSetId,
2828
};
2929
use sp_runtime::{
@@ -132,7 +132,7 @@ where
132132
(active_rounds.validators(), active_rounds.validator_set_id());
133133
let offender_id = proof.offender_id();
134134

135-
if !check_equivocation_proof::<_, _, BeefySignatureHasher>(&proof) {
135+
if !check_double_voting_proof::<_, _, BeefySignatureHasher>(&proof) {
136136
debug!(target: LOG_TARGET, "🥩 Skipping report for bad equivocation {:?}", proof);
137137
return Ok(());
138138
}
@@ -155,7 +155,7 @@ where
155155
for ProvedValidator { key_owner_proof, .. } in key_owner_proofs {
156156
self.runtime
157157
.runtime_api()
158-
.submit_report_equivocation_unsigned_extrinsic(
158+
.submit_report_double_voting_unsigned_extrinsic(
159159
best_block_hash,
160160
proof.clone(),
161161
key_owner_proof,

substrate/client/consensus/beefy/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ sp_api::mock_impl_runtime_apis! {
314314
self.inner.validator_set.clone()
315315
}
316316

317-
fn submit_report_equivocation_unsigned_extrinsic(
317+
fn submit_report_double_voting_unsigned_extrinsic(
318318
proof: DoubleVotingProof<NumberFor<Block>, AuthorityId, Signature>,
319319
_dummy: OpaqueKeyOwnershipProof,
320320
) -> Option<()> {

substrate/client/consensus/beefy/src/worker.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,7 @@ pub(crate) mod tests {
10391039
ecdsa_crypto, known_payloads,
10401040
known_payloads::MMR_ROOT_ID,
10411041
mmr::MmrRootProvider,
1042-
test_utils::{generate_equivocation_proof, Keyring},
1042+
test_utils::{generate_double_voting_proof, Keyring},
10431043
ConsensusLog, Payload, SignedCommitment,
10441044
};
10451045
use sp_runtime::traits::{Header as HeaderT, One};
@@ -1586,7 +1586,7 @@ pub(crate) mod tests {
15861586
let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]);
15871587

15881588
// generate an equivocation proof, with Bob as perpetrator
1589-
let good_proof = generate_equivocation_proof(
1589+
let good_proof = generate_double_voting_proof(
15901590
(block_num, payload1.clone(), set_id, &Keyring::Bob),
15911591
(block_num, payload2.clone(), set_id, &Keyring::Bob),
15921592
);
@@ -1618,7 +1618,7 @@ pub(crate) mod tests {
16181618
assert!(api_alice.reported_equivocations.as_ref().unwrap().lock().is_empty());
16191619

16201620
// now let's try reporting a self-equivocation
1621-
let self_proof = generate_equivocation_proof(
1621+
let self_proof = generate_double_voting_proof(
16221622
(block_num, payload1.clone(), set_id, &Keyring::Alice),
16231623
(block_num, payload2.clone(), set_id, &Keyring::Alice),
16241624
);

substrate/frame/beefy-mmr/src/lib.rs

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,22 @@
3333
//!
3434
//! and thanks to versioning can be easily updated in the future.
3535
36-
use sp_runtime::traits::{Convert, Member};
36+
use sp_runtime::traits::{Convert, Header, Member};
3737
use sp_std::prelude::*;
3838

3939
use codec::Decode;
40-
use pallet_mmr::{LeafDataProvider, ParentNumberAndHash};
40+
use pallet_mmr::{primitives::AncestryProof, LeafDataProvider, ParentNumberAndHash};
4141
use sp_consensus_beefy::{
42+
known_payloads,
4243
mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion},
43-
ValidatorSet as BeefyValidatorSet,
44+
AncestryHelper, Commitment, ConsensusLog, ValidatorSet as BeefyValidatorSet,
4445
};
4546

4647
use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get};
47-
use frame_system::pallet_prelude::BlockNumberFor;
48+
use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor};
4849

4950
pub use pallet::*;
51+
use sp_runtime::generic::OpaqueDigestItemId;
5052

5153
#[cfg(test)]
5254
mod mock;
@@ -172,6 +174,75 @@ where
172174
}
173175
}
174176

177+
impl<T: Config> AncestryHelper<HeaderFor<T>> for Pallet<T>
178+
where
179+
T: pallet_mmr::Config<Hashing = sp_consensus_beefy::MmrHashing>,
180+
{
181+
type Proof = AncestryProof<MerkleRootOf<T>>;
182+
type ValidationContext = MerkleRootOf<T>;
183+
184+
fn extract_validation_context(header: HeaderFor<T>) -> Option<Self::ValidationContext> {
185+
// Check if the provided header is canonical.
186+
let expected_hash = frame_system::Pallet::<T>::block_hash(header.number());
187+
if expected_hash != header.hash() {
188+
return None;
189+
}
190+
191+
// Extract the MMR root from the header digest
192+
header.digest().convert_first(|l| {
193+
l.try_to(OpaqueDigestItemId::Consensus(&sp_consensus_beefy::BEEFY_ENGINE_ID))
194+
.and_then(|log: ConsensusLog<<T as pallet_beefy::Config>::BeefyId>| match log {
195+
ConsensusLog::MmrRoot(mmr_root) => Some(mmr_root),
196+
_ => None,
197+
})
198+
})
199+
}
200+
201+
fn is_non_canonical(
202+
commitment: &Commitment<BlockNumberFor<T>>,
203+
proof: Self::Proof,
204+
context: Self::ValidationContext,
205+
) -> bool {
206+
let commitment_leaf_count =
207+
match pallet_mmr::Pallet::<T>::block_num_to_leaf_count(commitment.block_number) {
208+
Ok(commitment_leaf_count) => commitment_leaf_count,
209+
Err(_) => {
210+
// We can't prove that the commitment is non-canonical if the
211+
// `commitment.block_number` is invalid.
212+
return false
213+
},
214+
};
215+
if commitment_leaf_count != proof.prev_leaf_count {
216+
// Can't prove that the commitment is non-canonical if the `commitment.block_number`
217+
// doesn't match the ancestry proof.
218+
return false;
219+
}
220+
221+
let canonical_mmr_root = context;
222+
let canonical_prev_root =
223+
match pallet_mmr::Pallet::<T>::verify_ancestry_proof(canonical_mmr_root, proof) {
224+
Ok(canonical_prev_root) => canonical_prev_root,
225+
Err(_) => {
226+
// Can't prove that the commitment is non-canonical if the proof
227+
// is invalid.
228+
return false
229+
},
230+
};
231+
232+
let commitment_root =
233+
match commitment.payload.get_decoded::<MerkleRootOf<T>>(&known_payloads::MMR_ROOT_ID) {
234+
Some(commitment_root) => commitment_root,
235+
None => {
236+
// If the commitment doesn't contain any MMR root, while the proof is valid,
237+
// the commitment is invalid
238+
return true
239+
},
240+
};
241+
242+
canonical_prev_root != commitment_root
243+
}
244+
}
245+
175246
impl<T: Config> Pallet<T> {
176247
/// Return the currently active BEEFY authority set proof.
177248
pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {

0 commit comments

Comments
 (0)