1use std::{
5 num::NonZeroUsize,
6 sync::{Arc, LazyLock},
7};
8
9use super::{
10 PeerManager,
11 discovery::{DerivedDiscoveryBehaviourEvent, DiscoveryEvent, PeerInfo},
12};
13use crate::libp2p_bitswap::BitswapBehaviour;
14use crate::utils::{encoding::blake2b_256, version::FOREST_VERSION_STRING};
15use crate::{
16 libp2p::{
17 chain_exchange::ChainExchangeBehaviour,
18 config::Libp2pConfig,
19 discovery::{DiscoveryBehaviour, DiscoveryConfig},
20 gossip_params::{build_peer_score_params, build_peer_score_threshold},
21 hello::HelloBehaviour,
22 },
23 networks::GenesisNetworkName,
24};
25use ahash::{HashMap, HashSet};
26use libp2p::{
27 Multiaddr, allow_block_list, connection_limits,
28 gossipsub::{
29 self, IdentTopic as Topic, MaxCountSubscriptionFilter, MessageAuthenticity, MessageId,
30 PublishError, SubscriptionError, ValidationMode, WhitelistSubscriptionFilter,
31 },
32 identity::{Keypair, PeerId},
33 kad::QueryId,
34 metrics::{Metrics, Recorder},
35 ping, request_response,
36 swarm::NetworkBehaviour,
37};
38use tracing::info;
39
40#[derive(NetworkBehaviour)]
43pub(in crate::libp2p) struct ForestBehaviour {
44 connection_limits: connection_limits::Behaviour,
47 pub(super) blocked_peers: allow_block_list::Behaviour<allow_block_list::BlockedPeers>,
48 pub(super) discovery: DiscoveryBehaviour,
49 ping: ping::Behaviour,
50 gossipsub: Gossipsub,
51 pub(super) hello: HelloBehaviour,
52 pub(super) chain_exchange: ChainExchangeBehaviour,
53 pub(super) bitswap: BitswapBehaviour,
54}
55
56impl Recorder<ForestBehaviourEvent> for Metrics {
57 fn record(&self, event: &ForestBehaviourEvent) {
58 match event {
59 ForestBehaviourEvent::Gossipsub(e) => self.record(e),
60 ForestBehaviourEvent::Ping(ping_event) => self.record(ping_event),
61 ForestBehaviourEvent::Discovery(DiscoveryEvent::Discovery(e)) => match e.as_ref() {
62 DerivedDiscoveryBehaviourEvent::Identify(e) => self.record(e),
63 DerivedDiscoveryBehaviourEvent::Kademlia(e) => self.record(e),
64 _ => {}
65 },
66 _ => {}
67 }
68 }
69}
70
71pub(in crate::libp2p) type Gossipsub = gossipsub::Behaviour<
72 gossipsub::IdentityTransform,
73 MaxCountSubscriptionFilter<WhitelistSubscriptionFilter>,
74>;
75
76const MAX_SUBSCRIPTIONS_PER_REQUEST: usize = 100;
79
80pub(in crate::libp2p) fn build_subscription_filter(
82 network_name: &GenesisNetworkName,
83) -> MaxCountSubscriptionFilter<WhitelistSubscriptionFilter> {
84 let allowed: Vec<_> = crate::libp2p::pubsub_topics(network_name)
85 .map(|t| t.hash())
86 .collect();
87 MaxCountSubscriptionFilter {
88 max_subscribed_topics: allowed.len(),
91 max_subscriptions_per_request: MAX_SUBSCRIPTIONS_PER_REQUEST,
92 filter: WhitelistSubscriptionFilter(allowed.into_iter().collect()),
93 }
94}
95
96pub(in crate::libp2p) fn build_gossipsub(
97 local_key: &Keypair,
98 network_name: &GenesisNetworkName,
99) -> anyhow::Result<Gossipsub> {
100 let mut gs_config_builder = gossipsub::ConfigBuilder::default();
101 gs_config_builder.max_transmit_size(1 << 20);
102 gs_config_builder.validation_mode(ValidationMode::Strict);
103 gs_config_builder.message_id_fn(|msg: &gossipsub::Message| {
104 let s = blake2b_256(&msg.data);
105 MessageId::from(s)
106 });
107
108 let gossipsub_config = gs_config_builder.build()?;
109 let mut gossipsub = Gossipsub::new_with_subscription_filter(
110 MessageAuthenticity::Signed(local_key.clone()),
111 gossipsub_config,
112 build_subscription_filter(network_name),
113 )
114 .map_err(anyhow::Error::msg)?;
115
116 gossipsub
117 .with_peer_score(
118 build_peer_score_params(network_name),
119 build_peer_score_threshold(),
120 )
121 .map_err(anyhow::Error::msg)?;
122
123 Ok(gossipsub)
124}
125
126impl ForestBehaviour {
127 pub async fn new(
128 local_key: &Keypair,
129 config: &Libp2pConfig,
130 network_name: &GenesisNetworkName,
131 peer_manager: Arc<PeerManager>,
132 ) -> anyhow::Result<Self> {
133 const MAX_ESTABLISHED_PER_PEER: u32 = 4;
134 static MAX_CONCURRENT_REQUEST_RESPONSE_STREAMS_PER_PEER: LazyLock<usize> = LazyLock::new(
135 || {
136 std::env::var("FOREST_MAX_CONCURRENT_REQUEST_RESPONSE_STREAMS_PER_PEER")
137 .ok()
138 .map(|it|
139 it.parse::<NonZeroUsize>()
140 .expect("Failed to parse the `FOREST_MAX_CONCURRENT_REQUEST_RESPONSE_STREAMS_PER_PEER` environment variable value, a positive integer is expected.")
141 .get())
142 .unwrap_or(10)
143 },
144 );
145
146 let max_concurrent_request_response_streams = (config.target_peer_count as usize)
147 .saturating_mul(*MAX_CONCURRENT_REQUEST_RESPONSE_STREAMS_PER_PEER);
148
149 let gossipsub = build_gossipsub(local_key, network_name)?;
150
151 let bitswap = BitswapBehaviour::new(
152 &[
153 "/chain/ipfs/bitswap/1.2.0",
154 "/chain/ipfs/bitswap/1.1.0",
155 "/chain/ipfs/bitswap/1.0.0",
156 "/chain/ipfs/bitswap",
157 ],
158 request_response::Config::default()
159 .with_max_concurrent_streams(max_concurrent_request_response_streams),
160 );
161 crate::libp2p_bitswap::register_metrics(&mut crate::metrics::collector_registry());
162
163 let discovery = DiscoveryConfig::new(local_key.public(), network_name)
164 .with_mdns(config.mdns)
165 .with_kademlia(config.kademlia)
166 .with_user_defined(config.bootstrap_peers.clone())
167 .await?
168 .target_peer_count(u64::from(config.target_peer_count))
169 .finish()?;
170
171 let connection_limits = connection_limits::Behaviour::new(
172 connection_limits::ConnectionLimits::default()
173 .with_max_pending_incoming(Some(
174 config
175 .target_peer_count
176 .saturating_mul(MAX_ESTABLISHED_PER_PEER),
177 ))
178 .with_max_pending_outgoing(Some(
179 config
180 .target_peer_count
181 .saturating_mul(MAX_ESTABLISHED_PER_PEER),
182 ))
183 .with_max_established_incoming(Some(
184 config
185 .target_peer_count
186 .saturating_mul(MAX_ESTABLISHED_PER_PEER),
187 ))
188 .with_max_established_outgoing(Some(
189 config
190 .target_peer_count
191 .saturating_mul(MAX_ESTABLISHED_PER_PEER),
192 ))
193 .with_max_established_per_peer(Some(MAX_ESTABLISHED_PER_PEER)),
194 );
195
196 info!("libp2p Forest version: {}", FOREST_VERSION_STRING.as_str());
197 Ok(ForestBehaviour {
198 gossipsub,
199 discovery,
200 ping: Default::default(),
201 connection_limits,
202 blocked_peers: Default::default(),
203 bitswap,
204 hello: HelloBehaviour::new(
205 request_response::Config::default()
206 .with_max_concurrent_streams(max_concurrent_request_response_streams),
207 peer_manager,
208 ),
209 chain_exchange: ChainExchangeBehaviour::new(
210 request_response::Config::default()
211 .with_max_concurrent_streams(max_concurrent_request_response_streams),
212 ),
213 })
214 }
215
216 pub fn bootstrap(&mut self) -> anyhow::Result<QueryId> {
218 self.discovery.bootstrap()
219 }
220
221 pub fn publish(
223 &mut self,
224 topic: Topic,
225 data: impl Into<Vec<u8>>,
226 ) -> Result<MessageId, PublishError> {
227 self.gossipsub.publish(topic, data)
228 }
229
230 pub fn subscribe(&mut self, topic: &Topic) -> Result<bool, SubscriptionError> {
232 self.gossipsub.subscribe(topic)
233 }
234
235 pub fn peers(&self) -> &HashSet<PeerId> {
237 self.discovery.peers()
238 }
239
240 pub fn peer_addresses(&self) -> HashMap<PeerId, HashSet<Multiaddr>> {
242 self.discovery.peer_addresses()
243 }
244
245 pub fn peer_info(&self, peer_id: &PeerId) -> Option<&PeerInfo> {
246 self.discovery.peer_info(peer_id)
247 }
248}