Skip to content

Commit 131b173

Browse files
authored
optimize justification before submit (#1887)
* optimize justification before submit * fmt * spelling * clippy * fmt again * aaand compilation * clippy
1 parent 5bc279e commit 131b173

File tree

7 files changed

+290
-8
lines changed

7 files changed

+290
-8
lines changed

modules/grandpa/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,11 @@ mod tests {
11871187
bp_header_chain::storage_keys::pallet_operating_mode_key("Grandpa").0,
11881188
);
11891189

1190+
assert_eq!(
1191+
CurrentAuthoritySet::<TestRuntime>::storage_value_final_key().to_vec(),
1192+
bp_header_chain::storage_keys::current_authority_set_key("Grandpa").0,
1193+
);
1194+
11901195
assert_eq!(
11911196
BestFinalized::<TestRuntime>::storage_value_final_key().to_vec(),
11921197
bp_header_chain::storage_keys::best_finalized_key("Grandpa").0,

primitives/header-chain/src/justification.rs

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ pub enum Error {
7171
ExtraHeadersInVotesAncestries,
7272
}
7373

74+
/// Given GRANDPA authorities set size, return number of valid authorities votes that the
75+
/// justification must have to be valid.
76+
///
77+
/// This function assumes that all authorities have the same vote weight.
78+
pub fn required_justification_precommits(authorities_set_length: u32) -> u32 {
79+
authorities_set_length - authorities_set_length.saturating_sub(1) / 3
80+
}
81+
7482
/// Decode justification target.
7583
pub fn decode_justification_target<Header: HeaderT>(
7684
raw_justification: &[u8],
@@ -80,13 +88,111 @@ pub fn decode_justification_target<Header: HeaderT>(
8088
.map_err(|_| Error::JustificationDecode)
8189
}
8290

91+
/// Verify and optimize given justification by removing unknown and duplicate votes.
92+
pub fn optimize_justification<Header: HeaderT>(
93+
finalized_target: (Header::Hash, Header::Number),
94+
authorities_set_id: SetId,
95+
authorities_set: &VoterSet<AuthorityId>,
96+
justification: GrandpaJustification<Header>,
97+
) -> Result<GrandpaJustification<Header>, Error>
98+
where
99+
Header::Number: finality_grandpa::BlockNumberOps,
100+
{
101+
let mut optimizer = OptimizationCallbacks(Vec::new());
102+
verify_justification_with_callbacks(
103+
finalized_target,
104+
authorities_set_id,
105+
authorities_set,
106+
&justification,
107+
&mut optimizer,
108+
)?;
109+
Ok(optimizer.optimize(justification))
110+
}
111+
83112
/// Verify that justification, that is generated by given authority set, finalizes given header.
84113
pub fn verify_justification<Header: HeaderT>(
85114
finalized_target: (Header::Hash, Header::Number),
86115
authorities_set_id: SetId,
87116
authorities_set: &VoterSet<AuthorityId>,
88117
justification: &GrandpaJustification<Header>,
89118
) -> Result<(), Error>
119+
where
120+
Header::Number: finality_grandpa::BlockNumberOps,
121+
{
122+
verify_justification_with_callbacks(
123+
finalized_target,
124+
authorities_set_id,
125+
authorities_set,
126+
justification,
127+
&mut (),
128+
)
129+
}
130+
131+
/// Verification callbacks.
132+
trait VerificationCallbacks {
133+
/// Called when we see a precommit from unknown authority.
134+
fn on_unkown_authority(&mut self, precommit_idx: usize) -> Result<(), Error>;
135+
/// Called when we see a precommit with duplicate vote from known authority.
136+
fn on_duplicate_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error>;
137+
/// Called when we see a precommit after we've collected enough votes from authorities.
138+
fn on_redundant_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error>;
139+
}
140+
141+
/// Verification callbacks for justification optimization.
142+
struct OptimizationCallbacks(Vec<usize>);
143+
144+
impl OptimizationCallbacks {
145+
fn optimize<Header: HeaderT>(
146+
self,
147+
mut justification: GrandpaJustification<Header>,
148+
) -> GrandpaJustification<Header> {
149+
for invalid_precommit_idx in self.0.into_iter().rev() {
150+
justification.commit.precommits.remove(invalid_precommit_idx);
151+
}
152+
justification
153+
}
154+
}
155+
156+
impl VerificationCallbacks for OptimizationCallbacks {
157+
fn on_unkown_authority(&mut self, precommit_idx: usize) -> Result<(), Error> {
158+
self.0.push(precommit_idx);
159+
Ok(())
160+
}
161+
162+
fn on_duplicate_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error> {
163+
self.0.push(precommit_idx);
164+
Ok(())
165+
}
166+
167+
fn on_redundant_authority_vote(&mut self, precommit_idx: usize) -> Result<(), Error> {
168+
self.0.push(precommit_idx);
169+
Ok(())
170+
}
171+
}
172+
173+
// this implementation will be removed in https://github.com/paritytech/parity-bridges-common/pull/1882
174+
impl VerificationCallbacks for () {
175+
fn on_unkown_authority(&mut self, _precommit_idx: usize) -> Result<(), Error> {
176+
Ok(())
177+
}
178+
179+
fn on_duplicate_authority_vote(&mut self, _precommit_idx: usize) -> Result<(), Error> {
180+
Ok(())
181+
}
182+
183+
fn on_redundant_authority_vote(&mut self, _precommit_idx: usize) -> Result<(), Error> {
184+
Ok(())
185+
}
186+
}
187+
188+
/// Verify that justification, that is generated by given authority set, finalizes given header.
189+
fn verify_justification_with_callbacks<Header: HeaderT, C: VerificationCallbacks>(
190+
finalized_target: (Header::Hash, Header::Number),
191+
authorities_set_id: SetId,
192+
authorities_set: &VoterSet<AuthorityId>,
193+
justification: &GrandpaJustification<Header>,
194+
callbacks: &mut C,
195+
) -> Result<(), Error>
90196
where
91197
Header::Number: finality_grandpa::BlockNumberOps,
92198
{
@@ -95,17 +201,23 @@ where
95201
return Err(Error::InvalidJustificationTarget)
96202
}
97203

204+
let threshold = authorities_set.threshold().0.into();
98205
let mut chain = AncestryChain::new(&justification.votes_ancestries);
99206
let mut signature_buffer = Vec::new();
100207
let mut votes = BTreeSet::new();
101208
let mut cumulative_weight = 0u64;
102-
for signed in &justification.commit.precommits {
209+
for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
210+
// if we have collected enough precommits, we probabably want to fail/remove extra
211+
// precommits
212+
if cumulative_weight > threshold {
213+
callbacks.on_redundant_authority_vote(precommit_idx)?;
214+
}
215+
103216
// authority must be in the set
104217
let authority_info = match authorities_set.get(&signed.id) {
105218
Some(authority_info) => authority_info,
106219
None => {
107-
// just ignore precommit from unknown authority as
108-
// `finality_grandpa::import_precommit` does
220+
callbacks.on_unkown_authority(precommit_idx)?;
109221
continue
110222
},
111223
};
@@ -116,6 +228,7 @@ where
116228
// `finality-grandpa` crate (mostly related to reporting equivocations). But the only thing
117229
// that we care about is that only first vote from the authority is accepted
118230
if !votes.insert(signed.id.clone()) {
231+
callbacks.on_duplicate_authority_vote(precommit_idx)?;
119232
continue
120233
}
121234

@@ -142,6 +255,7 @@ where
142255
thus we'll never overflow the u64::MAX;\
143256
qed",
144257
);
258+
145259
// verify authority signature
146260
if !sp_finality_grandpa::check_message_signature_with_buffer(
147261
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
@@ -162,7 +276,6 @@ where
162276

163277
// check that the cumulative weight of validators voted for the justification target (or one
164278
// of its descendents) is larger than required threshold.
165-
let threshold = authorities_set.threshold().0.into();
166279
if cumulative_weight >= threshold {
167280
Ok(())
168281
} else {

primitives/header-chain/src/storage_keys.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
pub const PALLET_OPERATING_MODE_VALUE_NAME: &str = "PalletOperatingMode";
2121
/// Name of the `BestFinalized` storage value.
2222
pub const BEST_FINALIZED_VALUE_NAME: &str = "BestFinalized";
23+
/// Name of the `CurrentAuthoritySet` storage value.
24+
pub const CURRENT_AUTHORITY_SET_VALUE_NAME: &str = "CurrentAuthoritySet";
2325

2426
use sp_core::storage::StorageKey;
2527

@@ -34,6 +36,17 @@ pub fn pallet_operating_mode_key(pallet_prefix: &str) -> StorageKey {
3436
)
3537
}
3638

39+
/// Storage key of the `CurrentAuthoritySet` variable in the runtime storage.
40+
pub fn current_authority_set_key(pallet_prefix: &str) -> StorageKey {
41+
StorageKey(
42+
bp_runtime::storage_value_final_key(
43+
pallet_prefix.as_bytes(),
44+
CURRENT_AUTHORITY_SET_VALUE_NAME.as_bytes(),
45+
)
46+
.to_vec(),
47+
)
48+
}
49+
3750
/// Storage key of the best finalized header number and hash value in the runtime storage.
3851
pub fn best_finalized_key(pallet_prefix: &str) -> StorageKey {
3952
StorageKey(
@@ -63,6 +76,19 @@ mod tests {
6376
);
6477
}
6578

79+
#[test]
80+
fn current_authority_set_key_computed_properly() {
81+
// If this test fails, then something has been changed in module storage that is breaking
82+
// compatibility with previous pallet.
83+
let storage_key = current_authority_set_key("BridgeGrandpa").0;
84+
assert_eq!(
85+
storage_key,
86+
hex!("0b06f475eddb98cf933a12262e0388de24a7b8b5717ea33346fa595a66ccbcb0").to_vec(),
87+
"Unexpected storage key: {}",
88+
hex::encode(&storage_key),
89+
);
90+
}
91+
6692
#[test]
6793
fn best_finalized_key_computed_properly() {
6894
// If this test fails, then something has been changed in module storage that is breaking

primitives/header-chain/tests/justification.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
//! Tests for Grandpa Justification code.
1818
19-
use bp_header_chain::justification::{verify_justification, Error};
19+
use bp_header_chain::justification::{optimize_justification, verify_justification, Error};
2020
use bp_test_utils::*;
2121

2222
type TestHeader = sp_runtime::testing::Header;
@@ -190,3 +190,87 @@ fn justification_is_invalid_if_we_dont_meet_threshold() {
190190
Err(Error::TooLowCumulativeWeight),
191191
);
192192
}
193+
194+
#[test]
195+
fn optimizer_does_noting_with_minimal_justification() {
196+
let justification = make_default_justification::<TestHeader>(&test_header(1));
197+
198+
let num_precommits_before = justification.commit.precommits.len();
199+
let justification = optimize_justification::<TestHeader>(
200+
header_id::<TestHeader>(1),
201+
TEST_GRANDPA_SET_ID,
202+
&voter_set(),
203+
justification,
204+
)
205+
.unwrap();
206+
let num_precommits_after = justification.commit.precommits.len();
207+
208+
assert_eq!(num_precommits_before, num_precommits_after);
209+
}
210+
211+
#[test]
212+
fn unknown_authority_votes_are_removed_by_optimizer() {
213+
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
214+
justification.commit.precommits.push(signed_precommit::<TestHeader>(
215+
&bp_test_utils::Account(42),
216+
header_id::<TestHeader>(1),
217+
justification.round,
218+
TEST_GRANDPA_SET_ID,
219+
));
220+
221+
let num_precommits_before = justification.commit.precommits.len();
222+
let justification = optimize_justification::<TestHeader>(
223+
header_id::<TestHeader>(1),
224+
TEST_GRANDPA_SET_ID,
225+
&voter_set(),
226+
justification,
227+
)
228+
.unwrap();
229+
let num_precommits_after = justification.commit.precommits.len();
230+
231+
assert_eq!(num_precommits_before - 1, num_precommits_after);
232+
}
233+
234+
#[test]
235+
fn duplicate_authority_votes_are_removed_by_optimizer() {
236+
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
237+
justification
238+
.commit
239+
.precommits
240+
.push(justification.commit.precommits.first().cloned().unwrap());
241+
242+
let num_precommits_before = justification.commit.precommits.len();
243+
let justification = optimize_justification::<TestHeader>(
244+
header_id::<TestHeader>(1),
245+
TEST_GRANDPA_SET_ID,
246+
&voter_set(),
247+
justification,
248+
)
249+
.unwrap();
250+
let num_precommits_after = justification.commit.precommits.len();
251+
252+
assert_eq!(num_precommits_before - 1, num_precommits_after);
253+
}
254+
255+
#[test]
256+
fn redundant_authority_votes_are_removed_by_optimizer() {
257+
let mut justification = make_default_justification::<TestHeader>(&test_header(1));
258+
justification.commit.precommits.push(signed_precommit::<TestHeader>(
259+
&EVE,
260+
header_id::<TestHeader>(1),
261+
justification.round,
262+
TEST_GRANDPA_SET_ID,
263+
));
264+
265+
let num_precommits_before = justification.commit.precommits.len();
266+
let justification = optimize_justification::<TestHeader>(
267+
header_id::<TestHeader>(1),
268+
TEST_GRANDPA_SET_ID,
269+
&voter_set(),
270+
justification,
271+
)
272+
.unwrap();
273+
let num_precommits_after = justification.commit.precommits.len();
274+
275+
assert_eq!(num_precommits_before - 1, num_precommits_after);
276+
}

primitives/test-utils/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
1919
#![cfg_attr(not(feature = "std"), no_std)]
2020

21-
use bp_header_chain::justification::GrandpaJustification;
21+
use bp_header_chain::justification::{required_justification_precommits, GrandpaJustification};
2222
use codec::Encode;
2323
use sp_finality_grandpa::{AuthorityId, AuthoritySignature, AuthorityWeight, SetId};
2424
use sp_runtime::traits::{Header as HeaderT, One, Zero};
@@ -57,11 +57,12 @@ pub struct JustificationGeneratorParams<H> {
5757

5858
impl<H: HeaderT> Default for JustificationGeneratorParams<H> {
5959
fn default() -> Self {
60+
let required_signatures = required_justification_precommits(test_keyring().len() as _);
6061
Self {
6162
header: test_header(One::one()),
6263
round: TEST_GRANDPA_ROUND,
6364
set_id: TEST_GRANDPA_SET_ID,
64-
authorities: test_keyring(),
65+
authorities: test_keyring().into_iter().take(required_signatures as _).collect(),
6566
ancestors: 2,
6667
forks: 1,
6768
}

0 commit comments

Comments
 (0)