referrerpolicy=no-referrer-when-downgrade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.

// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.

//! Weight-related utilities.

use crate::weights::WeightInfo;

use bp_messages::{MessageNonce, UnrewardedRelayersState};
use bp_runtime::{PreComputedSize, Size};
use frame_support::weights::Weight;

/// Size of the message being delivered in benchmarks.
pub const EXPECTED_DEFAULT_MESSAGE_LENGTH: u32 = 128;

/// We assume that size of signed extensions on all our chains and size of all 'small' arguments of
/// calls we're checking here would fit 1KB.
const SIGNED_EXTENSIONS_SIZE: u32 = 1024;

/// Number of extra bytes (excluding size of storage value itself) of storage proof.
/// This mostly depends on number of entries (and their density) in the storage trie.
/// Some reserve is reserved to account future chain growth.
pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;

/// Ensure that weights from `WeightInfoExt` implementation are looking correct.
pub fn ensure_weights_are_correct<W: WeightInfoExt>() {
	// all components of weight formulae must have zero `proof_size`, because the `proof_size` is
	// benchmarked using `MaxEncodedLen` approach and there are no components that cause additional
	// db reads

	// W::receive_messages_proof_outbound_lane_state_overhead().ref_time() may be zero because:
	// the outbound lane state processing code (`InboundLane::receive_state_update`) is minimal and
	// may not be accounted by our benchmarks
	assert_eq!(W::receive_messages_proof_outbound_lane_state_overhead().proof_size(), 0);
	assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
	assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);

	// verify `receive_messages_delivery_proof` weight components
	assert_ne!(W::receive_messages_delivery_proof_overhead().ref_time(), 0);
	assert_ne!(W::receive_messages_delivery_proof_overhead().proof_size(), 0);
	// W::receive_messages_delivery_proof_messages_overhead(1).ref_time() may be zero because:
	// there's no code that iterates over confirmed messages in confirmation transaction
	assert_eq!(W::receive_messages_delivery_proof_messages_overhead(1).proof_size(), 0);
	// W::receive_messages_delivery_proof_relayers_overhead(1).ref_time() may be zero because:
	// runtime **can** choose not to pay any rewards to relayers
	// W::receive_messages_delivery_proof_relayers_overhead(1).proof_size() is an exception
	// it may or may not cause additional db reads, so proof size may vary
	assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
	assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);

	// verify `receive_message_proof` weight
	let receive_messages_proof_weight =
		W::receive_messages_proof_weight(&PreComputedSize(1), 10, Weight::zero());
	assert_ne!(receive_messages_proof_weight.ref_time(), 0);
	assert_ne!(receive_messages_proof_weight.proof_size(), 0);
	messages_proof_size_does_not_affect_proof_size::<W>();
	messages_count_does_not_affect_proof_size::<W>();

	// verify `receive_message_proof` weight
	let receive_messages_delivery_proof_weight = W::receive_messages_delivery_proof_weight(
		&PreComputedSize(1),
		&UnrewardedRelayersState::default(),
	);
	assert_ne!(receive_messages_delivery_proof_weight.ref_time(), 0);
	assert_ne!(receive_messages_delivery_proof_weight.proof_size(), 0);
	messages_delivery_proof_size_does_not_affect_proof_size::<W>();
	total_messages_in_delivery_proof_does_not_affect_proof_size::<W>();
}

/// Ensure that we are able to dispatch maximal size messages.
pub fn ensure_maximal_message_dispatch<W: WeightInfoExt>(
	max_incoming_message_size: u32,
	max_incoming_message_dispatch_weight: Weight,
) {
	let message_dispatch_weight = W::message_dispatch_weight(max_incoming_message_size);
	assert!(
		message_dispatch_weight.all_lte(max_incoming_message_dispatch_weight),
		"Dispatch weight of maximal message {message_dispatch_weight:?} must be lower \
		than the hardcoded {max_incoming_message_dispatch_weight:?}",
	);
}

