Skip to content

Commit

Permalink
never rate limit outgoing response messages (RespondBlock, RespondBlo…
Browse files Browse the repository at this point in the history
…cks, RejectBlock, RejectBlocks). Instead, disconnect any peer sending unsolicited response blocks
  • Loading branch information
arvidn committed Dec 4, 2024
1 parent e03042c commit fc89378
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 18 deletions.
37 changes: 25 additions & 12 deletions chia/full_node/full_node_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -402,29 +403,41 @@ 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(
self,
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()
Expand Down
1 change: 1 addition & 0 deletions chia/protocols/protocol_timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions chia/server/rate_limit_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions chia/server/rate_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions chia/server/ws_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down

0 comments on commit fc89378

Please sign in to comment.