referrerpolicy=no-referrer-when-downgrade

sc_network_sync/
block_request_handler.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Substrate.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// Substrate is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// Substrate is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with Substrate. If not, see <https://www.gnu.org/licenses/>.
17
18//! Helper for handling (i.e. answering) block requests from a remote peer via the
19//! `crate::request_responses::RequestResponsesBehaviour`.
20
21use crate::{
22	block_relay_protocol::{BlockDownloader, BlockRelayParams, BlockResponseError, BlockServer},
23	schema::v1::{
24		block_request::FromBlock as FromBlockSchema, BlockRequest as BlockRequestSchema,
25		BlockResponse as BlockResponseSchema, BlockResponse, Direction,
26	},
27	service::network::NetworkServiceHandle,
28	LOG_TARGET,
29};
30
31use codec::{Decode, DecodeAll, Encode};
32use futures::{channel::oneshot, stream::StreamExt};
33use log::debug;
34use prost::Message;
35use schnellru::{ByLength, LruMap};
36
37use sc_client_api::BlockBackend;
38use sc_network::{
39	config::ProtocolId,
40	request_responses::{IfDisconnected, IncomingRequest, OutgoingResponse, RequestFailure},
41	service::traits::RequestResponseConfig,
42	types::ProtocolName,
43	NetworkBackend, MAX_RESPONSE_SIZE,
44};
45use sc_network_common::sync::message::{BlockAttributes, BlockData, BlockRequest, FromBlock};
46use sc_network_types::PeerId;
47use sp_blockchain::HeaderBackend;
48use sp_runtime::{
49	generic::BlockId,
50	traits::{Block as BlockT, Header, One, Zero},
51};
52
53use std::{
54	cmp::min,
55	hash::{Hash, Hasher},
56	sync::Arc,
57	time::{Duration, Instant},
58};
59
60/// Maximum blocks per response.
61pub(crate) const MAX_BLOCKS_IN_RESPONSE: usize = 128;
62
63const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2;
64
65/// Reset duplicate counts this long after the first fulfilled response, allowing legitimate
66/// retries.
67const SAME_REQUEST_WINDOW: Duration = Duration::from_secs(60);
68
69mod rep {
70	use sc_network::ReputationChange as Rep;
71
72	/// Reputation change when a peer sent us the same request multiple times.
73	pub const SAME_REQUEST: Rep = Rep::new(-(1 << 12), "Same block request multiple times");
74
75	/// Reputation change when a peer sent us the same "small" request multiple times.
76	pub const SAME_SMALL_REQUEST: Rep =
77		Rep::new(-(1 << 10), "same small block request multiple times");
78}
79
80/// Generates a `RequestResponseProtocolConfig` for the block request protocol,
81/// refusing incoming requests.
82pub fn generate_protocol_config<
83	Hash: AsRef<[u8]>,
84	B: BlockT,
85	N: NetworkBackend<B, <B as BlockT>::Hash>,
86>(
87	protocol_id: &ProtocolId,
88	genesis_hash: Hash,
89	fork_id: Option<&str>,
90	inbound_queue: async_channel::Sender<IncomingRequest>,
91) -> N::RequestResponseProtocolConfig {
92	N::request_response_config(
93		generate_protocol_name(genesis_hash, fork_id).into(),
94		std::iter::once(generate_legacy_protocol_name(protocol_id).into()).collect(),
95		1024 * 1024,
96		MAX_RESPONSE_SIZE,
97		Duration::from_secs(20),
98		Some(inbound_queue),
99	)
100}
101
102/// Generate the block protocol name from the genesis hash and fork id.
103fn generate_protocol_name<Hash: AsRef<[u8]>>(genesis_hash: Hash, fork_id: Option<&str>) -> String {
104	let genesis_hash = genesis_hash.as_ref();
105	if let Some(fork_id) = fork_id {
106		format!("/{}/{}/sync/2", array_bytes::bytes2hex("", genesis_hash), fork_id)
107	} else {
108		format!("/{}/sync/2", array_bytes::bytes2hex("", genesis_hash))
109	}
110}
111
112/// Generate the legacy block protocol name from chain specific protocol identifier.
113fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String {
114	format!("/{}/sync/2", protocol_id.as_ref())
115}
116
117/// The key of [`BlockRequestHandler::seen_requests`].
118#[derive(Eq, PartialEq, Clone)]
119struct SeenRequestsKey<B: BlockT> {
120	peer: PeerId,
121	from: BlockId<B>,
122	max_blocks: usize,
123	direction: Direction,
124	attributes: BlockAttributes,
125	support_multiple_justifications: bool,
126}
127
128#[allow(clippy::derived_hash_with_manual_eq)]
129impl<B: BlockT> Hash for SeenRequestsKey<B> {
130	fn hash<H: Hasher>(&self, state: &mut H) {
131		self.peer.hash(state);
132		self.max_blocks.hash(state);
133		self.direction.hash(state);
134		self.attributes.hash(state);
135		self.support_multiple_justifications.hash(state);
136		match self.from {
137			BlockId::Hash(h) => h.hash(state),
138			BlockId::Number(n) => n.hash(state),
139		}
140	}
141}
142
143/// The value of [`BlockRequestHandler::seen_requests`].
144enum SeenRequestsValue {
145	/// First time we have seen the request.
146	First,
147	/// Requests seen since the first fulfilled response.
148	Fulfilled { requests: usize, since: Instant },
149}
150
151/// The full block server implementation of [`BlockServer`]. It handles
152/// the incoming block requests from a remote peer.
153pub struct BlockRequestHandler<B: BlockT, Client> {
154	client: Arc<Client>,
155	request_receiver: async_channel::Receiver<IncomingRequest>,
156	/// Maps from request to number of times we have seen this request.
157	///
158	/// This is used to check if a peer is spamming us with the same request.
159	seen_requests: LruMap<SeenRequestsKey<B>, SeenRequestsValue>,
160}
161
162impl<B, Client> BlockRequestHandler<B, Client>
163where
164	B: BlockT,
165	Client: HeaderBackend<B> + BlockBackend<B> + Send + Sync + 'static,
166{
167	/// Create a new [`BlockRequestHandler`].
168	pub fn new<N: NetworkBackend<B, <B as BlockT>::Hash>>(
169		network: NetworkServiceHandle,
170		protocol_id: &ProtocolId,
171		fork_id: Option<&str>,
172		client: Arc<Client>,
173		num_peer_hint: usize,
174	) -> BlockRelayParams<B, N> {
175		// Reserve enough request slots for one request per peer when we are at the maximum
176		// number of peers.
177		let capacity = std::cmp::max(num_peer_hint, 1);
178		let (tx, request_receiver) = async_channel::bounded(capacity);
179
180		let protocol_config = generate_protocol_config::<_, B, N>(
181			protocol_id,
182			client
183				.block_hash(0u32.into())
184				.ok()
185				.flatten()
186				.expect("Genesis block exists; qed"),
187			fork_id,
188			tx,
189		);
190
191		let capacity = ByLength::new(num_peer_hint.max(1) as u32 * 2);
192		let seen_requests = LruMap::new(capacity);
193
194		BlockRelayParams {
195			server: Box::new(Self { client, request_receiver, seen_requests }),
196			downloader: Arc::new(FullBlockDownloader::new(
197				protocol_config.protocol_name().clone(),
198				network,
199			)),
200			request_response_config: protocol_config,
201		}
202	}
203
204	/// Run [`BlockRequestHandler`].
205	async fn process_requests(&mut self) {
206		while let Some(request) = self.request_receiver.next().await {
207			let IncomingRequest { peer, payload, pending_response } = request;
208
209			match self.handle_request(payload, pending_response, &peer) {
210				Ok(()) => debug!(target: LOG_TARGET, "Handled block request from {}.", peer),
211				Err(e) => debug!(
212					target: LOG_TARGET,
213					"Failed to handle block request from {}: {}", peer, e,
214				),
215			}
216		}
217	}
218
219	fn handle_request(
220		&mut self,
221		payload: Vec<u8>,
222		pending_response: oneshot::Sender<OutgoingResponse>,
223		peer: &PeerId,
224	) -> Result<(), HandleRequestError> {
225		let request = crate::schema::v1::BlockRequest::decode(&payload[..])?;
226
227		let from_block_id = match request.from_block.ok_or(HandleRequestError::MissingFromField)? {
228			FromBlockSchema::Hash(ref h) => {
229				let h = Decode::decode(&mut h.as_ref())?;
230				BlockId::<B>::Hash(h)
231			},
232			FromBlockSchema::Number(ref n) => {
233				let n = Decode::decode(&mut n.as_ref())?;
234				BlockId::<B>::Number(n)
235			},
236		};
237
238		let max_blocks = if request.max_blocks == 0 {
239			MAX_BLOCKS_IN_RESPONSE
240		} else {
241			min(request.max_blocks as usize, MAX_BLOCKS_IN_RESPONSE)
242		};
243
244		let direction =
245			i32::try_into(request.direction).map_err(|_| HandleRequestError::ParseDirection)?;
246
247		let attributes = BlockAttributes::from_be_u32(request.fields)?;
248
249		let support_multiple_justifications = request.support_multiple_justifications;
250
251		let key = SeenRequestsKey {
252			peer: *peer,
253			max_blocks,
254			direction,
255			from: from_block_id,
256			attributes,
257			support_multiple_justifications,
258		};
259
260		let mut reputation_change = None;
261
262		let small_request = attributes
263			.difference(BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION)
264			.is_empty();
265
266		match self.seen_requests.get(&key) {
267			Some(SeenRequestsValue::First) => {},
268			Some(SeenRequestsValue::Fulfilled { requests, since })
269				if since.elapsed() <= SAME_REQUEST_WINDOW =>
270			{
271				*requests = requests.saturating_add(1);
272
273				if *requests > MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER {
274					reputation_change = Some(if small_request {
275						rep::SAME_SMALL_REQUEST
276					} else {
277						rep::SAME_REQUEST
278					});
279				}
280			},
281			Some(value @ SeenRequestsValue::Fulfilled { .. }) => {
282				*value = SeenRequestsValue::First;
283			},
284			None => {
285				self.seen_requests.insert(key.clone(), SeenRequestsValue::First);
286			},
287		}
288
289		debug!(
290			target: LOG_TARGET,
291			"Handling block request from {peer}: Starting at `{from_block_id:?}` with \
292			maximum blocks of `{max_blocks}`, reputation_change: `{reputation_change:?}`, \
293			small_request `{small_request:?}`, direction `{direction:?}` and \
294			attributes `{attributes:?}`.",
295		);
296
297		let maybe_block_response = if reputation_change.is_none() || small_request {
298			let block_response = self.get_block_response(
299				attributes,
300				from_block_id,
301				direction,
302				max_blocks,
303				support_multiple_justifications,
304			)?;
305
306			// If any of the blocks contains any data, we can consider it as successful request.
307			if block_response
308				.blocks
309				.iter()
310				.any(|b| !b.header.is_empty() || !b.body.is_empty() || b.is_empty_justification)
311			{
312				if let Some(value) = self.seen_requests.get(&key) {
313					if let SeenRequestsValue::First = value {
314						*value =
315							SeenRequestsValue::Fulfilled { requests: 1, since: Instant::now() };
316					}
317				}
318			}
319
320			Some(block_response)
321		} else {
322			None
323		};
324
325		debug!(
326			target: LOG_TARGET,
327			"Sending result of block request from {peer} starting at `{from_block_id:?}`: \
328			blocks: {:?}, data: {:?}",
329			maybe_block_response.as_ref().map(|res| res.blocks.len()),
330			maybe_block_response.as_ref().map(|res| res.encoded_len()),
331		);
332
333		let result = if let Some(block_response) = maybe_block_response {
334			let mut data = Vec::with_capacity(block_response.encoded_len());
335			block_response.encode(&mut data)?;
336			Ok(data)
337		} else {
338			Err(())
339		};
340
341		pending_response
342			.send(OutgoingResponse {
343				result,
344				reputation_changes: reputation_change.into_iter().collect(),
345				sent_feedback: None,
346			})
347			.map_err(|_| HandleRequestError::SendResponse)
348	}
349
350	fn get_block_response(
351		&self,
352		attributes: BlockAttributes,
353		mut block_id: BlockId<B>,
354		direction: Direction,
355		max_blocks: usize,
356		support_multiple_justifications: bool,
357	) -> Result<BlockResponse, HandleRequestError> {
358		let get_header = attributes.contains(BlockAttributes::HEADER);
359		let get_body = attributes.contains(BlockAttributes::BODY);
360		let get_justification = attributes.contains(BlockAttributes::JUSTIFICATION);
361
362		let mut blocks = Vec::new();
363
364		let mut total_size: usize = 0;
365
366		let client_header_from_block_id =
367			|block_id: BlockId<B>| -> Result<Option<B::Header>, HandleRequestError> {
368				if let Some(hash) = self.client.block_hash_from_id(&block_id)? {
369					return self.client.header(hash).map_err(Into::into);
370				}
371				Ok(None)
372			};
373
374		while let Some(header) = client_header_from_block_id(block_id).unwrap_or_default() {
375			let number = *header.number();
376			let hash = header.hash();
377			let parent_hash = *header.parent_hash();
378			let justifications =
379				if get_justification { self.client.justifications(hash)? } else { None };
380
381			let (justifications, justification, is_empty_justification) =
382				if support_multiple_justifications {
383					let justifications = match justifications {
384						Some(v) => v.encode(),
385						None => Vec::new(),
386					};
387					(justifications, Vec::new(), false)
388				} else {
389					// For now we keep compatibility by selecting precisely the GRANDPA one, and not
390					// just the first one. When sending we could have just taken the first one,
391					// since we don't expect there to be any other kind currently, but when
392					// receiving we need to add the engine ID tag.
393					// The ID tag is hardcoded here to avoid depending on the GRANDPA crate, and
394					// will be removed once we remove the backwards compatibility.
395					// See: https://github.com/paritytech/substrate/issues/8172
396					let justification =
397						justifications.and_then(|just| just.into_justification(*b"FRNK"));
398
399					let is_empty_justification =
400						justification.as_ref().map(|j| j.is_empty()).unwrap_or(false);
401
402					let justification = justification.unwrap_or_default();
403
404					(Vec::new(), justification, is_empty_justification)
405				};
406
407			let body = if get_body {
408				match self.client.block_body(hash)? {
409					Some(mut extrinsics) => {
410						extrinsics.iter_mut().map(|extrinsic| extrinsic.encode()).collect()
411					},
412					None => {
413						log::trace!(target: LOG_TARGET, "Missing data for block request.");
414						break;
415					},
416				}
417			} else {
418				Vec::new()
419			};
420
421			let block_data = crate::schema::v1::BlockData {
422				hash: hash.encode(),
423				header: if get_header { header.encode() } else { Vec::new() },
424				body,
425				receipt: Vec::new(),
426				message_queue: Vec::new(),
427				justification,
428				is_empty_justification,
429				justifications,
430			};
431
432			let new_total_size = total_size + block_data.encoded_len();
433
434			// Reserve 20 KiB for protocol overhead (length prefixes of `BlockData` + the final
435			// encoding in `BlockResponse`)
436			if new_total_size > (MAX_RESPONSE_SIZE as usize - 20 * 1024) {
437				if blocks.is_empty() {
438					log::error!(
439						target: LOG_TARGET,
440						"Single block response is bigger than the max allowed response size! This is a bug!"
441					);
442				}
443
444				break;
445			}
446
447			total_size = new_total_size;
448
449			blocks.push(block_data);
450
451			if blocks.len() >= max_blocks as usize {
452				break;
453			}
454
455			match direction {
456				Direction::Ascending => block_id = BlockId::Number(number + One::one()),
457				Direction::Descending => {
458					if number.is_zero() {
459						break;
460					}
461					block_id = BlockId::Hash(parent_hash)
462				},
463			}
464		}
465
466		Ok(BlockResponse { blocks })
467	}
468}
469
470#[async_trait::async_trait]
471impl<B, Client> BlockServer<B> for BlockRequestHandler<B, Client>
472where
473	B: BlockT,
474	Client: HeaderBackend<B> + BlockBackend<B> + Send + Sync + 'static,
475{
476	async fn run(&mut self) {
477		self.process_requests().await;
478	}
479}
480
481#[derive(Debug, thiserror::Error)]
482enum HandleRequestError {
483	#[error("Failed to decode request: {0}.")]
484	DecodeProto(#[from] prost::DecodeError),
485	#[error("Failed to encode response: {0}.")]
486	EncodeProto(#[from] prost::EncodeError),
487	#[error("Failed to decode block hash: {0}.")]
488	DecodeScale(#[from] codec::Error),
489	#[error("Missing `BlockRequest::from_block` field.")]
490	MissingFromField,
491	#[error("Failed to parse BlockRequest::direction.")]
492	ParseDirection,
493	#[error(transparent)]
494	Client(#[from] sp_blockchain::Error),
495	#[error("Failed to send response.")]
496	SendResponse,
497}
498
499/// The full block downloader implementation of [`BlockDownloader].
500#[derive(Debug)]
501pub struct FullBlockDownloader {
502	protocol_name: ProtocolName,
503	network: NetworkServiceHandle,
504}
505
506impl FullBlockDownloader {
507	fn new(protocol_name: ProtocolName, network: NetworkServiceHandle) -> Self {
508		Self { protocol_name, network }
509	}
510
511	/// Extracts the blocks from the response schema.
512	fn blocks_from_schema<B: BlockT>(
513		&self,
514		request: &BlockRequest<B>,
515		response: BlockResponseSchema,
516	) -> Result<Vec<BlockData<B>>, String> {
517		response
518			.blocks
519			.into_iter()
520			.map(|block_data| {
521				Ok(BlockData::<B> {
522					hash: Decode::decode(&mut block_data.hash.as_ref())?,
523					header: if !block_data.header.is_empty() {
524						Some(Decode::decode(&mut block_data.header.as_ref())?)
525					} else {
526						None
527					},
528					body: if request.fields.contains(BlockAttributes::BODY) {
529						Some(
530							block_data
531								.body
532								.iter()
533								.map(|body| Decode::decode(&mut body.as_ref()))
534								.collect::<Result<Vec<_>, _>>()?,
535						)
536					} else {
537						None
538					},
539					receipt: if !block_data.receipt.is_empty() {
540						Some(block_data.receipt)
541					} else {
542						None
543					},
544					message_queue: if !block_data.message_queue.is_empty() {
545						Some(block_data.message_queue)
546					} else {
547						None
548					},
549					justification: if !block_data.justification.is_empty() {
550						Some(block_data.justification)
551					} else if block_data.is_empty_justification {
552						Some(Vec::new())
553					} else {
554						None
555					},
556					justifications: if !block_data.justifications.is_empty() {
557						Some(DecodeAll::decode_all(&mut block_data.justifications.as_ref())?)
558					} else {
559						None
560					},
561				})
562			})
563			.collect::<Result<_, _>>()
564			.map_err(|error: codec::Error| error.to_string())
565	}
566}
567
568#[async_trait::async_trait]
569impl<B: BlockT> BlockDownloader<B> for FullBlockDownloader {
570	fn protocol_name(&self) -> &ProtocolName {
571		&self.protocol_name
572	}
573
574	async fn download_blocks(
575		&self,
576		who: PeerId,
577		request: BlockRequest<B>,
578	) -> Result<Result<(Vec<u8>, ProtocolName), RequestFailure>, oneshot::Canceled> {
579		// Build the request protobuf.
580		let bytes = BlockRequestSchema {
581			fields: request.fields.to_be_u32(),
582			from_block: match request.from {
583				FromBlock::Hash(h) => Some(FromBlockSchema::Hash(h.encode())),
584				FromBlock::Number(n) => Some(FromBlockSchema::Number(n.encode())),
585			},
586			direction: request.direction as i32,
587			max_blocks: request.max.unwrap_or(0),
588			support_multiple_justifications: true,
589		}
590		.encode_to_vec();
591
592		let (tx, rx) = oneshot::channel();
593		self.network.start_request(
594			who,
595			self.protocol_name.clone(),
596			bytes,
597			tx,
598			IfDisconnected::ImmediateError,
599		);
600		rx.await
601	}
602
603	fn block_response_into_blocks(
604		&self,
605		request: &BlockRequest<B>,
606		response: Vec<u8>,
607	) -> Result<Vec<BlockData<B>>, BlockResponseError> {
608		// Decode the response protobuf
609		let response_schema = BlockResponseSchema::decode(response.as_slice())
610			.map_err(|error| BlockResponseError::DecodeFailed(error.to_string()))?;
611
612		// Extract the block data from the protobuf
613		self.blocks_from_schema::<B>(request, response_schema)
614			.map_err(|error| BlockResponseError::ExtractionFailed(error.to_string()))
615	}
616}
617
618#[cfg(test)]
619mod tests {
620	use super::*;
621	use futures::executor::block_on;
622	use sc_block_builder::BlockBuilderBuilder;
623	use sp_consensus::BlockOrigin;
624	use substrate_test_runtime_client::{
625		runtime::Block, ClientBlockImportExt, DefaultTestClientBuilderExt, TestClient,
626		TestClientBuilder, TestClientBuilderExt,
627	};
628
629	fn test_handler() -> BlockRequestHandler<Block, TestClient> {
630		let client = Arc::new(TestClientBuilder::new().build());
631
632		let block = BlockBuilderBuilder::new(&*client)
633			.on_parent_block(client.chain_info().genesis_hash)
634			.with_parent_block_number(0)
635			.build()
636			.unwrap()
637			.build()
638			.unwrap()
639			.block;
640		block_on(client.import(BlockOrigin::Own, block)).unwrap();
641
642		let (_tx, request_receiver) = async_channel::bounded(1);
643		BlockRequestHandler {
644			client,
645			request_receiver,
646			seen_requests: LruMap::new(ByLength::new(16)),
647		}
648	}
649
650	fn make_request(attributes: BlockAttributes) -> Vec<u8> {
651		BlockRequestSchema {
652			fields: attributes.to_be_u32(),
653			from_block: Some(FromBlockSchema::Number(Encode::encode(&1u64))),
654			direction: Direction::Ascending as i32,
655			max_blocks: 1,
656			support_multiple_justifications: true,
657		}
658		.encode_to_vec()
659	}
660
661	fn send_request(
662		handler: &mut BlockRequestHandler<Block, TestClient>,
663		peer: &PeerId,
664		attributes: BlockAttributes,
665	) -> OutgoingResponse {
666		let (tx, mut rx) = oneshot::channel();
667		handler.handle_request(make_request(attributes), tx, peer).unwrap();
668		rx.try_recv().unwrap().unwrap()
669	}
670
671	#[test]
672	fn same_request_limit_resets_after_window() {
673		let mut handler = test_handler();
674		let peer = PeerId::random();
675		let attributes = BlockAttributes::HEADER | BlockAttributes::BODY;
676
677		for _ in 0..MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER {
678			let response = send_request(&mut handler, &peer, attributes);
679			assert!(response.result.is_ok());
680			assert!(response.reputation_changes.is_empty());
681		}
682
683		let response = send_request(&mut handler, &peer, attributes);
684		assert!(response.result.is_err());
685		assert_eq!(response.reputation_changes, vec![rep::SAME_REQUEST]);
686		assert!(rep::SAME_REQUEST.value > i32::MIN);
687
688		// Expire the window without sleeping.
689		let key = SeenRequestsKey::<Block> {
690			peer,
691			from: BlockId::Number(1),
692			max_blocks: 1,
693			direction: Direction::Ascending,
694			attributes,
695			support_multiple_justifications: true,
696		};
697		match handler.seen_requests.get(&key) {
698			Some(SeenRequestsValue::Fulfilled { since, .. }) => {
699				*since = Instant::now() - SAME_REQUEST_WINDOW - Duration::from_secs(1)
700			},
701			_ => panic!("entry must be in the fulfilled state"),
702		}
703
704		let response = send_request(&mut handler, &peer, attributes);
705		assert!(response.result.is_ok());
706		assert!(response.reputation_changes.is_empty());
707
708		// The new window has the same limit.
709		let response = send_request(&mut handler, &peer, attributes);
710		assert!(response.result.is_ok());
711		let response = send_request(&mut handler, &peer, attributes);
712		assert!(response.result.is_err());
713		assert_eq!(response.reputation_changes, vec![rep::SAME_REQUEST]);
714	}
715
716	#[test]
717	fn same_small_request_answered_but_penalized() {
718		let mut handler = test_handler();
719		let peer = PeerId::random();
720		// Header-only requests remain answerable after the limit.
721		let attributes = BlockAttributes::HEADER;
722
723		for _ in 0..MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER {
724			let response = send_request(&mut handler, &peer, attributes);
725			assert!(response.result.is_ok());
726			assert!(response.reputation_changes.is_empty());
727		}
728
729		let response = send_request(&mut handler, &peer, attributes);
730		assert!(response.result.is_ok());
731		assert_eq!(response.reputation_changes, vec![rep::SAME_SMALL_REQUEST]);
732	}
733
734	#[test]
735	fn deprecated_indexed_body_attribute_is_ignored() {
736		let mut handler = test_handler();
737		let peer = PeerId::random();
738		// Older peers may still set the removed indexed-body bit. The request must
739		// decode and be answered, with the bit ignored.
740		let attributes = BlockAttributes::HEADER | BlockAttributes::DEPRECATED_INDEXED_BODY;
741		let response = send_request(&mut handler, &peer, attributes);
742		assert!(response.result.is_ok());
743		let decoded = BlockResponseSchema::decode(&response.result.unwrap()[..]).unwrap();
744		assert_eq!(decoded.blocks.len(), 1);
745		assert!(!decoded.blocks[0].header.is_empty());
746	}
747}