/// Ensure that we're able to receive maximal (by-size and by-weight) message from other chain.
pub fn ensure_able_to_receive_message<W: WeightInfoExt>(
	max_extrinsic_size: u32,
	max_extrinsic_weight: Weight,
	max_incoming_message_proof_size: u32,
	max_incoming_message_dispatch_weight: Weight,
) {
	// verify that we're able to receive proof of maximal-size message
	let max_delivery_transaction_size =
		max_incoming_message_proof_size.saturating_add(SIGNED_EXTENSIONS_SIZE);
	assert!(
		max_delivery_transaction_size <= max_extrinsic_size,
		"Size of maximal message delivery transaction {max_incoming_message_proof_size} + \
		{SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
	);

	// verify that we're able to receive proof of maximal-size message with maximal dispatch weight
	let max_delivery_transaction_dispatch_weight = W::receive_messages_proof_weight(
		&PreComputedSize(
			(max_incoming_message_proof_size + W::expected_extra_storage_proof_size()) as usize,
		),
		1,
		max_incoming_message_dispatch_weight,
	);
	assert!(
		max_delivery_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
		"Weight of maximal message delivery transaction + {max_delivery_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
	);
}

/// Ensure that we're able to receive maximal confirmation from other chain.
pub fn ensure_able_to_receive_confirmation<W: WeightInfoExt>(
	max_extrinsic_size: u32,
	max_extrinsic_weight: Weight,
	max_inbound_lane_data_proof_size_from_peer_chain: u32,
	max_unrewarded_relayer_entries_at_peer_inbound_lane: MessageNonce,
	max_unconfirmed_messages_at_inbound_lane: MessageNonce,
) {
	// verify that we're able to receive confirmation of maximal-size
	let max_confirmation_transaction_size =
		max_inbound_lane_data_proof_size_from_peer_chain.saturating_add(SIGNED_EXTENSIONS_SIZE);
	assert!(
		max_confirmation_transaction_size <= max_extrinsic_size,
		"Size of maximal message delivery confirmation transaction {max_inbound_lane_data_proof_size_from_peer_chain} + {SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
	);

	// verify that we're able to reward maximal number of relayers that have delivered maximal
	// number of messages
	let max_confirmation_transaction_dispatch_weight = W::receive_messages_delivery_proof_weight(
		&PreComputedSize(max_inbound_lane_data_proof_size_from_peer_chain as usize),
		&UnrewardedRelayersState {
			unrewarded_relayer_entries: max_unrewarded_relayer_entries_at_peer_inbound_lane,
			total_messages: max_unconfirmed_messages_at_inbound_lane,
			..Default::default()
		},
	);
	assert!(
		max_confirmation_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
		"Weight of maximal confirmation transaction {max_confirmation_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
	);
}

/// Panics if `proof_size` of message delivery call depends on the message proof size.
fn messages_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
	let dispatch_weight = Weight::zero();
	let weight_when_proof_size_is_8k =
		W::receive_messages_proof_weight(&PreComputedSize(8 * 1024), 1, dispatch_weight);
	let weight_when_proof_size_is_16k =
		W::receive_messages_proof_weight(&PreComputedSize(16 * 1024), 1, dispatch_weight);

	ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
	ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
	ensure_proof_size_is_the_same(
		weight_when_proof_size_is_8k,
		weight_when_proof_size_is_16k,
		"Messages proof size does not affect values that we read from our storage",
	);
}

