diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 6bf09d867d1d..aaa50715946c 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -34,6 +34,7 @@ from chia.protocols import farmer_protocol, full_node_protocol, introducer_protocol, timelord_protocol, wallet_protocol from chia.protocols.full_node_protocol import RejectBlock, RejectBlocks from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.protocols.protocol_timing import RATE_LIMITER_BAN_SECONDS from chia.protocols.shared_protocol import Capability from chia.protocols.wallet_protocol import ( CoinState, @@ -402,17 +403,32 @@ async def request_blocks(self, request: full_node_protocol.RequestBlocks) -> Opt return msg - @metadata.request() - async def reject_block(self, request: full_node_protocol.RejectBlock) -> None: - self.log.debug(f"reject_block {request.height}") + @metadata.request(peer_required=True) + async def reject_block( + self, + request: full_node_protocol.RejectBlock, + peer: WSChiaConnection, + ) -> None: + self.log.warning(f"unsolicited reject_block {request.height}") + await peer.close(RATE_LIMITER_BAN_SECONDS) - @metadata.request() - async def reject_blocks(self, request: full_node_protocol.RejectBlocks) -> None: - self.log.debug(f"reject_blocks {request.start_height} {request.end_height}") + @metadata.request(peer_required=True) + async def reject_blocks( + self, + request: full_node_protocol.RejectBlocks, + peer: WSChiaConnection, + ) -> None: + self.log.warning(f"reject_blocks {request.start_height} {request.end_height}") + await peer.close(RATE_LIMITER_BAN_SECONDS) - @metadata.request() - async def respond_blocks(self, request: full_node_protocol.RespondBlocks) -> None: + @metadata.request(peer_required=True) + async def respond_blocks( + self, + request: full_node_protocol.RespondBlocks, + peer: WSChiaConnection, + ) -> None: self.log.warning("Received unsolicited/late blocks") + await peer.close(RATE_LIMITER_BAN_SECONDS) @metadata.request(peer_required=True) async def respond_block( @@ -420,11 +436,8 @@ async def respond_block( respond_block: full_node_protocol.RespondBlock, peer: WSChiaConnection, ) -> Optional[Message]: - """ - Receive a full block from a peer full node (or ourselves). - """ - self.log.warning(f"Received unsolicited/late block from peer {peer.get_peer_logging()}") + await peer.close(RATE_LIMITER_BAN_SECONDS) return None @metadata.request() diff --git a/chia/protocols/protocol_timing.py b/chia/protocols/protocol_timing.py index 215d4e2a1859..1015da5ba0a2 100644 --- a/chia/protocols/protocol_timing.py +++ b/chia/protocols/protocol_timing.py @@ -5,3 +5,4 @@ API_EXCEPTION_BAN_SECONDS = 10 INTERNAL_PROTOCOL_ERROR_BAN_SECONDS = 10 # Don't flap if our client is at fault CONSENSUS_ERROR_BAN_SECONDS = 600 +RATE_LIMITER_BAN_SECONDS = 300 diff --git a/chia/server/rate_limit_numbers.py b/chia/server/rate_limit_numbers.py index 521cf73ac134..20ab40deb19a 100644 --- a/chia/server/rate_limit_numbers.py +++ b/chia/server/rate_limit_numbers.py @@ -94,11 +94,11 @@ def compose_rate_limits(old_rate_limits: dict[str, Any], new_rate_limits: dict[s ProtocolMessageTypes.request_proof_of_weight: RLSettings(5, 100), ProtocolMessageTypes.respond_proof_of_weight: RLSettings(5, 50 * 1024 * 1024, 100 * 1024 * 1024), ProtocolMessageTypes.request_block: RLSettings(200, 100), - ProtocolMessageTypes.reject_block: RLSettings(200, 100), + ProtocolMessageTypes.reject_block: None, ProtocolMessageTypes.request_blocks: RLSettings(500, 100), - ProtocolMessageTypes.respond_blocks: RLSettings(100, 50 * 1024 * 1024, 5 * 50 * 1024 * 1024), - ProtocolMessageTypes.reject_blocks: RLSettings(100, 100), - ProtocolMessageTypes.respond_block: RLSettings(200, 2 * 1024 * 1024, 10 * 2 * 1024 * 1024), + ProtocolMessageTypes.respond_blocks: None, + ProtocolMessageTypes.reject_blocks: None, + ProtocolMessageTypes.respond_block: None, ProtocolMessageTypes.new_unfinished_block: RLSettings(200, 100), ProtocolMessageTypes.request_unfinished_block: RLSettings(200, 100), ProtocolMessageTypes.new_unfinished_block2: RLSettings(200, 100), diff --git a/chia/server/rate_limits.py b/chia/server/rate_limits.py index ab39b0d34afb..293271ba0e92 100644 --- a/chia/server/rate_limits.py +++ b/chia/server/rate_limits.py @@ -79,6 +79,12 @@ def process_msg_and_check( limits = rate_limits["rate_limits_tx"][message_type] elif message_type in rate_limits["rate_limits_other"]: limits = rate_limits["rate_limits_other"][message_type] + if limits is None: + # this message type is not rate limited. This is used for + # response messages and must be combined with banning peers + # sending unsolicited responses of this type + ret = True + return None non_tx_freq = rate_limits["non_tx_freq"] non_tx_max_total_size = rate_limits["non_tx_max_total_size"] new_non_tx_count = self.non_tx_message_counts + 1 diff --git a/chia/server/ws_connection.py b/chia/server/ws_connection.py index 093c5efa7cfb..5528c1eba178 100644 --- a/chia/server/ws_connection.py +++ b/chia/server/ws_connection.py @@ -22,6 +22,7 @@ API_EXCEPTION_BAN_SECONDS, CONSENSUS_ERROR_BAN_SECONDS, INTERNAL_PROTOCOL_ERROR_BAN_SECONDS, + RATE_LIMITER_BAN_SECONDS, ) from chia.protocols.shared_protocol import Capability, Error, Handshake, protocol_version from chia.server.api_protocol import ApiMetadata, ApiProtocol @@ -713,7 +714,7 @@ async def _read_one_message(self) -> Optional[Message]: self.log.error(f"Peer has been rate limited and will be disconnected: {details}") # Only full node disconnects peers, to prevent abuse and crashing timelords, farmers, etc # TODO: stop dropping tasks on the floor - asyncio.create_task(self.close(300)) # noqa: RUF006 + asyncio.create_task(self.close(RATE_LIMITER_BAN_SECONDS)) # noqa: RUF006 await asyncio.sleep(3) return None else: @@ -727,7 +728,7 @@ async def _read_one_message(self) -> Optional[Message]: self.log.error(f"WebSocket Error: {message}") if isinstance(message.data, WebSocketError) and message.data.code == WSCloseCode.MESSAGE_TOO_BIG: # TODO: stop dropping tasks on the floor - asyncio.create_task(self.close(300)) # noqa: RUF006 + asyncio.create_task(self.close(RATE_LIMITER_BAN_SECONDS)) # noqa: RUF006 else: # TODO: stop dropping tasks on the floor asyncio.create_task(self.close()) # noqa: RUF006