From 06433c98896de9ea435ca276d846d01de662ed5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomusdrw@users.noreply.github.com>
Date: Mon, 28 Oct 2019 16:06:20 +0100
Subject: [PATCH] Fix a import+prune+replace case for multi-provides
 transactions. (#3939)

* Fix a import+prune+replace case for multi-provides transactions.

* Fix tests.
---
 .../core/transaction-pool/graph/src/pool.rs   |  3 +
 .../core/transaction-pool/graph/src/ready.rs  | 15 ++++-
 substrate/core/transaction-pool/src/tests.rs  | 57 ++++++++++++++++---
 3 files changed, 65 insertions(+), 10 deletions(-)

diff --git a/substrate/core/transaction-pool/graph/src/pool.rs b/substrate/core/transaction-pool/graph/src/pool.rs
index 97244f1cec3..dcb54f710f1 100644
--- a/substrate/core/transaction-pool/graph/src/pool.rs
+++ b/substrate/core/transaction-pool/graph/src/pool.rs
@@ -240,6 +240,7 @@ impl<B: ChainApi> Pool<B> {
 		tags: impl IntoIterator<Item=Tag>,
 		known_imported_hashes: impl IntoIterator<Item=ExHash<B>> + Clone,
 	) -> impl Future<Output=Result<(), B::Error>> {
+		log::trace!(target: "txpool", "Pruning at {:?}", at);
 		// Prune all transactions that provide given tags
 		let prune_status = match self.validated_pool.prune_tags(tags) {
 			Ok(prune_status) => prune_status,
@@ -257,6 +258,7 @@ impl<B: ChainApi> Pool<B> {
 		let pruned_transactions = prune_status.pruned.into_iter().map(|tx| tx.data.clone());
 		let reverify_future = self.verify(at, pruned_transactions, false);
 
+		log::trace!(target: "txpool", "Prunning at {:?}. Resubmitting transactions.", at);
 		// And finally - submit reverified transactions back to the pool
 		let at = at.clone();
 		let validated_pool = self.validated_pool.clone();
@@ -908,3 +910,4 @@ mod tests {
 		}
 	}
 }
+
diff --git a/substrate/core/transaction-pool/graph/src/ready.rs b/substrate/core/transaction-pool/graph/src/ready.rs
index 85bb4dd783c..3698bf447ee 100644
--- a/substrate/core/transaction-pool/graph/src/ready.rs
+++ b/substrate/core/transaction-pool/graph/src/ready.rs
@@ -338,7 +338,20 @@ impl<Hash: hash::Hash + Member + Serialize, Ex> ReadyTransactions<Hash, Ex> {
 					}
 				}
 
-				debug!(target: "txpool", "[{:?}] Pruned.", tx.hash);
+				// we also need to remove all other tags that this transaction provides,
+				// but since all the hard work is done, we only clear the provided_tag -> hash
+				// mapping.
+				let current_tag = &tag;
+				for tag in &tx.provides {
+					let removed = self.provided_tags.remove(tag);
+					assert_eq!(
+						removed.as_ref(),
+						if current_tag == tag { None } else { Some(&tx.hash) },
+						"The pool contains exactly one transaction providing given tag; the removed transaction
+						claims to provide that tag, so it has to be mapped to it's hash; qed"
+					);
+				}
+
 				removed.push(tx);
 			}
 		}
diff --git a/substrate/core/transaction-pool/src/tests.rs b/substrate/core/transaction-pool/src/tests.rs
index d1ad27dd260..60a9e0562fc 100644
--- a/substrate/core/transaction-pool/src/tests.rs
+++ b/substrate/core/transaction-pool/src/tests.rs
@@ -27,11 +27,15 @@ use sr_primitives::{
 	transaction_validity::{TransactionValidity, ValidTransaction},
 };
 
-struct TestApi;
+struct TestApi {
+	pub modifier: Box<dyn Fn(&mut ValidTransaction) + Send + Sync>,
+}
 
 impl TestApi {
 	fn default() -> Self {
-		TestApi
+		TestApi {
+			modifier: Box::new(|_| {}),
+		}
 	}
 }
 
@@ -54,14 +58,18 @@ impl txpool::ChainApi for TestApi {
 		};
 		let provides = vec![vec![uxt.transfer().nonce as u8]];
 
+		let mut validity = ValidTransaction {
+			priority: 1,
+			requires,
+			provides,
+			longevity: 64,
+			propagate: true,
+		};
+
+		(self.modifier)(&mut validity);
+
 		futures::future::ready(Ok(
-			Ok(ValidTransaction {
-				priority: 1,
-				requires,
-				provides,
-				longevity: 64,
-				propagate: true,
-			})
+			Ok(validity)
 		))
 	}
 
@@ -181,3 +189,34 @@ fn should_ban_invalid_transactions() {
 	// then
 	block_on(pool.submit_one(&BlockId::number(0), uxt.clone())).unwrap_err();
 }
+
+#[test]
+fn should_correctly_prune_transactions_providing_more_than_one_tag() {
+	let mut api = TestApi::default();
+	api.modifier = Box::new(|v: &mut ValidTransaction| {
+		v.provides.push(vec![155]);
+	});
+	let pool = Pool::new(Default::default(), api);
+	let xt = uxt(Alice, 209);
+	block_on(pool.submit_one(&BlockId::number(0), xt.clone())).expect("1. Imported");
+	assert_eq!(pool.status().ready, 1);
+
+	// remove the transaction that just got imported.
+	block_on(pool.prune_tags(&BlockId::number(1), vec![vec![209]], vec![])).expect("1. Pruned");
+	assert_eq!(pool.status().ready, 0);
+	// it's re-imported to future
+	assert_eq!(pool.status().future, 1);
+
+	// so now let's insert another transaction that also provides the 155
+	let xt = uxt(Alice, 211);
+	block_on(pool.submit_one(&BlockId::number(2), xt.clone())).expect("2. Imported");
+	assert_eq!(pool.status().ready, 1);
+	assert_eq!(pool.status().future, 1);
+	let pending: Vec<_> = pool.ready().map(|a| a.data.transfer().nonce).collect();
+	assert_eq!(pending, vec![211]);
+
+	// prune it and make sure the pool is empty
+	block_on(pool.prune_tags(&BlockId::number(3), vec![vec![155]], vec![])).expect("2. Pruned");
+	assert_eq!(pool.status().ready, 0);
+	assert_eq!(pool.status().future, 2);
+}
-- 
GitLab