/// Panics if `proof_size` of message delivery call depends on the messages count.
///
/// In practice, it will depend on the messages count, because most probably every
/// message will read something from db during dispatch. But this must be accounted
/// by the `dispatch_weight`.
fn messages_count_does_not_affect_proof_size<W: WeightInfoExt>() {
	let messages_proof_size = PreComputedSize(8 * 1024);
	let dispatch_weight = Weight::zero();
	let weight_of_one_incoming_message =
		W::receive_messages_proof_weight(&messages_proof_size, 1, dispatch_weight);
	let weight_of_two_incoming_messages =
		W::receive_messages_proof_weight(&messages_proof_size, 2, dispatch_weight);

	ensure_weight_components_are_not_zero(weight_of_one_incoming_message);
	ensure_weight_components_are_not_zero(weight_of_two_incoming_messages);
	ensure_proof_size_is_the_same(
		weight_of_one_incoming_message,
		weight_of_two_incoming_messages,
		"Number of same-lane incoming messages does not affect values that we read from our storage",
	);
}

/// Panics if `proof_size` of delivery confirmation call depends on the delivery proof size.
fn messages_delivery_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
	let relayers_state = UnrewardedRelayersState {
		unrewarded_relayer_entries: 1,
		messages_in_oldest_entry: 1,
		total_messages: 1,
		last_delivered_nonce: 1,
	};
	let weight_when_proof_size_is_8k =
		W::receive_messages_delivery_proof_weight(&PreComputedSize(8 * 1024), &relayers_state);
	let weight_when_proof_size_is_16k =
		W::receive_messages_delivery_proof_weight(&PreComputedSize(16 * 1024), &relayers_state);

	ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
	ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
	ensure_proof_size_is_the_same(
		weight_when_proof_size_is_8k,
		weight_when_proof_size_is_16k,
		"Messages delivery proof size does not affect values that we read from our storage",
	);
}

/// Panics if `proof_size` of delivery confirmation call depends on the number of confirmed
/// messages.
fn total_messages_in_delivery_proof_does_not_affect_proof_size<W: WeightInfoExt>() {
	let proof_size = PreComputedSize(8 * 1024);
	let weight_when_1k_messages_confirmed = W::receive_messages_delivery_proof_weight(
		&proof_size,
		&UnrewardedRelayersState {
			unrewarded_relayer_entries: 1,
			messages_in_oldest_entry: 1,
			total_messages: 1024,
			last_delivered_nonce: 1,
		},
	);
	let weight_when_2k_messages_confirmed = W::receive_messages_delivery_proof_weight(
		&proof_size,
		&UnrewardedRelayersState {
			unrewarded_relayer_entries: 1,
			messages_in_oldest_entry: 1,
			total_messages: 2048,
			last_delivered_nonce: 1,
		},
	);

	ensure_weight_components_are_not_zero(weight_when_1k_messages_confirmed);
	ensure_weight_components_are_not_zero(weight_when_2k_messages_confirmed);
	ensure_proof_size_is_the_same(
		weight_when_1k_messages_confirmed,
		weight_when_2k_messages_confirmed,
		"More messages in delivery proof does not affect values that we read from our storage",
	);
}

/// Panics if either Weight' `proof_size` or `ref_time` are zero.
fn ensure_weight_components_are_not_zero(weight: Weight) {
	assert_ne!(weight.ref_time(), 0);
	assert_ne!(weight.proof_size(), 0);
}

/// Panics if `proof_size` of `weight1` is not equal to `proof_size` of `weight2`.
fn ensure_proof_size_is_the_same(weight1: Weight, weight2: Weight, msg: &str) {
	assert_eq!(
		weight1.proof_size(),
		weight2.proof_size(),
		"{msg}: {} must be equal to {}",
		weight1.proof_size(),
		weight2.proof_size(),
	);
}

/// Extended weight info.
pub trait WeightInfoExt: WeightInfo {
	/// Size of proof that is already included in the single message delivery weight.
	///
	/// The message submitter (at source chain) has already covered this cost. But there are two
	/// factors that may increase proof size: (1) the message size may be larger than predefined
	/// and (2) relayer may add extra trie nodes to the proof. So if proof size is larger than
	/// this value, we're going to charge relayer for that.
	fn expected_extra_storage_proof_size() -> u32;

	// Our configuration assumes that the runtime has special signed extensions used to:
	//
	// 1) reject obsolete delivery and confirmation transactions;
	//
	// 2) refund transaction cost to relayer and register his rewards.
	//
	// The checks in (1) are trivial, so its computation weight may be ignored. And we only touch
	// storage values that are read during the call. So we may ignore the weight of this check.
	//
	// However, during (2) we read and update storage values of other pallets
	// (`pallet-bridge-relayers` and balances/assets pallet). So we need to add this weight to the
	// weight of our call. Hence two following methods.

	/// Extra weight that is added to the `receive_messages_proof` call weight by signed extensions
	/// that are declared at runtime level.
	fn receive_messages_proof_overhead_from_runtime() -> Weight;

	/// Extra weight that is added to the `receive_messages_delivery_proof` call weight by signed
	/// extensions that are declared at runtime level.
	fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight;

	// Functions that are directly mapped to extrinsics weights.

	/// Weight of message delivery extrinsic.
	fn receive_messages_proof_weight(
		proof: &impl Size,
		messages_count: u32,
		dispatch_weight: Weight,
	) -> Weight {
		// basic components of extrinsic weight
		let base_weight = Self::receive_n_messages_proof(messages_count);
		let transaction_overhead_from_runtime =
			Self::receive_messages_proof_overhead_from_runtime();
		let outbound_state_delivery_weight =
			Self::receive_messages_proof_outbound_lane_state_overhead();
		let messages_dispatch_weight = dispatch_weight;

		// proof size overhead weight
		let expected_proof_size = EXPECTED_DEFAULT_MESSAGE_LENGTH
			.saturating_mul(messages_count.saturating_sub(1))
			.saturating_add(Self::expected_extra_storage_proof_size());
		let actual_proof_size = proof.size();
		let proof_size_overhead = Self::storage_proof_size_overhead(
			actual_proof_size.saturating_sub(expected_proof_size),
		);

		base_weight
			.saturating_add(transaction_overhead_from_runtime)
			.saturating_add(outbound_state_delivery_weight)
			.saturating_add(messages_dispatch_weight)
			.saturating_add(proof_size_overhead)
	}

	/// Weight of confirmation delivery extrinsic.
	fn receive_messages_delivery_proof_weight(
		proof: &impl Size,
		relayers_state: &UnrewardedRelayersState,
	) -> Weight {
		// basic components of extrinsic weight
		let transaction_overhead = Self::receive_messages_delivery_proof_overhead();
		let transaction_overhead_from_runtime =
			Self::receive_messages_delivery_proof_overhead_from_runtime();
		let messages_overhead =
			Self::receive_messages_delivery_proof_messages_overhead(relayers_state.total_messages);
		let relayers_overhead = Self::receive_messages_delivery_proof_relayers_overhead(
			relayers_state.unrewarded_relayer_entries,
		);

		// proof size overhead weight
		let expected_proof_size = Self::expected_extra_storage_proof_size();
		let actual_proof_size = proof.size();
		let proof_size_overhead = Self::storage_proof_size_overhead(
			actual_proof_size.saturating_sub(expected_proof_size),
		);

		transaction_overhead
			.saturating_add(transaction_overhead_from_runtime)
			.saturating_add(messages_overhead)
			.saturating_add(relayers_overhead)
			.saturating_add(proof_size_overhead)
	}

	// Functions that are used by extrinsics weights formulas.

	/// Returns weight that needs to be accounted when message delivery transaction
	/// (`receive_messages_proof`) is carrying outbound lane state proof.
	fn receive_messages_proof_outbound_lane_state_overhead() -> Weight {
		let weight_of_single_message_and_lane_state =
			Self::receive_single_message_proof_with_outbound_lane_state();
		let weight_of_single_message = Self::receive_single_message_proof();
		weight_of_single_message_and_lane_state.saturating_sub(weight_of_single_message)
	}

