Repository: mintlayer/mintlayer-core
Pull request #604 (2022-12-27 15:58:59)

Basic peer discovery
Changes:

**Allow listening on multiple addresses.**
The node was listening on only one IPv6 socket by default and so it was not clear how incoming connections from IPv4 peers should be handled.
There are two ways to fix this:

1. Unset `IPV6_V6ONLY` socket flag and bind to default IPv6 address ([::]). Incoming IPv4 connections will be reported as IPv6 (like `::ffff:192.168.1.2`).
2. Set `IPV6_V6ONLY` and bind to two sockets (unless user specified something different).

I decided to go with the second option because it appears to be more robust and future-proof (bitcoin core and libp2p-rust also work this way). Users will be able to specify any number of bind addresses.
We need to set `IPV6_V6ONLY` explicitly in any case because its default value differs between platforms. For this I used `socket2` crate which is de-factor standard (and we already have it in the dependency tree).

**Add `PeerAddress` type**
I needed some concrete type which we can send over network and to work with (take IP address and apply port change).
`TransportAddress` trait is added to be able covert from abstract transport address type to `PeerAddress` and back.
`PeerAddress` might be extended later to also include addresses from other transports (for example TOR onion address).

**Add `--p2p-add-node` command line parameter**
It's used to manually specify node addresses to which node will try to connect, which is useful in tests. Works like `-addnode=` in `bitcoind`.

**Send listening port**
Mock backend might send its listining port in outbound connection handshake.
If listining port is set, inbound nodes will take IP address of the remote socket and apply this listening socket.
This new socket address will be handled as newly discovered peer address.
This approach will solve the problem with discovering IP address for nodes with public IP addresses and for nodes behind NAT with port forwarding.

**Allow only one connection per remote socket address**
I noticed in tests that we do not prevent duplicated connections to same socket address.
That was not problem with libp2p, because only one connection is allowed to same peer id.
But this is a problem with mock backend where we do not have globally unique peer ids.
To fix this I updated PeerManager to allow only one connection to same socket address.
Still peers on mock backend might have two connections to each others:

- A connects to B.
- B discovers listening address of A.
- B connects to newly discovered address of A.

We can fix this later allowing only 1 connection per IP address.

