Skip to content

Commit 401adcd

Browse files
committed
Expand round trip API to block bodies
1 parent 2d4594a commit 401adcd

File tree

8 files changed

+564
-177
lines changed

8 files changed

+564
-177
lines changed

p2p/peer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ async def request_stuff() -> None:
10631063
peer_pool.logger.info("Waiting for peer connection...")
10641064
await asyncio.sleep(0.2)
10651065
peer = peer_pool.highest_td_peer
1066-
headers = await peer.requests.get_block_headers(2440319, max_headers=100) # type: ignore
1066+
headers = await peer.requests.get_block_headers(2440319, max_headers=100)
10671067
hashes = [header.hash for header in headers]
10681068
if peer_class == ETHPeer:
10691069
peer = cast(ETHPeer, peer)
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import os
2+
import random
3+
import time
4+
5+
import pytest
6+
7+
import rlp
8+
9+
from eth_hash.auto import keccak
10+
11+
from eth_utils import (
12+
to_tuple,
13+
big_endian_to_int,
14+
)
15+
16+
from eth.db.trie import make_trie_root_and_nodes
17+
from eth.rlp.headers import BlockHeader
18+
from eth.rlp.transactions import BaseTransactionFields
19+
20+
from p2p.exceptions import ValidationError
21+
22+
from trinity.rlp.block_body import BlockBody
23+
from trinity.protocol.eth.requests import BlockBodiesRequest
24+
25+
26+
def mk_uncle(block_number):
27+
return BlockHeader(
28+
state_root=os.urandom(32),
29+
difficulty=1000000,
30+
block_number=block_number,
31+
gas_limit=3141592,
32+
timestamp=int(time.time()),
33+
)
34+
35+
36+
def mk_transaction():
37+
return BaseTransactionFields(
38+
nonce=0,
39+
gas=21000,
40+
gas_price=1,
41+
to=os.urandom(20),
42+
value=random.randint(0, 100),
43+
data=b'',
44+
v=27,
45+
r=big_endian_to_int(os.urandom(32)),
46+
s=big_endian_to_int(os.urandom(32)),
47+
)
48+
49+
50+
def mk_header_and_body(block_number, num_transactions, num_uncles):
51+
transactions = tuple(mk_transaction() for _ in range(num_transactions))
52+
uncles = tuple(mk_uncle(block_number - 1) for _ in range(num_uncles))
53+
54+
transaction_root, trie_data = make_trie_root_and_nodes(transactions)
55+
uncles_hash = keccak(rlp.encode(uncles))
56+
57+
body = BlockBody(transactions=transactions, uncles=uncles)
58+
59+
header = BlockHeader(
60+
difficulty=1000000,
61+
block_number=block_number,
62+
gas_limit=3141592,
63+
timestamp=int(time.time()),
64+
transaction_root=transaction_root,
65+
uncles_hash=uncles_hash,
66+
)
67+
68+
return header, body, transaction_root, trie_data, uncles_hash
69+
70+
71+
@to_tuple
72+
def mk_headers(*counts):
73+
for idx, (num_transactions, num_uncles) in enumerate(counts, 1):
74+
yield mk_header_and_body(idx, num_transactions, num_uncles)
75+
76+
77+
def test_block_bodies_request_empty_response_is_valid():
78+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
79+
headers, _, _, _, _ = zip(*headers_bundle)
80+
request = BlockBodiesRequest(headers)
81+
request.validate_response(tuple(), tuple())
82+
83+
84+
def test_block_bodies_request_valid_with_full_response():
85+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
86+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
87+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
88+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
89+
request = BlockBodiesRequest(headers)
90+
request.validate_response(bodies, bodies_bundle)
91+
92+
93+
def test_block_bodies_request_valid_with_partial_response():
94+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
95+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
96+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
97+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
98+
request = BlockBodiesRequest(headers)
99+
100+
request.validate_response(bodies[:2], bodies_bundle[:2])
101+
request.validate_response(bodies[2:], bodies_bundle[2:])
102+
request.validate_response(
103+
(bodies[0], bodies[2], bodies[3]),
104+
(bodies_bundle[0], bodies_bundle[2], bodies_bundle[3]),
105+
)
106+
107+
108+
def test_block_bodies_request_with_fully_invalid_response():
109+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
110+
headers, _, _, _, _ = zip(*headers_bundle)
111+
112+
wrong_headers_bundle = mk_headers((3, 2), (4, 8), (1, 0), (0, 0))
113+
w_headers, w_bodies, w_transactions_roots, w_trie_data_dicts, w_uncles_hashes = zip(
114+
*wrong_headers_bundle
115+
)
116+
w_transactions_bundles = tuple(zip(w_transactions_roots, w_trie_data_dicts))
117+
w_bodies_bundle = tuple(zip(w_bodies, w_transactions_bundles, w_uncles_hashes))
118+
119+
request = BlockBodiesRequest(headers)
120+
with pytest.raises(ValidationError):
121+
request.validate_response(w_bodies, w_bodies_bundle)
122+
123+
124+
def test_block_bodies_request_with_extra_unrequested_bodies():
125+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
126+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
127+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
128+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
129+
request = BlockBodiesRequest(headers)
130+
131+
wrong_headers_bundle = mk_headers((3, 2), (4, 8), (1, 0), (0, 0))
132+
w_headers, w_bodies, w_transactions_roots, w_trie_data_dicts, w_uncles_hashes = zip(
133+
*wrong_headers_bundle
134+
)
135+
w_transactions_bundles = tuple(zip(w_transactions_roots, w_trie_data_dicts))
136+
w_bodies_bundle = tuple(zip(w_bodies, w_transactions_bundles, w_uncles_hashes))
137+
138+
request = BlockBodiesRequest(headers)
139+
with pytest.raises(ValidationError):
140+
request.validate_response(
141+
bodies + w_bodies,
142+
bodies_bundle + w_bodies_bundle,
143+
)
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import asyncio
2+
import os
3+
import random
4+
import time
5+
6+
import pytest
7+
8+
import rlp
9+
10+
from eth_utils import (
11+
big_endian_to_int,
12+
keccak,
13+
to_tuple,
14+
)
15+
16+
from eth.db.trie import make_trie_root_and_nodes
17+
from eth.rlp.headers import BlockHeader
18+
from eth.rlp.transactions import BaseTransactionFields
19+
20+
from trinity.protocol.eth.peer import ETHPeer
21+
from trinity.rlp.block_body import BlockBody
22+
23+
from tests.trinity.core.peer_helpers import (
24+
get_directly_linked_peers,
25+
)
26+
27+
28+
def mk_uncle(block_number):
29+
return BlockHeader(
30+
state_root=os.urandom(32),
31+
difficulty=1000000,
32+
block_number=block_number,
33+
gas_limit=3141592,
34+
timestamp=int(time.time()),
35+
)
36+
37+
38+
def mk_transaction():
39+
return BaseTransactionFields(
40+
nonce=0,
41+
gas=21000,
42+
gas_price=1,
43+
to=os.urandom(20),
44+
value=random.randint(0, 100),
45+
data=b'',
46+
v=27,
47+
r=big_endian_to_int(os.urandom(32)),
48+
s=big_endian_to_int(os.urandom(32)),
49+
)
50+
51+
52+
def mk_header_and_body(block_number, num_transactions, num_uncles):
53+
transactions = tuple(mk_transaction() for _ in range(num_transactions))
54+
uncles = tuple(mk_uncle(block_number - 1) for _ in range(num_uncles))
55+
56+
transaction_root, trie_data = make_trie_root_and_nodes(transactions)
57+
uncles_hash = keccak(rlp.encode(uncles))
58+
59+
body = BlockBody(transactions=transactions, uncles=uncles)
60+
61+
header = BlockHeader(
62+
difficulty=1000000,
63+
block_number=block_number,
64+
gas_limit=3141592,
65+
timestamp=int(time.time()),
66+
transaction_root=transaction_root,
67+
uncles_hash=uncles_hash,
68+
)
69+
70+
return header, body, transaction_root, trie_data, uncles_hash
71+
72+
73+
@to_tuple
74+
def mk_headers(*counts):
75+
for idx, (num_transactions, num_uncles) in enumerate(counts, 1):
76+
yield mk_header_and_body(idx, num_transactions, num_uncles)
77+
78+
79+
@pytest.fixture
80+
async def eth_peer_and_remote(request, event_loop):
81+
peer, remote = await get_directly_linked_peers(
82+
request,
83+
event_loop,
84+
peer1_class=ETHPeer,
85+
peer2_class=ETHPeer,
86+
)
87+
return peer, remote
88+
89+
90+
@pytest.mark.asyncio
91+
async def test_eth_peer_get_block_bodies_round_trip_with_empty_response(eth_peer_and_remote):
92+
peer, remote = eth_peer_and_remote
93+
94+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
95+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
96+
97+
async def send_block_bodies():
98+
remote.sub_proto.send_block_bodies([])
99+
await asyncio.sleep(0)
100+
101+
asyncio.ensure_future(send_block_bodies())
102+
response = await peer.requests.get_block_bodies(headers)
103+
104+
assert len(response) == 0
105+
106+
107+
@pytest.mark.asyncio
108+
async def test_eth_peer_get_block_bodies_round_trip_with_full_response(eth_peer_and_remote):
109+
peer, remote = eth_peer_and_remote
110+
111+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
112+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
113+
114+
async def send_block_bodies():
115+
remote.sub_proto.send_block_bodies(bodies)
116+
await asyncio.sleep(0)
117+
118+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
119+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
120+
121+
asyncio.ensure_future(send_block_bodies())
122+
response = await peer.requests.get_block_bodies(headers)
123+
124+
assert len(response) == 4
125+
assert response == bodies_bundle
126+
127+
128+
@pytest.mark.asyncio
129+
async def test_eth_peer_get_block_bodies_round_trip_with_partial_response(eth_peer_and_remote):
130+
peer, remote = eth_peer_and_remote
131+
132+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
133+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
134+
135+
async def send_block_bodies():
136+
remote.sub_proto.send_block_bodies(bodies[1:])
137+
await asyncio.sleep(0)
138+
139+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
140+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
141+
142+
asyncio.ensure_future(send_block_bodies())
143+
response = await peer.requests.get_block_bodies(headers)
144+
145+
assert len(response) == 3
146+
assert response == bodies_bundle[1:]
147+
148+
149+
@pytest.mark.asyncio
150+
async def test_eth_peer_get_block_bodies_round_trip_with_noise(eth_peer_and_remote):
151+
peer, remote = eth_peer_and_remote
152+
153+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
154+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
155+
156+
async def send_block_bodies():
157+
remote.sub_proto.send_node_data((b'', b'arst'))
158+
await asyncio.sleep(0)
159+
remote.sub_proto.send_block_bodies(bodies)
160+
await asyncio.sleep(0)
161+
remote.sub_proto.send_node_data((b'', b'arst'))
162+
await asyncio.sleep(0)
163+
164+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
165+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
166+
167+
asyncio.ensure_future(send_block_bodies())
168+
response = await peer.requests.get_block_bodies(headers)
169+
170+
assert len(response) == 4
171+
assert response == bodies_bundle
172+
173+
174+
@pytest.mark.asyncio
175+
async def test_eth_peer_get_block_bodies_round_trip_no_match_invalid_response(eth_peer_and_remote):
176+
peer, remote = eth_peer_and_remote
177+
178+
headers_bundle = mk_headers((2, 3), (8, 4), (0, 1), (0, 0))
179+
headers, bodies, transactions_roots, trie_data_dicts, uncles_hashes = zip(*headers_bundle)
180+
181+
wrong_headers_bundle = mk_headers((4, 1), (3, 5), (2, 0), (7, 3))
182+
_, wrong_bodies, _, _, _ = zip(*wrong_headers_bundle)
183+
184+
async def send_block_bodies():
185+
remote.sub_proto.send_block_bodies(wrong_bodies)
186+
await asyncio.sleep(0)
187+
remote.sub_proto.send_block_bodies(bodies)
188+
await asyncio.sleep(0)
189+
190+
transactions_bundles = tuple(zip(transactions_roots, trie_data_dicts))
191+
bodies_bundle = tuple(zip(bodies, transactions_bundles, uncles_hashes))
192+
193+
asyncio.ensure_future(send_block_bodies())
194+
response = await peer.requests.get_block_bodies(headers)
195+
196+
assert len(response) == 4
197+
assert response == bodies_bundle

trinity/protocol/eth/handlers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
)
44

55
from .managers import (
6+
GetBlockBodiesRequestManager,
67
GetBlockHeadersRequestManager,
78
GetNodeDataRequestManager,
89
GetReceiptsRequestManager,
@@ -11,12 +12,14 @@
1112

1213
class ETHRequestResponseHandler(BaseRequestResponseHandler):
1314
_managers = {
15+
'get_block_bodies': GetBlockBodiesRequestManager,
1416
'get_block_headers': GetBlockHeadersRequestManager,
1517
'get_node_data': GetNodeDataRequestManager,
1618
'get_receipts': GetReceiptsRequestManager,
1719
}
1820

1921
# These are needed only to please mypy.
22+
get_block_bodies: GetBlockBodiesRequestManager
2023
get_block_headers: GetBlockHeadersRequestManager
2124
get_node_data: GetNodeDataRequestManager
2225
get_receipts: GetReceiptsRequestManager

0 commit comments

Comments
 (0)