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}