**PeerDb updated**
`PeerDb` was updated to not rely on globally unique peer ids.
Also `PeerDb` unit tests moved to submodule of peerdb mod. This allowed make some internal functions private.



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index cdbaa29be..a8315dd4f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2059,6 +2059,7 @@ dependencies = [
  "serde",
  "serialization",
  "snowstorm",
+ "socket2",
  "sscanf",
  "subsystem",
  "tap",
diff --git a/common/src/primitives/ip_address.rs b/common/src/primitives/ip_address.rs
new file mode 100644
index 000000000..b63fc3e4a
--- /dev/null
+++ b/common/src/primitives/ip_address.rs
@@ -0,0 +1,56 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use serialization::{Decode, Encode};
+
+/// IPv4 address.
+///
+/// Use custom type to be able implement Encode and Decode.
+#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Decode, Encode)]
+pub struct Ip4 {
+    inner: [u8; 4],
+}
+
+/// IPv6 address
+///
+/// Use custom type to be able implement Encode and Decode.
+#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Decode, Encode)]
+pub struct Ip6 {
+    inner: [u8; 16],
+}
+
+impl From<std::net::Ipv4Addr> for Ip4 {
+    fn from(ip: std::net::Ipv4Addr) -> Self {
+        Self { inner: ip.octets() }
+    }
+}
+
+impl From<std::net::Ipv6Addr> for Ip6 {
+    fn from(ip: std::net::Ipv6Addr) -> Self {
+        Self { inner: ip.octets() }
+    }
+}
+
+impl From<Ip4> for std::net::Ipv4Addr {
+    fn from(ip: Ip4) -> Self {
+        ip.inner.into()
+    }
+}
+
+impl From<Ip6> for std::net::Ipv6Addr {
+    fn from(ip: Ip6) -> Self {
+        ip.inner.into()
+    }
+}
diff --git a/common/src/primitives/mod.rs b/common/src/primitives/mod.rs
index a1b3256f6..8ddd0fad8 100644
--- a/common/src/primitives/mod.rs
+++ b/common/src/primitives/mod.rs
@@ -20,6 +20,7 @@ pub mod error;
 mod hash_encoded;
 pub mod height;
 pub mod id;
+pub mod ip_address;
 pub mod merkle;
 pub mod semver;
 pub mod signed_amount;
diff --git a/node/src/config_files/mod.rs b/node/src/config_files/mod.rs
index 5cf893c88..3c49f548e 100644
--- a/node/src/config_files/mod.rs
+++ b/node/src/config_files/mod.rs
@@ -136,7 +136,8 @@ fn chainstate_config(
 
 fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
     let P2pConfigFile {
-        bind_address,
+        bind_addresses,
+        added_nodes,
         ban_threshold,
         ban_duration,
         outbound_connection_timeout,
@@ -144,7 +145,8 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
         node_type,
     } = config;
 
-    let bind_address = options.p2p_addr.clone().or(bind_address);
+    let bind_addresses = options.p2p_addr.clone().or(bind_addresses);
+    let added_nodes = options.p2p_add_node.clone().or(added_nodes);
     let ban_threshold = options.p2p_ban_threshold.or(ban_threshold);
     let outbound_connection_timeout =
         options.p2p_outbound_connection_timeout.or(outbound_connection_timeout);
@@ -157,7 +159,8 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
     );
 
     P2pConfigFile {
-        bind_address,
+        bind_addresses,
+        added_nodes,
         ban_threshold,
         ban_duration,
         outbound_connection_timeout,
diff --git a/node/src/config_files/p2p.rs b/node/src/config_files/p2p.rs
index 04a98c284..aa7bc7dc3 100644
--- a/node/src/config_files/p2p.rs
+++ b/node/src/config_files/p2p.rs
@@ -122,7 +122,9 @@ impl FromStr for NodeTypeConfigFile {
 #[derive(Serialize, Deserialize, Debug, Default)]
 pub struct P2pConfigFile {
     /// Address to bind P2P to.
-    pub bind_address: Option<String>,
+    pub bind_addresses: Option<Vec<String>>,
+    /// Optional list of initial node addresses to connect.
+    pub added_nodes: Option<Vec<String>>,
     /// The score threshold after which a peer is banned.
     pub ban_threshold: Option<u32>,
     /// Duration of bans in seconds.
@@ -139,7 +141,8 @@ impl From<P2pConfigFile> for P2pConfig {
     fn from(c: P2pConfigFile) -> Self {
         let mdns_config: Option<MdnsConfig> = c.mdns_config.map(|v| v.into());
         P2pConfig {
-            bind_address: c.bind_address.into(),
+            bind_addresses: c.bind_addresses.clone().unwrap_or_default(),
+            added_nodes: c.added_nodes.clone().unwrap_or_default(),
             ban_threshold: c.ban_threshold.into(),
             ban_duration: c.ban_duration.map(Duration::from_secs).into(),
             outbound_connection_timeout: c.outbound_connection_timeout.into(),
diff --git a/node/src/options.rs b/node/src/options.rs
index 601c5fa9e..985307eee 100644
--- a/node/src/options.rs
+++ b/node/src/options.rs
@@ -81,7 +81,11 @@ pub struct RunOptions {
 
     /// Address to bind P2P to.
     #[clap(long, value_name = "ADDR")]
-    pub p2p_addr: Option<String>,
+    pub p2p_addr: Option<Vec<String>>,
+
+    /// Optional list of initial node addresses to connect.
+    #[clap(long, value_name = "NODE")]
+    pub p2p_add_node: Option<Vec<String>>,
 
     /// The p2p score threshold after which a peer is baned.
     #[clap(long)]
diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml
index 3d40a7ce7..c4a0744f3 100644
--- a/p2p/Cargo.toml
+++ b/p2p/Cargo.toml
@@ -34,6 +34,7 @@ tokio = { version = "1", default-features = false, features = ["io-util", "macro
 tokio-util = {version = "0.7", default-features = false, features = ["codec"] }
 snowstorm = "0.4.0"
 serde = "1.0"
+socket2 = { version = "0.4.0", features = ["all"] }
 
 [dev-dependencies]
 chainstate-storage = { path = "../chainstate/storage" }
diff --git a/p2p/src/config.rs b/p2p/src/config.rs
index 7103881c1..d04b922a0 100644
--- a/p2p/src/config.rs
+++ b/p2p/src/config.rs
@@ -19,11 +19,12 @@ use utils::make_config_setting;
 
 use crate::net::types::PubSubTopic;
 
+pub const DEFAULT_BIND_PORT: u16 = 3031;
+
 // TODO: does this constant make sense to be zero? Find the justification for it.
 pub const MDNS_DEFAULT_QUERY_INTERVAL: u64 = 0;
 pub const MDNS_DEFAULT_IPV6_STATE: bool = false;
 
-make_config_setting!(P2pBindAddress, String, "[::1]:3031".into());
 make_config_setting!(BanThreshold, u32, 100);
 make_config_setting!(BanDuration, Duration, Duration::from_secs(60 * 60 * 24));
 make_config_setting!(OutboundConnectionTimeout, u64, 10);
@@ -84,7 +85,9 @@ impl From<NodeType> for BTreeSet<PubSubTopic> {
 #[derive(Debug, Default)]
 pub struct P2pConfig {
     /// Address to bind P2P to.
-    pub bind_address: P2pBindAddress,
+    pub bind_addresses: Vec<String>,
+    /// Optional list of initial node addresses, could be used to specify boot nodes for example.
+    pub added_nodes: Vec<String>,
     /// The score threshold after which a peer is banned.
     pub ban_threshold: BanThreshold,
     /// Duration of bans in seconds.
diff --git a/p2p/src/event.rs b/p2p/src/event.rs
index 86fa37fe8..7bd37ef6b 100644
--- a/p2p/src/event.rs
+++ b/p2p/src/event.rs
@@ -31,7 +31,7 @@ pub enum PeerManagerEvent<T: NetworkingService> {
     GetPeerCount(oneshot::Sender<usize>),
 
     /// Get the bind address of the local node
-    GetBindAddress(oneshot::Sender<String>),
+    GetBindAddresses(oneshot::Sender<Vec<String>>),
 
     /// Get peer IDs and addresses of connected peers
     GetConnectedPeers(oneshot::Sender<Vec<ConnectedPeer>>),
diff --git a/p2p/src/interface/p2p_interface.rs b/p2p/src/interface/p2p_interface.rs
index 293119106..64064eb09 100644
--- a/p2p/src/interface/p2p_interface.rs
+++ b/p2p/src/interface/p2p_interface.rs
@@ -23,7 +23,7 @@ pub trait P2pInterface: Send + Sync {
 
     async fn get_peer_count(&self) -> crate::Result<usize>;
 
-    async fn get_bind_address(&self) -> crate::Result<String>;
+    async fn get_bind_addresses(&self) -> crate::Result<Vec<String>>;
 
     async fn get_connected_peers(&self) -> crate::Result<Vec<ConnectedPeer>>;
 }
diff --git a/p2p/src/interface/p2p_interface_impl.rs b/p2p/src/interface/p2p_interface_impl.rs
index a0b6cc011..ffe4a4581 100644
--- a/p2p/src/interface/p2p_interface_impl.rs
+++ b/p2p/src/interface/p2p_interface_impl.rs
@@ -69,10 +69,10 @@ where
         rx.await.map_err(P2pError::from)
     }
 
-    async fn get_bind_address(&self) -> crate::Result<String> {
+    async fn get_bind_addresses(&self) -> crate::Result<Vec<String>> {
         let (tx, rx) = oneshot::channel();
         self.tx_peer_manager
-            .send(PeerManagerEvent::GetBindAddress(tx))
+            .send(PeerManagerEvent::GetBindAddresses(tx))
             .map_err(P2pError::from)?;
         rx.await.map_err(P2pError::from)
     }
diff --git a/p2p/src/interface/p2p_interface_impl_delegation.rs b/p2p/src/interface/p2p_interface_impl_delegation.rs
index 3a63aa86d..71404d284 100644
--- a/p2p/src/interface/p2p_interface_impl_delegation.rs
+++ b/p2p/src/interface/p2p_interface_impl_delegation.rs
@@ -33,8 +33,8 @@ impl<T: Deref<Target = dyn P2pInterface> + DerefMut<Target = dyn P2pInterface> +
         self.deref().get_peer_count().await
     }
 
-    async fn get_bind_address(&self) -> crate::Result<String> {
-        self.deref().get_bind_address().await
+    async fn get_bind_addresses(&self) -> crate::Result<Vec<String>> {
+        self.deref().get_bind_addresses().await
     }
 
     async fn get_connected_peers(&self) -> crate::Result<Vec<ConnectedPeer>> {
diff --git a/p2p/src/interface/types.rs b/p2p/src/interface/types.rs
index ec8412b08..2a50801ce 100644
--- a/p2p/src/interface/types.rs
+++ b/p2p/src/interface/types.rs
@@ -13,11 +13,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// TODO: Implement From PeerContext for ConnectedPeer
 #[derive(Debug, serde::Serialize)]
 pub struct ConnectedPeer {
     // TODO: Replace String with actual type, once libp2p removed
     pub peer_id: String,
+
     // TODO: Replace String with actual type, once libp2p removed
-    pub addr: String,
+    pub address: String,
+
+    pub inbound: bool,
+
+    pub ban_score: u32,
 }
diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs
index c102bedac..e13d72b3a 100644
--- a/p2p/src/lib.rs
+++ b/p2p/src/lib.rs
@@ -25,8 +25,9 @@ pub mod rpc;
 pub mod sync;
 #[cfg(feature = "testing_utils")]
 pub mod testing_utils;
+pub mod types;
 
-use std::{fmt::Debug, str::FromStr, sync::Arc};
+use std::sync::Arc;
 
 use interface::p2p_interface::P2pInterface;
 use tap::TapFallible;
@@ -76,18 +77,20 @@ where
         p2p_config: Arc<P2pConfig>,
         chainstate_handle: subsystem::Handle<Box<dyn chainstate_interface::ChainstateInterface>>,
         _mempool_handle: mempool::MempoolHandle,
-    ) -> Result<Self>
-    where
-        <T as NetworkingService>::Address: FromStr,
-        <<T as NetworkingService>::Address as FromStr>::Err: Debug,
-    {
+    ) -> crate::Result<Self> {
+        let bind_addresses = p2p_config
+            .bind_addresses
+            .iter()
+            .map(|address| {
+                address.parse::<T::Address>().map_err(|_| {
+                    P2pError::ConversionError(ConversionError::InvalidAddress(address.clone()))
+                })
+            })
+            .collect::<Result<Vec<_>>>()?;
+
         let (conn, sync) = T::start(
             transport,
-            p2p_config.bind_address.parse::<T::Address>().map_err(|_| {
-                P2pError::ConversionError(ConversionError::InvalidAddress(
-                    p2p_config.bind_address.clone().into(),
-                ))
-            })?,
+            bind_addresses,
             Arc::clone(&chain_config),
             Arc::clone(&p2p_config),
         )
@@ -105,22 +108,17 @@ where
         let (tx_p2p_sync, rx_p2p_sync) = mpsc::unbounded_channel();
         let (_tx_sync, _rx_sync) = mpsc::unbounded_channel();
 
-        {
-            let chain_config = Arc::clone(&chain_config);
-            let p2p_config = Arc::clone(&p2p_config);
-            tokio::spawn(async move {
-                peer_manager::PeerManager::<T>::new(
-                    chain_config,
-                    p2p_config,
-                    conn,
-                    rx_peer_manager,
-                    tx_p2p_sync,
-                )
-                .run()
-                .await
-                .tap_err(|err| log::error!("PeerManager failed: {err}"))
-            });
-        }
+        let mut peer_manager = peer_manager::PeerManager::<T>::new(
+            Arc::clone(&chain_config),
+            Arc::clone(&p2p_config),
+            conn,
+            rx_peer_manager,
+            tx_p2p_sync,
+        )?;
+        tokio::spawn(async move {
+            peer_manager.run().await.tap_err(|err| log::error!("PeerManager failed: {err}"))
+        });
+
         {
             let chainstate_handle = chainstate_handle.clone();
             let tx_peer_manager = tx_peer_manager.clone();
diff --git a/p2p/src/net/mock/backend.rs b/p2p/src/net/mock/backend.rs
index eb690e0d9..68d945818 100644
--- a/p2p/src/net/mock/backend.rs
+++ b/p2p/src/net/mock/backend.rs
@@ -53,12 +53,13 @@ use crate::{
                 MockRequestId, PeerEvent, SyncingEvent,
             },
         },
-        types::PubSubTopic,
+        types::{PubSubTopic, Role},
         Announcement,
     },
+    types::peer_address::PeerAddress,
 };
 
-use super::peer::Role;
+use super::transport::TransportAddress;
 
 /// Active peer data
 struct PeerContext {
@@ -78,7 +79,7 @@ pub struct Backend<T: TransportSocket> {
     transport: T,
 
     /// Socket address of the backend
-    address: T::Address,
+    addresses: Vec<T::Address>,
 
     /// Socket for listening to incoming connections
     socket: T::Listener,
@@ -125,7 +126,7 @@ where
     #[allow(clippy::too_many_arguments)]
     pub fn new(
         transport: T,
-        address: T::Address,
+        addresses: Vec<T::Address>,
         socket: T::Listener,
         chain_config: Arc<ChainConfig>,
         p2p_config: Arc<P2pConfig>,
@@ -136,7 +137,7 @@ where
     ) -> Self {
         Self {
             transport,
-            address,
+            addresses,
             socket,
             cmd_rx,
             conn_tx,
@@ -157,7 +158,10 @@ where
         address: T::Address,
         response: oneshot::Sender<crate::Result<()>>,
     ) -> crate::Result<()> {
-        if self.address == address {
+        // TODO: This is not very robust. Send random nonce in handshake
+        // to determine if we connect to ourselves (like it's done in bitcoin).
+        // local_address can be removed then as it's not used otherwise.
+        if self.addresses.iter().any(|a| *a == address) {
             response
                 .send(Err(P2pError::DialError(DialError::IoError(
                     ErrorKind::AddrNotAvailable,
@@ -169,9 +173,7 @@ where
 
         match timeout(self.timeout, self.transport.connect(address.clone())).await {
             Ok(event) => match event {
-                Ok(socket) => {
-                    self.create_peer(socket, MockPeerId::new(), peer::Role::Outbound, address)
-                }
+                Ok(socket) => self.create_peer(socket, MockPeerId::new(), Role::Outbound, address),
                 Err(err) => {
                     log::error!("Failed to establish connection: {err}");
 
@@ -358,7 +360,7 @@ where
                     self.create_peer(
                         stream,
                         MockPeerId::new(),
-                        peer::Role::Inbound,
+                        Role::Inbound,
                         address,
                     )?;
                 }
@@ -375,6 +377,19 @@ where
         }
     }
 
+    /// Handle received listening port from inbound remote peer
+    fn handle_own_receiver_address(
+        &mut self,
+        receiver_address: PeerAddress,
+        role: Role,
+    ) -> crate::Result<()> {
+        log::debug!("new own receiver address {receiver_address} found from {role:?} connection");
+
+        // TODO: Handle receiver address
+
+        Ok(())
+    }
+
     /// Create new peer
     ///
     /// Move the connection to `pending` where it stays until either the connection is closed
@@ -384,11 +399,13 @@ where
         &mut self,
         socket: T::Stream,
         remote_peer_id: MockPeerId,
-        role: peer::Role,
+        role: Role,
         address: T::Address,
     ) -> crate::Result<()> {
         let (tx, rx) = mpsc::channel(16);
 
+        let receiver_address = Some(address.as_peer_address());
+
         self.pending.insert(remote_peer_id, PendingPeerContext { address, role, tx });
 
         let tx = self.peer_chan.0.clone();
@@ -402,6 +419,7 @@ where
                 chain_config,
                 p2p_config,
                 socket,
+                receiver_address,
                 tx,
                 rx,
             );
@@ -425,6 +443,7 @@ where
                 network,
                 version,
                 subscriptions,
+                receiver_address,
             } => {
                 let PendingPeerContext { address, role, tx } =
                     self.pending.remove(&peer_id).expect("peer to exist");
@@ -433,7 +452,7 @@ where
                     Role::Outbound => {
                         self.conn_tx
                             .send(ConnectivityEvent::OutboundAccepted {
-                                address: address.clone(),
+                                address,
                                 peer_info: MockPeerInfo {
                                     peer_id,
                                     network,
@@ -462,6 +481,10 @@ where
                     }
                 }
 
+                if let Some(receiver_address) = receiver_address {
+                    self.handle_own_receiver_address(receiver_address, role)?;
+                }
+
                 self.peers.insert(peer_id, PeerContext { subscriptions, tx });
                 let _ = self.request_mgr.register_peer(peer_id);
             }
diff --git a/p2p/src/net/mock/mod.rs b/p2p/src/net/mock/mod.rs
index 5abc21c94..97bc9aaba 100644
--- a/p2p/src/net/mock/mod.rs
+++ b/p2p/src/net/mock/mod.rs
@@ -48,8 +48,8 @@ pub struct MockService<T: TransportSocket>(PhantomData<T>);
 
 #[derive(Debug)]
 pub struct MockConnectivityHandle<S: NetworkingService, T: TransportSocket> {
-    /// The local address of a network service provider.
-    local_addr: S::Address,
+    /// The local addresses of a network service provider.
+    local_addresses: Vec<S::Address>,
 
     /// TX channel for sending commands to mock backend
     cmd_tx: mpsc::Sender<types::Command<T>>,
@@ -117,22 +117,22 @@ impl<T: TransportSocket> NetworkingService for MockService<T> {
 
     async fn start(
         transport: Self::Transport,
-        addr: Self::Address,
+        bind_addresses: Vec<Self::Address>,
         chain_config: Arc<common::chain::ChainConfig>,
         p2p_config: Arc<config::P2pConfig>,
     ) -> crate::Result<(Self::ConnectivityHandle, Self::SyncingMessagingHandle)> {
         let (cmd_tx, cmd_rx) = mpsc::channel(16);
         let (conn_tx, conn_rx) = mpsc::channel(16);
         let (sync_tx, sync_rx) = mpsc::channel(16);
-        let socket = transport.bind(addr).await?;
-        let local_addr = socket.local_address().expect("to have bind address available");
+        let socket = transport.bind(bind_addresses).await?;
+        let local_addresses = socket.local_addresses().expect("to have bind address available");
 
-        let address = local_addr.clone();
+        let addresses = local_addresses.clone();
         tokio::spawn(async move {
             let timeout = *p2p_config.outbound_connection_timeout;
             let mut backend = backend::Backend::<T>::new(
                 transport,
-                address,
+                addresses,
                 socket,
                 chain_config,
                 p2p_config,
@@ -149,7 +149,7 @@ impl<T: TransportSocket> NetworkingService for MockService<T> {
 
         Ok((
             Self::ConnectivityHandle {
-                local_addr,
+                local_addresses,
                 cmd_tx: cmd_tx.clone(),
                 conn_rx,
                 _marker: Default::default(),
@@ -201,8 +201,8 @@ where
         rx.await?
     }
 
-    async fn local_addr(&self) -> crate::Result<Option<S::Address>> {
-        Ok(Some(self.local_addr.clone()))
+    async fn local_addresses(&self) -> crate::Result<Vec<S::Address>> {
+        Ok(self.local_addresses.clone())
     }
 
     async fn poll_next(&mut self) -> crate::Result<ConnectivityEvent<S>> {
@@ -228,6 +228,11 @@ where
             types::ConnectivityEvent::Misbehaved { peer_id, error } => {
                 Ok(ConnectivityEvent::Misbehaved { peer_id, error })
             }
+            types::ConnectivityEvent::AddressDiscovered { address } => {
+                Ok(ConnectivityEvent::Discovered {
+                    addresses: vec![address],
+                })
+            }
         }
     }
 }
@@ -350,7 +355,7 @@ mod tests {
 
         let (mut conn1, _) = MockService::<T>::start(
             A::make_transport(),
-            A::make_address(),
+            vec![A::make_address()],
             Arc::clone(&config),
             Arc::clone(&p2p_config),
         )
@@ -359,20 +364,20 @@ mod tests {
 
         let (conn2, _) = MockService::<T>::start(
             A::make_transport(),
-            A::make_address(),
+            vec![A::make_address()],
             Arc::clone(&config),
             Arc::clone(&p2p_config),
         )
         .await
         .unwrap();
 
-        let addr = conn2.local_addr().await.unwrap().unwrap();
-        assert_eq!(conn1.connect(addr).await, Ok(()));
+        let addr = conn2.local_addresses().await.unwrap();
+        assert_eq!(conn1.connect(addr[0].clone()).await, Ok(()));
 
         if let Ok(ConnectivityEvent::OutboundAccepted { address, peer_info }) =
             conn1.poll_next().await
         {
-            assert_eq!(address, conn2.local_addr().await.unwrap().unwrap());
+            assert_eq!(address, conn2.local_addresses().await.unwrap()[0]);
             assert_eq!(&peer_info.magic_bytes, config.magic_bytes());
             assert_eq!(peer_info.version, SemVer::new(0, 1, 0));
             assert_eq!(peer_info.agent, None);
@@ -410,7 +415,7 @@ mod tests {
 
         let (mut conn1, _) = MockService::<T>::start(
             A::make_transport(),
-            A::make_address(),
+            vec![A::make_address()],
             Arc::clone(&config),
             Arc::clone(&p2p_config),
         )
@@ -419,15 +424,15 @@ mod tests {
 
         let (mut conn2, _) = MockService::<T>::start(
             A::make_transport(),
-            A::make_address(),
+            vec![A::make_address()],
             Arc::clone(&config),
             Arc::clone(&p2p_config),
         )
         .await
         .unwrap();
 
-        let bind_address = conn2.local_addr().await.unwrap().unwrap();
-        let (_res1, res2) = tokio::join!(conn1.connect(bind_address), conn2.poll_next());
+        let bind_address = conn2.local_addresses().await.unwrap();
+        let (_res1, res2) = tokio::join!(conn1.connect(bind_address[0].clone()), conn2.poll_next());
         match res2.unwrap() {
             ConnectivityEvent::InboundAccepted {
                 address: _,
@@ -469,19 +474,23 @@ mod tests {
 
         let (mut conn1, _) = MockService::<T>::start(
             A::make_transport(),
-            A::make_address(),
+            vec![A::make_address()],
             Arc::clone(&config),
             Arc::clone(&p2p_config),
         )
         .await
         .unwrap();
-        let (mut conn2, _) =
-            MockService::<T>::start(A::make_transport(), A::make_address(), config, p2p_config)
-                .await
-                .unwrap();
+        let (mut conn2, _) = MockService::<T>::start(
+            A::make_transport(),
+            vec![A::make_address()],
+            config,
+            p2p_config,
+        )
+        .await
+        .unwrap();
 
         let (_res1, res2) = tokio::join!(
-            conn1.connect(conn2.local_addr().await.unwrap().unwrap()),
+            conn1.connect(conn2.local_addresses().await.unwrap()[0].clone()),
             conn2.poll_next()
         );
 
diff --git a/p2p/src/net/mock/peer.rs b/p2p/src/net/mock/peer.rs
index e85a7a7f4..3c941a504 100644
--- a/p2p/src/net/mock/peer.rs
+++ b/p2p/src/net/mock/peer.rs
@@ -23,22 +23,20 @@ use logging::log;
 use crate::{
     config::P2pConfig,
     error::{P2pError, ProtocolError},
-    net::mock::{
-        transport::TransportSocket,
-        types::{self, MockEvent, MockPeerId, PeerEvent},
+    net::{
+        mock::{
+            transport::TransportSocket,
+            types::{self, MockEvent, MockPeerId, PeerEvent},
+        },
+        types::Role,
     },
+    types::peer_address::PeerAddress,
 };
 
 use super::transport::BufferedTranscoder;
 
 const PEER_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
 
-#[derive(Debug, Clone, Copy)]
-pub enum Role {
-    Inbound,
-    Outbound,
-}
-
 pub struct Peer<T: TransportSocket> {
     /// Peer ID of the remote node
     peer_id: MockPeerId,
@@ -54,6 +52,9 @@ pub struct Peer<T: TransportSocket> {
     /// Peer socket
     socket: BufferedTranscoder<T::Stream>,
 
+    /// Socket address of the remote peer as seen by this node (addr_you in bitcoin)
+    receiver_address: Option<PeerAddress>,
+
     /// TX channel for communicating with backend
     tx: mpsc::Sender<(MockPeerId, PeerEvent)>,
 
@@ -72,6 +73,7 @@ where
         chain_config: Arc<ChainConfig>,
         p2p_config: Arc<P2pConfig>,
         socket: T::Stream,
+        receiver_address: Option<PeerAddress>,
         tx: mpsc::Sender<(MockPeerId, PeerEvent)>,
         rx: mpsc::Receiver<MockEvent>,
     ) -> Self {
@@ -82,6 +84,7 @@ where
             chain_config,
             p2p_config,
             socket,
+            receiver_address,
             tx,
             rx,
         }
@@ -94,6 +97,7 @@ where
                     version,
                     network,
                     subscriptions,
+                    receiver_address,
                 })) = self.socket.recv().await
                 else {
                     return Err(P2pError::ProtocolError(ProtocolError::InvalidMessage));
@@ -105,6 +109,7 @@ where
                             version: *self.chain_config.version(),
                             network: *self.chain_config.magic_bytes(),
                             subscriptions: (*self.p2p_config.node_type.as_ref()).into(),
+                            receiver_address: self.receiver_address.clone(),
                         },
                     ))
                     .await?;
@@ -116,12 +121,11 @@ where
                             network,
                             version,
                             subscriptions,
+                            receiver_address,
                         },
                     ))
                     .await
                     .map_err(P2pError::from)?;
-
-                Ok(())
             }
             Role::Outbound => {
                 self.socket
@@ -129,6 +133,7 @@ where
                         version: *self.chain_config.version(),
                         network: *self.chain_config.magic_bytes(),
                         subscriptions: (*self.p2p_config.node_type.as_ref()).into(),
+                        receiver_address: self.receiver_address.clone(),
                     }))
                     .await?;
 
@@ -136,6 +141,7 @@ where
                     version,
                     network,
                     subscriptions,
+                    receiver_address,
                 })) = self.socket.recv().await
                 else {
                     return Err(P2pError::ProtocolError(ProtocolError::InvalidMessage));
@@ -148,14 +154,15 @@ where
                             network,
                             version,
                             subscriptions,
+                            receiver_address,
                         },
                     ))
                     .await
                     .map_err(P2pError::from)?;
-
-                Ok(())
             }
         }
+
+        Ok(())
     }
 
     pub async fn destroy(self) {
@@ -244,6 +251,7 @@ mod tests {
             Arc::clone(&chain_config),
             p2p_config,
             socket1,
+            None,
             tx1,
             rx2,
         );
@@ -262,11 +270,12 @@ mod tests {
                 subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
                     .into_iter()
                     .collect(),
+                receiver_address: None,
             }))
             .await
             .is_ok());
 
-        let _peer = handle.await;
+        let _peer = handle.await.unwrap();
         assert_eq!(
             rx1.try_recv().unwrap().1,
             types::PeerEvent::PeerInfoReceived {
@@ -275,6 +284,7 @@ mod tests {
                 subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
                     .into_iter()
                     .collect(),
+                receiver_address: None,
             }
         );
     }
@@ -312,6 +322,7 @@ mod tests {
             Arc::clone(&chain_config),
             p2p_config,
             socket1,
+            None,
             tx1,
             rx2,
         );
@@ -331,12 +342,13 @@ mod tests {
                     subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
                         .into_iter()
                         .collect(),
+                    receiver_address: None,
                 }
             ))
             .await
             .is_ok());
 
-        let _peer = handle.await;
+        let _peer = handle.await.unwrap();
         assert_eq!(
             rx1.try_recv(),
             Ok((
@@ -347,6 +359,7 @@ mod tests {
                     subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
                         .into_iter()
                         .collect(),
+                    receiver_address: None,
                 }
             ))
         );
@@ -385,6 +398,7 @@ mod tests {
             Arc::clone(&chain_config),
             p2p_config,
             socket1,
+            None,
             tx1,
             rx2,
         );
@@ -400,6 +414,7 @@ mod tests {
                 subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
                     .into_iter()
                     .collect(),
+                receiver_address: None,
             }))
             .await
             .is_ok());
@@ -440,6 +455,7 @@ mod tests {
             chain_config,
             p2p_config,
             socket1,
+            None,
             tx1,
             rx2,
         );
@@ -486,8 +502,8 @@ mod tests {
     {
         let transport = A::make_transport();
         let addr = A::make_address();
-        let mut server = transport.bind(addr).await.unwrap();
-        let peer_fut = transport.connect(server.local_address().unwrap());
+        let mut server = transport.bind(vec![addr]).await.unwrap();
+        let peer_fut = transport.connect(server.local_addresses().unwrap()[0].clone());
 
         let (res1, res2) = tokio::join!(server.accept(), peer_fut);
         (res1.unwrap().0, res2.unwrap())
diff --git a/p2p/src/net/mock/transport/impls/channel.rs b/p2p/src/net/mock/transport/impls/channel.rs
index e3f8bd45b..7690ddff4 100644
--- a/p2p/src/net/mock/transport/impls/channel.rs
+++ b/p2p/src/net/mock/transport/impls/channel.rs
@@ -16,7 +16,7 @@
 use std::{
     collections::BTreeMap,
     sync::{
-        atomic::{AtomicU64, Ordering},
+        atomic::{AtomicU32, Ordering},
         Mutex,
     },
 };
@@ -34,13 +34,31 @@ use tokio::{
 use crate::{
     error::DialError,
     net::{
-        mock::transport::{PeerStream, TransportListener, TransportSocket},
+        mock::transport::{
+            traits::TransportAddress, PeerStream, TransportListener, TransportSocket,
+        },
         AsBannableAddress,
     },
+    types::peer_address::{PeerAddress, PeerAddressIp4},
     P2pError, Result,
 };
 
-type Address = u64;
+type Address = u32;
+
+impl TransportAddress for Address {
+    fn as_peer_address(&self) -> PeerAddress {
+        PeerAddress::Ip4(PeerAddressIp4 {
+            // Address of the first "host" will be 0.0.0.1
+            ip: std::net::Ipv4Addr::from(*self).into(),
+            // There is only one "port" in MockChannelTransport per "host", use arbitrary value
+            port: 10000,
+        })
+    }
+
+    fn from_peer_address(_address: &PeerAddress) -> Option<Self> {
+        None
+    }
+}
 
 /// Zero address has special meaning: bind to a free address.
 const ZERO_ADDRESS: Address = 0;
@@ -53,7 +71,7 @@ type IncomingConnection = (Address, Sender<DuplexStream>);
 static CONNECTIONS: Lazy<Mutex<BTreeMap<Address, UnboundedSender<IncomingConnection>>>> =
     Lazy::new(Default::default);
 
-static NEXT_ADDRESS: AtomicU64 = AtomicU64::new(1);
+static NEXT_ADDRESS: AtomicU32 = AtomicU32::new(1);
 
 // Creating new transport is like attaching new "host" to the network.
 // New unique address is registered for the new "host".
@@ -78,13 +96,15 @@ impl TransportSocket for MockChannelTransport {
     type Listener = MockChannelListener;
     type Stream = ChannelMockStream;
 
-    async fn bind(&self, address: Self::Address) -> Result<Self::Listener> {
+    async fn bind(&self, addresses: Vec<Self::Address>) -> Result<Self::Listener> {
         // It's not possible to bind to random address
-        if address != ZERO_ADDRESS && address != self.local_address {
-            return Err(P2pError::DialError(DialError::IoError(
-                std::io::ErrorKind::AddrNotAvailable,
-            )));
-        };
+        for address in addresses.iter() {
+            if *address != ZERO_ADDRESS && *address != self.local_address {
+                return Err(P2pError::DialError(DialError::IoError(
+                    std::io::ErrorKind::AddrNotAvailable,
+                )));
+            };
+        }
 
         let mut connections = CONNECTIONS.lock().expect("Connections mutex is poisoned");
 
@@ -145,8 +165,8 @@ impl TransportListener<ChannelMockStream, Address> for MockChannelListener {
         Ok((server, remote_address))
     }
 
-    fn local_address(&self) -> Result<Address> {
-        Ok(self.address)
+    fn local_addresses(&self) -> Result<Vec<Address>> {
+        Ok(vec![self.address])
     }
 }
 
@@ -160,7 +180,6 @@ impl Drop for MockChannelListener {
 
 pub type ChannelMockStream = tokio::io::DuplexStream;
 
-#[async_trait]
 impl PeerStream for ChannelMockStream {}
 
 impl AsBannableAddress for Address {
@@ -186,8 +205,8 @@ mod tests {
     async fn send_recv() {
         let transport = MockChannelTransport::new();
         let address = ZERO_ADDRESS;
-        let mut server = transport.bind(address).await.unwrap();
-        let peer_fut = transport.connect(server.local_address().unwrap());
+        let mut server = transport.bind(vec![address]).await.unwrap();
+        let peer_fut = transport.connect(server.local_addresses().unwrap()[0]);
 
         let (server_res, peer_res) = tokio::join!(server.accept(), peer_fut);
         let server_stream = server_res.unwrap().0;
diff --git a/p2p/src/net/mock/transport/impls/stream_adapter/identity.rs b/p2p/src/net/mock/transport/impls/stream_adapter/identity.rs
index 03fa2e225..6f9574f22 100644
--- a/p2p/src/net/mock/transport/impls/stream_adapter/identity.rs
+++ b/p2p/src/net/mock/transport/impls/stream_adapter/identity.rs
@@ -15,7 +15,7 @@
 
 use futures::future::{ready, BoxFuture};
 
-use crate::net::mock::{peer::Role, transport::PeerStream};
+use crate::net::{mock::transport::PeerStream, types::Role};
 
 use super::StreamAdapter;
 
diff --git a/p2p/src/net/mock/transport/impls/stream_adapter/noise.rs b/p2p/src/net/mock/transport/impls/stream_adapter/noise.rs
index a3bd6fc22..57f884874 100644
--- a/p2p/src/net/mock/transport/impls/stream_adapter/noise.rs
+++ b/p2p/src/net/mock/transport/impls/stream_adapter/noise.rs
@@ -21,7 +21,7 @@ use tokio::time::timeout;
 
 use crate::{
     error::P2pError,
-    net::mock::{peer::Role, transport::PeerStream},
+    net::{mock::transport::PeerStream, types::Role},
 };
 
 use super::StreamAdapter;
diff --git a/p2p/src/net/mock/transport/impls/stream_adapter/traits.rs b/p2p/src/net/mock/transport/impls/stream_adapter/traits.rs
index f177d8159..5e18ac80d 100644
--- a/p2p/src/net/mock/transport/impls/stream_adapter/traits.rs
+++ b/p2p/src/net/mock/transport/impls/stream_adapter/traits.rs
@@ -16,7 +16,7 @@
 use futures::future::BoxFuture;
 
 use crate::{
-    net::mock::{peer::Role, transport::PeerStream},
+    net::{mock::transport::PeerStream, types::Role},
     Result,
 };
 
diff --git a/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_listener.rs b/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_listener.rs
index 30f5166f0..05eb9c8d5 100644
--- a/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_listener.rs
+++ b/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_listener.rs
@@ -20,11 +20,11 @@ use futures::{
 };
 
 use crate::{
-    net::mock::{
-        peer::Role,
-        transport::{
+    net::{
+        mock::transport::{
             impls::stream_adapter::traits::StreamAdapter, TransportListener, TransportSocket,
         },
+        types::Role,
     },
     Result,
 };
@@ -91,7 +91,7 @@ impl<S: StreamAdapter<T::Stream>, T: TransportSocket> TransportListener<S::Strea
         }
     }
 
-    fn local_address(&self) -> Result<T::Address> {
-        self.listener.local_address()
+    fn local_addresses(&self) -> Result<Vec<T::Address>> {
+        self.listener.local_addresses()
     }
 }
diff --git a/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_socket.rs b/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_socket.rs
index b05fa1cd0..7d536b7b0 100644
--- a/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_socket.rs
+++ b/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/wrapped_socket.rs
@@ -15,9 +15,9 @@
 
 use async_trait::async_trait;
 
-use crate::net::mock::{
-    peer::Role,
-    transport::{impls::stream_adapter::traits::StreamAdapter, TransportSocket},
+use crate::net::{
+    mock::transport::{impls::stream_adapter::traits::StreamAdapter, TransportSocket},
+    types::Role,
 };
 
 use super::wrapped_listener::AdaptedListener;
@@ -52,9 +52,9 @@ impl<S: StreamAdapter<T::Stream>, T: TransportSocket> TransportSocket
     type Listener = AdaptedListener<S, T>;
     type Stream = S::Stream;
 
-    async fn bind(&self, address: Self::Address) -> Result<Self::Listener> {
+    async fn bind(&self, addresses: Vec<Self::Address>) -> Result<Self::Listener> {
         let stream_adapter = self.stream_adapter.clone();
-        let listener = self.base_transport.bind(address).await?;
+        let listener = self.base_transport.bind(addresses).await?;
         Ok(AdaptedListener::new(stream_adapter, listener))
     }
 
diff --git a/p2p/src/net/mock/transport/impls/tcp.rs b/p2p/src/net/mock/transport/impls/tcp.rs
index e20107966..372b3976e 100644
--- a/p2p/src/net/mock/transport/impls/tcp.rs
+++ b/p2p/src/net/mock/transport/impls/tcp.rs
@@ -16,16 +16,31 @@
 use std::net::{IpAddr, SocketAddr};
 
 use async_trait::async_trait;
+use futures::{stream::FuturesUnordered, StreamExt};
 use tokio::net::{TcpListener, TcpStream};
 
 use crate::{
+    config::DEFAULT_BIND_PORT,
     net::{
-        mock::transport::{PeerStream, TransportListener, TransportSocket},
+        mock::transport::{
+            traits::TransportAddress, PeerStream, TransportListener, TransportSocket,
+        },
         AsBannableAddress,
     },
+    types::peer_address::PeerAddress,
     Result,
 };
 
+impl TransportAddress for SocketAddr {
+    fn as_peer_address(&self) -> PeerAddress {
+        (*self).into()
+    }
+
+    fn from_peer_address(address: &PeerAddress) -> Option<Self> {
+        Some(address.into())
+    }
+}
+
 #[derive(Debug)]
 pub struct TcpTransportSocket;
 
@@ -42,8 +57,8 @@ impl TransportSocket for TcpTransportSocket {
     type Listener = TcpTransportListener;
     type Stream = TcpTransportStream;
 
-    async fn bind(&self, address: Self::Address) -> Result<Self::Listener> {
-        TcpTransportListener::new(address).await
+    async fn bind(&self, addresses: Vec<Self::Address>) -> Result<Self::Listener> {
+        TcpTransportListener::new(addresses)
     }
 
     async fn connect(&self, address: Self::Address) -> Result<Self::Stream> {
@@ -53,26 +68,76 @@ impl TransportSocket for TcpTransportSocket {
 }
 
 pub struct TcpTransportListener {
-    listener: TcpListener,
+    listeners: Vec<TcpListener>,
 }
 
 impl TcpTransportListener {
-    async fn new(address: SocketAddr) -> Result<Self> {
-        let listener = TcpListener::bind(address).await?;
+    fn new(addresses: Vec<SocketAddr>) -> Result<Self> {
+        let addresses = if addresses.is_empty() {
+            vec![
+                SocketAddr::new(std::net::Ipv4Addr::UNSPECIFIED.into(), DEFAULT_BIND_PORT),
+                SocketAddr::new(std::net::Ipv6Addr::UNSPECIFIED.into(), DEFAULT_BIND_PORT),
+            ]
+        } else {
+            addresses
+        };
+
+        let listeners = addresses
+            .into_iter()
+            .map(|address| -> Result<TcpListener> {
+                // Use socket2 crate because we need consistent behavior between platforms.
+                // See https://github.com/tokio-rs/tokio-core/issues/227
+                let socket = socket2::Socket::new(
+                    socket2::Domain::for_address(address),
+                    socket2::Type::STREAM,
+                    None,
+                )?;
+
+                socket.set_nonblocking(true)?;
+
+                if address.is_ipv6() {
+                    // When IPV6_V6ONLY is disabled listening IPv6 socket will also accept incoming connections from IPv4.
+                    // Remote address will be reported as IPv4 mapped to IPv6 (for example ::ffff:192.168.1.2).
+                    // Enable IPV6_V6ONLY explicitly because default value differs between platforms
+                    // (true on windows and false on most other OSs).
+                    // Bitcoin and libp2p work same way.
+                    socket.set_only_v6(true)?;
+                }
+
+                // Allow faster app restarts on *nix (same way it's done in tokio/mio)
+                #[cfg(not(windows))]
+                socket.set_reuse_address(true)?;
+
+                socket.bind(&address.into())?;
+
+                // Set max count of pending TCP connections, we don't need a lot
+                socket.listen(32)?;
+
+                let listener = TcpListener::from_std(socket.into())?;
+
+                Ok(listener)
+            })
+            .collect::<Result<Vec<_>>>()?;
 
-        Ok(Self { listener })
+        Ok(Self { listeners })
     }
 }
 
 #[async_trait]
 impl TransportListener<TcpTransportStream, SocketAddr> for TcpTransportListener {
     async fn accept(&mut self) -> Result<(TcpTransportStream, SocketAddr)> {
-        let (stream, address) = self.listener.accept().await?;
+        let mut tasks: FuturesUnordered<_> =
+            self.listeners.iter().map(|listener| listener.accept()).collect();
+        let (stream, address) = tasks.select_next_some().await?;
         Ok((stream, address))
     }
 
-    fn local_address(&self) -> Result<SocketAddr> {
-        let local_addr = self.listener.local_addr()?;
+    fn local_addresses(&self) -> Result<Vec<SocketAddr>> {
+        let local_addr = self
+            .listeners
+            .iter()
+            .map(|listener| listener.local_addr())
+            .collect::<std::result::Result<Vec<_>, _>>()?;
         Ok(local_addr)
     }
 }
@@ -87,7 +152,6 @@ impl AsBannableAddress for SocketAddr {
 
 pub type TcpTransportStream = TcpStream;
 
-#[async_trait]
 impl PeerStream for TcpTransportStream {}
 
 #[cfg(test)]
@@ -106,8 +170,8 @@ mod tests {
     #[tokio::test]
     async fn send_recv() {
         let transport = TcpTransportSocket::new();
-        let mut server = transport.bind(TestTransportTcp::make_address()).await.unwrap();
-        let peer_fut = transport.connect(server.local_address().unwrap());
+        let mut server = transport.bind(vec![TestTransportTcp::make_address()]).await.unwrap();
+        let peer_fut = transport.connect(server.local_addresses().unwrap()[0]);
 
         let (server_res, peer_res) = tokio::join!(server.accept(), peer_fut);
         let server_stream = server_res.unwrap().0;
@@ -137,8 +201,8 @@ mod tests {
     #[tokio::test]
     async fn send_2_reqs() {
         let transport = TcpTransportSocket::new();
-        let mut server = transport.bind(TestTransportTcp::make_address()).await.unwrap();
-        let peer_fut = transport.connect(server.local_address().unwrap());
+        let mut server = transport.bind(vec![TestTransportTcp::make_address()]).await.unwrap();
+        let peer_fut = transport.connect(server.local_addresses().unwrap()[0]);
 
         let (server_res, peer_res) = tokio::join!(server.accept(), peer_fut);
         let server_stream = server_res.unwrap().0;
diff --git a/p2p/src/net/mock/transport/mod.rs b/p2p/src/net/mock/transport/mod.rs
index 32e8e4f06..dafbf351a 100644
--- a/p2p/src/net/mock/transport/mod.rs
+++ b/p2p/src/net/mock/transport/mod.rs
@@ -27,7 +27,7 @@ pub use self::{
         wrapped_transport::wrapped_socket::WrappedTransportSocket,
     },
     tcp::TcpTransportSocket,
-    traits::{PeerStream, TransportListener, TransportSocket},
+    traits::{PeerStream, TransportAddress, TransportListener, TransportSocket},
 };
 
 pub type NoiseTcpTransport = WrappedTransportSocket<NoiseEncryptionAdapter, TcpTransportSocket>;
diff --git a/p2p/src/net/mock/transport/traits/address.rs b/p2p/src/net/mock/transport/traits/address.rs
new file mode 100644
index 000000000..47d8019a7
--- /dev/null
+++ b/p2p/src/net/mock/transport/traits/address.rs
@@ -0,0 +1,29 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::types::peer_address::PeerAddress;
+
+/// Allow working with abstract socket address types.
+/// For example change socket port or encode for sending on wire.
+/// It's might better to completely replace abstract socket types with PeerAddress.
+pub trait TransportAddress: Sized {
+    /// Convert abstract socket address to concrete type (PeerAddress)
+    fn as_peer_address(&self) -> PeerAddress;
+
+    /// Try get address back from PeerAddress.
+    ///
+    /// This might fail if an address is from some other transport.
+    fn from_peer_address(address: &PeerAddress) -> Option<Self>;
+}
diff --git a/p2p/src/net/mock/transport/traits/listener.rs b/p2p/src/net/mock/transport/traits/listener.rs
index 142beadd0..b7cc70d11 100644
--- a/p2p/src/net/mock/transport/traits/listener.rs
+++ b/p2p/src/net/mock/transport/traits/listener.rs
@@ -25,5 +25,5 @@ pub trait TransportListener<Stream, Address>: Send {
     async fn accept(&mut self) -> Result<(Stream, Address)>;
 
     /// Returns the local address of the listener.
-    fn local_address(&self) -> Result<Address>;
+    fn local_addresses(&self) -> Result<Vec<Address>>;
 }
diff --git a/p2p/src/net/mock/transport/traits/mod.rs b/p2p/src/net/mock/transport/traits/mod.rs
index 03c0c7da2..2e667a779 100644
--- a/p2p/src/net/mock/transport/traits/mod.rs
+++ b/p2p/src/net/mock/transport/traits/mod.rs
@@ -13,10 +13,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+mod address;
 mod listener;
 mod socket;
 mod stream;
 
+pub use address::TransportAddress;
 pub use listener::TransportListener;
 pub use socket::TransportSocket;
 pub use stream::PeerStream;
diff --git a/p2p/src/net/mock/transport/traits/socket.rs b/p2p/src/net/mock/transport/traits/socket.rs
index 1ab2c67fd..f4f70d4ae 100644
--- a/p2p/src/net/mock/transport/traits/socket.rs
+++ b/p2p/src/net/mock/transport/traits/socket.rs
@@ -19,7 +19,7 @@ use async_trait::async_trait;
 
 use crate::{net::AsBannableAddress, Result};
 
-use super::{listener::TransportListener, stream::PeerStream};
+use super::{listener::TransportListener, stream::PeerStream, TransportAddress};
 
 /// An abstraction layer for the transport layer at the highest level, which is responsible for:
 /// 1. Binding to a socket at a specific port, where we listen to connections.
@@ -28,7 +28,8 @@ use super::{listener::TransportListener, stream::PeerStream};
 #[async_trait]
 pub trait TransportSocket: Send + Sync + 'static {
     /// An address type.
-    type Address: Clone
+    type Address: TransportAddress
+        + Clone
         + Debug
         + Eq
         + Hash
@@ -48,7 +49,7 @@ pub trait TransportSocket: Send + Sync + 'static {
     type Stream: PeerStream;
 
     /// Creates a new listener bound to the specified address.
-    async fn bind(&self, address: Self::Address) -> Result<Self::Listener>;
+    async fn bind(&self, address: Vec<Self::Address>) -> Result<Self::Listener>;
 
     /// Open a connection to the given address.
     async fn connect(&self, address: Self::Address) -> Result<Self::Stream>;
diff --git a/p2p/src/net/mock/types.rs b/p2p/src/net/mock/types.rs
index 93fa25876..f5ec51c1c 100644
--- a/p2p/src/net/mock/types.rs
+++ b/p2p/src/net/mock/types.rs
@@ -28,6 +28,7 @@ use serialization::{Decode, Encode};
 use crate::{
     error, message,
     net::{self, mock::transport::TransportSocket, types::PubSubTopic},
+    types::peer_address::PeerAddress,
 };
 
 pub enum Command<T: TransportSocket> {
@@ -96,6 +97,9 @@ pub enum ConnectivityEvent<T: TransportSocket> {
         peer_id: MockPeerId,
         error: error::P2pError,
     },
+    AddressDiscovered {
+        address: T::Address,
+    },
 }
 
 // TODO: use two events, one for txs and one for blocks?
@@ -172,6 +176,7 @@ pub enum PeerEvent {
         network: [u8; 4],
         version: SemVer,
         subscriptions: BTreeSet<PubSubTopic>,
+        receiver_address: Option<PeerAddress>,
     },
 
     /// Connection closed to remote
@@ -188,17 +193,25 @@ pub enum MockEvent {
     SendMessage(Box<Message>),
 }
 
+// TODO: Decide what to do about protocol upgrades.
+// For example adding new address type to PeerAddress might break handshakes with older nodes.
 #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq)]
 pub enum HandshakeMessage {
     Hello {
         version: SemVer,
         network: [u8; 4],
         subscriptions: BTreeSet<PubSubTopic>,
+
+        /// Socket address of the remote peer as seen by this node (addr_you in bitcoin)
+        receiver_address: Option<PeerAddress>,
     },
     HelloAck {
         version: SemVer,
         network: [u8; 4],
         subscriptions: BTreeSet<PubSubTopic>,
+
+        /// Socket address of the remote peer as seen by this node (addr_you in bitcoin)
+        receiver_address: Option<PeerAddress>,
     },
 }
 
diff --git a/p2p/src/net/mod.rs b/p2p/src/net/mod.rs
index 6ce522598..58b776d41 100644
--- a/p2p/src/net/mod.rs
+++ b/p2p/src/net/mod.rs
@@ -79,7 +79,7 @@ pub trait NetworkingService {
     /// `chain_config` - chain config of the node
     async fn start(
         transport: Self::Transport,
-        bind_addr: Self::Address,
+        bind_addresses: Vec<Self::Address>,
         chain_config: Arc<common::chain::ChainConfig>,
         p2p_config: Arc<config::P2pConfig>,
     ) -> crate::Result<(Self::ConnectivityHandle, Self::SyncingMessagingHandle)>;
@@ -111,7 +111,7 @@ where
     /// Return the socket address of the network service provider
     ///
     /// If the address isn't available yet, `None` is returned
-    async fn local_addr(&self) -> crate::Result<Option<T::Address>>;
+    async fn local_addresses(&self) -> crate::Result<Vec<T::Address>>;
 
     /// Poll events from the network service provider
     ///
diff --git a/p2p/src/net/types/mod.rs b/p2p/src/net/types/mod.rs
index b8dd4bb9c..a6d7b7fba 100644
--- a/p2p/src/net/types/mod.rs
+++ b/p2p/src/net/types/mod.rs
@@ -20,17 +20,10 @@ use serialization::{Decode, Encode};
 
 use crate::{message, NetworkingService, P2pError};
 
-/// Discovered peer address information
-#[derive(Debug, PartialEq, Eq)]
-pub struct AddrInfo<T: NetworkingService> {
-    /// Unique ID of the peer
-    pub peer_id: T::PeerId,
-
-    /// List of discovered IPv4 addresses
-    pub ip4: Vec<T::Address>,
-
-    /// List of discovered IPv6 addresses
-    pub ip6: Vec<T::Address>,
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Role {
+    Inbound,
+    Outbound,
 }
 
 // TODO: Introduce and check the maximum allowed peer information size. See
@@ -115,13 +108,13 @@ pub enum ConnectivityEvent<T: NetworkingService> {
     /// One or more peers discovered (libp2p defines discovering as finding new addresses through mDNS or otherwise)
     Discovered {
         /// Address information
-        peers: Vec<AddrInfo<T>>,
+        addresses: Vec<T::Address>,
     },
 
     /// One one more peers have expired (libp2p defines expired addresses as addresses that haven't appeared in later refreshes of available addresses)
     Expired {
         /// Address information
-        peers: Vec<AddrInfo<T>>,
+        addresses: Vec<T::Address>,
     },
 
     /// Protocol violation
diff --git a/p2p/src/peer_manager/helpers.rs b/p2p/src/peer_manager/helpers.rs
deleted file mode 100644
index 5eda9264c..000000000
--- a/p2p/src/peer_manager/helpers.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2022 RBB S.r.l
-// opensource@mintlayer.org
-// SPDX-License-Identifier: MIT
-// Licensed under the MIT License;
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#![allow(clippy::unwrap_used)]
-
-use std::time::Duration;
-
-use tokio::time::timeout;
-
-use crate::net::{
-    types::{ConnectivityEvent, PeerInfo},
-    ConnectivityService, NetworkingService,
-};
-
-/// Can be used in tests only, will panic in case of errors
-pub async fn connect_services<T>(
-    conn1: &mut T::ConnectivityHandle,
-    conn2: &mut T::ConnectivityHandle,
-) -> (T::Address, PeerInfo<T>, PeerInfo<T>)
-where
-    T: NetworkingService + std::fmt::Debug,
-    T::ConnectivityHandle: ConnectivityService<T>,
-{
-    let addr = timeout(Duration::from_secs(5), conn2.local_addr())
-        .await
-        .expect("local address fetch not to timeout")
-        .unwrap()
-        .unwrap();
-    conn1.connect(addr).await.expect("dial to succeed");
-
-    let (address, peer_info1) = match timeout(Duration::from_secs(5), conn2.poll_next()).await {
-        Ok(event) => match event.unwrap() {
-            ConnectivityEvent::InboundAccepted { address, peer_info } => (address, peer_info),
-            event => panic!("expected `InboundAccepted`, got {event:?}"),
-        },
-        Err(_err) => panic!("did not receive `InboundAccepted` in time"),
-    };
-
-    let peer_info2 = match timeout(Duration::from_secs(5), conn1.poll_next()).await {
-        Ok(event) => match event.unwrap() {
-            ConnectivityEvent::OutboundAccepted {
-                address: _,
-                peer_info,
-            } => peer_info,
-            event => panic!("expected `OutboundAccepted`, got {event:?}"),
-        },
-        Err(_err) => panic!("did not receive `OutboundAccepted` in time"),
-    };
-
-    (address, peer_info1, peer_info2)
-}
diff --git a/p2p/src/peer_manager/mod.rs b/p2p/src/peer_manager/mod.rs
index b2707e63d..80b4bc847 100644
--- a/p2p/src/peer_manager/mod.rs
+++ b/p2p/src/peer_manager/mod.rs
@@ -21,10 +21,13 @@
 
 #![allow(rustdoc::private_intra_doc_links)]
 
-pub mod helpers;
 pub mod peerdb;
 
-use std::{collections::HashMap, fmt::Debug, str::FromStr, sync::Arc, time::Duration};
+use std::{
+    collections::HashMap,
+    sync::Arc,
+    time::{Duration, Instant},
+};
 
 use tokio::sync::{mpsc, oneshot};
 
@@ -37,15 +40,16 @@ use crate::{
     config::P2pConfig,
     error::{P2pError, PeerError, ProtocolError},
     event::{PeerManagerEvent, SyncControlEvent},
-    interface::types::ConnectedPeer,
-    net::{self, AsBannableAddress, ConnectivityService, NetworkingService},
+    net::{self, types::Role, AsBannableAddress, ConnectivityService, NetworkingService},
 };
 
 /// Maximum number of connections the [`PeerManager`] is allowed to have open
 const MAX_ACTIVE_CONNECTIONS: usize = 128;
 
 /// Lower bound for how often [`PeerManager::heartbeat()`] is called
-const PEER_MGR_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
+const PEER_MGR_HEARTBEAT_INTERVAL_MIN: Duration = Duration::from_secs(5);
+/// Upper bound for how often [`PeerManager::heartbeat()`] is called
+const PEER_MGR_HEARTBEAT_INTERVAL_MAX: Duration = Duration::from_secs(30);
 
 pub struct PeerManager<T>
 where
@@ -71,14 +75,14 @@ where
 
     /// Peer database
     peerdb: peerdb::PeerDb<T>,
+
+    last_heartbeat: Instant,
 }
 
 impl<T> PeerManager<T>
 where
     T: NetworkingService + 'static,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: FromStr,
-    <<T as NetworkingService>::Address as FromStr>::Err: Debug,
 {
     pub fn new(
         chain_config: Arc<ChainConfig>,
@@ -86,28 +90,31 @@ where
         handle: T::ConnectivityHandle,
         rx_peer_manager: mpsc::UnboundedReceiver<PeerManagerEvent<T>>,
         tx_sync: mpsc::UnboundedSender<SyncControlEvent<T>>,
-    ) -> Self {
-        Self {
+    ) -> crate::Result<Self> {
+        Ok(Self {
             peer_connectivity_handle: handle,
             rx_peer_manager,
             tx_sync,
-            peerdb: peerdb::PeerDb::new(Arc::clone(&p2p_config)),
+            peerdb: peerdb::PeerDb::new(Arc::clone(&p2p_config))?,
             pending: HashMap::new(),
             chain_config,
             _p2p_config: p2p_config,
-        }
+            last_heartbeat: Instant::now(),
+        })
     }
 
     /// Update the list of known peers or known peer's list of addresses
-    fn peer_discovered(&mut self, peers: &[net::types::AddrInfo<T>]) {
-        peers.iter().for_each(|peer| {
+    fn peer_discovered(&mut self, addresses: &[T::Address]) {
+        addresses.iter().for_each(|peer| {
             self.peerdb.peer_discovered(peer);
         })
     }
 
     /// Update the list of known peers or known peer's list of addresses
-    fn peer_expired(&mut self, peers: &[net::types::AddrInfo<T>]) {
-        self.peerdb.expire_peers(peers)
+    fn peer_expired(&mut self, addresses: &[T::Address]) {
+        addresses.iter().for_each(|peer| {
+            self.peerdb.peer_expired(peer);
+        })
     }
 
     /// Verify software version compatibility
@@ -126,10 +133,10 @@ where
     fn accept_connection(
         &mut self,
         address: T::Address,
+        role: Role,
         info: net::types::PeerInfo<T>,
     ) -> crate::Result<()> {
         let peer_id = info.peer_id;
-        log::debug!("peer {peer_id} connected, address {address:?}, {info}");
 
         ensure!(
             info.magic_bytes == *self.chain_config.magic_bytes(),
@@ -149,8 +156,13 @@ where
             !self.peerdb.is_active_peer(&info.peer_id),
             P2pError::PeerError(PeerError::PeerAlreadyExists),
         );
+        ensure!(
+            !self.peerdb.is_address_connected(&address),
+            P2pError::PeerError(PeerError::PeerAlreadyExists),
+        );
 
-        self.peerdb.peer_connected(address, info);
+        log::debug!("peer {peer_id} connected, address {address:?}, {info}");
+        self.peerdb.peer_connected(address, role, info);
         self.tx_sync.send(SyncControlEvent::Connected(peer_id)).map_err(P2pError::from)
     }
 
@@ -186,11 +198,10 @@ where
         // knows of all peers and later on if the number of connections falls below
         // the desired threshold, `PeerManager::heartbeat()` may connect to this peer.
         if self.peerdb.active_peer_count() >= MAX_ACTIVE_CONNECTIONS {
-            self.peerdb.register_peer_info(address, info);
             return Err(P2pError::PeerError(PeerError::TooManyPeers));
         }
 
-        self.accept_connection(address, info)
+        self.accept_connection(address, Role::Inbound, info)
     }
 
     /// Close connection to a remote peer
@@ -199,10 +210,14 @@ where
     /// or by the [`PeerManager::heartbeat()`] function which has decided to cull
     /// this connection in favor of another potential connection.
     fn close_connection(&mut self, peer_id: T::PeerId) -> crate::Result<()> {
-        log::debug!("connection closed for peer {peer_id}");
+        // Mock backend is always sending ConnectionClosed event when somebody disconnects, ensure that the peer is active
+        if self.peerdb.is_active_peer(&peer_id) {
+            log::debug!("connection closed for peer {peer_id}");
+
+            self.tx_sync.send(SyncControlEvent::Disconnected(peer_id))?;
 
-        self.tx_sync.send(SyncControlEvent::Disconnected(peer_id))?;
-        self.peerdb.peer_disconnected(&peer_id);
+            self.peerdb.peer_disconnected(&peer_id);
+        }
         Ok(())
     }
 
@@ -281,17 +296,15 @@ where
     /// establish new connections. After that it updates the peer scores and discards any records
     /// that no longer need to be stored.
     async fn heartbeat(&mut self) -> crate::Result<()> {
-        // TODO: check when was the last update and exit early if this update is to soon
-
         let npeers = std::cmp::min(
-            self.peerdb.idle_peer_count(),
+            self.peerdb.available_addresses_count(),
             MAX_ACTIVE_CONNECTIONS
-                .saturating_sub(self.peerdb.idle_peer_count())
+                .saturating_sub(self.peerdb.available_addresses_count())
                 .saturating_sub(self.pending.len()),
         );
 
         for _ in 0..npeers {
-            if let Some(addr) = self.peerdb.take_best_peer_addr()? {
+            if let Some(addr) = self.peerdb.take_best_peer_addr() {
                 match self.connect(addr.clone()).await {
                     Ok(_) => {
                         self.pending.insert(addr, None);
@@ -391,20 +404,13 @@ where
                     PeerManagerEvent::GetPeerCount(response) => {
                         response.send(self.peerdb.active_peer_count()).map_err(|_| P2pError::ChannelClosed)?;
                     }
-                    PeerManagerEvent::GetBindAddress(response) => {
-                        let addr = self.peer_connectivity_handle.local_addr();
-                        // TODO: change the return to Option<String> and avoid using special values for None
-                        let addr = addr.await?.map_or("<unavailable>".to_string(), |addr| addr.to_string());
+                    PeerManagerEvent::GetBindAddresses(response) => {
+                        let addr = self.peer_connectivity_handle.local_addresses();
+                        let addr = addr.await.unwrap_or_default().into_iter().map(|addr| addr.to_string()).collect();
                         response.send(addr).map_err(|_| P2pError::ChannelClosed)?;
                     }
                     PeerManagerEvent::GetConnectedPeers(response) => {
-                        let peers = self.peerdb
-                            .active_peers()
-                            .iter()
-                            .filter_map(|(peer_id, info)| info.address.as_ref().map(|addr| {
-                                ConnectedPeer { addr: addr.to_string(), peer_id: peer_id.to_string() }
-                            }))
-                            .collect::<Vec<_>>();
+                        let peers = self.peerdb.get_connected_peers();
                         response.send(peers).map_err(|_| P2pError::ChannelClosed)?
                     }
                 },
@@ -431,7 +437,7 @@ where
                         }
                         net::types::ConnectivityEvent::OutboundAccepted { address, peer_info } => {
                             let peer_id = peer_info.peer_id;
-                            let res = self.accept_connection(address.clone(), peer_info);
+                            let res = self.accept_connection(address.clone(), Role::Outbound, peer_info);
                             self.handle_result(Some(peer_id), res).await?;
 
                             match self.pending.remove(&address) {
@@ -448,11 +454,11 @@ where
                             let res = self.handle_outbound_error(address, error);
                             self.handle_result(None, res).await?;
                         }
-                        net::types::ConnectivityEvent::Discovered { peers } => {
-                            self.peer_discovered(&peers);
+                        net::types::ConnectivityEvent::Discovered { addresses } => {
+                            self.peer_discovered(&addresses);
                         }
-                        net::types::ConnectivityEvent::Expired { peers } => {
-                            self.peer_expired(&peers);
+                        net::types::ConnectivityEvent::Expired { addresses } => {
+                            self.peer_expired(&addresses);
                         }
                         net::types::ConnectivityEvent::Misbehaved { peer_id, error } => {
                             let res = self.adjust_peer_score(peer_id, error.ban_score()).await;
@@ -464,11 +470,15 @@ where
                         self.handle_result(None, Err(err)).await?;
                     }
                 },
-                _event = tokio::time::sleep(PEER_MGR_HEARTBEAT_INTERVAL) => {}
+                _event = tokio::time::sleep(PEER_MGR_HEARTBEAT_INTERVAL_MAX) => {}
             }
 
             // finally update peer manager state
-            self.heartbeat().await?;
+            let now = Instant::now();
+            if now.duration_since(self.last_heartbeat) > PEER_MGR_HEARTBEAT_INTERVAL_MIN {
+                self.heartbeat().await?;
+                self.last_heartbeat = now;
+            }
         }
     }
 }
diff --git a/p2p/src/peer_manager/peerdb.rs b/p2p/src/peer_manager/peerdb.rs
deleted file mode 100644
index c649e6ab5..000000000
--- a/p2p/src/peer_manager/peerdb.rs
+++ /dev/null
@@ -1,449 +0,0 @@
-// Copyright (c) 2022 RBB S.r.l
-// opensource@mintlayer.org
-// SPDX-License-Identifier: MIT
-// Licensed under the MIT License;
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Peer database
-//!
-//! The peer database recognizes three peer types:
-//! - active peers
-//! - idle peers
-//! - reserved peers (not implemented)
-//!
-//! Active peers are those peers that the [`crate::peer_manager::PeerManager`] has an active
-//! connection with whereas idle peers are the peers that are known to `PeerDb` but are not
-//! connected. Idle peers are discovered through various peer discovery mechanisms and they are
-//! used by [`crate::peer_manager::PeerManager::heartbeat()`] to establish new outbound connections
-//! if the actual number of active connection is less than the desired number of connections.
-//!
-//! TODO: reserved peers
-
-use std::{
-    collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque},
-    sync::Arc,
-    time::{Duration, SystemTime, UNIX_EPOCH},
-};
-
-use logging::log;
-
-use crate::{
-    config,
-    error::P2pError,
-    net::{types, AsBannableAddress, NetworkingService},
-};
-
-#[derive(Debug)]
-pub struct PeerContext<T: NetworkingService> {
-    /// Peer information
-    pub info: types::PeerInfo<T>,
-
-    /// Peer's active address, if known
-    pub address: Option<T::Address>,
-
-    /// Set of available addresses
-    pub addresses: HashSet<T::Address>,
-
-    /// Peer score
-    pub score: u32,
-}
-
-#[derive(Debug)]
-pub enum BannedPeer<T: NetworkingService> {
-    Known(PeerContext<T>),
-    Discovered(VecDeque<T::Address>),
-    Unknown,
-}
-
-impl<T: NetworkingService> BannedPeer<T> {
-    pub fn address(&self) -> Option<&T::Address> {
-        match self {
-            BannedPeer::Known(c) => c.address.as_ref(),
-            BannedPeer::Discovered(_) | BannedPeer::Unknown => None,
-        }
-    }
-}
-
-#[derive(Debug)]
-pub enum Peer<T: NetworkingService> {
-    /// Active peer
-    Active(PeerContext<T>),
-
-    /// Idle peer (`types::PeerInfo<t>` received)
-    Idle(PeerContext<T>),
-
-    /// Active/known peer that has been banned
-    Banned(BannedPeer<T>),
-
-    /// Discovered peer (addresses have been received)
-    Discovered(VecDeque<T::Address>),
-}
-
-impl<T: NetworkingService> Peer<T> {
-    pub fn address(&self) -> Option<&T::Address> {
-        match self {
-            Peer::Active(c) => c.address.as_ref(),
-            Peer::Idle(c) => c.address.as_ref(),
-            Peer::Banned(b) => b.address(),
-            Peer::Discovered(_) => None,
-        }
-    }
-}
-
-// TODO: store available peers into a binary heap
-// TODO: find a way to persist this data in some database for when the node is restarted
-// (data of banned, discovered, and at-least-once used should be stored)
-pub struct PeerDb<T: NetworkingService> {
-    /// P2P configuration
-    p2p_config: Arc<config::P2pConfig>,
-
-    /// Set of peers known to `PeerDb`
-    peers: HashMap<T::PeerId, Peer<T>>,
-
-    /// Set of available (idle) peers
-    available: HashSet<T::PeerId>,
-
-    /// Pending connections
-    pending: HashMap<T::Address, T::PeerId>,
-
-    /// Banned addresses along with the duration of the ban.
-    ///
-    /// The duration represents the `UNIX_EPOCH + duration` time point, so the ban should end
-    /// when `current_time > ban_duration`.
-    banned: BTreeMap<T::BannableAddress, Duration>,
-}
-
-impl<T: NetworkingService> PeerDb<T> {
-    pub fn new(p2p_config: Arc<config::P2pConfig>) -> Self {
-        Self {
-            peers: Default::default(),
-            available: Default::default(),
-            pending: Default::default(),
-            banned: Default::default(),
-            p2p_config,
-        }
-    }
-
-    /// Get the number of idle (available) peers
-    pub fn idle_peer_count(&self) -> usize {
-        self.available.len()
-    }
-
-    /// Get the number of active peers
-    pub fn active_peer_count(&self) -> usize {
-        self.peers
-            .len()
-            .checked_sub(self.pending.len())
-            .and_then(|acc| acc.checked_sub(self.available.len()))
-            .and_then(|acc| acc.checked_sub(self.banned.len()))
-            .expect("`PeerDb` state to be consistent")
-    }
-
-    pub fn active_peers(&self) -> Vec<(&T::PeerId, &PeerContext<T>)> {
-        self.peers
-            .iter()
-            .filter_map(|(id, info)| match info {
-                Peer::Active(inner) => Some((id, inner)),
-                Peer::Idle(_) | Peer::Banned(_) | Peer::Discovered(_) => None,
-            })
-            .collect::<Vec<_>>()
-    }
-
-    /// Get reference to the peer store
-    pub fn peers(&mut self) -> &HashMap<T::PeerId, Peer<T>> {
-        &self.peers
-    }
-
-    /// Get reference to the available peer store
-    pub fn available(&mut self) -> &HashSet<T::PeerId> {
-        &self.available
-    }
-
-    /// Get reference to the pending peer store
-    pub fn pending(&mut self) -> &HashMap<T::Address, T::PeerId> {
-        &self.pending
-    }
-
-    /// Checks if the given address is banned.
-    pub fn is_address_banned(&mut self, address: &T::BannableAddress) -> bool {
-        if let Some(banned_till) = self.banned.get(address) {
-            // Check if the ban has expired.
-            let now = SystemTime::now()
-                .duration_since(UNIX_EPOCH)
-                // This can fail only if `SystemTime::now()` returns the time before `UNIX_EPOCH`.
-                .expect("Invalid system time");
-            if now > *banned_till {
-                self.banned.remove(address);
-            } else {
-                return true;
-            }
-        }
-
-        false
-    }
-
-    /// Checks if the peer is active.
-    pub fn is_active_peer(&self, peer_id: &T::PeerId) -> bool {
-        std::matches!(self.peers.get(peer_id), Some(Peer::Active(_)))
-    }
-
-    /// Get socket address of the next best peer (TODO: in terms of peer score)
-    // TODO: rewrite all of this
-    pub fn take_best_peer_addr(&mut self) -> crate::Result<Option<T::Address>> {
-        // TODO: improve peer selection
-        let peer_id = match self.available.iter().next() {
-            Some(peer_id) => *peer_id,
-            None => return Ok(None),
-        };
-
-        match self.peers.get_mut(&peer_id) {
-            Some(Peer::Idle(_info) | Peer::Active(_info)) => {
-                // TODO: implement
-                Ok(None)
-            }
-            Some(Peer::Banned(_info)) => {
-                // TODO: implement
-                Ok(None)
-            }
-            Some(Peer::Discovered(addr_info)) => addr_info.pop_front().map_or(Ok(None), |addr| {
-                self.available.remove(&peer_id);
-                self.pending.insert(addr.clone(), peer_id);
-                Ok(Some(addr))
-            }),
-            None => Err(P2pError::DatabaseFailure),
-        }
-    }
-
-    /// Discover new peer addresses
-    pub fn peer_discovered(&mut self, info: &types::AddrInfo<T>) {
-        match self.peers.entry(info.peer_id) {
-            Entry::Occupied(mut entry) => match entry.get_mut() {
-                Peer::Discovered(addr_info) => {
-                    // TODO: duplicates
-                    addr_info.extend(info.ip6.clone());
-                    addr_info.extend(info.ip4.clone());
-                }
-                Peer::Idle(_info) | Peer::Active(_info) => {
-                    // TODO: update existing information of a known peer
-                }
-                Peer::Banned(_info) => {
-                    // TODO: update existing information of a known peer
-                }
-            },
-            Entry::Vacant(entry) => {
-                entry.insert(Peer::Discovered(VecDeque::from_iter(
-                    info.ip6.iter().cloned().chain(info.ip4.iter().cloned()).collect::<Vec<_>>(),
-                )));
-                self.available.insert(info.peer_id);
-            }
-        }
-    }
-
-    /// Expire discovered peer addresses
-    pub fn expire_peers(&mut self, _peers: &[types::AddrInfo<T>]) {
-        // TODO: implement
-    }
-
-    /// Report outbound connection failure
-    ///
-    /// When [`crate::peer_manager::PeerManager::heartbeat()`] has initiated an outbound connection
-    /// and the connection is refused, it's reported back to the `PeerDb` so it knows to update
-    /// the peer information accordingly by forgetting the address and adjusting the peer score
-    /// appropriately.
-    pub fn report_outbound_failure(&mut self, _address: T::Address) {
-        // TODO: implement
-    }
-
-    /// Register peer information to `PeerDb`
-    ///
-    /// If the peer is known (either fully known or only discovered), its information
-    /// is updated with the new received information. If the peer is unknown to `PeerDb`
-    /// (new inbound connection from an unknown peer), a new entry is created for the peer.
-    ///
-    /// Finally the peer is marked as available so future connection attempts may try to
-    /// dial this peer from the last known address.
-    pub fn register_peer_info(&mut self, address: T::Address, info: types::PeerInfo<T>) {
-        let peer_id = info.peer_id;
-
-        let entry = match self.peers.remove(&peer_id) {
-            Some(Peer::Discovered(addr_info)) => {
-                self.available.insert(peer_id);
-                Peer::Idle(PeerContext {
-                    info,
-                    score: 0,
-                    address: Some(address.clone()),
-                    addresses: HashSet::from_iter(addr_info),
-                })
-            }
-            Some(Peer::Idle(peer_info)) => {
-                self.available.insert(peer_id);
-                Peer::Idle(PeerContext {
-                    info,
-                    score: peer_info.score,
-                    address: Some(address.clone()),
-                    addresses: peer_info.addresses,
-                })
-            }
-            None => {
-                self.available.insert(peer_id);
-                Peer::Idle(PeerContext {
-                    info,
-                    score: 0,
-                    address: Some(address.clone()),
-                    addresses: HashSet::new(),
-                })
-            }
-            Some(entry @ Peer::Active(_)) => entry,
-            Some(entry @ Peer::Banned(_)) => entry,
-        };
-
-        self.peers.insert(peer_id, entry);
-        self.pending.remove(&address);
-    }
-
-    /// Mark peer as connected
-    ///
-    /// After `PeerManager` has established either an inbound or an outbound connection,
-    /// it informs the `PeerDb` about it which updates the peer information it has and
-    /// marks the peer as unavailable for future dial attempts.
-    pub fn peer_connected(&mut self, address: T::Address, info: types::PeerInfo<T>) {
-        let peer_id = info.peer_id;
-
-        let entry = match self.peers.remove(&peer_id) {
-            Some(Peer::Discovered(addr_info)) => Peer::Active(PeerContext {
-                info,
-                score: 0,
-                address: Some(address.clone()),
-                addresses: HashSet::from_iter(addr_info),
-            }),
-            Some(Peer::Idle(peer_info)) => Peer::Active(PeerContext {
-                info,
-                score: peer_info.score,
-                address: Some(address.clone()),
-                addresses: peer_info.addresses,
-            }),
-            None => Peer::Active(PeerContext {
-                info,
-                score: 0,
-                address: Some(address.clone()),
-                addresses: HashSet::new(),
-            }),
-            Some(entry @ Peer::Active(_)) => entry,
-            Some(entry @ Peer::Banned(_)) => entry,
-        };
-
-        self.peers.insert(peer_id, entry);
-        self.available.remove(&peer_id);
-        self.pending.remove(&address);
-    }
-
-    /// Handle peer disconnection event
-    ///
-    /// Close the connection to an active peer and change the peer state
-    /// the `Peer::Idle` so it can be used again for connection later.
-    pub fn peer_disconnected(&mut self, peer_id: &T::PeerId) {
-        if let Some(entry) = self.peers.remove(peer_id) {
-            let entry = match entry {
-                Peer::Active(inner) => {
-                    self.available.insert(*peer_id);
-                    Peer::Idle(inner)
-                }
-                Peer::Discovered(inner) => Peer::Discovered(inner),
-                Peer::Idle(inner) => Peer::Idle(inner),
-                Peer::Banned(inner) => Peer::Banned(inner),
-            };
-
-            self.peers.insert(*peer_id, entry);
-        }
-    }
-
-    /// Changes the peer state to `Peer::Banned` and bans it for 24 hours.
-    pub fn ban_peer(&mut self, peer_id: &T::PeerId) {
-        if let Some(entry) = self.peers.remove(peer_id) {
-            let entry = match entry {
-                Peer::Active(inner) | Peer::Idle(inner) => {
-                    log::info!(
-                        "ban known/idle peer {}, peer address {:?}",
-                        peer_id,
-                        inner.address
-                    );
-                    Peer::Banned(BannedPeer::Known(inner))
-                }
-                Peer::Discovered(inner) => {
-                    log::info!(
-                        "ban discovered peer {}, peer addresses {:?}",
-                        peer_id,
-                        inner
-                    );
-                    Peer::Banned(BannedPeer::Discovered(inner))
-                }
-                Peer::Banned(inner) => Peer::Banned(inner),
-            };
-
-            self.peers.insert(*peer_id, entry);
-        }
-
-        self.available.remove(peer_id);
-
-        if let Some(address) =
-            self.peers.get(peer_id).and_then(|p| p.address()).map(|a| a.as_bannable())
-        {
-            let ban_till = SystemTime::now()
-                .duration_since(UNIX_EPOCH)
-                // This can fail only if `SystemTime::now()` returns the time before `UNIX_EPOCH`.
-                .expect("Invalid system time")
-                + *self.p2p_config.ban_duration;
-            self.banned.insert(address, ban_till);
-        } else {
-            log::error!("Failed to get address for peer {}", peer_id);
-        }
-    }
-
-    /// Adjust peer score
-    ///
-    /// If the peer is known, update its existing peer score and if it is not
-    /// known (either at all or fully), just use `score` to make the decision whether
-    /// to ban the peer or not.
-    ///
-    /// If peer is banned, it is still kept in the peer storage but it is removed
-    /// from the `available` storage so it won't be picked up again and its peer ID
-    /// is recorded into the `banned` storage which keeps track of all banned peers.
-    pub fn adjust_peer_score(&mut self, peer_id: &T::PeerId, score: u32) -> bool {
-        let final_score = match self.peers.entry(*peer_id) {
-            Entry::Vacant(entry) => {
-                entry.insert(Peer::Banned(BannedPeer::Unknown));
-                score
-            }
-            Entry::Occupied(mut entry) => match entry.get_mut() {
-                Peer::Discovered(_) => score,
-                Peer::Banned(inner) => match inner {
-                    BannedPeer::Known(info) => {
-                        info.score = info.score.saturating_add(score);
-                        info.score
-                    }
-                    BannedPeer::Discovered(_) | BannedPeer::Unknown => score,
-                },
-                Peer::Idle(info) | Peer::Active(info) => {
-                    info.score = info.score.saturating_add(score);
-                    info.score
-                }
-            },
-        };
-
-        if final_score >= *self.p2p_config.ban_threshold {
-            self.ban_peer(peer_id);
-            return true;
-        }
-
-        false
-    }
-}
diff --git a/p2p/src/peer_manager/peerdb/mod.rs b/p2p/src/peer_manager/peerdb/mod.rs
new file mode 100644
index 000000000..056434cfc
--- /dev/null
+++ b/p2p/src/peer_manager/peerdb/mod.rs
@@ -0,0 +1,270 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Peer database
+//!
+//! The peer database stores:
+//! - connected peers
+//! - available (discovered) addresses
+//! - banned addresses
+//!
+//! Connected peers are those peers that the [`crate::peer_manager::PeerManager`] has an active
+//! connection with. Available addresses are discovered through various peer discovery mechanisms and they are
+//! used by [`crate::peer_manager::PeerManager::heartbeat()`] to establish new outbound connections
+//! if the actual number of active connection is less than the desired number of connections.
+
+use std::{
+    collections::{BTreeMap, HashMap, HashSet},
+    sync::Arc,
+    time::{Duration, SystemTime, UNIX_EPOCH},
+};
+
+use logging::log;
+
+use crate::{
+    config,
+    error::{ConversionError, P2pError},
+    interface::types::ConnectedPeer,
+    net::{
+        types::{self, Role},
+        AsBannableAddress, NetworkingService,
+    },
+};
+
+#[derive(Debug)]
+pub struct PeerContext<T: NetworkingService> {
+    /// Peer information
+    pub info: types::PeerInfo<T>,
+
+    /// Peer's address
+    pub address: T::Address,
+
+    /// Peer's role (inbound or outbound)
+    pub role: Role,
+
+    /// Peer score
+    pub score: u32,
+}
+
+impl<T: NetworkingService> From<&PeerContext<T>> for ConnectedPeer {
+    fn from(context: &PeerContext<T>) -> Self {
+        ConnectedPeer {
+            peer_id: context.info.peer_id.to_string(),
+            address: context.address.to_string(),
+            inbound: context.role == Role::Inbound,
+            ban_score: context.score,
+        }
+    }
+}
+
+// TODO: Store available addresses in a binary heap (sorting by their availability).
+// TODO: Find a way to persist this data in some database for when the node is restarted
+// (banned, available, and at-least-once used should be restored)
+pub struct PeerDb<T: NetworkingService> {
+    /// P2P configuration
+    p2p_config: Arc<config::P2pConfig>,
+
+    /// Set of active peers
+    peers: HashMap<T::PeerId, PeerContext<T>>,
+
+    addresses: HashSet<T::Address>,
+
+    /// Set of available addresses
+    available: HashSet<T::Address>,
+
+    /// Banned addresses along with the duration of the ban.
+    ///
+    /// The duration represents the `UNIX_EPOCH + duration` time point, so the ban should end
+    /// when `current_time > ban_duration`.
+    banned: BTreeMap<T::BannableAddress, Duration>,
+}
+
+impl<T: NetworkingService> PeerDb<T> {
+    pub fn new(p2p_config: Arc<config::P2pConfig>) -> crate::Result<Self> {
+        let added_nodes = p2p_config
+            .added_nodes
+            .iter()
+            .map(|addr| {
+                addr.parse::<T::Address>().map_err(|_err| {
+                    P2pError::ConversionError(ConversionError::InvalidAddress(addr.clone()))
+                })
+            })
+            .collect::<Result<HashSet<_>, _>>()?;
+        Ok(Self {
+            peers: Default::default(),
+            addresses: Default::default(),
+            // TODO: We need to handle added nodes differently from ordinary nodes.
+            // There are peers that we want to persistently have, and others that we want to just give a "shot" at connecting at.
+            available: added_nodes,
+            banned: Default::default(),
+            p2p_config,
+        })
+    }
+
+    /// Get the number of idle (available) addresses
+    pub fn available_addresses_count(&self) -> usize {
+        self.available.len()
+    }
+
+    /// Get the number of active peers
+    pub fn active_peer_count(&self) -> usize {
+        self.peers.len()
+    }
+
+    pub fn get_connected_peers(&self) -> Vec<ConnectedPeer> {
+        self.peers.values().map(Into::into).collect()
+    }
+
+    /// Checks if the given address is already connected.
+    pub fn is_address_connected(&self, address: &T::Address) -> bool {
+        self.addresses.contains(address)
+    }
+
+    /// Checks if the given address is banned.
+    pub fn is_address_banned(&mut self, address: &T::BannableAddress) -> bool {
+        if let Some(banned_till) = self.banned.get(address) {
+            // Check if the ban has expired.
+            let now = SystemTime::now()
+                .duration_since(UNIX_EPOCH)
+                // This can fail only if `SystemTime::now()` returns the time before `UNIX_EPOCH`.
+                .expect("Invalid system time");
+            if now > *banned_till {
+                self.banned.remove(address);
+            } else {
+                return true;
+            }
+        }
+
+        false
+    }
+
+    /// Checks if the peer is active
+    pub fn is_active_peer(&self, peer_id: &T::PeerId) -> bool {
+        self.peers.get(peer_id).is_some()
+    }
+
+    /// Get socket address of the next best peer (TODO: in terms of peer score).
+    // TODO: Rewrite this.
+    pub fn take_best_peer_addr(&mut self) -> Option<T::Address> {
+        let address = self.available.iter().next().cloned();
+        if let Some(address) = &address {
+            self.available.remove(address);
+        }
+        address
+    }
+
+    /// Add new peer addresses
+    pub fn peer_discovered(&mut self, address: &T::Address) {
+        self.available.insert(address.clone());
+    }
+
+    /// Expire discovered peer addresses
+    pub fn peer_expired(&mut self, address: &T::Address) {
+        self.available.remove(address);
+    }
+
+    /// Report outbound connection failure
+    ///
+    /// When [`crate::peer_manager::PeerManager::heartbeat()`] has initiated an outbound connection
+    /// and the connection is refused, it's reported back to the `PeerDb` so it marks the address as unreachable.
+    pub fn report_outbound_failure(&mut self, _address: T::Address) {
+        // TODO: implement
+    }
+
+    /// Mark peer as connected
+    ///
+    /// After `PeerManager` has established either an inbound or an outbound connection,
+    /// it informs the `PeerDb` about it.
+    pub fn peer_connected(&mut self, address: T::Address, role: Role, info: types::PeerInfo<T>) {
+        log::info!(
+            "peer connected, peer_id: {}, address: {address:?}, {:?}",
+            info.peer_id,
+            role
+        );
+
+        let old_value = self.peers.insert(
+            info.peer_id,
+            PeerContext {
+                info,
+                address: address.clone(),
+                role,
+                score: 0,
+            },
+        );
+        assert!(old_value.is_none());
+
+        let old_value = self.addresses.insert(address);
+        assert!(old_value);
+    }
+
+    /// Handle peer disconnection event
+    ///
+    /// Close the connection to an active peer.
+    pub fn peer_disconnected(&mut self, peer_id: &T::PeerId) {
+        let removed = self.peers.remove(peer_id);
+        let peer = removed.expect("peer must be known");
+
+        let removed = self.addresses.remove(&peer.address);
+        assert!(removed);
+
+        log::info!(
+            "peer disconnected, peer_id: {}, address: {:?}",
+            peer.info.peer_id,
+            peer.address
+        );
+    }
+
+    /// Changes the peer state to `Peer::Banned` and bans it for 24 hours.
+    fn ban_peer(&mut self, peer_id: &T::PeerId) {
+        if let Some(peer) = self.peers.remove(peer_id) {
+            let bannable_address = peer.address.as_bannable();
+            let ban_till = SystemTime::now()
+                .duration_since(UNIX_EPOCH)
+                // This can fail only if `SystemTime::now()` returns the time before `UNIX_EPOCH`.
+                .expect("Invalid system time")
+                + *self.p2p_config.ban_duration;
+            self.banned.insert(bannable_address, ban_till);
+        } else {
+            log::error!("Failed to get address for peer {}", peer_id);
+        }
+    }
+
+    /// Adjust peer score
+    ///
+    /// If the peer is known, update its existing peer score and report
+    /// if it should be disconnected when score reached the threshold.
+    /// Unknown peers are reported as to be disconnected.
+    ///
+    /// If peer is banned, it is removed from the connected peers
+    /// and its address is marked as banned.
+    pub fn adjust_peer_score(&mut self, peer_id: &T::PeerId, score: u32) -> bool {
+        let peer = match self.peers.get_mut(peer_id) {
+            Some(peer) => peer,
+            None => return true,
+        };
+
+        peer.score = peer.score.saturating_add(score);
+
+        if peer.score >= *self.p2p_config.ban_threshold {
+            self.ban_peer(peer_id);
+            return true;
+        }
+
+        false
+    }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/p2p/src/rpc.rs b/p2p/src/rpc.rs
index 14a03725e..03ce7c9e3 100644
--- a/p2p/src/rpc.rs
+++ b/p2p/src/rpc.rs
@@ -31,8 +31,8 @@ trait P2pRpc {
     async fn get_peer_count(&self) -> rpc::Result<usize>;
 
     /// Get bind address of the local node
-    #[method(name = "get_bind_address")]
-    async fn get_bind_address(&self) -> rpc::Result<String>;
+    #[method(name = "get_bind_addresses")]
+    async fn get_bind_addresses(&self) -> rpc::Result<Vec<String>>;
 
     /// Get details of connected peers
     #[method(name = "get_connected_peers")]
@@ -56,8 +56,8 @@ impl P2pRpcServer for super::P2pHandle {
         handle_error(res)
     }
 
-    async fn get_bind_address(&self) -> rpc::Result<String> {
-        let res = self.call_async(|this| Box::pin(this.get_bind_address())).await;
+    async fn get_bind_addresses(&self) -> rpc::Result<Vec<String>> {
+        let res = self.call_async(|this| Box::pin(this.get_bind_addresses())).await;
         handle_error(res)
     }
 
diff --git a/p2p/src/types/mod.rs b/p2p/src/types/mod.rs
new file mode 100644
index 000000000..5587b28b7
--- /dev/null
+++ b/p2p/src/types/mod.rs
@@ -0,0 +1,16 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod peer_address;
diff --git a/p2p/src/types/peer_address.rs b/p2p/src/types/peer_address.rs
new file mode 100644
index 000000000..2e82b7e82
--- /dev/null
+++ b/p2p/src/types/peer_address.rs
@@ -0,0 +1,82 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fmt::Display;
+
+use common::primitives::ip_address::{Ip4, Ip6};
+use serialization::{Decode, Encode};
+
+#[derive(Debug, Encode, Decode, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct PeerAddressIp4 {
+    pub ip: Ip4,
+    pub port: u16,
+}
+
+#[derive(Debug, Encode, Decode, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
+pub struct PeerAddressIp6 {
+    pub ip: Ip6,
+    pub port: u16,
+}
+
+/// Type used to serialize information about peer address.
+///
+/// Same as std::net::SocketAddr for now but can be later extended with other address types.
+/// Use custom type to be able implement Encode and Decode.
+#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq)]
+pub enum PeerAddress {
+    #[codec(index = 0)]
+    Ip4(PeerAddressIp4),
+    #[codec(index = 1)]
+    Ip6(PeerAddressIp6),
+}
+
+impl Display for PeerAddress {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let self_copy: std::net::SocketAddr = self.into();
+        std::fmt::Display::fmt(&self_copy, f)
+    }
+}
+
+impl From<std::net::SocketAddr> for PeerAddress {
+    fn from(address: std::net::SocketAddr) -> Self {
+        match address {
+            std::net::SocketAddr::V4(ip) => PeerAddress::Ip4(PeerAddressIp4 {
+                ip: (*ip.ip()).into(),
+                port: address.port(),
+            }),
+            std::net::SocketAddr::V6(ip) => PeerAddress::Ip6(PeerAddressIp6 {
+                ip: (*ip.ip()).into(),
+                port: address.port(),
+            }),
+        }
+    }
+}
+
+impl From<&PeerAddress> for std::net::SocketAddr {
+    fn from(address: &PeerAddress) -> Self {
+        match address {
+            PeerAddress::Ip4(socket4) => std::net::SocketAddr::V4(std::net::SocketAddrV4::new(
+                socket4.ip.into(),
+                socket4.port,
+            )),
+            PeerAddress::Ip6(socket6) => std::net::SocketAddr::V6(std::net::SocketAddrV6::new(
+                socket6.ip.into(),
+                socket6.port,
+                0,
+                0,
+            )),
+        }
+    }
+}


Test patch:
diff --git a/node/tests/cli.rs b/node/tests/cli.rs
index 15d2581b2..b845253c5 100644
--- a/node/tests/cli.rs
+++ b/node/tests/cli.rs
@@ -60,7 +60,7 @@ fn create_default_config() {
     );
     assert_eq!(config.chainstate.chainstate_config.max_orphan_blocks, None);
 
-    assert_eq!(config.p2p.bind_address, None);
+    assert!(config.p2p.bind_addresses.unwrap_or_default().is_empty());
     assert_eq!(config.p2p.ban_threshold, None);
     assert_eq!(config.p2p.outbound_connection_timeout, None);
 
@@ -87,6 +87,7 @@ fn read_config_override_values() {
     let max_db_commit_attempts = 1;
     let max_orphan_blocks = 2;
     let p2p_addr = "address";
+    let p2p_add_node = "add_node";
     let p2p_ban_threshold = 3;
     let p2p_timeout = 10000;
     let http_rpc_addr = SocketAddr::from_str("127.0.0.1:5432").unwrap();
@@ -100,7 +101,8 @@ fn read_config_override_values() {
         max_db_commit_attempts: Some(max_db_commit_attempts),
         max_orphan_blocks: Some(max_orphan_blocks),
         tx_index_enabled: Some(false),
-        p2p_addr: Some(p2p_addr.into()),
+        p2p_addr: Some(vec![p2p_addr.to_owned()]),
+        p2p_add_node: Some(vec![p2p_add_node.to_owned()]),
         p2p_ban_threshold: Some(p2p_ban_threshold),
         p2p_outbound_connection_timeout: Some(p2p_timeout),
         p2p_enable_mdns: Some(enable_mdns),
@@ -136,7 +138,8 @@ fn read_config_override_values() {
         Some(max_tip_age)
     );
 
-    assert_eq!(config.p2p.bind_address, Some(p2p_addr.into()));
+    assert_eq!(config.p2p.bind_addresses, Some(vec!(p2p_addr.to_owned())));
+    assert_eq!(config.p2p.added_nodes, Some(vec!(p2p_add_node.to_owned())));
     assert_eq!(config.p2p.ban_threshold, Some(p2p_ban_threshold));
     assert_eq!(config.p2p.outbound_connection_timeout, Some(p2p_timeout));
     assert_eq!(config.p2p.node_type, Some(node_type));
@@ -201,6 +204,7 @@ fn default_run_options() -> RunOptions {
         max_orphan_blocks: None,
         tx_index_enabled: None,
         p2p_addr: None,
+        p2p_add_node: None,
         p2p_ban_threshold: None,
         p2p_outbound_connection_timeout: None,
         p2p_enable_mdns: None,
diff --git a/p2p/backend-test-suite/src/ban.rs b/p2p/backend-test-suite/src/ban.rs
index 050ed3509..dc9d6f0af 100644
--- a/p2p/backend-test-suite/src/ban.rs
+++ b/p2p/backend-test-suite/src/ban.rs
@@ -17,14 +17,13 @@ use std::{fmt::Debug, sync::Arc};
 
 use tokio::sync::mpsc;
 
-use p2p::testing_utils::TestTransportMaker;
+use p2p::testing_utils::{connect_services, TestTransportMaker};
 use p2p::{
     config::P2pConfig,
     error::{P2pError, PublishError},
     event::PeerManagerEvent,
     message::{Announcement, HeaderListResponse, Request, Response},
     net::{types::SyncingEvent, ConnectivityService, NetworkingService, SyncingMessagingService},
-    peer_manager::helpers::connect_services,
     sync::BlockSyncManager,
 };
 use p2p_test_utils::TestBlockInfo;
@@ -49,7 +48,7 @@ where
 
     let (mut conn1, sync1) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&chain_config),
         Default::default(),
     )
@@ -67,7 +66,7 @@ where
 
     let (mut conn2, mut sync2) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&chain_config),
         Arc::clone(&p2p_config),
     )
@@ -141,7 +140,7 @@ where
 
     let (mut conn1, sync1) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&chain_config),
         Default::default(),
     )
@@ -150,7 +149,7 @@ where
 
     let (mut conn2, _sync2) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&chain_config),
         Default::default(),
     )
diff --git a/p2p/backend-test-suite/src/block_announcement.rs b/p2p/backend-test-suite/src/block_announcement.rs
index 1e302b726..4d85197b4 100644
--- a/p2p/backend-test-suite/src/block_announcement.rs
+++ b/p2p/backend-test-suite/src/block_announcement.rs
@@ -32,7 +32,7 @@ use common::{
 };
 use serialization::Encode;
 
-use p2p::testing_utils::TestTransportMaker;
+use p2p::testing_utils::{connect_services, TestTransportMaker};
 use p2p::{
     config::{NodeType, P2pConfig},
     error::{P2pError, PublishError},
@@ -41,7 +41,6 @@ use p2p::{
         mock::constants::ANNOUNCEMENT_MAX_SIZE, types::SyncingEvent, ConnectivityService,
         NetworkingService, SyncingMessagingService,
     },
-    peer_manager::helpers::connect_services,
 };
 
 tests![
@@ -60,7 +59,7 @@ where
     let config = Arc::new(common::chain::config::create_mainnet());
     let (mut conn1, mut sync1) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
@@ -68,7 +67,7 @@ where
     .unwrap();
     let (mut conn2, mut sync2) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
@@ -140,7 +139,8 @@ where
 {
     let chain_config = Arc::new(common::chain::config::create_mainnet());
     let p2p_config = Arc::new(P2pConfig {
-        bind_address: Default::default(),
+        bind_addresses: Vec::new(),
+        added_nodes: Vec::new(),
         ban_threshold: Default::default(),
         ban_duration: Default::default(),
         outbound_connection_timeout: Default::default(),
@@ -149,7 +149,7 @@ where
     });
     let (mut conn1, mut sync1) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&chain_config),
         Arc::clone(&p2p_config),
     )
@@ -157,7 +157,7 @@ where
     .unwrap();
     let (mut conn2, _sync2) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         chain_config,
         p2p_config,
     )
@@ -197,7 +197,7 @@ where
     let config = Arc::new(common::chain::config::create_mainnet());
     let (mut conn1, mut sync1) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
@@ -206,7 +206,7 @@ where
 
     let (mut conn2, _sync2) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
diff --git a/p2p/backend-test-suite/src/connect.rs b/p2p/backend-test-suite/src/connect.rs
index 35aa758b7..19e05d526 100644
--- a/p2p/backend-test-suite/src/connect.rs
+++ b/p2p/backend-test-suite/src/connect.rs
@@ -40,7 +40,7 @@ where
     let config = Arc::new(common::chain::config::create_mainnet());
     S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         config,
         Default::default(),
     )
@@ -61,15 +61,15 @@ where
     let config = Arc::new(common::chain::config::create_mainnet());
     let (connectivity, _sync) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
     .await
     .unwrap();
 
-    let address = connectivity.local_addr().await.unwrap().unwrap();
-    let res = S::start(A::make_transport(), address, config, Default::default())
+    let addresses = connectivity.local_addresses().await.unwrap();
+    let res = S::start(A::make_transport(), addresses, config, Default::default())
         .await
         .expect_err("address is not in use");
     assert!(matches!(
@@ -92,7 +92,7 @@ where
     let config = Arc::new(common::chain::config::create_mainnet());
     let (mut service1, _) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
@@ -100,15 +100,15 @@ where
     .unwrap();
     let (mut service2, _) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
     .await
     .unwrap();
 
-    let conn_addr = service1.local_addr().await.unwrap().unwrap();
-    let (res1, res2) = tokio::join!(service1.poll_next(), service2.connect(conn_addr));
+    let conn_addr = service1.local_addresses().await.unwrap();
+    let (res1, res2) = tokio::join!(service1.poll_next(), service2.connect(conn_addr[0].clone()));
     res1.unwrap();
     res2.unwrap();
 }
diff --git a/p2p/backend-test-suite/src/sync.rs b/p2p/backend-test-suite/src/sync.rs
index 3662e7528..617183b2b 100644
--- a/p2p/backend-test-suite/src/sync.rs
+++ b/p2p/backend-test-suite/src/sync.rs
@@ -27,7 +27,7 @@ use common::{
     primitives::{Id, Idable},
 };
 
-use p2p::testing_utils::TestTransportMaker;
+use p2p::testing_utils::{connect_services, filter_connectivity_event, TestTransportMaker};
 use p2p::{
     config::P2pConfig,
     error::P2pError,
@@ -37,7 +37,6 @@ use p2p::{
         types::{ConnectivityEvent, SyncingEvent},
         ConnectivityService, NetworkingService, SyncingMessagingService,
     },
-    peer_manager::helpers::connect_services,
     sync::BlockSyncManager,
 };
 use p2p_test_utils::TestBlockInfo;
@@ -1063,8 +1062,13 @@ where
 
     mgr1.unregister_peer(peer_info2.peer_id);
     assert_eq!(conn1.disconnect(peer_info2.peer_id).await, Ok(()));
+
+    let event = filter_connectivity_event::<S, _>(&mut conn2, |event| {
+        !std::matches!(event, Ok(ConnectivityEvent::Discovered { .. }))
+    })
+    .await;
     assert!(std::matches!(
-        conn2.poll_next().await,
+        event,
         Ok(ConnectivityEvent::ConnectionClosed { .. })
     ));
     assert!(std::matches!(
@@ -1166,7 +1170,7 @@ where
     let p2p_config = Arc::new(p2p_config);
     let (conn, sync) = T::start(
         transport,
-        addr,
+        vec![addr],
         Arc::clone(&chain_config),
         Arc::clone(&p2p_config),
     )
diff --git a/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/tests.rs b/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/tests.rs
index de168f004..ab3e17770 100644
--- a/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/tests.rs
+++ b/p2p/src/net/mock/transport/impls/stream_adapter/wrapped_transport/tests.rs
@@ -52,8 +52,8 @@ async fn send_recv<T: PeerStream>(sender: &mut T, receiver: &mut T, len: usize)
 }
 
 async fn test<A: TestTransportMaker<Address = T::Address>, T: TransportSocket>(transport: T) {
-    let mut server = transport.bind(A::make_address()).await.unwrap();
-    let peer_fut = transport.connect(server.local_address().unwrap());
+    let mut server = transport.bind(vec![A::make_address()]).await.unwrap();
+    let peer_fut = transport.connect(server.local_addresses().unwrap()[0].clone());
 
     let (server_res, peer_res) = tokio::join!(server.accept(), peer_fut);
     let mut server_stream = server_res.unwrap().0;
@@ -136,8 +136,8 @@ impl TransportSocket for TestMockTransport {
     type Listener = TestMockListener;
     type Stream = <MockChannelTransport as TransportSocket>::Stream;
 
-    async fn bind(&self, address: Self::Address) -> crate::Result<Self::Listener> {
-        let listener = self.transport.bind(address).await.unwrap();
+    async fn bind(&self, addresses: Vec<Self::Address>) -> crate::Result<Self::Listener> {
+        let listener = self.transport.bind(addresses).await.unwrap();
         *self.port_open.lock().unwrap() = true;
         Ok(TestMockListener {
             listener,
@@ -166,8 +166,10 @@ impl
         self.listener.accept().await
     }
 
-    fn local_address(&self) -> crate::Result<<MockChannelTransport as TransportSocket>::Address> {
-        self.listener.local_address()
+    fn local_addresses(
+        &self,
+    ) -> crate::Result<Vec<<MockChannelTransport as TransportSocket>::Address>> {
+        self.listener.local_addresses()
     }
 }
 
@@ -186,7 +188,7 @@ async fn test_bind_port_closed() {
     );
     assert!(!*transport.base_transport.port_open.lock().unwrap());
 
-    let listener = transport.bind(0).await.unwrap();
+    let listener = transport.bind(vec![0]).await.unwrap();
     assert!(*transport.base_transport.port_open.lock().unwrap());
 
     drop(listener);
@@ -199,8 +201,8 @@ async fn send_2_reqs() {
         NoiseEncryptionAdapter::gen_new(),
         TcpTransportSocket::new(),
     );
-    let mut server = transport.bind(TestTransportTcp::make_address()).await.unwrap();
-    let peer_fut = transport.connect(server.local_address().unwrap());
+    let mut server = transport.bind(vec![TestTransportTcp::make_address()]).await.unwrap();
+    let peer_fut = transport.connect(server.local_addresses().unwrap()[0]);
 
     let (server_res, peer_res) = tokio::join!(server.accept(), peer_fut);
     let server_stream = server_res.unwrap().0;
@@ -249,8 +251,8 @@ async fn pending_handshakes() {
         NoiseEncryptionAdapter::gen_new(),
         TcpTransportSocket::new(),
     );
-    let mut server = transport.bind(TestTransportTcp::make_address()).await.unwrap();
-    let local_addr = server.local_address().unwrap();
+    let mut server = transport.bind(vec![TestTransportTcp::make_address()]).await.unwrap();
+    let local_addr = server.local_addresses().unwrap();
 
     let join_handle = tokio::spawn(async move {
         loop {
@@ -260,19 +262,19 @@ async fn pending_handshakes() {
 
     // Connect MAX_CONCURRENT_HANDSHAKES amount of idle clients
     let mut sockets = futures::stream::iter(0..MAX_CONCURRENT_HANDSHAKES)
-        .then(|_| async { tokio::net::TcpStream::connect(local_addr).await.unwrap() })
+        .then(|_| async { tokio::net::TcpStream::connect(local_addr[0]).await.unwrap() })
         .collect::<Vec<_>>()
         .await;
 
     // Noise connection will fail because of too many connected idle TCP clients
-    let pending_fut = timeout(Duration::from_millis(100), transport.connect(local_addr)).await;
+    let pending_fut = timeout(Duration::from_millis(100), transport.connect(local_addr[0])).await;
     assert!(matches!(pending_fut, Err(_)));
 
     // Disconnect one idle client
     sockets.pop();
 
     // Noise connection should succeed now
-    let pending_fut = timeout(Duration::from_millis(100), transport.connect(local_addr)).await;
+    let pending_fut = timeout(Duration::from_millis(100), transport.connect(local_addr[0])).await;
     assert!(matches!(pending_fut, Ok(Ok(_))));
 
     join_handle.abort();
@@ -284,8 +286,8 @@ async fn handshake_timeout() {
         NoiseEncryptionAdapter::gen_new(),
         TcpTransportSocket::new(),
     );
-    let mut server = transport.bind(TestTransportTcp::make_address()).await.unwrap();
-    let local_addr = server.local_address().unwrap();
+    let mut server = transport.bind(vec![TestTransportTcp::make_address()]).await.unwrap();
+    let local_addr = server.local_addresses().unwrap();
 
     let join_handle = tokio::spawn(async move {
         loop {
@@ -293,7 +295,7 @@ async fn handshake_timeout() {
         }
     });
 
-    let mut bad_client = tokio::net::TcpStream::connect(local_addr).await.unwrap();
+    let mut bad_client = tokio::net::TcpStream::connect(local_addr[0]).await.unwrap();
     // Server should disconnect the bad client because of handshake timeout
     let read_res = bad_client.read_u8().await;
     assert!(read_res.is_err());
diff --git a/p2p/src/peer_manager/peerdb/tests.rs b/p2p/src/peer_manager/peerdb/tests.rs
new file mode 100644
index 000000000..f2e435e4f
--- /dev/null
+++ b/p2p/src/peer_manager/peerdb/tests.rs
@@ -0,0 +1,210 @@
+// Copyright (c) 2022 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::{sync::Arc, time::Duration};
+
+use crate::{
+    config::P2pConfig,
+    net::{
+        mock::{
+            transport::{MockChannelTransport, NoiseTcpTransport, TcpTransportSocket},
+            types::MockPeerId,
+            MockService,
+        },
+        types::{PeerInfo, PubSubTopic, Role},
+        AsBannableAddress, NetworkingService,
+    },
+    peer_manager::peerdb::PeerDb,
+    testing_utils::{RandomAddressMaker, TestChannelAddressMaker, TestTcpAddressMaker},
+};
+
+fn make_peer_info<S, A>() -> (S::PeerId, S::Address, PeerInfo<S>)
+where
+    S: NetworkingService<PeerId = MockPeerId>,
+    A: RandomAddressMaker<Address = S::Address>,
+{
+    let peer_id = MockPeerId::new();
+
+    (
+        peer_id,
+        A::new(),
+        PeerInfo::<S> {
+            peer_id,
+            magic_bytes: [1, 2, 3, 4],
+            version: common::primitives::semver::SemVer::new(0, 1, 0),
+            agent: None,
+            subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
+        },
+    )
+}
+
+fn add_active_peer<S, A>(peerdb: &mut PeerDb<S>) -> S::PeerId
+where
+    S: NetworkingService<PeerId = MockPeerId>,
+    A: RandomAddressMaker<Address = S::Address>,
+{
+    let (peer_id, address, info) = make_peer_info::<S, A>();
+    peerdb.peer_connected(address, Role::Inbound, info);
+    peer_id
+}
+
+fn adjust_peer_score_normal_threshold<S, A>()
+where
+    S: NetworkingService<PeerId = MockPeerId>,
+    A: RandomAddressMaker<Address = S::Address>,
+{
+    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default())).unwrap();
+
+    let peer_id = add_active_peer::<S, A>(&mut peerdb);
+    let address = peerdb.peers.get(&peer_id).unwrap().address.as_bannable();
+    assert!(peerdb.adjust_peer_score(&peer_id, 100));
+    assert!(peerdb.is_address_banned(&address));
+}
+
+#[test]
+fn adjust_peer_score_normal_threshold_mock_tcp() {
+    adjust_peer_score_normal_threshold::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
+}
+
+#[test]
+fn adjust_peer_score_normal_threshold_mock_channels() {
+    adjust_peer_score_normal_threshold::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
+    );
+}
+
+#[test]
+fn adjust_peer_score_normal_threshold_mock_noise() {
+    adjust_peer_score_normal_threshold::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
+}
+
+fn adjust_peer_score_higher_threshold<S, A>()
+where
+    S: NetworkingService<PeerId = MockPeerId>,
+    A: RandomAddressMaker<Address = S::Address>,
+{
+    let config = P2pConfig {
+        bind_addresses: Default::default(),
+        added_nodes: Default::default(),
+        ban_threshold: 200.into(),
+        ban_duration: Default::default(),
+        outbound_connection_timeout: Default::default(),
+        mdns_config: Default::default(),
+        node_type: Default::default(),
+    };
+    let mut peerdb = PeerDb::<S>::new(Arc::new(config)).unwrap();
+
+    let peer_id = add_active_peer::<S, A>(&mut peerdb);
+    assert!(!peerdb.adjust_peer_score(&peer_id, 100));
+
+    let address = peerdb.peers.get(&peer_id).unwrap().address.as_bannable();
+    assert!(!peerdb.is_address_banned(&address));
+}
+
+#[test]
+fn adjust_peer_score_higher_threshold_mock_tcp() {
+    adjust_peer_score_higher_threshold::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
+}
+
+#[test]
+fn adjust_peer_score_higher_threshold_mock_channels() {
+    adjust_peer_score_higher_threshold::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
+    );
+}
+
+#[test]
+fn adjust_peer_score_higher_threshold_mock_noise() {
+    adjust_peer_score_higher_threshold::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
+}
+
+fn adjust_peer_score_lower_threshold<S, A>()
+where
+    S: NetworkingService<PeerId = MockPeerId>,
+    A: RandomAddressMaker<Address = S::Address>,
+{
+    let config = P2pConfig {
+        bind_addresses: Default::default(),
+        added_nodes: Default::default(),
+        ban_threshold: 20.into(),
+        ban_duration: Default::default(),
+        outbound_connection_timeout: Default::default(),
+        mdns_config: Default::default(),
+        node_type: Default::default(),
+    };
+    let mut peerdb = PeerDb::<S>::new(Arc::new(config)).unwrap();
+
+    let peer_id = add_active_peer::<S, A>(&mut peerdb);
+    let address = peerdb.peers.get(&peer_id).unwrap().address.as_bannable();
+    assert!(peerdb.adjust_peer_score(&peer_id, 30));
+    assert!(peerdb.is_address_banned(&address));
+}
+
+#[test]
+fn adjust_peer_score_lower_threshold_mock_tcp() {
+    adjust_peer_score_lower_threshold::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
+}
+
+#[test]
+fn adjust_peer_score_lower_threshold_mock_channels() {
+    adjust_peer_score_lower_threshold::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
+    );
+}
+
+#[test]
+fn adjust_peer_score_lower_threshold_mock_noise() {
+    adjust_peer_score_lower_threshold::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
+}
+
+async fn unban_peer<S, A>()
+where
+    S: NetworkingService<PeerId = MockPeerId>,
+    A: RandomAddressMaker<Address = S::Address>,
+{
+    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig {
+        bind_addresses: Default::default(),
+        added_nodes: Default::default(),
+        ban_threshold: Default::default(),
+        ban_duration: Duration::from_secs(2).into(),
+        outbound_connection_timeout: Default::default(),
+        mdns_config: Default::default(),
+        node_type: Default::default(),
+    }))
+    .unwrap();
+
+    let peer_id = add_active_peer::<S, A>(&mut peerdb);
+    let address = peerdb.peers.get(&peer_id).unwrap().address.as_bannable();
+
+    peerdb.ban_peer(&peer_id);
+
+    tokio::time::sleep(Duration::from_secs(1)).await;
+    assert!(peerdb.is_address_banned(&address));
+
+    tokio::time::sleep(Duration::from_secs(2)).await;
+    assert!(!peerdb.is_address_banned(&address));
+}
+
+#[tokio::test]
+async fn unban_peer_mock_tcp() {
+    unban_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>().await;
+}
+
+#[tokio::test]
+async fn unban_peer_mock_channels() {
+    unban_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>().await;
+}
+
+#[tokio::test]
+async fn unban_peer_mock_noise() {
+    unban_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>().await;
+}
diff --git a/p2p/src/peer_manager/tests/ban.rs b/p2p/src/peer_manager/tests/ban.rs
index 3aa25e615..272305c24 100644
--- a/p2p/src/peer_manager/tests/ban.rs
+++ b/p2p/src/peer_manager/tests/ban.rs
@@ -15,8 +15,13 @@
 
 use std::sync::Arc;
 
-use crate::testing_utils::{
-    TestTransportChannel, TestTransportMaker, TestTransportNoise, TestTransportTcp,
+use crate::{
+    net::types::Role,
+    testing_utils::{
+        connect_services, filter_connectivity_event, RandomAddressMaker, TestChannelAddressMaker,
+        TestTcpAddressMaker, TestTransportChannel, TestTransportMaker, TestTransportNoise,
+        TestTransportTcp,
+    },
 };
 use common::{chain::config, primitives::semver::SemVer};
 
@@ -32,7 +37,7 @@ use crate::{
         types::PubSubTopic,
         AsBannableAddress, ConnectivityService, NetworkingService,
     },
-    peer_manager::{helpers::connect_services, tests::make_peer_manager},
+    peer_manager::tests::make_peer_manager,
 };
 
 // ban peer whose connected to us
@@ -41,8 +46,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as net::NetworkingService>::Address: std::str::FromStr,
-    <<T as net::NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -60,10 +63,16 @@ where
     pm2.accept_inbound_connection(address, peer_info).unwrap();
 
     assert_eq!(pm2.adjust_peer_score(peer_id, 1000).await, Ok(()));
-    let addr1 = pm1.peer_connectivity_handle.local_addr().await.unwrap().unwrap().as_bannable();
+    let addr1 = pm1.peer_connectivity_handle.local_addresses().await.unwrap()[0]
+        .clone()
+        .as_bannable();
     assert!(pm2.peerdb.is_address_banned(&addr1));
+    let event = filter_connectivity_event::<T, _>(&mut pm2.peer_connectivity_handle, |event| {
+        !std::matches!(event, Ok(net::types::ConnectivityEvent::Discovered { .. }))
+    })
+    .await;
     assert!(std::matches!(
-        pm2.peer_connectivity_handle.poll_next().await,
+        event,
         Ok(net::types::ConnectivityEvent::ConnectionClosed { .. })
     ));
 }
@@ -88,8 +97,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + std::fmt::Debug + 'static,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as net::NetworkingService>::Address: std::str::FromStr,
-    <<T as net::NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -107,10 +114,16 @@ where
     pm2.accept_inbound_connection(address, peer_info).unwrap();
 
     assert_eq!(pm2.adjust_peer_score(peer_id, 1000).await, Ok(()));
-    let addr1 = pm1.peer_connectivity_handle.local_addr().await.unwrap().unwrap().as_bannable();
+    let addr1 = pm1.peer_connectivity_handle.local_addresses().await.unwrap()[0]
+        .clone()
+        .as_bannable();
     assert!(pm2.peerdb.is_address_banned(&addr1));
+    let event = filter_connectivity_event::<T, _>(&mut pm2.peer_connectivity_handle, |event| {
+        !std::matches!(event, Ok(net::types::ConnectivityEvent::Discovered { .. }))
+    })
+    .await;
     assert!(std::matches!(
-        pm2.peer_connectivity_handle.poll_next().await,
+        event,
         Ok(net::types::ConnectivityEvent::ConnectionClosed { .. })
     ));
 }
@@ -137,8 +150,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as net::NetworkingService>::Address: std::str::FromStr,
-    <<T as net::NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -156,14 +167,20 @@ where
     pm2.accept_inbound_connection(address, peer_info1).unwrap();
 
     assert_eq!(pm2.adjust_peer_score(peer_id, 1000).await, Ok(()));
-    let addr1 = pm1.peer_connectivity_handle.local_addr().await.unwrap().unwrap().as_bannable();
+    let addr1 = pm1.peer_connectivity_handle.local_addresses().await.unwrap()[0]
+        .clone()
+        .as_bannable();
     assert!(pm2.peerdb.is_address_banned(&addr1));
+    let event = filter_connectivity_event::<T, _>(&mut pm2.peer_connectivity_handle, |event| {
+        !std::matches!(event, Ok(net::types::ConnectivityEvent::Discovered { .. }))
+    })
+    .await;
     assert!(matches!(
-        pm2.peer_connectivity_handle.poll_next().await,
+        event,
         Ok(net::types::ConnectivityEvent::ConnectionClosed { .. })
     ));
 
-    let remote_addr = pm1.peer_connectivity_handle.local_addr().await.unwrap().unwrap();
+    let remote_addr = pm1.peer_connectivity_handle.local_addresses().await.unwrap()[0].clone();
 
     tokio::spawn(async move {
         loop {
@@ -198,36 +215,21 @@ async fn connect_to_banned_peer_mock_noise() {
     connect_to_banned_peer::<TestTransportNoise, MockService<NoiseTcpTransport>>().await;
 }
 
-async fn validate_invalid_outbound_connection<A, S>(peer_address: S::Address, peer_id: S::PeerId)
+async fn validate_invalid_outbound_connection<A, S, B>(peer_id: S::PeerId)
 where
     A: TestTransportMaker<Transport = S::Transport, Address = S::Address>,
     S: NetworkingService + 'static + std::fmt::Debug,
     S::ConnectivityHandle: ConnectivityService<S>,
-    <S as net::NetworkingService>::Address: std::str::FromStr,
-    <<S as net::NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
+    B: RandomAddressMaker<Address = S::Address>,
 {
     let config = Arc::new(config::create_mainnet());
     let mut peer_manager =
         make_peer_manager::<S>(A::make_transport(), A::make_address(), Arc::clone(&config)).await;
 
-    // valid connection
-    let res = peer_manager.accept_connection(
-        peer_address.clone(),
-        net::types::PeerInfo::<S> {
-            peer_id,
-            magic_bytes: *config.magic_bytes(),
-            version: SemVer::new(0, 1, 0),
-            agent: None,
-            subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
-        },
-    );
-    assert_eq!(peer_manager.handle_result(Some(peer_id), res).await, Ok(()));
-    assert!(peer_manager.peerdb.is_active_peer(&peer_id));
-    assert!(!peer_manager.peerdb.is_address_banned(&peer_address.as_bannable()));
-
     // invalid magic bytes
     let res = peer_manager.accept_connection(
-        peer_address.clone(),
+        B::new(),
+        Role::Outbound,
         net::types::PeerInfo::<S> {
             peer_id,
             magic_bytes: [1, 2, 3, 4],
@@ -241,7 +243,8 @@ where
 
     // invalid version
     let res = peer_manager.accept_connection(
-        peer_address.clone(),
+        B::new(),
+        Role::Outbound,
         net::types::PeerInfo::<S> {
             peer_id,
             magic_bytes: *config.magic_bytes(),
@@ -253,9 +256,11 @@ where
     assert_eq!(peer_manager.handle_result(Some(peer_id), res).await, Ok(()));
     assert!(!peer_manager.peerdb.is_active_peer(&peer_id));
 
-    // protocol missing
+    // valid connection
+    let address = B::new();
     let res = peer_manager.accept_connection(
-        peer_address,
+        address.clone(),
+        Role::Outbound,
         net::types::PeerInfo::<S> {
             peer_id,
             magic_bytes: *config.magic_bytes(),
@@ -264,44 +269,48 @@ where
             subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
         },
     );
+    assert!(res.is_ok());
     assert_eq!(peer_manager.handle_result(Some(peer_id), res).await, Ok(()));
-    assert!(!peer_manager.peerdb.is_active_peer(&peer_id));
+    assert!(peer_manager.peerdb.is_active_peer(&peer_id));
+    assert!(!peer_manager.peerdb.is_address_banned(&address.as_bannable()));
 }
 
 #[tokio::test]
 async fn validate_invalid_outbound_connection_mock_tcp() {
-    validate_invalid_outbound_connection::<TestTransportTcp, MockService<TcpTransportSocket>>(
-        "210.113.67.107:2525".parse().unwrap(),
-        MockPeerId::new(),
-    )
+    validate_invalid_outbound_connection::<
+        TestTransportTcp,
+        MockService<TcpTransportSocket>,
+        TestTcpAddressMaker,
+    >(MockPeerId::new())
     .await;
 }
 
 #[tokio::test]
 async fn validate_invalid_outbound_connection_mock_channels() {
-    validate_invalid_outbound_connection::<TestTransportChannel, MockService<MockChannelTransport>>(
-        1,
-        MockPeerId::new(),
-    )
+    validate_invalid_outbound_connection::<
+        TestTransportChannel,
+        MockService<MockChannelTransport>,
+        TestChannelAddressMaker,
+    >(MockPeerId::new())
     .await;
 }
 
 #[tokio::test]
 async fn validate_invalid_outbound_connection_mock_noise() {
-    validate_invalid_outbound_connection::<TestTransportNoise, MockService<NoiseTcpTransport>>(
-        "210.113.67.107:2525".parse().unwrap(),
-        MockPeerId::new(),
-    )
+    validate_invalid_outbound_connection::<
+        TestTransportNoise,
+        MockService<NoiseTcpTransport>,
+        TestTcpAddressMaker,
+    >(MockPeerId::new())
     .await;
 }
 
-async fn validate_invalid_inbound_connection<A, S>(peer_address: S::Address, peer_id: S::PeerId)
+async fn validate_invalid_inbound_connection<A, S, B>(peer_id: S::PeerId)
 where
     A: TestTransportMaker<Transport = S::Transport, Address = S::Address>,
     S: NetworkingService + 'static + std::fmt::Debug,
     S::ConnectivityHandle: ConnectivityService<S>,
-    <S as net::NetworkingService>::Address: std::str::FromStr,
-    <<S as net::NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
+    B: RandomAddressMaker<Address = S::Address>,
 {
     let config = Arc::new(config::create_mainnet());
     let mut peer_manager =
@@ -309,7 +318,7 @@ where
 
     // invalid magic bytes
     let res = peer_manager.accept_inbound_connection(
-        peer_address.clone(),
+        B::new(),
         net::types::PeerInfo::<S> {
             peer_id,
             magic_bytes: [1, 2, 3, 4],
@@ -323,7 +332,7 @@ where
 
     // invalid version
     let res = peer_manager.accept_inbound_connection(
-        peer_address.clone(),
+        B::new(),
         net::types::PeerInfo::<S> {
             peer_id,
             magic_bytes: *config.magic_bytes(),
@@ -335,23 +344,10 @@ where
     assert_eq!(peer_manager.handle_result(Some(peer_id), res).await, Ok(()));
     assert!(!peer_manager.peerdb.is_active_peer(&peer_id));
 
-    // protocol missing
-    let res = peer_manager.accept_inbound_connection(
-        peer_address.clone(),
-        net::types::PeerInfo::<S> {
-            peer_id,
-            magic_bytes: *config.magic_bytes(),
-            version: common::primitives::semver::SemVer::new(0, 1, 0),
-            agent: None,
-            subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
-        },
-    );
-    assert_eq!(peer_manager.handle_result(Some(peer_id), res).await, Ok(()));
-    assert!(!peer_manager.peerdb.is_active_peer(&peer_id));
-
     // valid connection
+    let address = B::new();
     let res = peer_manager.accept_inbound_connection(
-        peer_address.clone(),
+        address.clone(),
         net::types::PeerInfo::<S> {
             peer_id,
             magic_bytes: *config.magic_bytes(),
@@ -360,34 +356,39 @@ where
             subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
         },
     );
+    assert!(res.is_ok());
     assert_eq!(peer_manager.handle_result(Some(peer_id), res).await, Ok(()));
-    assert!(!peer_manager.peerdb.is_address_banned(&peer_address.as_bannable()));
+    assert!(peer_manager.peerdb.is_active_peer(&peer_id));
+    assert!(!peer_manager.peerdb.is_address_banned(&address.as_bannable()));
 }
 
 #[tokio::test]
 async fn validate_invalid_inbound_connection_mock_tcp() {
-    validate_invalid_inbound_connection::<TestTransportTcp, MockService<TcpTransportSocket>>(
-        "210.113.67.107:2525".parse().unwrap(),
-        MockPeerId::new(),
-    )
+    validate_invalid_inbound_connection::<
+        TestTransportTcp,
+        MockService<TcpTransportSocket>,
+        TestTcpAddressMaker,
+    >(MockPeerId::new())
     .await;
 }
 
 #[tokio::test]
 async fn validate_invalid_inbound_connection_mock_channels() {
-    validate_invalid_inbound_connection::<TestTransportChannel, MockService<MockChannelTransport>>(
-        1,
-        MockPeerId::new(),
-    )
+    validate_invalid_inbound_connection::<
+        TestTransportChannel,
+        MockService<MockChannelTransport>,
+        TestChannelAddressMaker,
+    >(MockPeerId::new())
     .await;
 }
 
 #[tokio::test]
 async fn validate_invalid_inbound_connection_mock_noise() {
-    validate_invalid_inbound_connection::<TestTransportNoise, MockService<NoiseTcpTransport>>(
-        "210.113.67.107:2525".parse().unwrap(),
-        MockPeerId::new(),
-    )
+    validate_invalid_inbound_connection::<
+        TestTransportNoise,
+        MockService<NoiseTcpTransport>,
+        TestTcpAddressMaker,
+    >(MockPeerId::new())
     .await;
 }
 
@@ -396,8 +397,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as net::NetworkingService>::Address: std::str::FromStr,
-    <<T as net::NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -425,9 +424,11 @@ where
     // that tries to connect to the first manager
     tokio::spawn(async move { pm1.run().await });
 
-    if let Ok(net::types::ConnectivityEvent::ConnectionClosed { peer_id }) =
-        pm2.peer_connectivity_handle.poll_next().await
-    {
+    let event = filter_connectivity_event::<T, _>(&mut pm2.peer_connectivity_handle, |event| {
+        !std::matches!(event, Ok(net::types::ConnectivityEvent::Discovered { .. }))
+    })
+    .await;
+    if let Ok(net::types::ConnectivityEvent::ConnectionClosed { peer_id }) = event {
         assert_eq!(peer_id, peer_info.peer_id);
     } else {
         panic!("invalid event received");
diff --git a/p2p/src/peer_manager/tests/connections.rs b/p2p/src/peer_manager/tests/connections.rs
index 98475b9ba..2ba7abf85 100644
--- a/p2p/src/peer_manager/tests/connections.rs
+++ b/p2p/src/peer_manager/tests/connections.rs
@@ -13,12 +13,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::{net::SocketAddr, sync::Arc, time::Duration};
+use std::{
+    net::SocketAddr,
+    sync::Arc,
+    time::{Duration, Instant},
+};
 
 use tokio::{sync::oneshot, time::timeout};
 
-use crate::testing_utils::{
-    TestTransportChannel, TestTransportMaker, TestTransportNoise, TestTransportTcp,
+use crate::{
+    config::P2pConfig,
+    net::types::Role,
+    peer_manager::tests::run_peer_manager,
+    testing_utils::{
+        connect_services, filter_connectivity_event, TestTransportChannel, TestTransportMaker,
+        TestTransportNoise, TestTransportTcp,
+    },
 };
 use common::chain::config;
 
@@ -35,7 +45,7 @@ use crate::{
         types::PubSubTopic,
         ConnectivityService, NetworkingService,
     },
-    peer_manager::{self, helpers::connect_services, tests::make_peer_manager},
+    peer_manager::{self, tests::make_peer_manager},
 };
 
 // try to connect to an address that no one listening on and verify it fails
@@ -46,8 +56,6 @@ async fn test_peer_manager_connect<T: NetworkingService>(
 ) where
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let config = Arc::new(config::create_mainnet());
     let mut peer_manager = make_peer_manager::<T>(transport, bind_addr, config).await;
@@ -75,13 +83,11 @@ async fn test_peer_manager_connect_mock() {
 
 // verify that the auto-connect functionality works if the number of active connections
 // is below the desired threshold and there are idle peers in the peerdb
-async fn test_auto_connect<A, T>(peer_id: T::PeerId)
+async fn test_auto_connect<A, T>()
 where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -90,7 +96,7 @@ where
     let mut pm1 = make_peer_manager::<T>(A::make_transport(), addr1, Arc::clone(&config)).await;
     let mut pm2 = make_peer_manager::<T>(A::make_transport(), addr2, config).await;
 
-    let addr = pm2.peer_connectivity_handle.local_addr().await.unwrap().unwrap();
+    let addr = pm2.peer_connectivity_handle.local_addresses().await.unwrap()[0].clone();
 
     tokio::spawn(async move {
         loop {
@@ -99,11 +105,7 @@ where
     });
 
     // "discover" the other networking service
-    pm1.peer_discovered(&[net::types::AddrInfo {
-        peer_id,
-        ip4: vec![],
-        ip6: vec![addr],
-    }]);
+    pm1.peer_discovered(&[addr]);
     pm1.heartbeat().await.unwrap();
 
     assert_eq!(pm1.pending.len(), 1);
@@ -115,19 +117,17 @@ where
 
 #[tokio::test]
 async fn test_auto_connect_mock_tcp() {
-    test_auto_connect::<TestTransportTcp, MockService<TcpTransportSocket>>(MockPeerId::new()).await;
+    test_auto_connect::<TestTransportTcp, MockService<TcpTransportSocket>>().await;
 }
 
 #[tokio::test]
 async fn test_auto_connect_mock_channels() {
-    test_auto_connect::<TestTransportChannel, MockService<MockChannelTransport>>(MockPeerId::new())
-        .await;
+    test_auto_connect::<TestTransportChannel, MockService<MockChannelTransport>>().await;
 }
 
 #[tokio::test]
 async fn test_auto_connect_mock_noise() {
-    test_auto_connect::<TestTransportNoise, MockService<NoiseTcpTransport>>(MockPeerId::new())
-        .await;
+    test_auto_connect::<TestTransportNoise, MockService<NoiseTcpTransport>>().await;
 }
 
 async fn connect_outbound_same_network<A, T>()
@@ -135,8 +135,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -173,8 +171,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -218,8 +214,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -256,8 +250,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -311,8 +303,6 @@ where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
@@ -361,24 +351,21 @@ async fn remote_closes_connection_mock_noise() {
     remote_closes_connection::<TestTransportNoise, MockService<NoiseTcpTransport>>().await;
 }
 
-async fn inbound_connection_too_many_peers<A, T>(peers: Vec<net::types::PeerInfo<T>>)
+async fn inbound_connection_too_many_peers<A, T>(peers: Vec<(T::Address, net::types::PeerInfo<T>)>)
 where
     A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let addr1 = A::make_address();
     let addr2 = A::make_address();
-    let default_addr = A::make_address();
 
     let config = Arc::new(config::create_mainnet());
     let mut pm1 = make_peer_manager::<T>(A::make_transport(), addr1, Arc::clone(&config)).await;
     let mut pm2 = make_peer_manager::<T>(A::make_transport(), addr2, Arc::clone(&config)).await;
 
-    peers.into_iter().for_each(|info| {
-        pm1.peerdb.peer_connected(default_addr.clone(), info);
+    peers.into_iter().for_each(|peer| {
+        pm1.peerdb.peer_connected(peer.0, Role::Inbound, peer.1);
     });
     assert_eq!(
         pm1.peerdb.active_peer_count(),
@@ -395,9 +382,11 @@ where
     // that tries to connect to the first manager
     tokio::spawn(async move { pm1.run().await });
 
-    if let Ok(net::types::ConnectivityEvent::ConnectionClosed { peer_id }) =
-        pm2.peer_connectivity_handle.poll_next().await
-    {
+    let event = filter_connectivity_event::<T, _>(&mut pm2.peer_connectivity_handle, |event| {
+        !std::matches!(event, Ok(net::types::ConnectivityEvent::Discovered { .. }))
+    })
+    .await;
+    if let Ok(net::types::ConnectivityEvent::ConnectionClosed { peer_id }) = event {
         assert_eq!(peer_id, peer_info.peer_id);
     } else {
         panic!("invalid event received");
@@ -408,17 +397,20 @@ where
 async fn inbound_connection_too_many_peers_mock_tcp() {
     let config = Arc::new(config::create_mainnet());
     let peers = (0..peer_manager::MAX_ACTIVE_CONNECTIONS)
-        .map(
-            |_| net::types::PeerInfo::<MockService<TcpTransportSocket>> {
-                peer_id: MockPeerId::new(),
-                magic_bytes: *config.magic_bytes(),
-                version: common::primitives::semver::SemVer::new(0, 1, 0),
-                agent: None,
-                subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
-                    .into_iter()
-                    .collect(),
-            },
-        )
+        .map(|index| {
+            (
+                format!("127.0.0.1:{}", index + 10000).parse().expect("valid address"),
+                net::types::PeerInfo::<MockService<TcpTransportSocket>> {
+                    peer_id: MockPeerId::new(),
+                    magic_bytes: *config.magic_bytes(),
+                    version: common::primitives::semver::SemVer::new(0, 1, 0),
+                    agent: None,
+                    subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
+                        .into_iter()
+                        .collect(),
+                },
+            )
+        })
         .collect::<Vec<_>>();
 
     inbound_connection_too_many_peers::<TestTransportTcp, MockService<TcpTransportSocket>>(peers)
@@ -429,17 +421,20 @@ async fn inbound_connection_too_many_peers_mock_tcp() {
 async fn inbound_connection_too_many_peers_mock_channels() {
     let config = Arc::new(config::create_mainnet());
     let peers = (0..peer_manager::MAX_ACTIVE_CONNECTIONS)
-        .map(
-            |_| net::types::PeerInfo::<MockService<MockChannelTransport>> {
-                peer_id: MockPeerId::new(),
-                magic_bytes: *config.magic_bytes(),
-                version: common::primitives::semver::SemVer::new(0, 1, 0),
-                agent: None,
-                subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
-                    .into_iter()
-                    .collect(),
-            },
-        )
+        .map(|index| {
+            (
+                format!("{}", index + 10000).parse().expect("valid address"),
+                net::types::PeerInfo::<MockService<MockChannelTransport>> {
+                    peer_id: MockPeerId::new(),
+                    magic_bytes: *config.magic_bytes(),
+                    version: common::primitives::semver::SemVer::new(0, 1, 0),
+                    agent: None,
+                    subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
+                        .into_iter()
+                        .collect(),
+                },
+            )
+        })
         .collect::<Vec<_>>();
 
     inbound_connection_too_many_peers::<TestTransportChannel, MockService<MockChannelTransport>>(
@@ -452,12 +447,19 @@ async fn inbound_connection_too_many_peers_mock_channels() {
 async fn inbound_connection_too_many_peers_mock_noise() {
     let config = Arc::new(config::create_mainnet());
     let peers = (0..peer_manager::MAX_ACTIVE_CONNECTIONS)
-        .map(|_| net::types::PeerInfo::<MockService<NoiseTcpTransport>> {
-            peer_id: MockPeerId::new(),
-            magic_bytes: *config.magic_bytes(),
-            version: common::primitives::semver::SemVer::new(0, 1, 0),
-            agent: None,
-            subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
+        .map(|index| {
+            (
+                format!("127.0.0.1:{}", index + 10000).parse().expect("valid address"),
+                net::types::PeerInfo::<MockService<NoiseTcpTransport>> {
+                    peer_id: MockPeerId::new(),
+                    magic_bytes: *config.magic_bytes(),
+                    version: common::primitives::semver::SemVer::new(0, 1, 0),
+                    agent: None,
+                    subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions]
+                        .into_iter()
+                        .collect(),
+                },
+            )
         })
         .collect::<Vec<_>>();
 
@@ -469,8 +471,6 @@ async fn connection_timeout<T>(transport: T::Transport, addr1: T::Address, addr2
 where
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let config = Arc::new(config::create_mainnet());
     let mut pm1 = make_peer_manager::<T>(transport, addr1, Arc::clone(&config)).await;
@@ -532,14 +532,12 @@ async fn connection_timeout_rpc_notified<T>(
 ) where
     T: NetworkingService + 'static + std::fmt::Debug,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: std::str::FromStr,
-    <<T as NetworkingService>::Address as std::str::FromStr>::Err: std::fmt::Debug,
 {
     let config = Arc::new(config::create_mainnet());
     let p2p_config = Arc::new(Default::default());
     let (conn, _) = T::start(
         transport,
-        addr1,
+        vec![addr1],
         Arc::clone(&config),
         Arc::clone(&p2p_config),
     )
@@ -554,7 +552,8 @@ async fn connection_timeout_rpc_notified<T>(
         conn,
         rx,
         tx_sync,
-    );
+    )
+    .unwrap();
 
     tokio::spawn(async move { while rx_sync.recv().await.is_some() {} });
     tokio::spawn(async move {
@@ -607,3 +606,79 @@ async fn connection_timeout_rpc_notified_mock_noise() {
     )
     .await;
 }
+
+// verify that peer connection is made when valid add_node parameter is used
+async fn connection_add_node<A, T>()
+where
+    A: TestTransportMaker<Transport = T::Transport, Address = T::Address>,
+    T: NetworkingService + 'static + std::fmt::Debug,
+    T::ConnectivityHandle: ConnectivityService<T>,
+{
+    let chain_config = Arc::new(config::create_mainnet());
+
+    // Start first peer manager
+    let p2p_config_1 = Arc::new(P2pConfig::default());
+    let tx1 = run_peer_manager::<T>(
+        A::make_transport(),
+        A::make_address(),
+        Arc::clone(&chain_config),
+        p2p_config_1,
+    )
+    .await;
+
+    // Get the first peer manager's bind address
+    let (rtx, rrx) = oneshot::channel();
+    tx1.send(PeerManagerEvent::GetBindAddresses(rtx)).unwrap();
+    let bind_addresses = timeout(Duration::from_secs(1), rrx).await.unwrap().unwrap();
+    assert_eq!(bind_addresses.len(), 1);
+
+    // Start second peer manager and let it know about first manager via added_nodes
+    let p2p_config_2 = Arc::new(P2pConfig {
+        bind_addresses: Default::default(),
+        added_nodes: bind_addresses,
+        ban_threshold: Default::default(),
+        ban_duration: Default::default(),
+        outbound_connection_timeout: Default::default(),
+        mdns_config: Default::default(),
+        node_type: Default::default(),
+    });
+    let tx1 = run_peer_manager::<T>(
+        A::make_transport(),
+        A::make_address(),
+        Arc::clone(&chain_config),
+        p2p_config_2,
+    )
+    .await;
+
+    // The first peer manager must report new connection after some time
+    let started_at = Instant::now();
+    loop {
+        tokio::time::sleep(Duration::from_millis(100)).await;
+        let (rtx, rrx) = oneshot::channel();
+        tx1.send(PeerManagerEvent::GetConnectedPeers(rtx)).unwrap();
+        let connected_peers = timeout(Duration::from_secs(1), rrx).await.unwrap().unwrap();
+        // Mock backend will also make reverse connection.
+        if connected_peers.len() == 1 || connected_peers.len() == 2 {
+            break;
+        }
+        assert!(
+            Instant::now().duration_since(started_at) < Duration::from_secs(10),
+            "no peer connected on time"
+        );
+    }
+}
+
+#[tokio::test]
+async fn connection_add_node_mock_tcp() {
+    connection_add_node::<TestTransportTcp, MockService<TcpTransportSocket>>().await;
+}
+
+#[tokio::test]
+async fn connection_add_node_mock_noise() {
+    connection_add_node::<TestTransportNoise, MockService<NoiseTcpTransport>>().await;
+}
+
+#[tokio::test]
+async fn connection_add_node_mock_channel() {
+    connection_add_node::<TestTransportChannel, MockService<MockChannelTransport>>().await;
+}
diff --git a/p2p/src/peer_manager/tests/mod.rs b/p2p/src/peer_manager/tests/mod.rs
index 0972b2418..4a1d6f21d 100644
--- a/p2p/src/peer_manager/tests/mod.rs
+++ b/p2p/src/peer_manager/tests/mod.rs
@@ -15,40 +15,74 @@
 
 mod ban;
 mod connections;
-mod peerdb;
 
-use std::{fmt::Debug, str::FromStr, sync::Arc};
+use std::sync::Arc;
+
+use tokio::sync::mpsc::UnboundedSender;
 
 use crate::{
+    event::PeerManagerEvent,
     net::{ConnectivityService, NetworkingService},
     peer_manager::PeerManager,
     P2pConfig,
 };
-
-async fn make_peer_manager<T>(
+async fn make_peer_manager_custom<T>(
     transport: T::Transport,
     addr: T::Address,
     chain_config: Arc<common::chain::ChainConfig>,
-) -> PeerManager<T>
+    p2p_config: Arc<P2pConfig>,
+) -> (PeerManager<T>, UnboundedSender<PeerManagerEvent<T>>)
 where
     T: NetworkingService + 'static,
     T::ConnectivityHandle: ConnectivityService<T>,
-    <T as NetworkingService>::Address: FromStr,
-    <<T as NetworkingService>::Address as FromStr>::Err: Debug,
 {
     let (conn, _) = T::start(
         transport,
-        addr,
+        vec![addr],
         Arc::clone(&chain_config),
         Default::default(),
     )
     .await
     .unwrap();
-    let (_, rx) = tokio::sync::mpsc::unbounded_channel();
+    let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
     let (tx_sync, mut rx_sync) = tokio::sync::mpsc::unbounded_channel();
 
     tokio::spawn(async move { while rx_sync.recv().await.is_some() {} });
 
+    let peer_manager = PeerManager::<T>::new(chain_config, p2p_config, conn, rx, tx_sync).unwrap();
+
+    (peer_manager, tx)
+}
+
+async fn make_peer_manager<T>(
+    transport: T::Transport,
+    addr: T::Address,
+    chain_config: Arc<common::chain::ChainConfig>,
+) -> PeerManager<T>
+where
+    T: NetworkingService + 'static,
+    T::ConnectivityHandle: ConnectivityService<T>,
+{
     let p2p_config = Arc::new(P2pConfig::default());
-    PeerManager::<T>::new(chain_config, p2p_config, conn, rx, tx_sync)
+    let (peer_manager, _tx) =
+        make_peer_manager_custom::<T>(transport, addr, chain_config, p2p_config).await;
+    peer_manager
+}
+
+async fn run_peer_manager<T>(
+    transport: T::Transport,
+    addr: T::Address,
+    chain_config: Arc<common::chain::ChainConfig>,
+    p2p_config: Arc<P2pConfig>,
+) -> UnboundedSender<PeerManagerEvent<T>>
+where
+    T: NetworkingService + 'static,
+    T::ConnectivityHandle: ConnectivityService<T>,
+{
+    let (mut peer_manager, tx) =
+        make_peer_manager_custom::<T>(transport, addr, chain_config, p2p_config).await;
+    tokio::spawn(async move {
+        peer_manager.run().await.unwrap();
+    });
+    tx
 }
diff --git a/p2p/src/peer_manager/tests/peerdb.rs b/p2p/src/peer_manager/tests/peerdb.rs
deleted file mode 100644
index c0662fd7c..000000000
--- a/p2p/src/peer_manager/tests/peerdb.rs
+++ /dev/null
@@ -1,924 +0,0 @@
-// Copyright (c) 2022 RBB S.r.l
-// opensource@mintlayer.org
-// SPDX-License-Identifier: MIT
-// Licensed under the MIT License;
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use std::{sync::Arc, time::Duration};
-
-use crate::{
-    config::{MdnsConfig, NodeType, P2pConfig},
-    net::{
-        mock::{
-            transport::{MockChannelTransport, NoiseTcpTransport, TcpTransportSocket},
-            types::MockPeerId,
-            MockService,
-        },
-        types::{AddrInfo, PeerInfo, PubSubTopic},
-        AsBannableAddress,
-    },
-    peer_manager::peerdb::{Peer, PeerDb},
-    testing_utils::{RandomAddressMaker, TestChannelAddressMaker, TestTcpAddressMaker},
-    NetworkingService,
-};
-
-fn make_peer_info<S>() -> (S::PeerId, PeerInfo<S>)
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-{
-    let peer_id = MockPeerId::new();
-
-    (
-        peer_id,
-        PeerInfo::<S> {
-            peer_id,
-            magic_bytes: [1, 2, 3, 4],
-            version: common::primitives::semver::SemVer::new(0, 1, 0),
-            agent: None,
-            subscriptions: [PubSubTopic::Blocks, PubSubTopic::Transactions].into_iter().collect(),
-        },
-    )
-}
-
-fn add_active_peer<S, A>(peerdb: &mut PeerDb<S>) -> S::PeerId
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let (id, info) = make_peer_info::<S>();
-    peerdb.peer_connected(A::new(), info);
-
-    id
-}
-
-fn add_idle_peer<S, A>(peerdb: &mut PeerDb<S>) -> S::PeerId
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let (id, info) = make_peer_info::<S>();
-    peerdb.register_peer_info(A::new(), info);
-
-    id
-}
-
-fn add_discovered_peer<S>(peerdb: &mut PeerDb<S>) -> S::PeerId
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-{
-    let peer_id = MockPeerId::new();
-    peerdb.peer_discovered(&AddrInfo {
-        peer_id,
-        ip4: vec![],
-        ip6: vec![],
-    });
-
-    peer_id
-}
-
-fn add_banned_peer_address<S>(peerdb: &mut PeerDb<S>, address: S::Address) -> S::PeerId
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-{
-    let (id, info) = make_peer_info::<S>();
-    peerdb.register_peer_info(address, info);
-    peerdb.ban_peer(&id);
-
-    id
-}
-
-fn add_banned_peer<S, A>(peerdb: &mut PeerDb<S>) -> S::PeerId
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    add_banned_peer_address::<S>(peerdb, A::new())
-}
-
-fn num_active_peers<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    assert_eq!(peerdb.idle_peer_count(), 0);
-    assert_eq!(peerdb.active_peer_count(), 0);
-
-    // add three active peers
-    for _ in 0..3 {
-        let _id = add_active_peer::<S, A>(&mut peerdb);
-    }
-    assert_eq!(peerdb.idle_peer_count(), 0);
-    assert_eq!(peerdb.active_peer_count(), 3);
-    assert_eq!(peerdb.peers().len(), 3);
-
-    // add 2 idle peers
-    for _ in 0..2 {
-        let _id = add_idle_peer::<S, A>(&mut peerdb);
-    }
-    assert_eq!(peerdb.idle_peer_count(), 2);
-    assert_eq!(peerdb.active_peer_count(), 3);
-    assert_eq!(peerdb.peers().len(), 5);
-
-    // add 4 discovered peers
-    for _ in 0..2 {
-        let _id = add_discovered_peer::<S>(&mut peerdb);
-    }
-    assert_eq!(peerdb.idle_peer_count(), 4);
-    assert_eq!(peerdb.active_peer_count(), 3);
-    assert_eq!(peerdb.peers().len(), 7);
-
-    // add 5 banned peers
-    for _ in 0..5 {
-        let _id = add_banned_peer_address::<S>(&mut peerdb, A::new());
-    }
-    assert_eq!(peerdb.idle_peer_count(), 4);
-    assert_eq!(peerdb.active_peer_count(), 3);
-    assert_eq!(peerdb.peers().len(), 12);
-}
-
-#[test]
-fn num_active_peers_mock_tcp() {
-    num_active_peers::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn num_active_peers_mock_channels() {
-    num_active_peers::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn num_active_peers_mock_noise() {
-    num_active_peers::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn is_active_peer<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id1 = add_active_peer::<S, A>(&mut peerdb);
-    assert!(peerdb.is_active_peer(&id1));
-
-    let id2 = add_idle_peer::<S, A>(&mut peerdb);
-    assert!(!peerdb.is_active_peer(&id2));
-
-    let id3 = add_discovered_peer::<S>(&mut peerdb);
-    assert!(!peerdb.is_active_peer(&id3));
-
-    let id4 = add_banned_peer::<S, A>(&mut peerdb);
-    assert!(!peerdb.is_active_peer(&id4));
-}
-
-#[test]
-fn is_active_peer_mock_tcp() {
-    is_active_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn is_active_peer_mock_channels() {
-    is_active_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn is_active_peer_mock_noise() {
-    is_active_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn adjust_peer_score_normal_threshold<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id = add_active_peer::<S, A>(&mut peerdb);
-    assert!(peerdb.adjust_peer_score(&id, 100));
-
-    let address = peerdb.peers().get(&id).unwrap().address().unwrap().as_bannable();
-    assert!(peerdb.is_address_banned(&address));
-}
-
-#[test]
-fn adjust_peer_score_normal_threshold_mock_tcp() {
-    adjust_peer_score_normal_threshold::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn adjust_peer_score_normal_threshold_mock_channels() {
-    adjust_peer_score_normal_threshold::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
-    );
-}
-
-#[test]
-fn adjust_peer_score_normal_threshold_mock_noise() {
-    adjust_peer_score_normal_threshold::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn adjust_peer_score_higher_threshold<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let config = P2pConfig {
-        bind_address: "[::1]:3031".to_owned().into(),
-        ban_threshold: 200.into(),
-        ban_duration: Default::default(),
-        outbound_connection_timeout: 10.into(),
-        mdns_config: MdnsConfig::Disabled.into(),
-        node_type: NodeType::Full.into(),
-    };
-    let mut peerdb = PeerDb::<S>::new(Arc::new(config));
-
-    let id = add_active_peer::<S, A>(&mut peerdb);
-    assert!(!peerdb.adjust_peer_score(&id, 100));
-
-    let address = peerdb.peers().get(&id).unwrap().address().unwrap().as_bannable();
-    assert!(!peerdb.is_address_banned(&address));
-}
-
-#[test]
-fn adjust_peer_score_higher_threshold_mock_tcp() {
-    adjust_peer_score_higher_threshold::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn adjust_peer_score_higher_threshold_mock_channels() {
-    adjust_peer_score_higher_threshold::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
-    );
-}
-
-#[test]
-fn adjust_peer_score_higher_threshold_mock_noise() {
-    adjust_peer_score_higher_threshold::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn adjust_peer_score_lower_threshold<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let config = P2pConfig {
-        bind_address: "[::1]:3031".to_owned().into(),
-        ban_threshold: 20.into(),
-        ban_duration: Default::default(),
-        outbound_connection_timeout: 10.into(),
-        mdns_config: MdnsConfig::Disabled.into(),
-        node_type: NodeType::Full.into(),
-    };
-    let mut peerdb = PeerDb::<S>::new(Arc::new(config));
-
-    let id = add_active_peer::<S, A>(&mut peerdb);
-    assert!(peerdb.adjust_peer_score(&id, 30));
-    let address = peerdb.peers().get(&id).unwrap().address().unwrap().as_bannable();
-    assert!(peerdb.is_address_banned(&address));
-}
-
-#[test]
-fn adjust_peer_score_lower_threshold_mock_tcp() {
-    adjust_peer_score_lower_threshold::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn adjust_peer_score_lower_threshold_mock_channels() {
-    adjust_peer_score_lower_threshold::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
-    );
-}
-
-#[test]
-fn adjust_peer_score_lower_threshold_mock_noise() {
-    adjust_peer_score_lower_threshold::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn ban_peer<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    // idle peer
-    let id = add_banned_peer::<S, A>(&mut peerdb);
-    peerdb.ban_peer(&id);
-
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Banned(_))
-    ));
-    let address = peerdb.peers().get(&id).unwrap().address().unwrap().as_bannable();
-    assert!(peerdb.is_address_banned(&address));
-    assert!(!peerdb.available().contains(&id));
-
-    // active peer
-    let id = add_active_peer::<S, A>(&mut peerdb);
-    peerdb.ban_peer(&id);
-
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Banned(_))
-    ));
-    let address = peerdb.peers().get(&id).unwrap().address().unwrap().as_bannable();
-    assert!(peerdb.is_address_banned(&address));
-    assert!(!peerdb.available().contains(&id));
-
-    // discovered peer
-    let id = add_discovered_peer::<S>(&mut peerdb);
-    peerdb.ban_peer(&id);
-
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Banned(_))
-    ));
-    assert!(!peerdb.available().contains(&id));
-}
-
-#[test]
-fn ban_peer_mock_tcp() {
-    ban_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn ban_peer_mock_channels() {
-    ban_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn ban_peer_mock_noise() {
-    ban_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_disconnected_unknown<S>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    // unknown peer doesn't cause any changes
-    assert_eq!(peerdb.peers().len(), 0);
-    peerdb.peer_disconnected(&MockPeerId::new());
-    assert_eq!(peerdb.peers().len(), 0);
-}
-
-#[test]
-fn peer_disconnected_unknown_mock_tcp() {
-    peer_disconnected_unknown::<MockService<TcpTransportSocket>>();
-}
-
-#[test]
-fn peer_disconnected_unknown_mock_channels() {
-    peer_disconnected_unknown::<MockService<MockChannelTransport>>();
-}
-
-#[test]
-fn peer_disconnected_unknown_mock_noise() {
-    peer_disconnected_unknown::<MockService<NoiseTcpTransport>>();
-}
-
-fn peer_disconnected_idle<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    // idle peer
-    let id = add_idle_peer::<S, A>(&mut peerdb);
-    peerdb.peer_disconnected(&id);
-    assert!(std::matches!(peerdb.peers().get(&id), Some(Peer::Idle(_))));
-    assert!(peerdb.available().contains(&id));
-}
-
-#[test]
-fn peer_disconnected_idle_mock_tcp() {
-    peer_disconnected_idle::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_disconnected_idle_mock_channels() {
-    peer_disconnected_idle::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_disconnected_idle_mock_noise() {
-    peer_disconnected_idle::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_disconnected_discovered<S>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id = add_discovered_peer::<S>(&mut peerdb);
-    peerdb.peer_disconnected(&id);
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Discovered(_))
-    ));
-    assert!(peerdb.available().contains(&id));
-}
-
-#[test]
-fn peer_disconnected_discovered_mock_tcp() {
-    peer_disconnected_discovered::<MockService<TcpTransportSocket>>();
-}
-
-#[test]
-fn peer_disconnected_discovered_mock_channels() {
-    peer_disconnected_discovered::<MockService<MockChannelTransport>>();
-}
-
-#[test]
-fn peer_disconnected_discovered_mock_noise() {
-    peer_disconnected_discovered::<MockService<NoiseTcpTransport>>();
-}
-
-fn peer_disconnected_banned<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id = add_banned_peer::<S, A>(&mut peerdb);
-    peerdb.peer_disconnected(&id);
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Banned(_))
-    ));
-}
-
-#[test]
-fn peer_disconnected_banned_mock_tcp() {
-    peer_disconnected_banned::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_disconnected_banned_mock_channels() {
-    peer_disconnected_banned::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_disconnected_banned_mock_noise() {
-    peer_disconnected_banned::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_disconnected_active<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id = add_active_peer::<S, A>(&mut peerdb);
-    peerdb.peer_disconnected(&id);
-    assert!(std::matches!(peerdb.peers().get(&id), Some(Peer::Idle(_))));
-    assert!(peerdb.available().contains(&id));
-}
-
-#[test]
-fn peer_disconnected_active_mock_tcp() {
-    peer_disconnected_active::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_disconnected_active_mock_channels() {
-    peer_disconnected_active::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_disconnected_active_mock_noise() {
-    peer_disconnected_active::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_connected_discovered<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-    let remote_addr = A::new();
-
-    // register information for a discovered peer
-    let (peer_id, info) = make_peer_info::<S>();
-    peerdb.peer_discovered(&AddrInfo {
-        peer_id,
-        ip4: vec![],
-        ip6: vec![remote_addr.clone(), A::new()],
-    });
-    assert!(std::matches!(
-        peerdb.peers().get(&peer_id),
-        Some(Peer::Discovered(_))
-    ));
-    assert!(peerdb.take_best_peer_addr().unwrap().is_some());
-
-    assert!(peerdb.pending().contains_key(&remote_addr));
-    peerdb.peer_connected(remote_addr.clone(), info);
-
-    assert!(std::matches!(
-        peerdb.peers().get(&peer_id),
-        Some(Peer::Active(_))
-    ));
-    assert!(!peerdb.available().contains(&peer_id));
-    assert!(!peerdb.pending().contains_key(&remote_addr));
-}
-
-#[test]
-fn peer_connected_discovered_mock_tcp() {
-    peer_connected_discovered::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_connected_discovered_mock_channels() {
-    peer_connected_discovered::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_connected_discovered_mock_noise() {
-    peer_connected_discovered::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_connected_idle<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-    let remote_addr = A::new();
-
-    let (id, info) = make_peer_info::<S>();
-    let (_id, mut new_info) = make_peer_info::<S>();
-    new_info.peer_id = id;
-
-    peerdb.register_peer_info(A::new(), info);
-    assert!(peerdb.available().contains(&id));
-
-    peerdb.peer_connected(remote_addr.clone(), new_info);
-
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Active(_))
-    ));
-    assert!(!peerdb.available().contains(&id));
-    assert!(!peerdb.pending().contains_key(&remote_addr));
-}
-
-#[test]
-fn peer_connected_idle_mock_tcp() {
-    peer_connected_idle::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_connected_idle_mock_channels() {
-    peer_connected_idle::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_connected_idle_mock_noise() {
-    peer_connected_idle::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_connected_unknown<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-    let remote_addr = A::new();
-
-    let (id, info) = make_peer_info::<S>();
-
-    assert!(!peerdb.available().contains(&id));
-    assert!(!peerdb.peers().contains_key(&id));
-    assert!(!peerdb.pending().contains_key(&remote_addr));
-
-    peerdb.peer_connected(remote_addr.clone(), info);
-
-    assert!(std::matches!(
-        peerdb.peers().get(&id),
-        Some(Peer::Active(_))
-    ));
-    assert!(!peerdb.available().contains(&id));
-    assert!(!peerdb.pending().contains_key(&remote_addr));
-}
-
-#[test]
-fn peer_connected_unknown_mock_tcp() {
-    peer_connected_unknown::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_connected_unknown_mock_channels() {
-    peer_connected_unknown::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_connected_unknown_mock_noise() {
-    peer_connected_unknown::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_connected_active<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    // active peer
-    let id1 = add_active_peer::<S, A>(&mut peerdb);
-    let (_id, mut info1) = make_peer_info::<S>();
-    info1.peer_id = id1;
-
-    peerdb.peer_connected(A::new(), info1);
-    assert!(std::matches!(
-        peerdb.peers().get(&id1),
-        Some(Peer::Active(_))
-    ));
-    assert!(!peerdb.available().contains(&id1));
-}
-
-#[test]
-fn peer_connected_active_mock_tcp() {
-    peer_connected_active::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_connected_active_mock_channels() {
-    peer_connected_active::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_connected_active_mock_noise() {
-    peer_connected_active::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn peer_connected_banned<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id2 = add_banned_peer::<S, A>(&mut peerdb);
-    let (_id, mut info2) = make_peer_info::<S>();
-    info2.peer_id = id2;
-
-    peerdb.peer_connected(A::new(), info2);
-    assert!(std::matches!(
-        peerdb.peers().get(&id2),
-        Some(Peer::Banned(_))
-    ));
-    assert!(!peerdb.available().contains(&id2));
-}
-
-#[test]
-fn peer_connected_banned_mock_tcp() {
-    peer_connected_banned::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn peer_connected_banned_mock_channels() {
-    peer_connected_banned::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn peer_connected_banned_mock_noise() {
-    peer_connected_banned::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn register_peer_info_discovered_peer<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-    let remote_addr = A::new();
-
-    // register information for a discovered peer
-    let (peer_id, info) = make_peer_info::<S>();
-    peerdb.peer_discovered(&AddrInfo {
-        peer_id,
-        ip4: vec![],
-        ip6: vec![remote_addr.clone(), A::new()],
-    });
-    assert!(std::matches!(
-        peerdb.peers().get(&peer_id),
-        Some(Peer::Discovered(_))
-    ));
-    assert!(peerdb.take_best_peer_addr().unwrap().is_some());
-
-    assert!(peerdb.pending().get(&remote_addr).is_some());
-    peerdb.register_peer_info(remote_addr, info);
-    assert!(std::matches!(
-        peerdb.peers().get(&peer_id),
-        Some(Peer::Idle(_))
-    ));
-    assert!(peerdb.available().contains(&peer_id));
-}
-
-#[test]
-fn register_peer_info_discovered_peer_mock_tcp() {
-    register_peer_info_discovered_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_discovered_peer_mock_channels() {
-    register_peer_info_discovered_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>(
-    );
-}
-
-#[test]
-fn register_peer_info_discovered_peer_mock_noise() {
-    register_peer_info_discovered_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-// for idle peers the information is updated
-fn register_peer_info_idle_peer<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id = add_idle_peer::<S, A>(&mut peerdb);
-    if let Some(Peer::Idle(ctx)) = peerdb.peers().get(&id) {
-        assert_eq!(ctx.info.magic_bytes, [1, 2, 3, 4]);
-    } else {
-        panic!("invalid peer type");
-    }
-
-    let (_id, mut info) = make_peer_info::<S>();
-    info.peer_id = id;
-    info.magic_bytes = [13, 37, 13, 38];
-    peerdb.register_peer_info(A::new(), info);
-
-    // verify that the information has been updated for an idle peer
-    if let Some(Peer::Idle(ctx)) = peerdb.peers().get(&id) {
-        assert_eq!(ctx.info.magic_bytes, [13, 37, 13, 38]);
-    } else {
-        panic!("invalid peer type");
-    }
-    assert!(peerdb.available().contains(&id));
-}
-
-#[test]
-fn register_peer_info_idle_peer_mock_tcp() {
-    register_peer_info_idle_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_idle_peer_mock_channels() {
-    register_peer_info_idle_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_idle_peer_mock_noise() {
-    register_peer_info_idle_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn register_peer_info_unknown_peer<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let (id, info) = make_peer_info::<S>();
-    assert!(peerdb.peers().get(&id).is_none());
-    peerdb.register_peer_info(A::new(), info);
-    assert!(std::matches!(peerdb.peers().get(&id), Some(Peer::Idle(_))));
-    assert!(peerdb.available().contains(&id));
-}
-
-#[test]
-fn register_peer_info_unknown_peer_mock_tcp() {
-    register_peer_info_unknown_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_unknown_peer_mock_channels() {
-    register_peer_info_unknown_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_unknown_peer_mock_noise() {
-    register_peer_info_unknown_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn register_peer_info_active<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    let id1 = add_active_peer::<S, A>(&mut peerdb);
-    let (_id, info1) = make_peer_info::<S>();
-
-    peerdb.register_peer_info(A::new(), info1);
-    assert!(std::matches!(
-        peerdb.peers().get(&id1),
-        Some(Peer::Active(_))
-    ));
-    assert!(!peerdb.available().contains(&id1));
-}
-
-#[test]
-fn register_peer_info_active_mock_tcp() {
-    register_peer_info_active::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_active_mock_channels() {
-    register_peer_info_active::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_active_mock_noise() {
-    register_peer_info_active::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-fn register_peer_info_banned<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig::default()));
-
-    // banned peer
-    let id2 = add_banned_peer::<S, A>(&mut peerdb);
-    let (_id, info2) = make_peer_info::<S>();
-
-    peerdb.register_peer_info(A::new(), info2);
-    assert!(std::matches!(
-        peerdb.peers().get(&id2),
-        Some(Peer::Banned(_))
-    ));
-    assert!(!peerdb.available().contains(&id2));
-}
-
-#[test]
-fn register_peer_info_banned_mock_tcp() {
-    register_peer_info_banned::<MockService<TcpTransportSocket>, TestTcpAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_banned_mock_channels() {
-    register_peer_info_banned::<MockService<MockChannelTransport>, TestChannelAddressMaker>();
-}
-
-#[test]
-fn register_peer_info_banned_mock_noise() {
-    register_peer_info_banned::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>();
-}
-
-async fn unban_peer<S, A>()
-where
-    S: NetworkingService<PeerId = MockPeerId>,
-    A: RandomAddressMaker<Address = S::Address>,
-{
-    let mut peerdb = PeerDb::<S>::new(Arc::new(P2pConfig {
-        bind_address: Default::default(),
-        ban_threshold: Default::default(),
-        ban_duration: Duration::from_secs(2).into(),
-        outbound_connection_timeout: Default::default(),
-        mdns_config: Default::default(),
-        node_type: Default::default(),
-    }));
-
-    let id = add_banned_peer::<S, A>(&mut peerdb);
-    let address = peerdb.peers().get(&id).unwrap().address().unwrap().as_bannable();
-
-    tokio::time::sleep(Duration::from_secs(1)).await;
-    assert!(peerdb.is_address_banned(&address));
-
-    tokio::time::sleep(Duration::from_secs(2)).await;
-    assert!(!peerdb.is_address_banned(&address));
-}
-
-#[tokio::test]
-async fn unban_peer_mock_tcp() {
-    unban_peer::<MockService<TcpTransportSocket>, TestTcpAddressMaker>().await;
-}
-
-#[tokio::test]
-async fn unban_peer_mock_channels() {
-    unban_peer::<MockService<MockChannelTransport>, TestChannelAddressMaker>().await;
-}
-
-#[tokio::test]
-async fn unban_peer_mock_noise() {
-    unban_peer::<MockService<NoiseTcpTransport>, TestTcpAddressMaker>().await;
-}
diff --git a/p2p/src/sync/tests/mod.rs b/p2p/src/sync/tests/mod.rs
index 6bbd74860..b26a621fb 100644
--- a/p2p/src/sync/tests/mod.rs
+++ b/p2p/src/sync/tests/mod.rs
@@ -70,7 +70,8 @@ where
 
     let chain_config = Arc::new(common::chain::config::create_unit_test_config());
     let p2p_config = Arc::new(P2pConfig {
-        bind_address: "[::1]:3031".to_owned().into(),
+        bind_addresses: vec!["/ip6/::1/tcp/3031".to_owned()],
+        added_nodes: Vec::new(),
         ban_threshold: 100.into(),
         ban_duration: Default::default(),
         outbound_connection_timeout: 10.into(),
@@ -79,7 +80,7 @@ where
     });
     let (conn, sync) = T::start(
         transport,
-        addr,
+        vec![addr],
         Arc::clone(&chain_config),
         Arc::clone(&p2p_config),
     )
diff --git a/p2p/src/sync/tests/request_response.rs b/p2p/src/sync/tests/request_response.rs
index 3604f94be..57ccee6b6 100644
--- a/p2p/src/sync/tests/request_response.rs
+++ b/p2p/src/sync/tests/request_response.rs
@@ -28,10 +28,10 @@ use crate::{
         },
         types::SyncingEvent,
     },
-    peer_manager::helpers::connect_services,
     sync::tests::make_sync_manager,
     testing_utils::{
-        TestTransportChannel, TestTransportMaker, TestTransportNoise, TestTransportTcp,
+        connect_services, TestTransportChannel, TestTransportMaker, TestTransportNoise,
+        TestTransportTcp,
     },
     ConnectivityService, NetworkingService, SyncingMessagingService,
 };
diff --git a/p2p/src/testing_utils.rs b/p2p/src/testing_utils.rs
index 7b87490c7..7689ec7d4 100644
--- a/p2p/src/testing_utils.rs
+++ b/p2p/src/testing_utils.rs
@@ -13,12 +13,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::net::{IpAddr, Ipv6Addr, SocketAddr};
+#![allow(clippy::unwrap_used)]
 
-use crypto::random::{make_pseudo_rng, Rng};
+use std::{
+    net::{IpAddr, Ipv6Addr, SocketAddr},
+    time::Duration,
+};
 
-use crate::net::mock::transport::{
-    MockChannelTransport, NoiseEncryptionAdapter, NoiseTcpTransport, TcpTransportSocket,
+use crypto::random::{make_pseudo_rng, Rng};
+use tokio::time::timeout;
+
+use crate::net::{
+    mock::transport::{
+        MockChannelTransport, NoiseEncryptionAdapter, NoiseTcpTransport, TcpTransportSocket,
+    },
+    types::{ConnectivityEvent, PeerInfo},
+    ConnectivityService, NetworkingService,
 };
 
 /// An interface for creating transports and addresses used in tests.
@@ -53,7 +63,7 @@ impl TestTransportMaker for TestTransportTcp {
     }
 
     fn make_address() -> Self::Address {
-        "[::1]:0".parse().expect("valid address")
+        "[::1]:0".parse().unwrap()
     }
 }
 
@@ -62,7 +72,7 @@ pub struct TestTransportChannel {}
 impl TestTransportMaker for TestTransportChannel {
     type Transport = MockChannelTransport;
 
-    type Address = u64;
+    type Address = u32;
 
     fn make_transport() -> Self::Transport {
         MockChannelTransport::new()
@@ -124,10 +134,71 @@ impl RandomAddressMaker for TestTcpAddressMaker {
 pub struct TestChannelAddressMaker {}
 
 impl RandomAddressMaker for TestChannelAddressMaker {
-    type Address = u64;
+    type Address = u32;
 
     fn new() -> Self::Address {
         let mut rng = make_pseudo_rng();
         rng.gen()
     }
 }
+
+/// Can be used in tests only, will panic in case of errors
+pub async fn connect_services<T>(
+    conn1: &mut T::ConnectivityHandle,
+    conn2: &mut T::ConnectivityHandle,
+) -> (T::Address, PeerInfo<T>, PeerInfo<T>)
+where
+    T: NetworkingService + std::fmt::Debug,
+    T::ConnectivityHandle: ConnectivityService<T>,
+{
+    let addr = timeout(Duration::from_secs(5), conn2.local_addresses())
+        .await
+        .expect("local address fetch not to timeout")
+        .unwrap();
+    conn1.connect(addr[0].clone()).await.expect("dial to succeed");
+
+    let (address, peer_info1) = match timeout(Duration::from_secs(5), conn2.poll_next()).await {
+        Ok(event) => match event.unwrap() {
+            ConnectivityEvent::InboundAccepted { address, peer_info } => (address, peer_info),
+            event => panic!("expected `InboundAccepted`, got {event:?}"),
+        },
+        Err(_err) => panic!("did not receive `InboundAccepted` in time"),
+    };
+
+    let peer_info2 = match timeout(Duration::from_secs(5), conn1.poll_next()).await {
+        Ok(event) => match event.unwrap() {
+            ConnectivityEvent::OutboundAccepted {
+                address: _,
+                peer_info,
+            } => peer_info,
+            event => panic!("expected `OutboundAccepted`, got {event:?}"),
+        },
+        Err(_err) => panic!("did not receive `OutboundAccepted` in time"),
+    };
+
+    (address, peer_info1, peer_info2)
+}
+
+/// Return first event that is accepted by predicate.
+///
+/// Used to skip events that are not of interest or that are different between backends
+/// (for example ConnectivityEvent::Discovered).
+/// Can be used in tests only, will panic in case of errors.
+pub async fn filter_connectivity_event<T, F>(
+    conn: &mut T::ConnectivityHandle,
+    predicate: F,
+) -> crate::Result<ConnectivityEvent<T>>
+where
+    T: NetworkingService,
+    T::ConnectivityHandle: ConnectivityService<T>,
+    F: Fn(&crate::Result<ConnectivityEvent<T>>) -> bool,
+{
+    loop {
+        let result = timeout(Duration::from_secs(10), conn.poll_next())
+            .await
+            .expect("unexpected timeout receiving connectivity event");
+        if predicate(&result) {
+            return result;
+        }
+    }
+}
diff --git a/p2p/tests/block_announcement.rs b/p2p/tests/block_announcement.rs
index 91664bc1e..165e1e190 100644
--- a/p2p/tests/block_announcement.rs
+++ b/p2p/tests/block_announcement.rs
@@ -21,7 +21,8 @@ use common::{
 };
 
 use p2p::testing_utils::{
-    TestTransportChannel, TestTransportMaker, TestTransportNoise, TestTransportTcp,
+    connect_services, TestTransportChannel, TestTransportMaker, TestTransportNoise,
+    TestTransportTcp,
 };
 use p2p::{
     error::{P2pError, PublishError},
@@ -34,7 +35,6 @@ use p2p::{
         types::SyncingEvent,
         ConnectivityService, NetworkingService, SyncingMessagingService,
     },
-    peer_manager::helpers::connect_services,
 };
 
 // Test announcements with multiple peers and verify that the message validation is done and peers
@@ -49,7 +49,7 @@ where
     let config = Arc::new(common::chain::config::create_mainnet());
     let (mut conn1, mut sync1) = S::start(
         A::make_transport(),
-        A::make_address(),
+        vec![A::make_address()],
         Arc::clone(&config),
         Default::default(),
     )
@@ -60,7 +60,7 @@ where
         let mut peers = futures::future::join_all((0..3).map(|_| async {
             let res = S::start(
                 A::make_transport(),
-                A::make_address(),
+                vec![A::make_address()],
                 Arc::clone(&config),
                 Default::default(),
             )
diff --git a/test/functional/test_framework/.mintlayer/config.toml b/test/functional/test_framework/.mintlayer/config.toml
index ed272c36e..1e510857f 100644
--- a/test/functional/test_framework/.mintlayer/config.toml
+++ b/test/functional/test_framework/.mintlayer/config.toml
@@ -5,7 +5,7 @@ max_db_commit_attempts = 10
 max_orphan_blocks = 512
 
 [p2p]
-bind_address = "[::1]:3031"
+bind_address = ["[::1]:3031"]
 ban_threshold = 100
 outbound_connection_timeout = 10
 
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 516cf26b3..077445d7e 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -576,7 +576,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
 
         connected = self.nodes[b].p2p_get_connected_peers()
         addr_a = p2p_url(a)
-        peer_id = next(item["peer_id"] for item in connected if item["addr"] == addr_a)
+        peer_id = next(item["peer_id"] for item in connected if item["address"] == addr_a)
         ret = self.nodes[b].p2p_disconnect(peer_id)
 
         wait_until_helper(lambda: