Skip to main content

subsoil/weights/
weight_meter.rs

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