referrerpolicy=no-referrer-when-downgrade

pallet_bridge_messages/
weights_ext.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Weight-related utilities.
18
19use crate::weights::WeightInfo;
20
21use bp_messages::{MessageNonce, UnrewardedRelayersState};
22use bp_runtime::{PreComputedSize, Size};
23use frame_support::weights::Weight;
24
25/// Size of the message being delivered in benchmarks.
26pub const EXPECTED_DEFAULT_MESSAGE_LENGTH: u32 = 128;
27
28/// We assume that size of signed extensions on all our chains and size of all 'small' arguments of
29/// calls we're checking here would fit 1KB.
30const SIGNED_EXTENSIONS_SIZE: u32 = 1024;
31
32/// Number of extra bytes (excluding size of storage value itself) of storage proof.
33/// This mostly depends on number of entries (and their density) in the storage trie.
34/// Some reserve is reserved to account future chain growth.
35pub const EXTRA_STORAGE_PROOF_SIZE: u32 = 1024;
36
37/// Ensure that weights from `WeightInfoExt` implementation are looking correct.
38pub fn ensure_weights_are_correct<W: WeightInfoExt>() {
39	// all components of weight formulae must have zero `proof_size`, because the `proof_size` is
40	// benchmarked using `MaxEncodedLen` approach and there are no components that cause additional
41	// db reads
42
43	// W::receive_messages_proof_outbound_lane_state_overhead().ref_time() may be zero because:
44	// the outbound lane state processing code (`InboundLane::receive_state_update`) is minimal and
45	// may not be accounted by our benchmarks
46	assert_eq!(W::receive_messages_proof_outbound_lane_state_overhead().proof_size(), 0);
47	assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
48	assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
49
50	// verify `receive_messages_delivery_proof` weight components
51	assert_ne!(W::receive_messages_delivery_proof_overhead().ref_time(), 0);
52	assert_ne!(W::receive_messages_delivery_proof_overhead().proof_size(), 0);
53	// W::receive_messages_delivery_proof_messages_overhead(1).ref_time() may be zero because:
54	// there's no code that iterates over confirmed messages in confirmation transaction
55	assert_eq!(W::receive_messages_delivery_proof_messages_overhead(1).proof_size(), 0);
56	// W::receive_messages_delivery_proof_relayers_overhead(1).ref_time() may be zero because:
57	// runtime **can** choose not to pay any rewards to relayers
58	// W::receive_messages_delivery_proof_relayers_overhead(1).proof_size() is an exception
59	// it may or may not cause additional db reads, so proof size may vary
60	assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0);
61	assert_eq!(W::storage_proof_size_overhead(1).proof_size(), 0);
62
63	// verify `receive_message_proof` weight
64	let receive_messages_proof_weight =
65		W::receive_messages_proof_weight(&PreComputedSize(1), 10, Weight::zero());
66	assert_ne!(receive_messages_proof_weight.ref_time(), 0);
67	assert_ne!(receive_messages_proof_weight.proof_size(), 0);
68	messages_proof_size_does_not_affect_proof_size::<W>();
69	messages_count_does_not_affect_proof_size::<W>();
70
71	// verify `receive_message_proof` weight
72	let receive_messages_delivery_proof_weight = W::receive_messages_delivery_proof_weight(
73		&PreComputedSize(1),
74		&UnrewardedRelayersState::default(),
75	);
76	assert_ne!(receive_messages_delivery_proof_weight.ref_time(), 0);
77	assert_ne!(receive_messages_delivery_proof_weight.proof_size(), 0);
78	messages_delivery_proof_size_does_not_affect_proof_size::<W>();
79	total_messages_in_delivery_proof_does_not_affect_proof_size::<W>();
80}
81
82/// Ensure that we are able to dispatch maximal size messages.
83pub fn ensure_maximal_message_dispatch<W: WeightInfoExt>(
84	max_incoming_message_size: u32,
85	max_incoming_message_dispatch_weight: Weight,
86) {
87	let message_dispatch_weight = W::message_dispatch_weight(max_incoming_message_size);
88	assert!(
89		message_dispatch_weight.all_lte(max_incoming_message_dispatch_weight),
90		"Dispatch weight of maximal message {message_dispatch_weight:?} must be lower \
91		than the hardcoded {max_incoming_message_dispatch_weight:?}",
92	);
93}
94
95/// Ensure that we're able to receive maximal (by-size and by-weight) message from other chain.
96pub fn ensure_able_to_receive_message<W: WeightInfoExt>(
97	max_extrinsic_size: u32,
98	max_extrinsic_weight: Weight,
99	max_incoming_message_proof_size: u32,
100	max_incoming_message_dispatch_weight: Weight,
101) {
102	// verify that we're able to receive proof of maximal-size message
103	let max_delivery_transaction_size =
104		max_incoming_message_proof_size.saturating_add(SIGNED_EXTENSIONS_SIZE);
105	assert!(
106		max_delivery_transaction_size <= max_extrinsic_size,
107		"Size of maximal message delivery transaction {max_incoming_message_proof_size} + \
108		{SIGNED_EXTENSIONS_SIZE} is larger than maximal possible transaction size {max_extrinsic_size}",
109	);
110
111	// verify that we're able to receive proof of maximal-size message with maximal dispatch weight
112	let max_delivery_transaction_dispatch_weight = W::receive_messages_proof_weight(
113		&PreComputedSize(
114			(max_incoming_message_proof_size + W::expected_extra_storage_proof_size()) as usize,
115		),
116		1,
117		max_incoming_message_dispatch_weight,
118	);
119	assert!(
120		max_delivery_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
121		"Weight of maximal message delivery transaction + {max_delivery_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
122	);
123}
124
125/// Ensure that we're able to receive maximal confirmation from other chain.
126pub fn ensure_able_to_receive_confirmation<W: WeightInfoExt>(
127	max_extrinsic_size: u32,
128	max_extrinsic_weight: Weight,
129	max_inbound_lane_data_proof_size_from_peer_chain: u32,
130	max_unrewarded_relayer_entries_at_peer_inbound_lane: MessageNonce,
131	max_unconfirmed_messages_at_inbound_lane: MessageNonce,
132) {
133	// verify that we're able to receive confirmation of maximal-size
134	let max_confirmation_transaction_size =
135		max_inbound_lane_data_proof_size_from_peer_chain.saturating_add(SIGNED_EXTENSIONS_SIZE);
136	assert!(
137		max_confirmation_transaction_size <= max_extrinsic_size,
138		"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}",
139	);
140
141	// verify that we're able to reward maximal number of relayers that have delivered maximal
142	// number of messages
143	let max_confirmation_transaction_dispatch_weight = W::receive_messages_delivery_proof_weight(
144		&PreComputedSize(max_inbound_lane_data_proof_size_from_peer_chain as usize),
145		&UnrewardedRelayersState {
146			unrewarded_relayer_entries: max_unrewarded_relayer_entries_at_peer_inbound_lane,
147			total_messages: max_unconfirmed_messages_at_inbound_lane,
148			..Default::default()
149		},
150	);
151	assert!(
152		max_confirmation_transaction_dispatch_weight.all_lte(max_extrinsic_weight),
153		"Weight of maximal confirmation transaction {max_confirmation_transaction_dispatch_weight} is larger than maximal possible transaction weight {max_extrinsic_weight}",
154	);
155}
156
157/// Panics if `proof_size` of message delivery call depends on the message proof size.
158fn messages_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
159	let dispatch_weight = Weight::zero();
160	let weight_when_proof_size_is_8k =
161		W::receive_messages_proof_weight(&PreComputedSize(8 * 1024), 1, dispatch_weight);
162	let weight_when_proof_size_is_16k =
163		W::receive_messages_proof_weight(&PreComputedSize(16 * 1024), 1, dispatch_weight);
164
165	ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
166	ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
167	ensure_proof_size_is_the_same(
168		weight_when_proof_size_is_8k,
169		weight_when_proof_size_is_16k,
170		"Messages proof size does not affect values that we read from our storage",
171	);
172}
173
174/// Panics if `proof_size` of message delivery call depends on the messages count.
175///
176/// In practice, it will depend on the messages count, because most probably every
177/// message will read something from db during dispatch. But this must be accounted
178/// by the `dispatch_weight`.
179fn messages_count_does_not_affect_proof_size<W: WeightInfoExt>() {
180	let messages_proof_size = PreComputedSize(8 * 1024);
181	let dispatch_weight = Weight::zero();
182	let weight_of_one_incoming_message =
183		W::receive_messages_proof_weight(&messages_proof_size, 1, dispatch_weight);
184	let weight_of_two_incoming_messages =
185		W::receive_messages_proof_weight(&messages_proof_size, 2, dispatch_weight);
186
187	ensure_weight_components_are_not_zero(weight_of_one_incoming_message);
188	ensure_weight_components_are_not_zero(weight_of_two_incoming_messages);
189	ensure_proof_size_is_the_same(
190		weight_of_one_incoming_message,
191		weight_of_two_incoming_messages,
192		"Number of same-lane incoming messages does not affect values that we read from our storage",
193	);
194}
195
196/// Panics if `proof_size` of delivery confirmation call depends on the delivery proof size.
197fn messages_delivery_proof_size_does_not_affect_proof_size<W: WeightInfoExt>() {
198	let relayers_state = UnrewardedRelayersState {
199		unrewarded_relayer_entries: 1,
200		messages_in_oldest_entry: 1,
201		total_messages: 1,
202		last_delivered_nonce: 1,
203	};
204	let weight_when_proof_size_is_8k =
205		W::receive_messages_delivery_proof_weight(&PreComputedSize(8 * 1024), &relayers_state);
206	let weight_when_proof_size_is_16k =
207		W::receive_messages_delivery_proof_weight(&PreComputedSize(16 * 1024), &relayers_state);
208
209	ensure_weight_components_are_not_zero(weight_when_proof_size_is_8k);
210	ensure_weight_components_are_not_zero(weight_when_proof_size_is_16k);
211	ensure_proof_size_is_the_same(
212		weight_when_proof_size_is_8k,
213		weight_when_proof_size_is_16k,
214		"Messages delivery proof size does not affect values that we read from our storage",
215	);
216}
217
218/// Panics if `proof_size` of delivery confirmation call depends on the number of confirmed
219/// messages.
220fn total_messages_in_delivery_proof_does_not_affect_proof_size<W: WeightInfoExt>() {
221	let proof_size = PreComputedSize(8 * 1024);
222	let weight_when_1k_messages_confirmed = W::receive_messages_delivery_proof_weight(
223		&proof_size,
224		&UnrewardedRelayersState {
225			unrewarded_relayer_entries: 1,
226			messages_in_oldest_entry: 1,
227			total_messages: 1024,
228			last_delivered_nonce: 1,
229		},
230	);
231	let weight_when_2k_messages_confirmed = W::receive_messages_delivery_proof_weight(
232		&proof_size,
233		&UnrewardedRelayersState {
234			unrewarded_relayer_entries: 1,
235			messages_in_oldest_entry: 1,
236			total_messages: 2048,
237			last_delivered_nonce: 1,
238		},
239	);
240
241	ensure_weight_components_are_not_zero(weight_when_1k_messages_confirmed);
242	ensure_weight_components_are_not_zero(weight_when_2k_messages_confirmed);
243	ensure_proof_size_is_the_same(
244		weight_when_1k_messages_confirmed,
245		weight_when_2k_messages_confirmed,
246		"More messages in delivery proof does not affect values that we read from our storage",
247	);
248}
249
250/// Panics if either Weight' `proof_size` or `ref_time` are zero.
251fn ensure_weight_components_are_not_zero(weight: Weight) {
252	assert_ne!(weight.ref_time(), 0);
253	assert_ne!(weight.proof_size(), 0);
254}
255
256/// Panics if `proof_size` of `weight1` is not equal to `proof_size` of `weight2`.
257fn ensure_proof_size_is_the_same(weight1: Weight, weight2: Weight, msg: &str) {
258	assert_eq!(
259		weight1.proof_size(),
260		weight2.proof_size(),
261		"{msg}: {} must be equal to {}",
262		weight1.proof_size(),
263		weight2.proof_size(),
264	);
265}
266
267/// Extended weight info.
268pub trait WeightInfoExt: WeightInfo {
269	/// Size of proof that is already included in the single message delivery weight.
270	///
271	/// The message submitter (at source chain) has already covered this cost. But there are two
272	/// factors that may increase proof size: (1) the message size may be larger than predefined
273	/// and (2) relayer may add extra trie nodes to the proof. So if proof size is larger than
274	/// this value, we're going to charge relayer for that.
275	fn expected_extra_storage_proof_size() -> u32;
276
277	// Our configuration assumes that the runtime has special signed extensions used to:
278	//
279	// 1) reject obsolete delivery and confirmation transactions;
280	//
281	// 2) refund transaction cost to relayer and register his rewards.
282	//
283	// The checks in (1) are trivial, so its computation weight may be ignored. And we only touch
284	// storage values that are read during the call. So we may ignore the weight of this check.
285	//
286	// However, during (2) we read and update storage values of other pallets
287	// (`pallet-bridge-relayers` and balances/assets pallet). So we need to add this weight to the
288	// weight of our call. Hence two following methods.
289
290	/// Extra weight that is added to the `receive_messages_proof` call weight by signed extensions
291	/// that are declared at runtime level.
292	fn receive_messages_proof_overhead_from_runtime() -> Weight;
293
294	/// Extra weight that is added to the `receive_messages_delivery_proof` call weight by signed
295	/// extensions that are declared at runtime level.
296	fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight;
297
298	// Functions that are directly mapped to extrinsics weights.
299
300	/// Weight of message delivery extrinsic.
301	fn receive_messages_proof_weight(
302		proof: &impl Size,
303		messages_count: u32,
304		dispatch_weight: Weight,
305	) -> Weight {
306		// basic components of extrinsic weight
307		let base_weight = Self::receive_n_messages_proof(messages_count);
308		let transaction_overhead_from_runtime =
309			Self::receive_messages_proof_overhead_from_runtime();
310		let outbound_state_delivery_weight =
311			Self::receive_messages_proof_outbound_lane_state_overhead();
312		let messages_dispatch_weight = dispatch_weight;
313
314		// proof size overhead weight
315		let expected_proof_size = EXPECTED_DEFAULT_MESSAGE_LENGTH
316			.saturating_mul(messages_count.saturating_sub(1))
317			.saturating_add(Self::expected_extra_storage_proof_size());
318		let actual_proof_size = proof.size();
319		let proof_size_overhead = Self::storage_proof_size_overhead(
320			actual_proof_size.saturating_sub(expected_proof_size),
321		);
322
323		base_weight
324			.saturating_add(transaction_overhead_from_runtime)
325			.saturating_add(outbound_state_delivery_weight)
326			.saturating_add(messages_dispatch_weight)
327			.saturating_add(proof_size_overhead)
328	}
329
330	/// Weight of confirmation delivery extrinsic.
331	fn receive_messages_delivery_proof_weight(
332		proof: &impl Size,
333		relayers_state: &UnrewardedRelayersState,
334	) -> Weight {
335		// basic components of extrinsic weight
336		let transaction_overhead = Self::receive_messages_delivery_proof_overhead();
337		let transaction_overhead_from_runtime =
338			Self::receive_messages_delivery_proof_overhead_from_runtime();
339		let messages_overhead =
340			Self::receive_messages_delivery_proof_messages_overhead(relayers_state.total_messages);
341		let relayers_overhead = Self::receive_messages_delivery_proof_relayers_overhead(
342			relayers_state.unrewarded_relayer_entries,
343		);
344
345		// proof size overhead weight
346		let expected_proof_size = Self::expected_extra_storage_proof_size();
347		let actual_proof_size = proof.size();
348		let proof_size_overhead = Self::storage_proof_size_overhead(
349			actual_proof_size.saturating_sub(expected_proof_size),
350		);
351
352		transaction_overhead
353			.saturating_add(transaction_overhead_from_runtime)
354			.saturating_add(messages_overhead)
355			.saturating_add(relayers_overhead)
356			.saturating_add(proof_size_overhead)
357	}
358
359	// Functions that are used by extrinsics weights formulas.
360
361	/// Returns weight that needs to be accounted when message delivery transaction
362	/// (`receive_messages_proof`) is carrying outbound lane state proof.
363	fn receive_messages_proof_outbound_lane_state_overhead() -> Weight {
364		let weight_of_single_message_and_lane_state =
365			Self::receive_single_message_proof_with_outbound_lane_state();
366		let weight_of_single_message = Self::receive_single_message_proof();
367		weight_of_single_message_and_lane_state.saturating_sub(weight_of_single_message)
368	}
369
370	/// Returns weight overhead of delivery confirmation transaction
371	/// (`receive_messages_delivery_proof`).
372	fn receive_messages_delivery_proof_overhead() -> Weight {
373		let weight_of_two_messages_and_two_tx_overheads =
374			Self::receive_delivery_proof_for_single_message().saturating_mul(2);
375		let weight_of_two_messages_and_single_tx_overhead =
376			Self::receive_delivery_proof_for_two_messages_by_single_relayer();
377		weight_of_two_messages_and_two_tx_overheads
378			.saturating_sub(weight_of_two_messages_and_single_tx_overhead)
379	}
380
381	/// Returns weight that needs to be accounted when receiving confirmations for given a number of
382	/// messages with delivery confirmation transaction (`receive_messages_delivery_proof`).
383	fn receive_messages_delivery_proof_messages_overhead(messages: MessageNonce) -> Weight {
384		let weight_of_two_messages =
385			Self::receive_delivery_proof_for_two_messages_by_single_relayer();
386		let weight_of_single_message = Self::receive_delivery_proof_for_single_message();
387		weight_of_two_messages
388			.saturating_sub(weight_of_single_message)
389			.saturating_mul(messages as _)
390	}
391
392	/// Returns weight that needs to be accounted when receiving confirmations for given a number of
393	/// relayers entries with delivery confirmation transaction (`receive_messages_delivery_proof`).
394	fn receive_messages_delivery_proof_relayers_overhead(relayers: MessageNonce) -> Weight {
395		let weight_of_two_messages_by_two_relayers =
396			Self::receive_delivery_proof_for_two_messages_by_two_relayers();
397		let weight_of_two_messages_by_single_relayer =
398			Self::receive_delivery_proof_for_two_messages_by_single_relayer();
399		weight_of_two_messages_by_two_relayers
400			.saturating_sub(weight_of_two_messages_by_single_relayer)
401			.saturating_mul(relayers as _)
402	}
403
404	/// Returns weight that needs to be accounted when storage proof of given size is received
405	/// (either in `receive_messages_proof` or `receive_messages_delivery_proof`).
406	///
407	/// **IMPORTANT**: this overhead is already included in the 'base' transaction cost - e.g. proof
408	/// size depends on messages count or number of entries in the unrewarded relayers set. So this
409	/// shouldn't be added to cost of transaction, but instead should act as a minimal cost that the
410	/// relayer must pay when it relays proof of given size (even if cost based on other parameters
411	/// is less than that cost).
412	fn storage_proof_size_overhead(proof_size: u32) -> Weight {
413		let proof_size_in_bytes = proof_size;
414		let byte_weight = Self::receive_single_n_bytes_message_proof(2) -
415			Self::receive_single_n_bytes_message_proof(1);
416		proof_size_in_bytes * byte_weight
417	}
418
419	// Functions that may be used by runtime developers.
420
421	/// Returns dispatch weight of message of given size.
422	///
423	/// This function would return correct value only if your runtime is configured to run
424	/// `receive_single_message_proof_with_dispatch` benchmark. See its requirements for
425	/// details.
426	fn message_dispatch_weight(message_size: u32) -> Weight {
427		let message_size_in_bytes = message_size;
428		Self::receive_single_n_bytes_message_proof_with_dispatch(message_size_in_bytes)
429			.saturating_sub(Self::receive_single_n_bytes_message_proof(message_size_in_bytes))
430	}
431}
432
433impl WeightInfoExt for () {
434	fn expected_extra_storage_proof_size() -> u32 {
435		EXTRA_STORAGE_PROOF_SIZE
436	}
437
438	fn receive_messages_proof_overhead_from_runtime() -> Weight {
439		Weight::zero()
440	}
441
442	fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
443		Weight::zero()
444	}
445}
446
447impl<T: frame_system::Config> WeightInfoExt for crate::weights::BridgeWeight<T> {
448	fn expected_extra_storage_proof_size() -> u32 {
449		EXTRA_STORAGE_PROOF_SIZE
450	}
451
452	fn receive_messages_proof_overhead_from_runtime() -> Weight {
453		Weight::zero()
454	}
455
456	fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight {
457		Weight::zero()
458	}
459}
460
461#[cfg(test)]
462mod tests {
463	use super::*;
464	use crate::{tests::mock::TestRuntime, weights::BridgeWeight};
465
466	#[test]
467	fn ensure_default_weights_are_correct() {
468		ensure_weights_are_correct::<BridgeWeight<TestRuntime>>();
469	}
470}