referrerpolicy=no-referrer-when-downgrade

sp_weights/
weight_meter.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Contains the `WeightMeter` primitive to meter weight usage.
19
20use super::Weight;
21
22use sp_arithmetic::Perbill;
23
24/// Meters consumed weight and a hard limit for the maximal consumable weight.
25///
26/// Can be used to check if enough weight for an operation is available before committing to it.
27///
28/// # Example
29///
30/// ```rust
31/// use sp_weights::{Weight, WeightMeter};
32///
33/// // The weight is limited to (10, 0).
34/// let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
35/// // There is enough weight remaining for an operation with (6, 0) weight.
36/// assert!(meter.try_consume(Weight::from_parts(6, 0)).is_ok());
37/// assert_eq!(meter.remaining(), Weight::from_parts(4, 0));
38/// // There is not enough weight remaining for an operation with (5, 0) weight.
39/// assert!(!meter.try_consume(Weight::from_parts(5, 0)).is_ok());
40/// // The total limit is obviously unchanged:
41/// assert_eq!(meter.limit(), Weight::from_parts(10, 0));
42/// ```
43#[derive(Debug, Clone)]
44pub struct WeightMeter {
45	/// The already consumed weight.
46	consumed: Weight,
47
48	/// The maximal consumable weight.
49	limit: Weight,
50}
51
52impl WeightMeter {
53	/// Creates [`Self`] from `consumed` and `limit`.
54	pub fn with_consumed_and_limit(consumed: Weight, limit: Weight) -> Self {
55		Self { consumed, limit }
56	}
57
58	/// Creates [`Self`] from a limit for the maximal consumable weight.
59	pub fn with_limit(limit: Weight) -> Self {
60		Self { consumed: Weight::zero(), limit }
61	}
62
63	/// Creates [`Self`] with the maximal possible limit for the consumable weight.
64	pub fn new() -> Self {
65		Self::with_limit(Weight::MAX)
66	}
67
68	/// Change the limit to the given `weight`.
69	///
70	/// The actual weight will be determined by `min(weight, self.remaining())`.
71	pub fn limit_to(self, weight: Weight) -> Self {
72		Self::with_limit(self.remaining().min(weight))
73	}
74
75	/// The already consumed weight.
76	pub fn consumed(&self) -> Weight {
77		self.consumed
78	}
79
80	/// The limit can ever be accrued.
81	pub fn limit(&self) -> Weight {
82		self.limit
83	}
84
85	/// The remaining weight that can still be consumed.
86	pub fn remaining(&self) -> Weight {
87		self.limit.saturating_sub(self.consumed)
88	}
89
90	/// The ratio of consumed weight to the limit.
91	///
92	/// Calculates one ratio per component and returns the largest.
93	///
94	/// # Example
95	/// ```rust
96	/// use sp_weights::{Weight, WeightMeter};
97	/// use sp_arithmetic::Perbill;
98	///
99	/// let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
100	/// // Nothing consumed so far:
101	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(0));
102	/// meter.consume(Weight::from_parts(5, 5));
103	/// // The ref-time is the larger ratio:
104	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50));
105	/// meter.consume(Weight::from_parts(1, 10));
106	/// // Now the larger ratio is proof-size:
107	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(75));
108	/// // Eventually it reaches 100%:
109	/// meter.consume(Weight::from_parts(4, 0));
110	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
111	/// // Saturating the second component won't change anything anymore:
112	/// meter.consume(Weight::from_parts(0, 5));
113	/// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
114	/// ```
115	pub fn consumed_ratio(&self) -> Perbill {
116		let time = Perbill::from_rational(self.consumed.ref_time(), self.limit.ref_time());
117		let pov = Perbill::from_rational(self.consumed.proof_size(), self.limit.proof_size());
118		time.max(pov)
119	}
120
121	/// Consume some weight and defensively fail if it is over the limit. Saturate in any case.
122	pub fn consume(&mut self, w: Weight) {
123		self.consumed.saturating_accrue(w);
124		debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow");
125	}
126
127	/// Consume the given weight after checking that it can be consumed.
128	///
129	/// Returns `Ok` if the weight can be consumed or otherwise an `Err`.
130	pub fn try_consume(&mut self, w: Weight) -> Result<(), ()> {
131		self.consumed.checked_add(&w).map_or(Err(()), |test| {
132			if test.any_gt(self.limit) {
133				Err(())
134			} else {
135				self.consumed = test;
136				Ok(())
137			}
138		})
139	}
140
141	/// Check if the given weight can be consumed.
142	pub fn can_consume(&self, w: Weight) -> bool {
143		self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit))
144	}
145
146	/// Reclaim the given weight.
147	pub fn reclaim_proof_size(&mut self, s: u64) {
148		self.consumed.saturating_reduce(Weight::from_parts(0, s));
149	}
150}
151
152#[cfg(test)]
153mod tests {
154	use crate::*;
155	use sp_arithmetic::traits::Zero;
156
157	#[test]
158	fn weight_meter_remaining_works() {
159		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
160
161		assert_eq!(meter.try_consume(Weight::from_parts(5, 0)), Ok(()));
162		assert_eq!(meter.consumed, Weight::from_parts(5, 0));
163		assert_eq!(meter.remaining(), Weight::from_parts(5, 20));
164
165		assert_eq!(meter.try_consume(Weight::from_parts(2, 10)), Ok(()));
166		assert_eq!(meter.consumed, Weight::from_parts(7, 10));
167		assert_eq!(meter.remaining(), Weight::from_parts(3, 10));
168
169		assert_eq!(meter.try_consume(Weight::from_parts(3, 10)), Ok(()));
170		assert_eq!(meter.consumed, Weight::from_parts(10, 20));
171		assert_eq!(meter.remaining(), Weight::from_parts(0, 0));
172	}
173
174	#[test]
175	fn weight_meter_can_consume_works() {
176		let meter = WeightMeter::with_limit(Weight::from_parts(1, 1));
177
178		assert!(meter.can_consume(Weight::from_parts(0, 0)));
179		assert!(meter.can_consume(Weight::from_parts(1, 1)));
180		assert!(!meter.can_consume(Weight::from_parts(0, 2)));
181		assert!(!meter.can_consume(Weight::from_parts(2, 0)));
182		assert!(!meter.can_consume(Weight::from_parts(2, 2)));
183	}
184
185	#[test]
186	fn weight_meter_try_consume_works() {
187		let mut meter = WeightMeter::with_limit(Weight::from_parts(2, 2));
188
189		assert_eq!(meter.try_consume(Weight::from_parts(0, 0)), Ok(()));
190		assert_eq!(meter.try_consume(Weight::from_parts(1, 1)), Ok(()));
191		assert_eq!(meter.try_consume(Weight::from_parts(0, 2)), Err(()));
192		assert_eq!(meter.try_consume(Weight::from_parts(2, 0)), Err(()));
193		assert_eq!(meter.try_consume(Weight::from_parts(2, 2)), Err(()));
194		assert_eq!(meter.try_consume(Weight::from_parts(0, 1)), Ok(()));
195		assert_eq!(meter.try_consume(Weight::from_parts(1, 0)), Ok(()));
196	}
197
198	#[test]
199	fn weight_meter_check_and_can_consume_works() {
200		let mut meter = WeightMeter::new();
201
202		assert!(meter.can_consume(Weight::from_parts(u64::MAX, 0)));
203		assert_eq!(meter.try_consume(Weight::from_parts(u64::MAX, 0)), Ok(()));
204
205		assert!(meter.can_consume(Weight::from_parts(0, u64::MAX)));
206		assert_eq!(meter.try_consume(Weight::from_parts(0, u64::MAX)), Ok(()));
207
208		assert!(!meter.can_consume(Weight::from_parts(0, 1)));
209		assert_eq!(meter.try_consume(Weight::from_parts(0, 1)), Err(()));
210
211		assert!(!meter.can_consume(Weight::from_parts(1, 0)));
212		assert_eq!(meter.try_consume(Weight::from_parts(1, 0)), Err(()));
213
214		assert!(meter.can_consume(Weight::zero()));
215		assert_eq!(meter.try_consume(Weight::zero()), Ok(()));
216	}
217
218	#[test]
219	fn consumed_ratio_works() {
220		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 20));
221
222		assert_eq!(meter.try_consume(Weight::from_parts(5, 0)), Ok(()));
223		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50));
224		assert_eq!(meter.try_consume(Weight::from_parts(0, 12)), Ok(()));
225		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(60));
226
227		assert_eq!(meter.try_consume(Weight::from_parts(2, 0)), Ok(()));
228		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(70));
229		assert_eq!(meter.try_consume(Weight::from_parts(0, 4)), Ok(()));
230		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(80));
231
232		assert_eq!(meter.try_consume(Weight::from_parts(3, 0)), Ok(()));
233		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
234		assert_eq!(meter.try_consume(Weight::from_parts(0, 4)), Ok(()));
235		assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100));
236	}
237
238	#[test]
239	fn try_consume_works() {
240		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
241
242		assert!(meter.try_consume(Weight::from_parts(11, 0)).is_err());
243		assert!(meter.consumed().is_zero(), "No modification");
244
245		assert!(meter.try_consume(Weight::from_parts(9, 0)).is_ok());
246		assert!(meter.try_consume(Weight::from_parts(2, 0)).is_err());
247		assert!(meter.try_consume(Weight::from_parts(1, 0)).is_ok());
248		assert!(meter.remaining().is_zero());
249		assert_eq!(meter.consumed(), Weight::from_parts(10, 0));
250	}
251
252	#[test]
253	fn can_consume_works() {
254		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
255
256		assert!(!meter.can_consume(Weight::from_parts(11, 0)));
257		assert!(meter.consumed().is_zero(), "No modification");
258
259		assert!(meter.can_consume(Weight::from_parts(9, 0)));
260		meter.consume(Weight::from_parts(9, 0));
261		assert!(!meter.can_consume(Weight::from_parts(2, 0)));
262		assert!(meter.can_consume(Weight::from_parts(1, 0)));
263	}
264
265	#[test]
266	#[cfg(debug_assertions)]
267	fn consume_works() {
268		let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10));
269
270		meter.consume(Weight::from_parts(4, 0));
271		assert_eq!(meter.remaining(), Weight::from_parts(1, 10));
272		meter.consume(Weight::from_parts(1, 0));
273		assert_eq!(meter.remaining(), Weight::from_parts(0, 10));
274		meter.consume(Weight::from_parts(0, 10));
275		assert_eq!(meter.consumed(), Weight::from_parts(5, 10));
276	}
277
278	#[test]
279	#[cfg(debug_assertions)]
280	fn reclaim_works() {
281		let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10));
282
283		meter.consume(Weight::from_parts(5, 10));
284		assert_eq!(meter.consumed(), Weight::from_parts(5, 10));
285
286		meter.reclaim_proof_size(3);
287		assert_eq!(meter.consumed(), Weight::from_parts(5, 7));
288
289		meter.reclaim_proof_size(10);
290		assert_eq!(meter.consumed(), Weight::from_parts(5, 0));
291	}
292
293	#[test]
294	#[cfg(debug_assertions)]
295	#[should_panic(expected = "Weight counter overflow")]
296	fn consume_defensive_fail() {
297		let mut meter = WeightMeter::with_limit(Weight::from_parts(10, 0));
298		let _ = meter.consume(Weight::from_parts(11, 0));
299	}
300}