From 2fa6f2fbd503bde3a5ea119cbd8ba91180b579e8 Mon Sep 17 00:00:00 2001
From: Pierre Krieger <pierre.krieger1708@gmail.com>
Date: Wed, 14 Apr 2021 15:19:33 +0200
Subject: [PATCH] Fix debug_assertion failing in authority discovery (#8599)

* Fix debug_assertion failing in authority discovery

* Improve test

* Change the map_or for invalid addresses

* Remove debug_assertion
---
 .../src/worker/addr_cache.rs                  | 126 +++++++++++++++++-
 1 file changed, 122 insertions(+), 4 deletions(-)

diff --git a/substrate/client/authority-discovery/src/worker/addr_cache.rs b/substrate/client/authority-discovery/src/worker/addr_cache.rs
index 13b259fbbb1..7cefff1aaff 100644
--- a/substrate/client/authority-discovery/src/worker/addr_cache.rs
+++ b/substrate/client/authority-discovery/src/worker/addr_cache.rs
@@ -24,6 +24,9 @@ use sc_network::PeerId;
 
 /// Cache for [`AuthorityId`] -> [`Vec<Multiaddr>`] and [`PeerId`] -> [`AuthorityId`] mappings.
 pub(super) struct AddrCache {
+	// The addresses found in `authority_id_to_addresses` are guaranteed to always match
+	// the peerids found in `peer_id_to_authority_id`. In other words, these two hashmaps
+	// are similar to a bi-directional map.
 	authority_id_to_addresses: HashMap<AuthorityId, Vec<Multiaddr>>,
 	peer_id_to_authority_id: HashMap<PeerId, AuthorityId>,
 }
@@ -43,17 +46,44 @@ impl AddrCache {
 			return;
 		}
 
+		addresses.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
+
 		// Insert into `self.peer_id_to_authority_id`.
 		let peer_ids = addresses.iter()
 			.map(|a| peer_id_from_multiaddr(a))
 			.filter_map(|peer_id| peer_id);
-		for peer_id in  peer_ids {
-			self.peer_id_to_authority_id.insert(peer_id, authority_id.clone());
+		for peer_id in peer_ids.clone() {
+			let former_auth = match self.peer_id_to_authority_id.insert(peer_id, authority_id.clone()) {
+				Some(a) if a != authority_id => a,
+				_ => continue,
+			};
+
+			// PeerId was associated to a different authority id before.
+			// Remove corresponding authority from `self.authority_id_to_addresses`.
+			let former_auth_addrs = match self.authority_id_to_addresses.get_mut(&former_auth) {
+				Some(a) => a,
+				None => { debug_assert!(false); continue }
+			};
+			former_auth_addrs.retain(|a| peer_id_from_multiaddr(a).map_or(true, |p| p != peer_id));
 		}
 
 		// Insert into `self.authority_id_to_addresses`.
-		addresses.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
-		self.authority_id_to_addresses.insert(authority_id, addresses);
+		for former_addr in
+			self.authority_id_to_addresses.insert(authority_id.clone(), addresses.clone()).unwrap_or_default()
+		{
+			// Must remove from `self.peer_id_to_authority_id` any PeerId formerly associated
+			// to that authority but that can't be found in its new addresses.
+
+			let peer_id = match peer_id_from_multiaddr(&former_addr) {
+				Some(p) => p,
+				None => continue,
+			};
+
+			if !peer_ids.clone().any(|p| p == peer_id) {
+				let _old_auth = self.peer_id_to_authority_id.remove(&peer_id);
+				debug_assert!(_old_auth.is_some());
+			}
+		}
 	}
 
 	/// Returns the number of authority IDs in the cache.
@@ -144,6 +174,25 @@ mod tests {
 		}
 	}
 
+	#[derive(Clone, Debug)]
+	struct TestMultiaddrsSamePeerCombo(Multiaddr, Multiaddr);
+
+	impl Arbitrary for TestMultiaddrsSamePeerCombo {
+		fn arbitrary(g: &mut Gen) -> Self {
+			let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
+			let peer_id = PeerId::from_multihash(
+				Multihash::wrap(multihash::Code::Sha2_256.into(), &seed).unwrap()
+			).unwrap();
+			let multiaddr1 = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333".parse::<Multiaddr>()
+				.unwrap()
+				.with(Protocol::P2p(peer_id.clone().into()));
+			let multiaddr2 = "/ip6/2002:db8:0:0:0:0:0:2/tcp/30133".parse::<Multiaddr>()
+				.unwrap()
+				.with(Protocol::P2p(peer_id.into()));
+			TestMultiaddrsSamePeerCombo(multiaddr1, multiaddr2)
+		}
+	}
+
 	#[test]
 	fn retains_only_entries_of_provided_authority_ids() {
 		fn property(
@@ -190,4 +239,73 @@ mod tests {
 			.max_tests(10)
 			.quickcheck(property as fn(_, _, _) -> TestResult)
 	}
+
+	#[test]
+	fn keeps_consistency_between_authority_id_and_peer_id() {
+		fn property(
+			authority1: TestAuthorityId,
+			authority2: TestAuthorityId,
+			multiaddr1: TestMultiaddr,
+			multiaddr2: TestMultiaddr,
+			multiaddr3: TestMultiaddrsSamePeerCombo,
+		) -> TestResult {
+			let authority1 = authority1.0;
+			let authority2 = authority2.0;
+			let multiaddr1 = multiaddr1.0;
+			let multiaddr2 = multiaddr2.0;
+			let TestMultiaddrsSamePeerCombo(multiaddr3, multiaddr4) = multiaddr3;
+
+			let mut cache = AddrCache::new();
+
+			cache.insert(authority1.clone(), vec![multiaddr1.clone()]);
+			cache.insert(authority1.clone(), vec![multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()]);
+
+			assert_eq!(
+				None,
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr1).unwrap())
+			);
+			assert_eq!(
+				Some(&authority1),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
+			);
+			assert_eq!(
+				Some(&authority1),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
+			);
+			assert_eq!(
+				Some(&authority1),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr4).unwrap())
+			);
+
+			cache.insert(authority2.clone(), vec![multiaddr2.clone()]);
+
+			assert_eq!(
+				Some(&authority2),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
+			);
+			assert_eq!(
+				Some(&authority1),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
+			);
+			assert_eq!(cache.get_addresses_by_authority_id(&authority1).unwrap().len(), 2);
+
+			cache.insert(authority2.clone(), vec![multiaddr2.clone(), multiaddr3.clone()]);
+
+			assert_eq!(
+				Some(&authority2),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
+			);
+			assert_eq!(
+				Some(&authority2),
+				cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
+			);
+			assert!(cache.get_addresses_by_authority_id(&authority1).unwrap().is_empty());
+
+			TestResult::passed()
+		}
+
+		QuickCheck::new()
+			.max_tests(10)
+			.quickcheck(property as fn(_, _, _, _, _) -> TestResult)
+	}
 }
-- 
GitLab