	/// Returns weight overhead of delivery confirmation transaction
	/// (`receive_messages_delivery_proof`).
	fn receive_messages_delivery_proof_overhead() -> Weight {
		let weight_of_two_messages_and_two_tx_overheads =
			Self::receive_delivery_proof_for_single_message().saturating_mul(2);
		let weight_of_two_messages_and_single_tx_overhead =
			Self::receive_delivery_proof_for_two_messages_by_single_relayer();
		weight_of_two_messages_and_two_tx_overheads
			.saturating_sub(weight_of_two_messages_and_single_tx_overhead)
	}

	/// Returns weight that needs to be accounted when receiving confirmations for given a number of
	/// messages with delivery confirmation transaction (`receive_messages_delivery_proof`).
	fn receive_messages_delivery_proof_messages_overhead(messages: MessageNonce) -> Weight {
		let weight_of_two_messages =
			Self::receive_delivery_proof_for_two_messages_by_single_relayer();
		let weight_of_single_message = Self::receive_delivery_proof_for_single_message();
		weight_of_two_messages
			.saturating_sub(weight_of_single_message)
			.saturating_mul(messages as _)
	}

	/// Returns weight that needs to be accounted when receiving confirmations for given a number of
	/// relayers entries with delivery confirmation transaction (`receive_messages_delivery_proof`).
	fn receive_messages_delivery_proof_relayers_overhead(relayers: MessageNonce) -> Weight {
		let weight_of_two_messages_by_two_relayers =
			Self::receive_delivery_proof_for_two_messages_by_two_relayers();
		let weight_of_two_messages_by_single_relayer =
			Self::receive_delivery_proof_for_two_messages_by_single_relayer();
		weight_of_two_messages_by_two_relayers
			.saturating_sub(weight_of_two_messages_by_single_relayer)
			.saturating_mul(relayers as _)
	}

	/// Returns weight that needs to be accounted when storage proof of given size is received
	/// (either in `receive_messages_proof` or `receive_messages_delivery_proof`).
	///
	/// **IMPORTANT**: this overhead is already included in the 'base' transaction cost - e.g. proof
	/// size depends on messages count or number of entries in the unrewarded relayers set. So this
	/// shouldn't be added to cost of transaction, but instead should act as a minimal cost that the
	/// relayer must pay when it relays proof of given size (even if cost based on other parameters
	/// is less than that cost).
	fn storage_proof_size_overhead(proof_size: u32) -> Weight {
		let proof_size_in_bytes = proof_size;
		let byte_weight = Self::receive_single_n_bytes_message_proof(2) -
			Self::receive_single_n_bytes_message_proof(1);
		proof_size_in_bytes * byte_weight
	}

	// Functions that may be used by runtime developers.

	/// Returns dispatch weight of message of given size.
	///
	/// This function would return correct value only if your runtime is configured to run
	/// `receive_single_message_proof_with_dispatch` benchmark. See its requirements for
	/// details.
	fn message_dispatch_weight(message_size: u32) -> Weight {
		let message_size_in_bytes = message_size;
		Self::receive_single_n_bytes_message_proof_with_dispatch(message_size_in_bytes)
			.saturating_sub(Self::receive_single_n_bytes_message_proof(message_size_in_bytes))
	}
}

impl WeightInfoExt for () {
	fn expected_extra_storage_proof_size() -> u32 {
		EXTRA_STORAGE_PROOF_SIZE
	}

	fn receive_messages_proof_overhead_from_runtime() -> Weight {
		Weight::zero()
	}

	fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
		Weight::zero()
	}
}

impl<T: frame_system::Config> WeightInfoExt for crate::weights::BridgeWeight<T> {
	fn expected_extra_storage_proof_size() -> u32 {
		EXTRA_STORAGE_PROOF_SIZE
	}

	fn receive_messages_proof_overhead_from_runtime() -> Weight {
		Weight::zero()
	}

	fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
		Weight::zero()
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use crate::{tests::mock::TestRuntime, weights::BridgeWeight};

	#[test]
	fn ensure_default_weights_are_correct() {
		ensure_weights_are_correct::<BridgeWeight<TestRuntime>>();
	}
}