Coverage Report

Created: 2024-05-16 12:16

/__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