/__w/smoldot/smoldot/repo/lib/src/transactions/pool.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Smoldot |
2 | | // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. |
3 | | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 |
4 | | |
5 | | // This program is free software: you can redistribute it and/or modify |
6 | | // it under the terms of the GNU General Public License as published by |
7 | | // the Free Software Foundation, either version 3 of the License, or |
8 | | // (at your option) any later version. |
9 | | |
10 | | // This program is distributed in the hope that it will be useful, |
11 | | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | // GNU General Public License for more details. |
14 | | |
15 | | // You should have received a copy of the GNU General Public License |
16 | | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 | | |
18 | | //! General-purpose transactions pool. |
19 | | //! |
20 | | //! The transactions pool is a complex data structure that holds a list of pending transactions, |
21 | | //! in other words transactions that should later be included in blocks, and a list of |
22 | | //! transactions that have been included in non-finalized blocks. |
23 | | //! |
24 | | //! See the [parent module's documentation](..) for an overview of transactions. |
25 | | //! |
26 | | //! # Overview |
27 | | //! |
28 | | //! The transactions pool stores a list of transactions that the local node desires to include in |
29 | | //! blocks, and a list of transactions that have already been included in blocks. Each of these |
30 | | //! transactions is either validated or not. A transaction in a block is assumed to always succeed |
31 | | //! validation. A validated transaction that isn't present in any block is a transaction that is |
32 | | //! assumed to be includable in a block in the future. |
33 | | //! |
34 | | //! The order in which transactions can be included in a block follows a complex system of |
35 | | //! "provided" and "required" tags. A transaction that *requires* some tags can only be included |
36 | | //! after all these tags have been *provided* by transactions earlier in the chain. |
37 | | //! |
38 | | //! The transactions pool isn't only about deciding which transactions to include in a block when |
39 | | //! authoring, but also about tracking the status of interesting transactions between the moment |
40 | | //! they become interesting and the moment the block they are included in becomes finalized. This |
41 | | //! is relevant both if the local node can potentially author blocks or not. |
42 | | //! |
43 | | //! The transactions pool tracks the height of the *best* chain, and only of the best chain. More |
44 | | //! precisely, it is aware of the height of the current best block. Forks are tracked. |
45 | | //! |
46 | | //! # Usage |
47 | | //! |
48 | | //! A [`Pool`] is a collection of transactions. Each transaction in the pool exposes three |
49 | | //! properties: |
50 | | //! |
51 | | //! - Whether or not it has been validated, and if yes, the block against which it has been |
52 | | //! validated and the characteristics of the transaction (as provided by the runtime). These |
53 | | //! characterstics are: the tags it provides and requires, its longevity, and its priority. |
54 | | //! See [the `validate` module](../validate) for more information. |
55 | | //! - The height of the block, if any, in which the transaction has been included. |
56 | | //! - A so-called user data, an opaque field controlled by the API user. |
57 | | //! |
58 | | //! Use [`Pool::add_unvalidated`] to add to the pool a transaction that should be included in a |
59 | | //! block at a later point in time. |
60 | | //! |
61 | | //! When a new block is considered as best, use [`Pool::retract_blocks`] to remove all the re-orged |
62 | | //! blocks, then [`Pool::append_empty_block`] and |
63 | | //! [`Pool::best_block_add_transaction_by_scale_encoding`] to add the new block(s). The |
64 | | //! transactions that are passed to [`Pool::best_block_add_transaction_by_scale_encoding`] are |
65 | | //! added to the pool. |
66 | | //! |
67 | | //! Use [`Pool::unvalidated_transactions`] to obtain the list of transactions that should be |
68 | | //! validated. Validation should be performed using the [`validate`](../validate) module, and |
69 | | //! the result reported with [`Pool::set_validation_result`]. |
70 | | //! |
71 | | //! Use [`Pool::remove_included`] when a block has been finalized to remove from the pool the |
72 | | //! transactions that are present in the finalized block and below. |
73 | | //! |
74 | | //! When authoring a block, use [`Pool::append_empty_block`] and |
75 | | //! [`Pool::best_block_includable_transactions`] to determine which transaction to include |
76 | | //! next. Use [`Pool::best_block_add_transaction_by_id`] when the transaction has been included. |
77 | | //! |
78 | | //! # Out of scope |
79 | | //! |
80 | | //! The following are examples of things that are related transactions pool to but out of scope of |
81 | | //! this data structure: |
82 | | //! |
83 | | //! - Watching the state of transactions. |
84 | | //! - Sending transactions to other peers. |
85 | | //! |
86 | | |
87 | | use alloc::{ |
88 | | collections::{btree_set, BTreeSet}, |
89 | | vec::Vec, |
90 | | }; |
91 | | use core::{fmt, iter, mem, ops}; |
92 | | use hashbrown::HashSet; |
93 | | |
94 | | mod tests; |
95 | | |
96 | | pub use super::validate::ValidTransaction; |
97 | | |
98 | | /// Identifier of a transaction stored within the [`Pool`]. |
99 | | /// |
100 | | /// Identifiers can be re-used by the pool. In other words, a transaction id can compare equal to |
101 | | /// an older transaction id that is no longer in the pool. |
102 | | // |
103 | | // Implementation note: corresponds to indices within [`Pool::transactions`]. |
104 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
105 | | pub struct TransactionId(usize); |
106 | | |
107 | | /// Configuration for [`Pool::new`]. |
108 | | pub struct Config { |
109 | | /// Number of transactions to initially allocate memory for. |
110 | | /// |
111 | | /// > **Note**: This should take into account the fact that the pool will contain the |
112 | | /// > transactions included in new blocks. In other words, it should be equal to |
113 | | /// > `expected_max_reorg_depth * expected_max_transactions_per_block + |
114 | | /// > max_concurrent_desired_transactions`. |
115 | | pub capacity: usize, |
116 | | |
117 | | /// Height of the finalized block at initialization. |
118 | | /// |
119 | | /// The [`Pool`] doesn't track which block is finalized. This value is only used to initialize |
120 | | /// the best block number. The field could also have been called `best_block_height`, but doing |
121 | | /// so might created confusion. |
122 | | /// |
123 | | /// Non-finalized blocks should be added to the pool after initialization using |
124 | | /// [`Pool::append_empty_block`]. |
125 | | pub finalized_block_height: u64, |
126 | | |
127 | | /// Seed for randomness used to avoid HashDoS attacks. |
128 | | pub randomness_seed: [u8; 16], |
129 | | } |
130 | | |
131 | | /// Data structure containing transactions. See the module-level documentation for more info. |
132 | | pub struct Pool<TTx> { |
133 | | /// Actual list of transactions. |
134 | | transactions: slab::Slab<Transaction<TTx>>, |
135 | | |
136 | | /// List of transactions (represented as indices within [`Pool::transactions`]) whose status |
137 | | /// is "not validated". |
138 | | // TODO: shrink_to_fit from time to time? |
139 | | not_validated: HashSet<TransactionId, fnv::FnvBuildHasher>, |
140 | | |
141 | | /// Transaction ids (i.e. indices within [`Pool::transactions`]) indexed by the BLAKE2 hash |
142 | | /// of the bytes of the transaction. |
143 | | by_hash: BTreeSet<([u8; 32], TransactionId)>, |
144 | | |
145 | | /// Transaction ids (i.e. indices within [`Pool::transactions`]) indexed by the block height |
146 | | /// in which the transaction is included. |
147 | | by_height: BTreeSet<(u64, TransactionId)>, |
148 | | |
149 | | /// Validated transaction ids (i.e. indices within [`Pool::transactions`]) that are includable |
150 | | /// in the chain, indexed by the priority value provided by the validation. |
151 | | includable: BTreeSet<(u64, TransactionId)>, |
152 | | |
153 | | /// Validated transaction ids (i.e. indices within [`Pool::transactions`]) indexed by the |
154 | | /// block height at which their validation expires. |
155 | | by_validation_expiration: BTreeSet<(u64, TransactionId)>, |
156 | | |
157 | | /// List of all tags that are in the `provides` or `requires` tag lists of any of the validated |
158 | | /// transactions. |
159 | | // TODO: shrink_to_fit from time to time? |
160 | | tags: hashbrown::HashMap<Vec<u8>, TagInfo, crate::util::SipHasherBuild>, |
161 | | |
162 | | /// Height of the latest best block, as known from the pool. |
163 | | best_block_height: u64, |
164 | | } |
165 | | |
166 | | /// Entry in [`Pool::transactions`]. |
167 | | struct Transaction<TTx> { |
168 | | /// Bytes corresponding to the SCALE-encoded transaction. |
169 | | scale_encoded: Vec<u8>, |
170 | | |
171 | | /// If `Some`, contains the outcome of the validation of this transaction and the block height |
172 | | /// it was validated against. |
173 | | validation: Option<(u64, ValidTransaction)>, |
174 | | |
175 | | /// If `Some`, the height of the block at which the transaction has been included. |
176 | | included_block_height: Option<u64>, |
177 | | |
178 | | /// User data chosen by the user. |
179 | | user_data: TTx, |
180 | | } |
181 | | |
182 | | /// Entry in [`Pool::tags`]. |
183 | | struct TagInfo { |
184 | | /// List of validated transactions that have the tag in their `provides` tags list. |
185 | | // TODO: shrink_to_fit from time to time? |
186 | | provided_by: hashbrown::HashSet<TransactionId, fnv::FnvBuildHasher>, |
187 | | |
188 | | /// List of validated transactions that have the tag in their `requires` tags list. |
189 | | // TODO: shrink_to_fit from time to time? |
190 | | required_by: hashbrown::HashSet<TransactionId, fnv::FnvBuildHasher>, |
191 | | |
192 | | /// Number of transactions in [`TagInfo::provided_by`] that are included in the chain. |
193 | | /// |
194 | | /// Note that a value strictly superior to 1 indicates some kind of bug in the logic of the |
195 | | /// runtime. However, we don't care about this in the pool and just want the pool to function |
196 | | /// properly. |
197 | | known_to_be_included_in_chain: usize, |
198 | | } |
199 | | |
200 | | impl<TTx> Pool<TTx> { |
201 | | /// Initializes a new transactions pool. |
202 | 1 | pub fn new(config: Config) -> Self { |
203 | 1 | Pool { |
204 | 1 | transactions: slab::Slab::with_capacity(config.capacity), |
205 | 1 | not_validated: HashSet::with_capacity_and_hasher(config.capacity, Default::default()), |
206 | 1 | by_hash: BTreeSet::new(), |
207 | 1 | by_height: BTreeSet::new(), |
208 | 1 | includable: BTreeSet::new(), |
209 | 1 | by_validation_expiration: BTreeSet::new(), |
210 | 1 | tags: hashbrown::HashMap::with_capacity_and_hasher( |
211 | 1 | 8, |
212 | 1 | crate::util::SipHasherBuild::new(config.randomness_seed), |
213 | 1 | ), |
214 | 1 | best_block_height: config.finalized_block_height, |
215 | 1 | } |
216 | 1 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE3newB6_ Line | Count | Source | 202 | 1 | pub fn new(config: Config) -> Self { | 203 | 1 | Pool { | 204 | 1 | transactions: slab::Slab::with_capacity(config.capacity), | 205 | 1 | not_validated: HashSet::with_capacity_and_hasher(config.capacity, Default::default()), | 206 | 1 | by_hash: BTreeSet::new(), | 207 | 1 | by_height: BTreeSet::new(), | 208 | 1 | includable: BTreeSet::new(), | 209 | 1 | by_validation_expiration: BTreeSet::new(), | 210 | 1 | tags: hashbrown::HashMap::with_capacity_and_hasher( | 211 | 1 | 8, | 212 | 1 | crate::util::SipHasherBuild::new(config.randomness_seed), | 213 | 1 | ), | 214 | 1 | best_block_height: config.finalized_block_height, | 215 | 1 | } | 216 | 1 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE3newB6_ |
217 | | |
218 | | /// Returns true if the pool is empty. |
219 | 0 | pub fn is_empty(&self) -> bool { |
220 | 0 | self.transactions.is_empty() |
221 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE8is_emptyB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE8is_emptyB6_ |
222 | | |
223 | | /// Returns the number of transactions in the pool. |
224 | 0 | pub fn len(&self) -> usize { |
225 | 0 | self.transactions.len() |
226 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE3lenB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE3lenB6_ |
227 | | |
228 | | /// Inserts a new non-validated transaction in the pool. |
229 | 1 | pub fn add_unvalidated(&mut self, scale_encoded: Vec<u8>, user_data: TTx) -> TransactionId { |
230 | 1 | self.add_unvalidated_inner(scale_encoded, None, user_data) |
231 | 1 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE15add_unvalidatedB6_ Line | Count | Source | 229 | 1 | pub fn add_unvalidated(&mut self, scale_encoded: Vec<u8>, user_data: TTx) -> TransactionId { | 230 | 1 | self.add_unvalidated_inner(scale_encoded, None, user_data) | 231 | 1 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE15add_unvalidatedB6_ |
232 | | |
233 | | /// Inserts a new non-validated transaction in the pool. |
234 | 1 | fn add_unvalidated_inner( |
235 | 1 | &mut self, |
236 | 1 | scale_encoded: impl AsRef<[u8]> + Into<Vec<u8>>, |
237 | 1 | included_block_height: Option<u64>, |
238 | 1 | user_data: TTx, |
239 | 1 | ) -> TransactionId { |
240 | 1 | let hash = blake2_hash(scale_encoded.as_ref()); |
241 | 1 | |
242 | 1 | let tx_id = TransactionId(self.transactions.insert(Transaction { |
243 | 1 | scale_encoded: scale_encoded.into(), |
244 | 1 | validation: None, |
245 | 1 | included_block_height, |
246 | 1 | user_data, |
247 | 1 | })); |
248 | 1 | |
249 | 1 | let _was_inserted = self.by_hash.insert((hash, tx_id)); |
250 | 1 | debug_assert!(_was_inserted); |
251 | | |
252 | 1 | let _was_inserted = self.not_validated.insert(tx_id); |
253 | 1 | debug_assert!(_was_inserted); |
254 | | |
255 | 1 | if let Some(included_block_height0 ) = included_block_height { |
256 | 0 | let _was_inserted = self.by_height.insert((included_block_height, tx_id)); |
257 | 0 | debug_assert!(_was_inserted); |
258 | 1 | } |
259 | | |
260 | 1 | tx_id |
261 | 1 | } _RINvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB3_4PooluE21add_unvalidated_innerINtNtCsdZExvAaxgia_5alloc3vec3VechEEB7_ Line | Count | Source | 234 | 1 | fn add_unvalidated_inner( | 235 | 1 | &mut self, | 236 | 1 | scale_encoded: impl AsRef<[u8]> + Into<Vec<u8>>, | 237 | 1 | included_block_height: Option<u64>, | 238 | 1 | user_data: TTx, | 239 | 1 | ) -> TransactionId { | 240 | 1 | let hash = blake2_hash(scale_encoded.as_ref()); | 241 | 1 | | 242 | 1 | let tx_id = TransactionId(self.transactions.insert(Transaction { | 243 | 1 | scale_encoded: scale_encoded.into(), | 244 | 1 | validation: None, | 245 | 1 | included_block_height, | 246 | 1 | user_data, | 247 | 1 | })); | 248 | 1 | | 249 | 1 | let _was_inserted = self.by_hash.insert((hash, tx_id)); | 250 | 1 | debug_assert!(_was_inserted); | 251 | | | 252 | 1 | let _was_inserted = self.not_validated.insert(tx_id); | 253 | 1 | debug_assert!(_was_inserted); | 254 | | | 255 | 1 | if let Some(included_block_height0 ) = included_block_height { | 256 | 0 | let _was_inserted = self.by_height.insert((included_block_height, tx_id)); | 257 | 0 | debug_assert!(_was_inserted); | 258 | 1 | } | 259 | | | 260 | 1 | tx_id | 261 | 1 | } |
Unexecuted instantiation: _RINvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB3_4PoolpE21add_unvalidated_innerpEB7_ |
262 | | |
263 | | /// Removes from the pool the transaction with the given identifier. |
264 | | /// |
265 | | /// # Panic |
266 | | /// |
267 | | /// Panics if the identifier is invalid. |
268 | | /// |
269 | | #[track_caller] |
270 | 0 | pub fn remove(&mut self, id: TransactionId) -> TTx { |
271 | 0 | self.unvalidate_transaction(id); |
272 | 0 | let _removed = self.not_validated.remove(&id); |
273 | 0 | debug_assert!(_removed); |
274 | | |
275 | 0 | let tx = self.transactions.remove(id.0); |
276 | | |
277 | 0 | if let Some(included_block_height) = tx.included_block_height { |
278 | 0 | let _removed = self.by_height.remove(&(included_block_height, id)); |
279 | 0 | debug_assert!(_removed); |
280 | 0 | } |
281 | | |
282 | 0 | let _removed = self.by_hash.remove(&(blake2_hash(&tx.scale_encoded), id)); |
283 | 0 | debug_assert!(_removed); |
284 | | |
285 | 0 | tx.user_data |
286 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE6removeB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE6removeB6_ |
287 | | |
288 | | /// Removes from the pool all the transactions that are included in a block whose height is |
289 | | /// inferior or equal to the one passed as parameter. |
290 | | /// |
291 | | /// Use this method when a block has been finalized. |
292 | | /// |
293 | | /// The returned iterator is guaranteed to remove all transactions even if it is dropped |
294 | | /// eagerly. |
295 | 0 | pub fn remove_included( |
296 | 0 | &'_ mut self, |
297 | 0 | block_inferior_of_equal: u64, |
298 | 0 | ) -> impl Iterator<Item = (TransactionId, TTx)> + '_ { |
299 | | // First, unvalidate all the transactions that we are going to remove. |
300 | | // This is done separately ahead of time in order to guarantee that there is no state |
301 | | // mismatch when `unvalidate_transaction` is entered. |
302 | 0 | for tx_id in self |
303 | 0 | .by_height |
304 | 0 | .range( |
305 | 0 | (0, TransactionId(usize::MIN)) |
306 | 0 | ..=(block_inferior_of_equal, TransactionId(usize::MAX)), |
307 | 0 | ) |
308 | 0 | .map(|(_, id)| *id) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE15remove_included0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE15remove_included0B8_ |
309 | 0 | .collect::<Vec<_>>() |
310 | 0 | { |
311 | 0 | self.unvalidate_transaction(tx_id); |
312 | 0 | } |
313 | | |
314 | | // Extracts all the transactions that we are about to remove from `by_height`. |
315 | 0 | let to_remove = { |
316 | 0 | let remaining_txs = self |
317 | 0 | .by_height |
318 | 0 | .split_off(&(block_inferior_of_equal + 1, TransactionId(usize::MIN))); |
319 | 0 | mem::replace(&mut self.by_height, remaining_txs) |
320 | 0 | }; |
321 | 0 |
|
322 | 0 | struct ToRemoveIterator<'a, TTx> { |
323 | 0 | pool: &'a mut Pool<TTx>, |
324 | 0 | transactions: btree_set::IntoIter<(u64, TransactionId)>, |
325 | 0 | } |
326 | 0 |
|
327 | 0 | impl<'a, TTx> Iterator for ToRemoveIterator<'a, TTx> |
328 | 0 | where |
329 | 0 | // `FusedIterator` is necessary in order for the `Drop` implementation to not panic. |
330 | 0 | btree_set::IntoIter<(u64, TransactionId)>: iter::FusedIterator, |
331 | 0 | { |
332 | 0 | type Item = (TransactionId, TTx); |
333 | 0 |
|
334 | 0 | fn next(&mut self) -> Option<Self::Item> { |
335 | 0 | let (_height, tx_id) = self.transactions.next()?; |
336 | 0 |
|
337 | 0 | let tx = self.pool.transactions.remove(tx_id.0); |
338 | 0 | debug_assert!(tx.validation.is_none()); |
339 | 0 | debug_assert_eq!(tx.included_block_height, Some(_height)); |
340 | 0 |
|
341 | 0 | let _removed = self |
342 | 0 | .pool |
343 | 0 | .by_hash |
344 | 0 | .remove(&(blake2_hash(&tx.scale_encoded), tx_id)); |
345 | 0 | debug_assert!(_removed); |
346 | 0 |
|
347 | 0 | Some((tx_id, tx.user_data)) |
348 | 0 | } Unexecuted instantiation: _RNvXININvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB8_4PoolpE15remove_included0pEINtB5_16ToRemoveIteratorpENtNtNtNtCsaYZPK01V26L_4core4iter6traits8iterator8Iterator4nextBc_ Unexecuted instantiation: _RNvXININvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB8_4PoolpE15remove_included0pEINtB5_16ToRemoveIteratorpENtNtNtNtCsaYZPK01V26L_4core4iter6traits8iterator8Iterator4nextBc_ |
349 | 0 |
|
350 | 0 | fn size_hint(&self) -> (usize, Option<usize>) { |
351 | 0 | self.transactions.size_hint() |
352 | 0 | } Unexecuted instantiation: _RNvXININvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB8_4PoolpE15remove_included0pEINtB5_16ToRemoveIteratorpENtNtNtNtCsaYZPK01V26L_4core4iter6traits8iterator8Iterator9size_hintBc_ Unexecuted instantiation: _RNvXININvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB8_4PoolpE15remove_included0pEINtB5_16ToRemoveIteratorpENtNtNtNtCsaYZPK01V26L_4core4iter6traits8iterator8Iterator9size_hintBc_ |
353 | 0 | } |
354 | 0 |
|
355 | 0 | impl<'a, TTx> ExactSizeIterator for ToRemoveIterator<'a, TTx> where |
356 | 0 | btree_set::IntoIter<(u64, TransactionId)>: ExactSizeIterator |
357 | 0 | { |
358 | 0 | } |
359 | 0 |
|
360 | 0 | impl<'a, TTx> Drop for ToRemoveIterator<'a, TTx> { |
361 | 0 | fn drop(&mut self) { |
362 | 0 | // Drain the rest of the iterator in order to remove the transactions even if |
363 | 0 | // the iterator is dropped early. |
364 | 0 | while self.next().is_some() {} |
365 | 0 | } Unexecuted instantiation: _RNvXININvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB8_4PoolpE15remove_includeds0_0pEINtB5_16ToRemoveIteratorpENtNtNtCsaYZPK01V26L_4core3ops4drop4Drop4dropBc_ Unexecuted instantiation: _RNvXININvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB8_4PoolpE15remove_includeds0_0pEINtB5_16ToRemoveIteratorpENtNtNtCsaYZPK01V26L_4core3ops4drop4Drop4dropBc_ |
366 | 0 | } |
367 | 0 |
|
368 | 0 | ToRemoveIterator { |
369 | 0 | pool: self, |
370 | 0 | transactions: to_remove.into_iter(), |
371 | 0 | } |
372 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE15remove_includedB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE15remove_includedB6_ |
373 | | |
374 | | /// Returns a list of transactions whose state is "not validated", their user data, and the |
375 | | /// height of the block they should be validated against. |
376 | | /// |
377 | | /// The block height a transaction should be validated against is always equal to either the |
378 | | /// block at which it has been included minus one, or the current best block. It is yielded by |
379 | | /// the iterator for convenience, to avoid writing error-prone code. |
380 | 0 | pub fn unvalidated_transactions( |
381 | 0 | &'_ self, |
382 | 0 | ) -> impl ExactSizeIterator<Item = (TransactionId, &TTx, u64)> + '_ { |
383 | 0 | self.not_validated.iter().copied().map(move |tx_id| { |
384 | 0 | let tx = self.transactions.get(tx_id.0).unwrap(); |
385 | 0 | let height = tx |
386 | 0 | .included_block_height |
387 | 0 | .map(|n| n.checked_sub(1).unwrap()) Unexecuted instantiation: _RNCNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB6_4PoolpE24unvalidated_transactions00Ba_ Unexecuted instantiation: _RNCNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB6_4PoolpE24unvalidated_transactions00Ba_ |
388 | 0 | .unwrap_or(self.best_block_height); |
389 | 0 | (tx_id, &tx.user_data, height) |
390 | 0 | }) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE24unvalidated_transactions0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE24unvalidated_transactions0B8_ |
391 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE24unvalidated_transactionsB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE24unvalidated_transactionsB6_ |
392 | | |
393 | | /// Returns the list of all transactions within the pool. |
394 | 0 | pub fn iter(&'_ self) -> impl Iterator<Item = (TransactionId, &'_ TTx)> + '_ { |
395 | 0 | self.transactions |
396 | 0 | .iter() |
397 | 0 | .map(|(id, tx)| (TransactionId(id), &tx.user_data)) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE4iter0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE4iter0B8_ |
398 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE4iterB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE4iterB6_ |
399 | | |
400 | | /// Returns the list of all transactions within the pool. |
401 | 0 | pub fn iter_mut(&'_ mut self) -> impl Iterator<Item = (TransactionId, &'_ mut TTx)> + '_ { |
402 | 0 | self.transactions |
403 | 0 | .iter_mut() |
404 | 0 | .map(|(id, tx)| (TransactionId(id), &mut tx.user_data)) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE8iter_mut0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE8iter_mut0B8_ |
405 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE8iter_mutB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE8iter_mutB6_ |
406 | | |
407 | | /// Returns the block height at which the given transaction has been included. |
408 | | /// |
409 | | /// A transaction has been included if it has been added to the pool with |
410 | | /// [`Pool::best_block_add_transaction_by_scale_encoding`] or |
411 | | /// [`Pool::best_block_add_transaction_by_id`]. |
412 | | /// |
413 | | /// Returns `None` if the identifier is invalid or the transaction doesn't belong to any |
414 | | /// block. |
415 | 0 | pub fn included_block_height(&self, id: TransactionId) -> Option<u64> { |
416 | 0 | self.transactions.get(id.0)?.included_block_height |
417 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE21included_block_heightB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE21included_block_heightB6_ |
418 | | |
419 | | /// Returns the bytes associated with a given transaction. |
420 | | /// |
421 | | /// Returns `None` if the identifier is invalid. |
422 | 0 | pub fn scale_encoding(&self, id: TransactionId) -> Option<&[u8]> { |
423 | 0 | Some(&self.transactions.get(id.0)?.scale_encoded) |
424 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE14scale_encodingB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE14scale_encodingB6_ |
425 | | |
426 | | /// Finds the transactions in the pool whose bytes are `scale_encoded`. |
427 | | /// |
428 | | /// This operation has a complexity of `O(log n)` where `n` is the number of entries in the |
429 | | /// pool. |
430 | 0 | pub fn transactions_by_scale_encoding( |
431 | 0 | &'_ self, |
432 | 0 | scale_encoded: &[u8], |
433 | 0 | ) -> impl Iterator<Item = TransactionId> + '_ { |
434 | 0 | let hash = blake2_hash(scale_encoded); |
435 | 0 | self.by_hash |
436 | 0 | .range((hash, TransactionId(usize::MIN))..=(hash, TransactionId(usize::MAX))) |
437 | 0 | .map(|(_, tx_id)| *tx_id) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE30transactions_by_scale_encoding0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE30transactions_by_scale_encoding0B8_ |
438 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE30transactions_by_scale_encodingB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE30transactions_by_scale_encodingB6_ |
439 | | |
440 | | /// Returns the best block height according to the pool. |
441 | | /// |
442 | | /// This initially corresponds to the value in [`Config::finalized_block_height`], is |
443 | | /// incremented by one every time [`Pool::append_empty_block`], and is decreased when |
444 | | /// [`Pool::retract_blocks`] is called. |
445 | 0 | pub fn best_block_height(&self) -> u64 { |
446 | 0 | self.best_block_height |
447 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE17best_block_heightB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE17best_block_heightB6_ |
448 | | |
449 | | /// Adds a block to the chain tracked by the transactions pool. |
450 | 2 | pub fn append_empty_block(&mut self) { |
451 | 2 | self.best_block_height = self.best_block_height.checked_add(1).unwrap(); |
452 | | |
453 | | // Un-validate the transactions whose validation longevity has expired. |
454 | 2 | for tx_id0 in self |
455 | 2 | .by_validation_expiration |
456 | 2 | .range( |
457 | 2 | (0, TransactionId(usize::MIN)) |
458 | 2 | ..=(self.best_block_height, TransactionId(usize::MAX)), |
459 | 2 | ) |
460 | 2 | .map(|(_, id)| *id0 ) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PooluE18append_empty_block0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE18append_empty_block0B8_ |
461 | 2 | .collect::<Vec<_>>() |
462 | 0 | { |
463 | 0 | self.unvalidate_transaction(tx_id); |
464 | 0 | } |
465 | 2 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE18append_empty_blockB6_ Line | Count | Source | 450 | 2 | pub fn append_empty_block(&mut self) { | 451 | 2 | self.best_block_height = self.best_block_height.checked_add(1).unwrap(); | 452 | | | 453 | | // Un-validate the transactions whose validation longevity has expired. | 454 | 2 | for tx_id0 in self | 455 | 2 | .by_validation_expiration | 456 | 2 | .range( | 457 | 2 | (0, TransactionId(usize::MIN)) | 458 | 2 | ..=(self.best_block_height, TransactionId(usize::MAX)), | 459 | 2 | ) | 460 | 2 | .map(|(_, id)| *id) | 461 | 2 | .collect::<Vec<_>>() | 462 | 0 | { | 463 | 0 | self.unvalidate_transaction(tx_id); | 464 | 0 | } | 465 | 2 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE18append_empty_blockB6_ |
466 | | |
467 | | /// Pop a certain number of blocks from the list of blocks. |
468 | | /// |
469 | | /// Transactions that were included in these blocks remain in the transactions pool. |
470 | | /// |
471 | | /// Returns the list of transactions that were in blocks that have been retracted, with the |
472 | | /// height of the block at which they were. |
473 | | /// |
474 | | /// # Panic |
475 | | /// |
476 | | /// Panics if `num_to_retract > self.best_block_height()`, in other words if the block number |
477 | | /// would go in the negative. |
478 | | /// |
479 | 0 | pub fn retract_blocks( |
480 | 0 | &mut self, |
481 | 0 | num_to_retract: u64, |
482 | 0 | ) -> impl Iterator<Item = (TransactionId, u64)> { |
483 | 0 | // Checks that there's no transaction included above `self.best_block_height`. |
484 | 0 | debug_assert!(self |
485 | 0 | .by_height |
486 | 0 | .range((self.best_block_height + 1, TransactionId(usize::MIN),)..,) |
487 | 0 | .next() |
488 | 0 | .is_none()); |
489 | | |
490 | | // Update `best_block_height` as first step, in order to panic sooner in case of underflow. |
491 | 0 | self.best_block_height = self.best_block_height.checked_sub(num_to_retract).unwrap(); |
492 | 0 |
|
493 | 0 | // List of transactions that were included in these blocks. |
494 | 0 | let transactions_to_retract = self |
495 | 0 | .by_height |
496 | 0 | .range((self.best_block_height + 1, TransactionId(usize::MIN))..) |
497 | 0 | .map(|(block_height, tx_id)| (*tx_id, *block_height)) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE14retract_blocks0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE14retract_blocks0B8_ |
498 | 0 | .collect::<Vec<_>>(); |
499 | | |
500 | | // Set `included_block_height` to `None` for each of them. |
501 | 0 | for (transaction_id, _) in &transactions_to_retract { |
502 | 0 | self.unvalidate_transaction(*transaction_id); |
503 | 0 |
|
504 | 0 | let tx_data = self.transactions.get_mut(transaction_id.0).unwrap(); |
505 | 0 | debug_assert!(tx_data.included_block_height.unwrap() > self.best_block_height); |
506 | 0 | self.by_height |
507 | 0 | .remove(&(tx_data.included_block_height.unwrap(), *transaction_id)); |
508 | 0 | tx_data.included_block_height = None; |
509 | | } |
510 | | |
511 | | // Return retracted transactions from highest block to lowest block. |
512 | 0 | transactions_to_retract.into_iter().rev() |
513 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE14retract_blocksB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE14retract_blocksB6_ |
514 | | |
515 | | /// Returns all the transactions that can be included in the highest block. |
516 | | /// |
517 | | /// Use this function if you are currently authoring a block. |
518 | | /// |
519 | | /// The transactions are returned by decreasing priority. Re-ordering the transactions might |
520 | | /// lead to the runtime returning errors. It is safe, however, to skip some transactions |
521 | | /// altogether if desired. |
522 | 3 | pub fn best_block_includable_transactions( |
523 | 3 | &'_ self, |
524 | 3 | ) -> impl Iterator<Item = (TransactionId, &'_ TTx)> + '_ { |
525 | 3 | self.includable |
526 | 3 | .iter() |
527 | 3 | .rev() |
528 | 3 | .map(|(_, tx_id)| (*tx_id, &self.transactions[tx_id.0].user_data)1 ) _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PooluE34best_block_includable_transactions0B8_ Line | Count | Source | 528 | 1 | .map(|(_, tx_id)| (*tx_id, &self.transactions[tx_id.0].user_data)) |
Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE34best_block_includable_transactions0B8_ |
529 | 3 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE34best_block_includable_transactionsB6_ Line | Count | Source | 522 | 3 | pub fn best_block_includable_transactions( | 523 | 3 | &'_ self, | 524 | 3 | ) -> impl Iterator<Item = (TransactionId, &'_ TTx)> + '_ { | 525 | 3 | self.includable | 526 | 3 | .iter() | 527 | 3 | .rev() | 528 | 3 | .map(|(_, tx_id)| (*tx_id, &self.transactions[tx_id.0].user_data)) | 529 | 3 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE34best_block_includable_transactionsB6_ |
530 | | |
531 | | /// Adds a single-SCALE-encoded transaction to the highest block. |
532 | | /// |
533 | | /// The transaction is compared against the list of non-included transactions that are already |
534 | | /// in the pool. If a non-included transaction with the same bytes is found, it is switched to |
535 | | /// the "included" state and [`AppendBlockTransaction::NonIncludedUpdated`] is returned. |
536 | | /// Otherwise, [`AppendBlockTransaction::Unknown`] is returned and the transaction can be |
537 | | /// inserted in the pool. |
538 | | /// |
539 | | /// > **Note**: This function is equivalent to calling |
540 | | /// > [`Pool::transactions_by_scale_encoding`], removing the transactions that are |
541 | | /// > already included (see [`Pool::included_block_height`]), then calling |
542 | | /// > [`Pool::best_block_add_transaction_by_id`] with one of the transactions |
543 | | /// > that remain. |
544 | 0 | pub fn best_block_add_transaction_by_scale_encoding<'a, 'b>( |
545 | 0 | &'a mut self, |
546 | 0 | bytes: &'b [u8], |
547 | 0 | ) -> AppendBlockTransaction<'a, 'b, TTx> { |
548 | 0 | let non_included = { |
549 | 0 | let hash = blake2_hash(bytes); |
550 | 0 | self.by_hash |
551 | 0 | .range((hash, TransactionId(usize::MIN))..=(hash, TransactionId(usize::MAX))) |
552 | 0 | .find(|(_, tx_id)| { |
553 | 0 | self.transactions |
554 | 0 | .get(tx_id.0) |
555 | 0 | .unwrap() |
556 | 0 | .included_block_height |
557 | 0 | .is_none() |
558 | 0 | }) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE44best_block_add_transaction_by_scale_encoding0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE44best_block_add_transaction_by_scale_encoding0B8_ |
559 | 0 | .map(|(_, tx_id)| *tx_id) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PoolpE44best_block_add_transaction_by_scale_encodings_0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE44best_block_add_transaction_by_scale_encodings_0B8_ |
560 | | }; |
561 | | |
562 | 0 | if let Some(tx_id) = non_included { |
563 | 0 | debug_assert_eq!(self.transactions[tx_id.0].scale_encoded, bytes); |
564 | 0 | self.best_block_add_transaction_by_id(tx_id); |
565 | 0 | AppendBlockTransaction::NonIncludedUpdated { |
566 | 0 | id: tx_id, |
567 | 0 | user_data: &mut self.transactions[tx_id.0].user_data, |
568 | 0 | } |
569 | | } else { |
570 | 0 | AppendBlockTransaction::Unknown(Vacant { inner: self, bytes }) |
571 | | } |
572 | 0 | } Unexecuted instantiation: _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PoolpE44best_block_add_transaction_by_scale_encodingB6_ Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE44best_block_add_transaction_by_scale_encodingB6_ |
573 | | |
574 | | /// Adds a transaction to the block being appended. |
575 | | /// |
576 | | /// # Panic |
577 | | /// |
578 | | /// Panics if the transaction with the given id is invalid. |
579 | | /// Panics if the transaction with the given id was already included in the chain. |
580 | | /// |
581 | 1 | pub fn best_block_add_transaction_by_id(&mut self, id: TransactionId) { |
582 | 1 | // Sanity check. |
583 | 1 | assert!(self.transactions[id.0].included_block_height.is_none()); |
584 | | |
585 | | // We can in principle always discard the current validation status of the transaction. |
586 | | // However, if the transaction has been validated against the parent of the block, we want |
587 | | // to keep this validation status as an optimization. |
588 | | // Since updating the status of a transaction is a rather complicated state change, the |
589 | | // approach taken here is to always un-validate the transaction then re-validate it. |
590 | 1 | let revalidation = if let Some(validation) = self.transactions[id.0].validation.as_ref() { |
591 | 1 | if validation.0 + 1 == self.best_block_height { |
592 | 1 | Some(validation.clone()) |
593 | | } else { |
594 | 0 | None |
595 | | } |
596 | | } else { |
597 | 0 | None |
598 | | }; |
599 | 1 | self.unvalidate_transaction(id); |
600 | 1 | |
601 | 1 | // Mark the transaction as included. |
602 | 1 | self.transactions[id.0].included_block_height = Some(self.best_block_height); |
603 | 1 | self.by_height.insert((self.best_block_height, id)); |
604 | | |
605 | | // Re-set the validation status of the transaction that was extracted earlier. |
606 | 1 | if let Some((block_number_validated_against, result)) = revalidation { |
607 | 1 | self.set_validation_result(id, block_number_validated_against, result); |
608 | 1 | }0 |
609 | 1 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE32best_block_add_transaction_by_idB6_ Line | Count | Source | 581 | 1 | pub fn best_block_add_transaction_by_id(&mut self, id: TransactionId) { | 582 | 1 | // Sanity check. | 583 | 1 | assert!(self.transactions[id.0].included_block_height.is_none()); | 584 | | | 585 | | // We can in principle always discard the current validation status of the transaction. | 586 | | // However, if the transaction has been validated against the parent of the block, we want | 587 | | // to keep this validation status as an optimization. | 588 | | // Since updating the status of a transaction is a rather complicated state change, the | 589 | | // approach taken here is to always un-validate the transaction then re-validate it. | 590 | 1 | let revalidation = if let Some(validation) = self.transactions[id.0].validation.as_ref() { | 591 | 1 | if validation.0 + 1 == self.best_block_height { | 592 | 1 | Some(validation.clone()) | 593 | | } else { | 594 | 0 | None | 595 | | } | 596 | | } else { | 597 | 0 | None | 598 | | }; | 599 | 1 | self.unvalidate_transaction(id); | 600 | 1 | | 601 | 1 | // Mark the transaction as included. | 602 | 1 | self.transactions[id.0].included_block_height = Some(self.best_block_height); | 603 | 1 | self.by_height.insert((self.best_block_height, id)); | 604 | | | 605 | | // Re-set the validation status of the transaction that was extracted earlier. | 606 | 1 | if let Some((block_number_validated_against, result)) = revalidation { | 607 | 1 | self.set_validation_result(id, block_number_validated_against, result); | 608 | 1 | }0 | 609 | 1 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE32best_block_add_transaction_by_idB6_ |
610 | | |
611 | | /// Sets the outcome of validating the transaction with the given identifier. |
612 | | /// |
613 | | /// The block number must be the block number against which the transaction has been |
614 | | /// validated. |
615 | | /// |
616 | | /// Has no effect if the transaction has been included in the chain and the validation has |
617 | | /// been performed against a block other than the parent of the block in which it was included. |
618 | | /// |
619 | | /// Has no effect if the transaction has already been validated against the same or a higher |
620 | | /// block. |
621 | | /// |
622 | | /// > **Note**: If the transaction validation fails, use [`Pool::remove`] to remove the |
623 | | /// > transaction instead. Invalid transactions stay invalid forever and thus aren't |
624 | | /// > meant to be left in the pool. |
625 | | /// |
626 | | /// # Panic |
627 | | /// |
628 | | /// Panics if the transaction with the given id is invalid. |
629 | | /// |
630 | 2 | pub fn set_validation_result( |
631 | 2 | &mut self, |
632 | 2 | id: TransactionId, |
633 | 2 | block_number_validated_against: u64, |
634 | 2 | result: ValidTransaction, |
635 | 2 | ) { |
636 | 2 | // If the transaction has been included in a block, immediately return if the validation |
637 | 2 | // has been performed against a different block. |
638 | 2 | if self |
639 | 2 | .transactions |
640 | 2 | .get(id.0) |
641 | 2 | .unwrap() |
642 | 2 | .included_block_height |
643 | 2 | .map_or(false, |b| b != block_number_validated_against + 11 ) _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PooluE21set_validation_result0B8_ Line | Count | Source | 643 | 1 | .map_or(false, |b| b != block_number_validated_against + 1) |
Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE21set_validation_result0B8_ |
644 | | { |
645 | 0 | return; |
646 | 2 | } |
647 | 2 | |
648 | 2 | // Immediately return if the transaction has been validated against a better block. |
649 | 2 | if self |
650 | 2 | .transactions |
651 | 2 | .get(id.0) |
652 | 2 | .unwrap() |
653 | 2 | .validation |
654 | 2 | .as_ref() |
655 | 2 | .map_or(false, |(b, _)| *b >= block_number_validated_against0 ) Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PooluE21set_validation_results_0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE21set_validation_results_0B8_ |
656 | | { |
657 | 0 | return; |
658 | 2 | } |
659 | 2 | |
660 | 2 | // If the transaction was already validated, we don't try to update all the fields of |
661 | 2 | // `self` as it would be rather complicated. Instead we mark the transaction as not |
662 | 2 | // validated then mark it again as validated. |
663 | 2 | self.unvalidate_transaction(id); |
664 | 2 | debug_assert!(self.transactions[id.0].validation.is_none()); |
665 | | |
666 | | // Whether the transaction can be included at the head of the chain. Set to `false` below |
667 | | // if there is a reason why not. |
668 | 2 | let mut includable = self.transactions[id.0].included_block_height.is_none(); |
669 | | |
670 | 2 | for tag0 in &result.provides { |
671 | 0 | let tag_info = self.tags.entry(tag.clone()).or_insert_with(|| TagInfo { |
672 | 0 | provided_by: Default::default(), |
673 | 0 | required_by: Default::default(), |
674 | 0 | known_to_be_included_in_chain: 0, |
675 | 0 | }); Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PooluE21set_validation_results0_0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE21set_validation_results0_0B8_ |
676 | 0 |
|
677 | 0 | if self.transactions[id.0].included_block_height.is_some() { |
678 | 0 | tag_info.known_to_be_included_in_chain += 1; |
679 | 0 |
|
680 | 0 | if tag_info.known_to_be_included_in_chain == 1 { |
681 | | // All other transactions that provide the same tag are not longer includable. |
682 | 0 | for other_tx_id in &tag_info.provided_by { |
683 | 0 | let _was_in = self.includable.remove(&( |
684 | 0 | self.transactions[other_tx_id.0] |
685 | 0 | .validation |
686 | 0 | .as_ref() |
687 | 0 | .unwrap() |
688 | 0 | .1 |
689 | 0 | .priority, |
690 | 0 | *other_tx_id, |
691 | 0 | )); |
692 | 0 | debug_assert!(_was_in); |
693 | | } |
694 | | |
695 | | // All other transactions that require this tag are now includable. |
696 | 0 | for other_tx_id in &tag_info.required_by { |
697 | 0 | let _was_inserted = self.includable.insert(( |
698 | 0 | self.transactions[other_tx_id.0] |
699 | 0 | .validation |
700 | 0 | .as_ref() |
701 | 0 | .unwrap() |
702 | 0 | .1 |
703 | 0 | .priority, |
704 | 0 | *other_tx_id, |
705 | 0 | )); |
706 | 0 | debug_assert!(_was_inserted); |
707 | | } |
708 | 0 | } |
709 | 0 | } |
710 | | |
711 | 0 | if tag_info.known_to_be_included_in_chain >= 1 { |
712 | 0 | includable = false; |
713 | 0 | } |
714 | | |
715 | 0 | tag_info.provided_by.insert(id); |
716 | | } |
717 | | |
718 | 2 | for tag0 in &result.requires { |
719 | 0 | let tag_info = self.tags.entry(tag.clone()).or_insert_with(|| TagInfo { |
720 | 0 | provided_by: Default::default(), |
721 | 0 | required_by: Default::default(), |
722 | 0 | known_to_be_included_in_chain: 0, |
723 | 0 | }); Unexecuted instantiation: _RNCNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB4_4PooluE21set_validation_results1_0B8_ Unexecuted instantiation: _RNCNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB4_4PoolpE21set_validation_results1_0B8_ |
724 | 0 |
|
725 | 0 | tag_info.required_by.insert(id); |
726 | 0 |
|
727 | 0 | if tag_info.known_to_be_included_in_chain == 0 { |
728 | 0 | includable = false; |
729 | 0 | } |
730 | | } |
731 | | |
732 | 2 | self.by_validation_expiration.insert(( |
733 | 2 | block_number_validated_against.saturating_add(result.longevity.get()), |
734 | 2 | id, |
735 | 2 | )); |
736 | 2 | |
737 | 2 | if includable { |
738 | 1 | self.includable.insert((result.priority, id)); |
739 | 1 | } |
740 | | |
741 | 2 | self.transactions[id.0].validation = Some((block_number_validated_against, result)); |
742 | 2 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE21set_validation_resultB6_ Line | Count | Source | 630 | 2 | pub fn set_validation_result( | 631 | 2 | &mut self, | 632 | 2 | id: TransactionId, | 633 | 2 | block_number_validated_against: u64, | 634 | 2 | result: ValidTransaction, | 635 | 2 | ) { | 636 | 2 | // If the transaction has been included in a block, immediately return if the validation | 637 | 2 | // has been performed against a different block. | 638 | 2 | if self | 639 | 2 | .transactions | 640 | 2 | .get(id.0) | 641 | 2 | .unwrap() | 642 | 2 | .included_block_height | 643 | 2 | .map_or(false, |b| b != block_number_validated_against + 1) | 644 | | { | 645 | 0 | return; | 646 | 2 | } | 647 | 2 | | 648 | 2 | // Immediately return if the transaction has been validated against a better block. | 649 | 2 | if self | 650 | 2 | .transactions | 651 | 2 | .get(id.0) | 652 | 2 | .unwrap() | 653 | 2 | .validation | 654 | 2 | .as_ref() | 655 | 2 | .map_or(false, |(b, _)| *b >= block_number_validated_against) | 656 | | { | 657 | 0 | return; | 658 | 2 | } | 659 | 2 | | 660 | 2 | // If the transaction was already validated, we don't try to update all the fields of | 661 | 2 | // `self` as it would be rather complicated. Instead we mark the transaction as not | 662 | 2 | // validated then mark it again as validated. | 663 | 2 | self.unvalidate_transaction(id); | 664 | 2 | debug_assert!(self.transactions[id.0].validation.is_none()); | 665 | | | 666 | | // Whether the transaction can be included at the head of the chain. Set to `false` below | 667 | | // if there is a reason why not. | 668 | 2 | let mut includable = self.transactions[id.0].included_block_height.is_none(); | 669 | | | 670 | 2 | for tag0 in &result.provides { | 671 | 0 | let tag_info = self.tags.entry(tag.clone()).or_insert_with(|| TagInfo { | 672 | | provided_by: Default::default(), | 673 | | required_by: Default::default(), | 674 | | known_to_be_included_in_chain: 0, | 675 | 0 | }); | 676 | 0 |
| 677 | 0 | if self.transactions[id.0].included_block_height.is_some() { | 678 | 0 | tag_info.known_to_be_included_in_chain += 1; | 679 | 0 |
| 680 | 0 | if tag_info.known_to_be_included_in_chain == 1 { | 681 | | // All other transactions that provide the same tag are not longer includable. | 682 | 0 | for other_tx_id in &tag_info.provided_by { | 683 | 0 | let _was_in = self.includable.remove(&( | 684 | 0 | self.transactions[other_tx_id.0] | 685 | 0 | .validation | 686 | 0 | .as_ref() | 687 | 0 | .unwrap() | 688 | 0 | .1 | 689 | 0 | .priority, | 690 | 0 | *other_tx_id, | 691 | 0 | )); | 692 | 0 | debug_assert!(_was_in); | 693 | | } | 694 | | | 695 | | // All other transactions that require this tag are now includable. | 696 | 0 | for other_tx_id in &tag_info.required_by { | 697 | 0 | let _was_inserted = self.includable.insert(( | 698 | 0 | self.transactions[other_tx_id.0] | 699 | 0 | .validation | 700 | 0 | .as_ref() | 701 | 0 | .unwrap() | 702 | 0 | .1 | 703 | 0 | .priority, | 704 | 0 | *other_tx_id, | 705 | 0 | )); | 706 | 0 | debug_assert!(_was_inserted); | 707 | | } | 708 | 0 | } | 709 | 0 | } | 710 | | | 711 | 0 | if tag_info.known_to_be_included_in_chain >= 1 { | 712 | 0 | includable = false; | 713 | 0 | } | 714 | | | 715 | 0 | tag_info.provided_by.insert(id); | 716 | | } | 717 | | | 718 | 2 | for tag0 in &result.requires { | 719 | 0 | let tag_info = self.tags.entry(tag.clone()).or_insert_with(|| TagInfo { | 720 | | provided_by: Default::default(), | 721 | | required_by: Default::default(), | 722 | | known_to_be_included_in_chain: 0, | 723 | 0 | }); | 724 | 0 |
| 725 | 0 | tag_info.required_by.insert(id); | 726 | 0 |
| 727 | 0 | if tag_info.known_to_be_included_in_chain == 0 { | 728 | 0 | includable = false; | 729 | 0 | } | 730 | | } | 731 | | | 732 | 2 | self.by_validation_expiration.insert(( | 733 | 2 | block_number_validated_against.saturating_add(result.longevity.get()), | 734 | 2 | id, | 735 | 2 | )); | 736 | 2 | | 737 | 2 | if includable { | 738 | 1 | self.includable.insert((result.priority, id)); | 739 | 1 | } | 740 | | | 741 | 2 | self.transactions[id.0].validation = Some((block_number_validated_against, result)); | 742 | 2 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE21set_validation_resultB6_ |
743 | | |
744 | | /// Sets a transaction's status to "not validated". |
745 | | /// |
746 | | /// # Panic |
747 | | /// |
748 | | /// Panics if the identifier is invalid. |
749 | | /// |
750 | 3 | fn unvalidate_transaction(&mut self, tx_id: TransactionId) { |
751 | | // No effect if wasn't validated. |
752 | 1 | let Some((block_height_validated_against, validation)) = |
753 | 3 | self.transactions[tx_id.0].validation.take() |
754 | | else { |
755 | 2 | return; |
756 | | }; |
757 | | |
758 | | // We don't care in this context whether the transaction was includable or not, and we |
759 | | // call `remove` in both cases. |
760 | 1 | self.includable.remove(&(validation.priority, tx_id)); |
761 | | |
762 | 1 | for tag0 in validation.provides { |
763 | 0 | let tag_info = self.tags.get_mut(&tag).unwrap(); |
764 | 0 |
|
765 | 0 | let _was_in = tag_info.provided_by.remove(&tx_id); |
766 | 0 | debug_assert!(_was_in); |
767 | | |
768 | 0 | if self.transactions[tx_id.0].included_block_height.is_some() { |
769 | 0 | tag_info.known_to_be_included_in_chain -= 1; |
770 | 0 |
|
771 | 0 | if tag_info.known_to_be_included_in_chain == 0 { |
772 | | // All other transactions that provide the same tag are now includable. |
773 | | // Note that in practice they most likely are not, but we prioritize the |
774 | | // consistency and simplify of the pool implementation rather than trying to |
775 | | // be smart. |
776 | 0 | for other_tx_id in &tag_info.provided_by { |
777 | 0 | let _was_inserted = self.includable.insert(( |
778 | 0 | self.transactions[other_tx_id.0] |
779 | 0 | .validation |
780 | 0 | .as_ref() |
781 | 0 | .unwrap() |
782 | 0 | .1 |
783 | 0 | .priority, |
784 | 0 | *other_tx_id, |
785 | 0 | )); |
786 | 0 | debug_assert!(_was_inserted); |
787 | | } |
788 | | |
789 | | // All other transactions that require this tag are no longer includable. |
790 | 0 | for other_tx_id in &tag_info.required_by { |
791 | 0 | let _was_in = self.includable.remove(&( |
792 | 0 | self.transactions[other_tx_id.0] |
793 | 0 | .validation |
794 | 0 | .as_ref() |
795 | 0 | .unwrap() |
796 | 0 | .1 |
797 | 0 | .priority, |
798 | 0 | *other_tx_id, |
799 | 0 | )); |
800 | 0 | debug_assert!(_was_in); |
801 | | } |
802 | 0 | } |
803 | 0 | } |
804 | | |
805 | 0 | if tag_info.provided_by.is_empty() && tag_info.required_by.is_empty() { |
806 | 0 | self.tags.remove(&tag).unwrap(); |
807 | 0 | } |
808 | | } |
809 | | |
810 | 1 | for tag0 in validation.requires { |
811 | 0 | let tag_info = self.tags.get_mut(&tag).unwrap(); |
812 | 0 |
|
813 | 0 | let _was_in = tag_info.required_by.remove(&tx_id); |
814 | 0 | debug_assert!(_was_in); |
815 | | |
816 | 0 | if tag_info.provided_by.is_empty() && tag_info.required_by.is_empty() { |
817 | 0 | self.tags.remove(&tag).unwrap(); |
818 | 0 | } |
819 | | } |
820 | | |
821 | 1 | self.not_validated.insert(tx_id); |
822 | 1 | |
823 | 1 | let _was_in = self.by_validation_expiration.remove(&( |
824 | 1 | block_height_validated_against.saturating_add(validation.longevity.get()), |
825 | 1 | tx_id, |
826 | 1 | )); |
827 | 1 | debug_assert!(_was_in); |
828 | 3 | } _RNvMNtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB2_4PooluE22unvalidate_transactionB6_ Line | Count | Source | 750 | 3 | fn unvalidate_transaction(&mut self, tx_id: TransactionId) { | 751 | | // No effect if wasn't validated. | 752 | 1 | let Some((block_height_validated_against, validation)) = | 753 | 3 | self.transactions[tx_id.0].validation.take() | 754 | | else { | 755 | 2 | return; | 756 | | }; | 757 | | | 758 | | // We don't care in this context whether the transaction was includable or not, and we | 759 | | // call `remove` in both cases. | 760 | 1 | self.includable.remove(&(validation.priority, tx_id)); | 761 | | | 762 | 1 | for tag0 in validation.provides { | 763 | 0 | let tag_info = self.tags.get_mut(&tag).unwrap(); | 764 | 0 |
| 765 | 0 | let _was_in = tag_info.provided_by.remove(&tx_id); | 766 | 0 | debug_assert!(_was_in); | 767 | | | 768 | 0 | if self.transactions[tx_id.0].included_block_height.is_some() { | 769 | 0 | tag_info.known_to_be_included_in_chain -= 1; | 770 | 0 |
| 771 | 0 | if tag_info.known_to_be_included_in_chain == 0 { | 772 | | // All other transactions that provide the same tag are now includable. | 773 | | // Note that in practice they most likely are not, but we prioritize the | 774 | | // consistency and simplify of the pool implementation rather than trying to | 775 | | // be smart. | 776 | 0 | for other_tx_id in &tag_info.provided_by { | 777 | 0 | let _was_inserted = self.includable.insert(( | 778 | 0 | self.transactions[other_tx_id.0] | 779 | 0 | .validation | 780 | 0 | .as_ref() | 781 | 0 | .unwrap() | 782 | 0 | .1 | 783 | 0 | .priority, | 784 | 0 | *other_tx_id, | 785 | 0 | )); | 786 | 0 | debug_assert!(_was_inserted); | 787 | | } | 788 | | | 789 | | // All other transactions that require this tag are no longer includable. | 790 | 0 | for other_tx_id in &tag_info.required_by { | 791 | 0 | let _was_in = self.includable.remove(&( | 792 | 0 | self.transactions[other_tx_id.0] | 793 | 0 | .validation | 794 | 0 | .as_ref() | 795 | 0 | .unwrap() | 796 | 0 | .1 | 797 | 0 | .priority, | 798 | 0 | *other_tx_id, | 799 | 0 | )); | 800 | 0 | debug_assert!(_was_in); | 801 | | } | 802 | 0 | } | 803 | 0 | } | 804 | | | 805 | 0 | if tag_info.provided_by.is_empty() && tag_info.required_by.is_empty() { | 806 | 0 | self.tags.remove(&tag).unwrap(); | 807 | 0 | } | 808 | | } | 809 | | | 810 | 1 | for tag0 in validation.requires { | 811 | 0 | let tag_info = self.tags.get_mut(&tag).unwrap(); | 812 | 0 |
| 813 | 0 | let _was_in = tag_info.required_by.remove(&tx_id); | 814 | 0 | debug_assert!(_was_in); | 815 | | | 816 | 0 | if tag_info.provided_by.is_empty() && tag_info.required_by.is_empty() { | 817 | 0 | self.tags.remove(&tag).unwrap(); | 818 | 0 | } | 819 | | } | 820 | | | 821 | 1 | self.not_validated.insert(tx_id); | 822 | 1 | | 823 | 1 | let _was_in = self.by_validation_expiration.remove(&( | 824 | 1 | block_height_validated_against.saturating_add(validation.longevity.get()), | 825 | 1 | tx_id, | 826 | 1 | )); | 827 | 1 | debug_assert!(_was_in); | 828 | 3 | } |
Unexecuted instantiation: _RNvMNtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB2_4PoolpE22unvalidate_transactionB6_ |
829 | | } |
830 | | |
831 | | impl<TTx> ops::Index<TransactionId> for Pool<TTx> { |
832 | | type Output = TTx; |
833 | | |
834 | 0 | fn index(&self, index: TransactionId) -> &Self::Output { |
835 | 0 | &self.transactions[index.0].user_data |
836 | 0 | } Unexecuted instantiation: _RNvXININtNtCsN16ciHI6Qf_7smoldot12transactions4pools_0pEINtB5_4PoolpEINtNtNtCsaYZPK01V26L_4core3ops5index5IndexNtB5_13TransactionIdE5indexB9_ Unexecuted instantiation: _RNvXININtNtCseuYC0Zibziv_7smoldot12transactions4pools_0pEINtB5_4PoolpEINtNtNtCsaYZPK01V26L_4core3ops5index5IndexNtB5_13TransactionIdE5indexB9_ |
837 | | } |
838 | | |
839 | | impl<TTx> ops::IndexMut<TransactionId> for Pool<TTx> { |
840 | 0 | fn index_mut(&mut self, index: TransactionId) -> &mut Self::Output { |
841 | 0 | &mut self.transactions[index.0].user_data |
842 | 0 | } Unexecuted instantiation: _RNvXININtNtCsN16ciHI6Qf_7smoldot12transactions4pools0_0pEINtB5_4PoolpEINtNtNtCsaYZPK01V26L_4core3ops5index8IndexMutNtB5_13TransactionIdE9index_mutB9_ Unexecuted instantiation: _RNvXININtNtCseuYC0Zibziv_7smoldot12transactions4pools0_0pEINtB5_4PoolpEINtNtNtCsaYZPK01V26L_4core3ops5index8IndexMutNtB5_13TransactionIdE9index_mutB9_ |
843 | | } |
844 | | |
845 | | impl<TTx: fmt::Debug> fmt::Debug for Pool<TTx> { |
846 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
847 | 0 | f.debug_list() |
848 | 0 | .entries( |
849 | 0 | self.transactions |
850 | 0 | .iter() |
851 | 0 | .map(|t| (TransactionId(t.0), &t.1.user_data)), Unexecuted instantiation: _RNCNvXININtNtCsN16ciHI6Qf_7smoldot12transactions4pools1_0pEINtB7_4PoolpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmt0Bb_ Unexecuted instantiation: _RNCNvXININtNtCseuYC0Zibziv_7smoldot12transactions4pools1_0pEINtB7_4PoolpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmt0Bb_ |
852 | 0 | ) |
853 | 0 | .finish() |
854 | 0 | } Unexecuted instantiation: _RNvXININtNtCsN16ciHI6Qf_7smoldot12transactions4pools1_0pEINtB5_4PoolpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmtB9_ Unexecuted instantiation: _RNvXININtNtCseuYC0Zibziv_7smoldot12transactions4pools1_0pEINtB5_4PoolpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmtB9_ |
855 | | } |
856 | | |
857 | | /// See [`Pool::best_block_add_transaction_by_scale_encoding`]. |
858 | | #[derive(Debug)] |
859 | | pub enum AppendBlockTransaction<'a, 'b, TTx> { |
860 | | /// Transaction to add isn't in the list of non-included transactions. It can be added to the |
861 | | /// pool. |
862 | | Unknown(Vacant<'a, 'b, TTx>), |
863 | | /// Transaction to add is present in the list of non-included transactions. It is now |
864 | | /// considered included. |
865 | | NonIncludedUpdated { |
866 | | /// Identifier of the non-included transaction with the same bytes. |
867 | | id: TransactionId, |
868 | | /// User data stored alongside with that transaction. |
869 | | user_data: &'a mut TTx, |
870 | | }, |
871 | | } |
872 | | |
873 | | /// See [`AppendBlockTransaction::Unknown`]. |
874 | | pub struct Vacant<'a, 'b, TTx> { |
875 | | inner: &'a mut Pool<TTx>, |
876 | | bytes: &'b [u8], |
877 | | } |
878 | | |
879 | | impl<'a, 'b, TTx> Vacant<'a, 'b, TTx> { |
880 | | /// Inserts the transaction in the pool. |
881 | 0 | pub fn insert(self, user_data: TTx) -> TransactionId { |
882 | 0 | self.inner |
883 | 0 | .add_unvalidated_inner(self.bytes, Some(self.inner.best_block_height), user_data) |
884 | 0 | } Unexecuted instantiation: _RNvMs2_NtNtCsN16ciHI6Qf_7smoldot12transactions4poolINtB5_6VacantpE6insertB9_ Unexecuted instantiation: _RNvMs2_NtNtCseuYC0Zibziv_7smoldot12transactions4poolINtB5_6VacantpE6insertB9_ |
885 | | } |
886 | | |
887 | | impl<'a, 'b, TTx: fmt::Debug> fmt::Debug for Vacant<'a, 'b, TTx> { |
888 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
889 | 0 | fmt::Debug::fmt(&self.inner, f) |
890 | 0 | } Unexecuted instantiation: _RNvXININtNtCsN16ciHI6Qf_7smoldot12transactions4pools3_0pEINtB5_6VacantpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmtB9_ Unexecuted instantiation: _RNvXININtNtCseuYC0Zibziv_7smoldot12transactions4pools3_0pEINtB5_6VacantpENtNtCsaYZPK01V26L_4core3fmt5Debug3fmtB9_ |
891 | | } |
892 | | |
893 | | /// Utility. Calculates the BLAKE2 hash of the given bytes. |
894 | 1 | fn blake2_hash(bytes: &[u8]) -> [u8; 32] { |
895 | 1 | <[u8; 32]>::try_from(blake2_rfc::blake2b::blake2b(32, &[], bytes).as_bytes()).unwrap() |
896 | 1 | } _RNvNtNtCsN16ciHI6Qf_7smoldot12transactions4pool11blake2_hash Line | Count | Source | 894 | 1 | fn blake2_hash(bytes: &[u8]) -> [u8; 32] { | 895 | 1 | <[u8; 32]>::try_from(blake2_rfc::blake2b::blake2b(32, &[], bytes).as_bytes()).unwrap() | 896 | 1 | } |
Unexecuted instantiation: _RNvNtNtCseuYC0Zibziv_7smoldot12transactions4pool11blake2_hash |