Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 22c1a28

Browse files
tomusdrwgavofyork
authored andcommitted
Transactions dependency graph (#787)
* Graph transaction pool. * Start future implementation. * Future -> Ready promotions. * Replacement logic. * Clear extern crates, add docs. * Move hash externally. * Implement remove_invalid * Implement ready transactions pruning. * Move & rename. * Add some logs. * Clean up deps. * Use Member trait. * Add missing docs, elaborate on the proof. * Expand on docs and proofs.
1 parent e4c07ba commit 22c1a28

8 files changed

Lines changed: 1506 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ members = [
2424
"core/client",
2525
"core/client/db",
2626
"core/executor",
27-
"core/transaction-pool",
2827
"core/keyring",
2928
"core/misbehavior-check",
3029
"core/network",
@@ -35,6 +34,8 @@ members = [
3534
"core/sr-sandbox",
3635
"core/sr-std",
3736
"core/sr-version",
37+
"core/transaction-graph",
38+
"core/transaction-pool",
3839
"srml/support",
3940
"srml/balances",
4041
"srml/consensus",

core/transaction-graph/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "substrate-transaction-graph"
3+
version = "0.1.0"
4+
authors = ["Parity Technologies <admin@parity.io>"]
5+
6+
[dependencies]
7+
error-chain = "0.12"
8+
log = "0.3.0"
9+
sr-primitives = { path = "../sr-primitives" }
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2018 Parity Technologies (UK) Ltd.
2+
// This file is part of Substrate.
3+
4+
// Substrate is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// Substrate is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
16+
17+
use sr_primitives::transaction_validity::TransactionPriority as Priority;
18+
19+
error_chain! {
20+
errors {
21+
/// The transaction is already in the pool.
22+
AlreadyImported {
23+
description("Transaction is already in the pool."),
24+
display("Already imported"),
25+
}
26+
/// The transaction cannot be imported cause it's a replacement and has too low priority.
27+
TooLowPriority(old: Priority, new: Priority) {
28+
description("The priority is too low to replace transactions already in the pool."),
29+
display("Too low priority ({} > {})", old, new)
30+
}
31+
/// Deps cycle detected and we couldn't import transaction.
32+
CycleDetected {
33+
description("Transaction was not imported because of detected cycle."),
34+
display("Cycle Detected"),
35+
}
36+
}
37+
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright 2018 Parity Technologies (UK) Ltd.
2+
// This file is part of Substrate.
3+
4+
// Substrate is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// Substrate is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
16+
17+
use std::{
18+
collections::{HashMap, HashSet},
19+
hash,
20+
};
21+
22+
use sr_primitives::transaction_validity::{
23+
TransactionTag as Tag,
24+
};
25+
26+
use pool::Transaction;
27+
28+
/// Transaction with partially satisfied dependencies.
29+
#[derive(Debug)]
30+
pub struct WaitingTransaction<Hash> {
31+
/// Transaction details.
32+
pub transaction: Transaction<Hash>,
33+
/// Tags that are required and have not been satisfied yet by other transactions in the pool.
34+
pub missing_tags: HashSet<Tag>,
35+
}
36+
37+
impl<Hash> WaitingTransaction<Hash> {
38+
/// Creates a new `WaitingTransaction`.
39+
///
40+
/// Computes the set of missing tags based on the requirements and tags that
41+
/// are provided by all transactions in the ready queue.
42+
pub fn new(transaction: Transaction<Hash>, provided: &HashMap<Tag, Hash>) -> Self {
43+
let missing_tags = transaction.requires
44+
.iter()
45+
.filter(|tag| !provided.contains_key(&**tag))
46+
.cloned()
47+
.collect();
48+
49+
WaitingTransaction {
50+
transaction,
51+
missing_tags,
52+
}
53+
}
54+
55+
/// Marks the tag as satisfied.
56+
pub fn satisfy_tag(&mut self, tag: &Tag) {
57+
self.missing_tags.remove(tag);
58+
}
59+
60+
/// Returns true if transaction has all requirements satisfied.
61+
pub fn is_ready(&self) -> bool {
62+
self.missing_tags.is_empty()
63+
}
64+
}
65+
66+
/// A pool of transactions that are not yet ready to be included in the block.
67+
///
68+
/// Contains transactions that are still awaiting for some other transactions that
69+
/// could provide a tag that they require.
70+
#[derive(Debug)]
71+
pub struct FutureTransactions<Hash: hash::Hash + Eq> {
72+
/// tags that are not yet provided by any transaction and we await for them
73+
wanted_tags: HashMap<Tag, HashSet<Hash>>,
74+
/// Transactions waiting for a particular other transaction
75+
waiting: HashMap<Hash, WaitingTransaction<Hash>>,
76+
}
77+
78+
impl<Hash: hash::Hash + Eq> Default for FutureTransactions<Hash> {
79+
fn default() -> Self {
80+
FutureTransactions {
81+
wanted_tags: Default::default(),
82+
waiting: Default::default(),
83+
}
84+
}
85+
}
86+
87+
const WAITING_PROOF: &str = r"#
88+
In import we always insert to `waiting` if we push to `wanted_tags`;
89+
when removing from `waiting` we always clear `wanted_tags`;
90+
every hash from `wanted_tags` is always present in `waiting`;
91+
qed
92+
#";
93+
94+
impl<Hash: hash::Hash + Eq + Clone> FutureTransactions<Hash> {
95+
/// Import transaction to Future queue.
96+
///
97+
/// Only transactions that don't have all their tags satisfied should occupy
98+
/// the Future queue.
99+
/// As soon as required tags are provided by some other transactions that are ready
100+
/// we should remove the transactions from here and move them to the Ready queue.
101+
pub fn import(&mut self, tx: WaitingTransaction<Hash>) {
102+
assert!(!tx.is_ready(), "Transaction is ready.");
103+
assert!(!self.waiting.contains_key(&tx.transaction.hash), "Transaction is already imported.");
104+
105+
// Add all tags that are missing
106+
for tag in &tx.missing_tags {
107+
let mut entry = self.wanted_tags.entry(tag.clone()).or_insert_with(HashSet::new);
108+
entry.insert(tx.transaction.hash.clone());
109+
}
110+
111+
// Add the transaction to a by-hash waiting map
112+
self.waiting.insert(tx.transaction.hash.clone(), tx);
113+
}
114+
115+
/// Returns true if given hash is part of the queue.
116+
pub fn contains(&self, hash: &Hash) -> bool {
117+
self.waiting.contains_key(hash)
118+
}
119+
120+
/// Satisfies provided tags in transactions that are waiting for them.
121+
///
122+
/// Returns (and removes) transactions that became ready after their last tag got
123+
/// satisfied and now we can remove them from Future and move to Ready queue.
124+
pub fn satisfy_tags<T: AsRef<Tag>>(&mut self, tags: impl IntoIterator<Item=T>) -> Vec<WaitingTransaction<Hash>> {
125+
let mut became_ready = vec![];
126+
127+
for tag in tags {
128+
if let Some(hashes) = self.wanted_tags.remove(tag.as_ref()) {
129+
for hash in hashes {
130+
let is_ready = {
131+
let mut tx = self.waiting.get_mut(&hash)
132+
.expect(WAITING_PROOF);
133+
tx.satisfy_tag(tag.as_ref());
134+
tx.is_ready()
135+
};
136+
137+
if is_ready {
138+
let tx = self.waiting.remove(&hash).expect(WAITING_PROOF);
139+
became_ready.push(tx);
140+
}
141+
}
142+
}
143+
}
144+
145+
became_ready
146+
}
147+
148+
/// Removes transactions for given list of hashes.
149+
///
150+
/// Returns a list of actually removed transactions.
151+
pub fn remove(&mut self, hashes: &[Hash]) -> Vec<Transaction<Hash>> {
152+
let mut removed = vec![];
153+
for hash in hashes {
154+
if let Some(waiting_tx) = self.waiting.remove(hash) {
155+
// remove from wanted_tags as well
156+
for tag in waiting_tx.missing_tags {
157+
let remove = if let Some(mut wanted) = self.wanted_tags.get_mut(&tag) {
158+
wanted.remove(hash);
159+
wanted.is_empty()
160+
} else { false };
161+
if remove {
162+
self.wanted_tags.remove(&tag);
163+
}
164+
}
165+
// add to result
166+
removed.push(waiting_tx.transaction)
167+
}
168+
}
169+
removed
170+
}
171+
172+
/// Returns number of transactions in the Future queue.
173+
#[cfg(test)]
174+
pub fn len(&self) -> usize {
175+
self.waiting.len()
176+
}
177+
}

core/transaction-graph/src/lib.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2018 Parity Technologies (UK) Ltd.
2+
// This file is part of Substrate.
3+
4+
// Substrate is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
9+
// Substrate is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
14+
// You should have received a copy of the GNU General Public License
15+
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
16+
17+
//! Generic Transaction Pool
18+
//!
19+
//! The pool is based on dependency graph between transactions
20+
//! and their priority.
21+
//! The pool is able to return an iterator that traverses transaction
22+
//! graph in the correct order taking into account priorities and dependencies.
23+
//!
24+
//! TODO [ToDr]
25+
//! - [ ] Longevity handling (remove obsolete transactions periodically)
26+
//! - [ ] Banning / Future-rotation (once rejected (as invalid) should not be accepted for some time)
27+
//! - [ ] Multi-threading (getting ready transactions should not block the pool)
28+
29+
#![warn(missing_docs)]
30+
#![warn(unused_extern_crates)]
31+
32+
extern crate sr_primitives;
33+
34+
#[macro_use]
35+
extern crate error_chain;
36+
37+
#[macro_use]
38+
extern crate log;
39+
40+
mod error;
41+
mod future;
42+
mod pool;
43+
mod ready;
44+
45+
pub use self::pool::{Transaction, Pool};

0 commit comments

Comments
 (0)