transaction_pool/scoring.rs
1// Copyright 2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! A transactions ordering abstraction.
10
11use crate::pool::Transaction;
12use std::{cmp, fmt};
13
14/// Represents a decision what to do with
15/// a new transaction that tries to enter the pool.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Choice {
18 /// New transaction should be rejected
19 /// (i.e. the old transaction that occupies the same spot
20 /// is better).
21 RejectNew,
22 /// The old transaction should be dropped
23 /// in favour of the new one.
24 ReplaceOld,
25 /// The new transaction should be inserted
26 /// and both (old and new) should stay in the pool.
27 InsertNew,
28}
29
30/// Describes a reason why the `Score` of transactions
31/// should be updated.
32/// The `Scoring` implementations can use this information
33/// to update the `Score` table more efficiently.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Change<T = ()> {
36 /// New transaction has been inserted at given index.
37 /// The Score at that index is initialized with default value
38 /// and needs to be filled in.
39 InsertedAt(usize),
40 /// The transaction has been removed at given index and other transactions
41 /// shifted to it's place.
42 /// The scores were removed and shifted as well.
43 /// For simple scoring algorithms no action is required here.
44 RemovedAt(usize),
45 /// The transaction at given index has replaced a previous transaction.
46 /// The score at that index needs to be update (it contains value from previous transaction).
47 ReplacedAt(usize),
48 /// Given number of stalled transactions has been culled from the beginning.
49 /// The scores has been removed from the beginning as well.
50 /// For simple scoring algorithms no action is required here.
51 Culled(usize),
52 /// Custom event to update the score triggered outside of the pool.
53 /// Handling this event is up to scoring implementation.
54 Event(T),
55}
56
57/// A transaction ordering.
58///
59/// The implementation should decide on order of transactions in the pool.
60/// Each transaction should also get assigned a `Score` which is used to later
61/// prioritize transactions in the pending set.
62///
63/// Implementation notes:
64/// - Returned `Score`s should match ordering of `compare` method.
65/// - `compare` will be called only within a context of transactions from the same sender.
66/// - `choose` may be called even if `compare` returns `Ordering::Equal`
67/// - `Score`s and `compare` should align with `Ready` implementation.
68///
69/// Example: Natural ordering of Ethereum transactions.
70/// - `compare`: compares transaction `nonce` ()
71/// - `choose`: compares transactions `gasPrice` (decides if old transaction should be replaced)
72/// - `update_scores`: score defined as `gasPrice` if `n==0` and `max(scores[n-1], gasPrice)` if `n>0`
73///
74pub trait Scoring<T>: fmt::Debug {
75 /// A score of a transaction.
76 type Score: cmp::Ord + Clone + Default + fmt::Debug + Send + fmt::LowerHex;
77 /// Custom scoring update event type.
78 type Event: fmt::Debug;
79
80 /// Decides on ordering of `T`s from a particular sender.
81 fn compare(&self, old: &T, other: &T) -> cmp::Ordering;
82
83 /// Decides how to deal with two transactions from a sender that seem to occupy the same slot in the queue.
84 fn choose(&self, old: &T, new: &T) -> Choice;
85
86 /// Updates the transaction scores given a list of transactions and a change to previous scoring.
87 /// NOTE: you can safely assume that both slices have the same length.
88 /// (i.e. score at index `i` represents transaction at the same index)
89 fn update_scores(&self, txs: &[Transaction<T>], scores: &mut [Self::Score], change: Change<Self::Event>);
90
91 /// Decides if the transaction should ignore per-sender limit in the pool.
92 ///
93 /// If you return `true` for given transaction it's going to be accepted even though
94 /// the per-sender limit is exceeded.
95 fn should_ignore_sender_limit(&self, _new: &T) -> bool {
96 false
97 }
98}
99
100/// A score with a reference to the transaction.
101#[derive(Debug)]
102pub struct ScoreWithRef<T, S> {
103 /// Score
104 pub score: S,
105 /// Shared transaction
106 pub transaction: Transaction<T>,
107}
108
109impl<T, S> ScoreWithRef<T, S> {
110 /// Creates a new `ScoreWithRef`
111 pub fn new(score: S, transaction: Transaction<T>) -> Self {
112 ScoreWithRef { score, transaction }
113 }
114}
115
116impl<T, S: Clone> Clone for ScoreWithRef<T, S> {
117 fn clone(&self) -> Self {
118 ScoreWithRef { score: self.score.clone(), transaction: self.transaction.clone() }
119 }
120}
121
122impl<S: cmp::Ord, T> Ord for ScoreWithRef<T, S> {
123 fn cmp(&self, other: &Self) -> cmp::Ordering {
124 other.score.cmp(&self.score).then(self.transaction.insertion_id.cmp(&other.transaction.insertion_id))
125 }
126}
127
128impl<S: cmp::Ord, T> PartialOrd for ScoreWithRef<T, S> {
129 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
130 Some(self.cmp(other))
131 }
132}
133
134impl<S: cmp::Ord, T> PartialEq for ScoreWithRef<T, S> {
135 fn eq(&self, other: &Self) -> bool {
136 self.score == other.score && self.transaction.insertion_id == other.transaction.insertion_id
137 }
138}
139
140impl<S: cmp::Ord, T> Eq for ScoreWithRef<T, S> {}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn score(score: u64, insertion_id: u64) -> ScoreWithRef<(), u64> {
147 ScoreWithRef { score, transaction: Transaction { insertion_id, transaction: Default::default() } }
148 }
149
150 #[test]
151 fn scoring_comparison() {
152 // the higher the score the better
153 assert_eq!(score(10, 0).cmp(&score(0, 0)), cmp::Ordering::Less);
154 assert_eq!(score(0, 0).cmp(&score(10, 0)), cmp::Ordering::Greater);
155
156 // equal is equal
157 assert_eq!(score(0, 0).cmp(&score(0, 0)), cmp::Ordering::Equal);
158
159 // lower insertion id is better
160 assert_eq!(score(0, 0).cmp(&score(0, 10)), cmp::Ordering::Less);
161 assert_eq!(score(0, 10).cmp(&score(0, 0)), cmp::Ordering::Greater);
162 }
163}