14
14
// You should have received a copy of the GNU General Public License
15
15
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
16
16
17
- use crate :: { Config , Error , Pallet } ;
17
+ use crate :: { weights:: WeightInfo , BridgedBlockNumber , BridgedHeader , Config , Error , Pallet } ;
18
+ use bp_header_chain:: { justification:: GrandpaJustification , ChainWithGrandpa } ;
18
19
use bp_runtime:: BlockNumberOf ;
19
- use frame_support:: { dispatch:: CallableCallFor , traits:: IsSubType } ;
20
+ use codec:: Encode ;
21
+ use frame_support:: { dispatch:: CallableCallFor , traits:: IsSubType , weights:: Weight , RuntimeDebug } ;
20
22
use sp_runtime:: {
21
- traits:: Header ,
23
+ traits:: { Header , Zero } ,
22
24
transaction_validity:: { InvalidTransaction , TransactionValidity , ValidTransaction } ,
25
+ SaturatedConversion ,
23
26
} ;
24
27
28
+ /// Info about a `SubmitParachainHeads` call which tries to update a single parachain.
29
+ #[ derive( Copy , Clone , PartialEq , RuntimeDebug ) ]
30
+ pub struct SubmitFinalityProofInfo < N > {
31
+ /// Number of the finality target.
32
+ pub block_number : N ,
33
+ /// Extra weight that we assume is included in the call.
34
+ ///
35
+ /// We have some assumptions about headers and justifications of the bridged chain.
36
+ /// We know that if our assumptions are correct, then the call must not have the
37
+ /// weight above some limit. The fee paid for weight above that limit, is never refunded.
38
+ pub extra_weight : Weight ,
39
+ /// Extra size (in bytes) that we assume are included in the call.
40
+ ///
41
+ /// We have some assumptions about headers and justifications of the bridged chain.
42
+ /// We know that if our assumptions are correct, then the call must not have the
43
+ /// weight above some limit. The fee paid for bytes above that limit, is never refunded.
44
+ pub extra_size : u32 ,
45
+ }
46
+
47
+ impl < N > SubmitFinalityProofInfo < N > {
48
+ /// Returns `true` if call size/weight is below our estimations for regular calls.
49
+ pub fn fits_limits ( & self ) -> bool {
50
+ self . extra_weight . is_zero ( ) && self . extra_size . is_zero ( )
51
+ }
52
+ }
53
+
25
54
/// Helper struct that provides methods for working with the `SubmitFinalityProof` call.
26
55
pub struct SubmitFinalityProofHelper < T : Config < I > , I : ' static > {
27
- pub _phantom_data : sp_std:: marker:: PhantomData < ( T , I ) > ,
56
+ _phantom_data : sp_std:: marker:: PhantomData < ( T , I ) > ,
28
57
}
29
58
30
59
impl < T : Config < I > , I : ' static > SubmitFinalityProofHelper < T , I > {
@@ -69,12 +98,17 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
69
98
pub trait CallSubType < T : Config < I , RuntimeCall = Self > , I : ' static > :
70
99
IsSubType < CallableCallFor < Pallet < T , I > , T > >
71
100
{
72
- /// Extract the finality target from a `SubmitParachainHeads` call.
73
- fn submit_finality_proof_info ( & self ) -> Option < BlockNumberOf < T :: BridgedChain > > {
74
- if let Some ( crate :: Call :: < T , I > :: submit_finality_proof { finality_target, .. } ) =
101
+ /// Extract finality proof info from a runtime call.
102
+ fn submit_finality_proof_info (
103
+ & self ,
104
+ ) -> Option < SubmitFinalityProofInfo < BridgedBlockNumber < T , I > > > {
105
+ if let Some ( crate :: Call :: < T , I > :: submit_finality_proof { finality_target, justification } ) =
75
106
self . is_sub_type ( )
76
107
{
77
- return Some ( * finality_target. number ( ) )
108
+ return Some ( submit_finality_proof_info_from_args :: < T , I > (
109
+ finality_target,
110
+ justification,
111
+ ) )
78
112
}
79
113
80
114
None
@@ -92,7 +126,7 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
92
126
_ => return Ok ( ValidTransaction :: default ( ) ) ,
93
127
} ;
94
128
95
- match SubmitFinalityProofHelper :: < T , I > :: check_obsolete ( finality_target) {
129
+ match SubmitFinalityProofHelper :: < T , I > :: check_obsolete ( finality_target. block_number ) {
96
130
Ok ( _) => Ok ( ValidTransaction :: default ( ) ) ,
97
131
Err ( Error :: < T , I > :: OldHeader ) => InvalidTransaction :: Stale . into ( ) ,
98
132
Err ( _) => InvalidTransaction :: Call . into ( ) ,
@@ -105,15 +139,66 @@ impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
105
139
{
106
140
}
107
141
142
+ /// Extract finality proof info from the submitted header and justification.
143
+ pub ( crate ) fn submit_finality_proof_info_from_args < T : Config < I > , I : ' static > (
144
+ finality_target : & BridgedHeader < T , I > ,
145
+ justification : & GrandpaJustification < BridgedHeader < T , I > > ,
146
+ ) -> SubmitFinalityProofInfo < BridgedBlockNumber < T , I > > {
147
+ let block_number = * finality_target. number ( ) ;
148
+
149
+ // the `submit_finality_proof` call will reject justifications with invalid, duplicate,
150
+ // unknown and extra signatures. It'll also reject justifications with less than necessary
151
+ // signatures. So we do not care about extra weight because of additional signatures here.
152
+ let precommits_len = justification. commit . precommits . len ( ) . saturated_into ( ) ;
153
+ let required_precommits = precommits_len;
154
+
155
+ // We do care about extra weight because of more-than-expected headers in the votes
156
+ // ancestries. But we have problems computing extra weight for additional headers (weight of
157
+ // additional header is too small, so that our benchmarks aren't detecting that). So if there
158
+ // are more than expected headers in votes ancestries, we will treat the whole call weight
159
+ // as an extra weight.
160
+ let votes_ancestries_len = justification. votes_ancestries . len ( ) . saturated_into ( ) ;
161
+ let extra_weight =
162
+ if votes_ancestries_len > T :: BridgedChain :: REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY {
163
+ T :: WeightInfo :: submit_finality_proof ( precommits_len, votes_ancestries_len)
164
+ } else {
165
+ Weight :: zero ( )
166
+ } ;
167
+
168
+ // we can estimate extra call size easily, without any additional significant overhead
169
+ let actual_call_size: u32 = finality_target
170
+ . encoded_size ( )
171
+ . saturating_add ( justification. encoded_size ( ) )
172
+ . saturated_into ( ) ;
173
+ let max_expected_call_size = max_expected_call_size :: < T , I > ( required_precommits) ;
174
+ let extra_size = actual_call_size. saturating_sub ( max_expected_call_size) ;
175
+
176
+ SubmitFinalityProofInfo { block_number, extra_weight, extra_size }
177
+ }
178
+
179
+ /// Returns maximal expected size of `submit_finality_proof` call arguments.
180
+ fn max_expected_call_size < T : Config < I > , I : ' static > ( required_precommits : u32 ) -> u32 {
181
+ let max_expected_justification_size =
182
+ GrandpaJustification :: max_reasonable_size :: < T :: BridgedChain > ( required_precommits) ;
183
+
184
+ // call arguments are header and justification
185
+ T :: BridgedChain :: MAX_HEADER_SIZE . saturating_add ( max_expected_justification_size)
186
+ }
187
+
108
188
#[ cfg( test) ]
109
189
mod tests {
110
190
use crate :: {
111
191
call_ext:: CallSubType ,
112
- mock:: { run_test, test_header, RuntimeCall , TestNumber , TestRuntime } ,
113
- BestFinalized ,
192
+ mock:: { run_test, test_header, RuntimeCall , TestBridgedChain , TestNumber , TestRuntime } ,
193
+ BestFinalized , Config , WeightInfo ,
114
194
} ;
195
+ use bp_header_chain:: ChainWithGrandpa ;
115
196
use bp_runtime:: HeaderId ;
116
- use bp_test_utils:: make_default_justification;
197
+ use bp_test_utils:: {
198
+ make_default_justification, make_justification_for_header, JustificationGeneratorParams ,
199
+ } ;
200
+ use frame_support:: weights:: Weight ;
201
+ use sp_runtime:: { testing:: DigestItem , traits:: Header as _, SaturatedConversion } ;
117
202
118
203
fn validate_block_submit ( num : TestNumber ) -> bool {
119
204
let bridge_grandpa_call = crate :: Call :: < TestRuntime , ( ) > :: submit_finality_proof {
@@ -160,4 +245,67 @@ mod tests {
160
245
assert ! ( validate_block_submit( 15 ) ) ;
161
246
} ) ;
162
247
}
248
+
249
+ #[ test]
250
+ fn extension_returns_correct_extra_size_if_call_arguments_are_too_large ( ) {
251
+ // when call arguments are below our limit => no refund
252
+ let small_finality_target = test_header ( 1 ) ;
253
+ let justification_params = JustificationGeneratorParams {
254
+ header : small_finality_target. clone ( ) ,
255
+ ..Default :: default ( )
256
+ } ;
257
+ let small_justification = make_justification_for_header ( justification_params) ;
258
+ let small_call = RuntimeCall :: Grandpa ( crate :: Call :: submit_finality_proof {
259
+ finality_target : Box :: new ( small_finality_target) ,
260
+ justification : small_justification,
261
+ } ) ;
262
+ assert_eq ! ( small_call. submit_finality_proof_info( ) . unwrap( ) . extra_size, 0 ) ;
263
+
264
+ // when call arguments are too large => partial refund
265
+ let mut large_finality_target = test_header ( 1 ) ;
266
+ large_finality_target
267
+ . digest_mut ( )
268
+ . push ( DigestItem :: Other ( vec ! [ 42u8 ; 1024 * 1024 ] ) ) ;
269
+ let justification_params = JustificationGeneratorParams {
270
+ header : large_finality_target. clone ( ) ,
271
+ ..Default :: default ( )
272
+ } ;
273
+ let large_justification = make_justification_for_header ( justification_params) ;
274
+ let large_call = RuntimeCall :: Grandpa ( crate :: Call :: submit_finality_proof {
275
+ finality_target : Box :: new ( large_finality_target) ,
276
+ justification : large_justification,
277
+ } ) ;
278
+ assert_ne ! ( large_call. submit_finality_proof_info( ) . unwrap( ) . extra_size, 0 ) ;
279
+ }
280
+
281
+ #[ test]
282
+ fn extension_returns_correct_extra_weight_if_there_are_too_many_headers_in_votes_ancestry ( ) {
283
+ let finality_target = test_header ( 1 ) ;
284
+ let mut justification_params = JustificationGeneratorParams {
285
+ header : finality_target. clone ( ) ,
286
+ ancestors : TestBridgedChain :: REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY ,
287
+ ..Default :: default ( )
288
+ } ;
289
+
290
+ // when there are `REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY` headers => no refund
291
+ let justification = make_justification_for_header ( justification_params. clone ( ) ) ;
292
+ let call = RuntimeCall :: Grandpa ( crate :: Call :: submit_finality_proof {
293
+ finality_target : Box :: new ( finality_target. clone ( ) ) ,
294
+ justification,
295
+ } ) ;
296
+ assert_eq ! ( call. submit_finality_proof_info( ) . unwrap( ) . extra_weight, Weight :: zero( ) ) ;
297
+
298
+ // when there are `REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY + 1` headers => full refund
299
+ justification_params. ancestors += 1 ;
300
+ let justification = make_justification_for_header ( justification_params) ;
301
+ let call_weight = <TestRuntime as Config >:: WeightInfo :: submit_finality_proof (
302
+ justification. commit . precommits . len ( ) . saturated_into ( ) ,
303
+ justification. votes_ancestries . len ( ) . saturated_into ( ) ,
304
+ ) ;
305
+ let call = RuntimeCall :: Grandpa ( crate :: Call :: submit_finality_proof {
306
+ finality_target : Box :: new ( finality_target) ,
307
+ justification,
308
+ } ) ;
309
+ assert_eq ! ( call. submit_finality_proof_info( ) . unwrap( ) . extra_weight, call_weight) ;
310
+ }
163
311
}
0 commit comments