@@ -71,6 +71,14 @@ pub enum Error {
71
71
ExtraHeadersInVotesAncestries ,
72
72
}
73
73
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
+
74
82
/// Decode justification target.
75
83
pub fn decode_justification_target < Header : HeaderT > (
76
84
raw_justification : & [ u8 ] ,
@@ -80,13 +88,111 @@ pub fn decode_justification_target<Header: HeaderT>(
80
88
. map_err ( |_| Error :: JustificationDecode )
81
89
}
82
90
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
+
83
112
/// Verify that justification, that is generated by given authority set, finalizes given header.
84
113
pub fn verify_justification < Header : HeaderT > (
85
114
finalized_target : ( Header :: Hash , Header :: Number ) ,
86
115
authorities_set_id : SetId ,
87
116
authorities_set : & VoterSet < AuthorityId > ,
88
117
justification : & GrandpaJustification < Header > ,
89
118
) -> 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 >
90
196
where
91
197
Header :: Number : finality_grandpa:: BlockNumberOps ,
92
198
{
@@ -95,17 +201,23 @@ where
95
201
return Err ( Error :: InvalidJustificationTarget )
96
202
}
97
203
204
+ let threshold = authorities_set. threshold ( ) . 0 . into ( ) ;
98
205
let mut chain = AncestryChain :: new ( & justification. votes_ancestries ) ;
99
206
let mut signature_buffer = Vec :: new ( ) ;
100
207
let mut votes = BTreeSet :: new ( ) ;
101
208
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
+
103
216
// authority must be in the set
104
217
let authority_info = match authorities_set. get ( & signed. id ) {
105
218
Some ( authority_info) => authority_info,
106
219
None => {
107
- // just ignore precommit from unknown authority as
108
- // `finality_grandpa::import_precommit` does
220
+ callbacks. on_unkown_authority ( precommit_idx) ?;
109
221
continue
110
222
} ,
111
223
} ;
@@ -116,6 +228,7 @@ where
116
228
// `finality-grandpa` crate (mostly related to reporting equivocations). But the only thing
117
229
// that we care about is that only first vote from the authority is accepted
118
230
if !votes. insert ( signed. id . clone ( ) ) {
231
+ callbacks. on_duplicate_authority_vote ( precommit_idx) ?;
119
232
continue
120
233
}
121
234
@@ -142,6 +255,7 @@ where
142
255
thus we'll never overflow the u64::MAX;\
143
256
qed",
144
257
) ;
258
+
145
259
// verify authority signature
146
260
if !sp_finality_grandpa:: check_message_signature_with_buffer (
147
261
& finality_grandpa:: Message :: Precommit ( signed. precommit . clone ( ) ) ,
@@ -162,7 +276,6 @@ where
162
276
163
277
// check that the cumulative weight of validators voted for the justification target (or one
164
278
// of its descendents) is larger than required threshold.
165
- let threshold = authorities_set. threshold ( ) . 0 . into ( ) ;
166
279
if cumulative_weight >= threshold {
167
280
Ok ( ( ) )
168
281
} else {
0 commit comments