Files
chia-blockchain/tests/core/server/test_rate_limits.py
T
ab53d48995 Request header blocks, and new rate limits (#11636)
* new blob block api method integrated into wallet

* direct msg streaming of headers, rename, tests

* perform_handshake call fix

* updated trusted sync with new block header calls

* add max blocks limit to fetch

* added tests for rejected block header msgs

* avoid parsing transactions info if not required

* avoid looking up capabilities setting

* move block tests out of a class

* test fix

* Merge changes

* added docs and increased rate limits

* increased block header request interval from 32 to 128

* remove fetching hashes and use height range

* fetching by height in db v2

* update capabilities, other fixes

* fixed range block header call

* Add type hints

* Start work on optimizing fetch_last_tx_from_peer

* Huge speedup in trusted wallet sync

* Revert unintentional changes

* Fix trade issue

* Improve the code

* Str format

* Optimize handling of farming rewards

* Fix bug

* Performance fixes

* Optimizations to wallet syncing

* Don't return all coins in respond_additions

* Revert concurrency numbers

* More optimization of the caches

* Small optimization in coin_added

* Optimize request_additions significantly by using a cache

* fixes from feedback

* capabilities check fixes

* Increase rate limits to allow 250tps in verification requests

* Start work on rate limits

* New rate limit versioning support

* Revert unrelated changes

* revert return False

* Lint

* Revert cbi

* try tests with trusted peer

* Revert unrelated wallet changes

* Revert more debug changes

* Add test and throw on an error if not found

* Reject invalid requests

* Revert bad change with uint32, and change warning to info

* Parametrize wallet sync test

* Merge and LGTM

* More clean way to choose peers

* Fix lint

* add the new RejectBlockHeaders, RequestBlockHeaders and RespondBlockHeaders to the network protocol regression test and regenerate test files

* Rate limit diffs only

* Improve performance

* Simpler

* Lint

Co-authored-by: Sebastjan <trepca@gmail.com>
Co-authored-by: arvidn <arvid@libtorrent.org>
2022-06-11 01:35:41 -05:00

358 lines
14 KiB
Python

import asyncio
from typing import List
import pytest
from chia.protocols.protocol_message_types import ProtocolMessageTypes
from chia.protocols.shared_protocol import Capability
from chia.server.outbound_message import make_msg
from chia.server.rate_limit_numbers import get_rate_limits_to_use, rate_limits as rl_numbers, compose_rate_limits
from chia.server.rate_limits import RateLimiter
from chia.server.server import ChiaServer
from chia.server.ws_connection import WSChiaConnection
from chia.types.peer_info import PeerInfo
from chia.util.ints import uint16
from tests.conftest import node_with_params
from tests.setup_nodes import test_constants
constants = test_constants
rl_v2 = [Capability.BASE, Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2]
rl_v1 = [Capability.BASE]
node_with_params_b = node_with_params
test_different_versions_results: List[int] = []
class TestRateLimits:
@pytest.mark.asyncio
async def test_get_rate_limits_to_use(self):
assert get_rate_limits_to_use(rl_v2, rl_v2) != get_rate_limits_to_use(rl_v2, rl_v1)
assert get_rate_limits_to_use(rl_v1, rl_v1) == get_rate_limits_to_use(rl_v2, rl_v1)
assert get_rate_limits_to_use(rl_v1, rl_v1) == get_rate_limits_to_use(rl_v1, rl_v2)
@pytest.mark.asyncio
async def test_too_many_messages(self):
# Too many messages
r = RateLimiter(incoming=True)
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
for i in range(4900):
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(4900):
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
# Non-tx message
r = RateLimiter(incoming=True)
new_peak_message = make_msg(ProtocolMessageTypes.new_peak, bytes([1] * 40))
for i in range(20):
assert r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(200):
response = r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.asyncio
async def test_large_message(self):
# Large tx
small_tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
large_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 3 * 1024 * 1024))
r = RateLimiter(incoming=True)
assert r.process_msg_and_check(small_tx_message, rl_v2, rl_v2)
assert not r.process_msg_and_check(large_tx_message, rl_v2, rl_v2)
small_vdf_message = make_msg(ProtocolMessageTypes.respond_signage_point, bytes([1] * 5 * 1024))
large_vdf_message = make_msg(ProtocolMessageTypes.respond_signage_point, bytes([1] * 600 * 1024))
r = RateLimiter(incoming=True)
assert r.process_msg_and_check(small_vdf_message, rl_v2, rl_v2)
assert r.process_msg_and_check(small_vdf_message, rl_v2, rl_v2)
assert not r.process_msg_and_check(large_vdf_message, rl_v2, rl_v2)
@pytest.mark.asyncio
async def test_too_much_data(self):
# Too much data
r = RateLimiter(incoming=True)
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
for i in range(10):
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(300):
response = r.process_msg_and_check(tx_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
r = RateLimiter(incoming=True)
block_message = make_msg(ProtocolMessageTypes.respond_block, bytes([1] * 1024 * 1024))
for i in range(10):
assert r.process_msg_and_check(block_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(40):
response = r.process_msg_and_check(block_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.asyncio
async def test_non_tx_aggregate_limits(self):
# Frequency limits
r = RateLimiter(incoming=True)
message_1 = make_msg(ProtocolMessageTypes.coin_state_update, bytes([1] * 32))
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 64))
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 64))
for i in range(450):
assert r.process_msg_and_check(message_1, rl_v2, rl_v2)
for i in range(450):
assert r.process_msg_and_check(message_2, rl_v2, rl_v2)
saw_disconnect = False
for i in range(450):
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
# Size limits
r = RateLimiter(incoming=True)
message_4 = make_msg(ProtocolMessageTypes.respond_proof_of_weight, bytes([1] * 49 * 1024 * 1024))
message_5 = make_msg(ProtocolMessageTypes.respond_blocks, bytes([1] * 49 * 1024 * 1024))
for i in range(2):
assert r.process_msg_and_check(message_4, rl_v2, rl_v2)
saw_disconnect = False
for i in range(2):
response = r.process_msg_and_check(message_5, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.asyncio
async def test_periodic_reset(self):
r = RateLimiter(True, 5)
tx_message = make_msg(ProtocolMessageTypes.respond_transaction, bytes([1] * 500 * 1024))
for i in range(10):
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(300):
response = r.process_msg_and_check(tx_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
assert not r.process_msg_and_check(tx_message, rl_v2, rl_v2)
await asyncio.sleep(6)
assert r.process_msg_and_check(tx_message, rl_v2, rl_v2)
# Counts reset also
r = RateLimiter(True, 5)
new_tx_message = make_msg(ProtocolMessageTypes.new_transaction, bytes([1] * 40))
for i in range(4900):
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(4900):
response = r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
await asyncio.sleep(6)
assert r.process_msg_and_check(new_tx_message, rl_v2, rl_v2)
@pytest.mark.asyncio
async def test_percentage_limits(self):
r = RateLimiter(True, 60, 40)
new_peak_message = make_msg(ProtocolMessageTypes.new_peak, bytes([1] * 40))
for i in range(50):
assert r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(50):
response = r.process_msg_and_check(new_peak_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
r = RateLimiter(True, 60, 40)
block_message = make_msg(ProtocolMessageTypes.respond_block, bytes([1] * 1024 * 1024))
for i in range(5):
assert r.process_msg_and_check(block_message, rl_v2, rl_v2)
saw_disconnect = False
for i in range(5):
response = r.process_msg_and_check(block_message, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
# Aggregate percentage limit count
r = RateLimiter(True, 60, 40)
message_1 = make_msg(ProtocolMessageTypes.coin_state_update, bytes([1] * 5))
message_2 = make_msg(ProtocolMessageTypes.request_blocks, bytes([1] * 32))
message_3 = make_msg(ProtocolMessageTypes.plot_sync_start, bytes([1] * 32))
for i in range(180):
assert r.process_msg_and_check(message_1, rl_v2, rl_v2)
for i in range(180):
assert r.process_msg_and_check(message_2, rl_v2, rl_v2)
saw_disconnect = False
for i in range(100):
response = r.process_msg_and_check(message_3, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
# Aggregate percentage limit max total size
r = RateLimiter(True, 60, 40)
message_4 = make_msg(ProtocolMessageTypes.respond_proof_of_weight, bytes([1] * 18 * 1024 * 1024))
message_5 = make_msg(ProtocolMessageTypes.respond_blocks, bytes([1] * 24 * 1024 * 1024))
for i in range(2):
assert r.process_msg_and_check(message_4, rl_v2, rl_v2)
saw_disconnect = False
for i in range(2):
response = r.process_msg_and_check(message_5, rl_v2, rl_v2)
if not response:
saw_disconnect = True
assert saw_disconnect
@pytest.mark.asyncio
async def test_too_many_outgoing_messages(self):
# Too many messages
r = RateLimiter(incoming=False)
new_peers_message = make_msg(ProtocolMessageTypes.respond_peers, bytes([1]))
non_tx_freq = get_rate_limits_to_use(rl_v2, rl_v2)["non_tx_freq"]
passed = 0
blocked = 0
for i in range(non_tx_freq):
if r.process_msg_and_check(new_peers_message, rl_v2, rl_v2):
passed += 1
else:
blocked += 1
assert passed == 10
assert blocked == non_tx_freq - passed
# ensure that *another* message type is not blocked because of this
new_signatures_message = make_msg(ProtocolMessageTypes.respond_signatures, bytes([1]))
assert r.process_msg_and_check(new_signatures_message, rl_v2, rl_v2)
@pytest.mark.asyncio
async def test_too_many_incoming_messages(self):
# Too many messages
r = RateLimiter(incoming=True)
new_peers_message = make_msg(ProtocolMessageTypes.respond_peers, bytes([1]))
non_tx_freq = get_rate_limits_to_use(rl_v2, rl_v2)["non_tx_freq"]
passed = 0
blocked = 0
for i in range(non_tx_freq):
if r.process_msg_and_check(new_peers_message, rl_v2, rl_v2):
passed += 1
else:
blocked += 1
assert passed == 10
assert blocked == non_tx_freq - passed
# ensure that other message types *are* blocked because of this
new_signatures_message = make_msg(ProtocolMessageTypes.respond_signatures, bytes([1]))
assert not r.process_msg_and_check(new_signatures_message, rl_v2, rl_v2)
@pytest.mark.parametrize(
"node_with_params",
[
dict(
disable_capabilities=[Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2],
),
dict(
disable_capabilities=[],
),
],
indirect=True,
)
@pytest.mark.parametrize(
"node_with_params_b",
[
dict(
disable_capabilities=[Capability.BLOCK_HEADERS, Capability.RATE_LIMITS_V2],
),
dict(
disable_capabilities=[],
),
],
indirect=True,
)
@pytest.mark.asyncio
async def test_different_versions(self, node_with_params, node_with_params_b, self_hostname):
node_a = node_with_params
node_b = node_with_params_b
full_node_server_a: ChiaServer = node_a.full_node.server
full_node_server_b: ChiaServer = node_b.full_node.server
await full_node_server_b.start_client(PeerInfo(self_hostname, uint16(full_node_server_a._port)), None)
assert len(full_node_server_b.get_connections()) == 1
assert len(full_node_server_a.get_connections()) == 1
a_con: WSChiaConnection = full_node_server_a.get_connections()[0]
b_con: WSChiaConnection = full_node_server_b.get_connections()[0]
print(a_con.local_capabilities, a_con.peer_capabilities)
print(b_con.local_capabilities, b_con.peer_capabilities)
# The two nodes will use the same rate limits even if their versions are different
assert get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities) == get_rate_limits_to_use(
b_con.local_capabilities, b_con.peer_capabilities
)
# The following code checks whether all of the runs resulted in the same number of items in "rate_limits_tx",
# which would mean the same rate limits are always used. This should not happen, since two nodes with V2
# will use V2.
total_tx_msg_count = len(
get_rate_limits_to_use(a_con.local_capabilities, a_con.peer_capabilities)["rate_limits_tx"]
)
test_different_versions_results.append(total_tx_msg_count)
if len(test_different_versions_results) >= 4:
assert len(set(test_different_versions_results)) >= 2
@pytest.mark.asyncio
async def test_compose(self):
rl_1 = rl_numbers[1]
rl_2 = rl_numbers[2]
assert ProtocolMessageTypes.respond_children in rl_1["rate_limits_other"]
assert ProtocolMessageTypes.respond_children not in rl_1["rate_limits_tx"]
assert ProtocolMessageTypes.respond_children not in rl_2["rate_limits_other"]
assert ProtocolMessageTypes.respond_children in rl_2["rate_limits_tx"]
assert ProtocolMessageTypes.request_block in rl_1["rate_limits_other"]
assert ProtocolMessageTypes.request_block not in rl_1["rate_limits_tx"]
assert ProtocolMessageTypes.request_block not in rl_2["rate_limits_other"]
assert ProtocolMessageTypes.request_block not in rl_2["rate_limits_tx"]
comps = compose_rate_limits(rl_1, rl_2)
# v2 limits are used if present
assert ProtocolMessageTypes.respond_children not in comps["rate_limits_other"]
assert ProtocolMessageTypes.respond_children in comps["rate_limits_tx"]
# Otherwise, fall back to v1
assert ProtocolMessageTypes.request_block in rl_1["rate_limits_other"]
assert ProtocolMessageTypes.request_block not in rl_1["rate_limits_tx"]