Skip to content

Commit faa08ae

Browse files
lrusakfselmo
authored andcommitted
Adds the following endpoints to Beacon and AsyncBeacon:
- get_peer_count - get_attester_duties - get_block_proposer_duties - add get_sync_committee_duties - get_attestations_rewards - add private methods to each class to make a POST request Signed-off-by: Lukas Rusak <[email protected]>
1 parent 8f579ac commit faa08ae

File tree

7 files changed

+280
-0
lines changed

7 files changed

+280
-0
lines changed

newsfragments/3504.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New ``Beacon`` and ``AsyncBeacon`` endpoints: ``get_peer_count``, ``get_attester_duties``, ``get_block_proposer_duties``, ``get_sync_committee_duties``, and ``get_attestation_rewards``.

tests/beacon/test_async_beacon.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,12 @@ async def test_async_cl_node_get_peer(async_beacon):
256256
_assert_valid_response(response)
257257

258258

259+
@pytest.mark.asyncio
260+
async def test_async_cl_node_get_peer_count(async_beacon):
261+
response = await async_beacon.get_peer_count()
262+
_assert_valid_response(response)
263+
264+
259265
@pytest.mark.asyncio
260266
async def test_async_cl_node_get_health(async_beacon):
261267
response = await async_beacon.get_health()
@@ -285,3 +291,82 @@ async def test_async_cl_node_get_blob_sidecars(async_beacon):
285291
# test with indices
286292
with_indices = await async_beacon.get_blob_sidecars("head", [0, 1])
287293
_assert_valid_response(with_indices)
294+
295+
296+
# Validator endpoint tests:
297+
298+
299+
@pytest.mark.asyncio
300+
async def test_async_cl_validator_get_attester_duties(async_beacon):
301+
finality_checkpoint_response = await async_beacon.get_finality_checkpoint()
302+
_assert_valid_response(finality_checkpoint_response)
303+
304+
finality_checkpoint = finality_checkpoint_response["data"]
305+
epoch = finality_checkpoint["finalized"]["epoch"]
306+
307+
validators_response = await async_beacon.get_validators()
308+
_assert_valid_response(validators_response)
309+
310+
validators = validators_response["data"]
311+
random_validator = validators[randint(0, len(validators))]
312+
random_validator_index = random_validator["index"]
313+
314+
response = await async_beacon.get_attester_duties(epoch, [random_validator_index])
315+
_assert_valid_response(response)
316+
317+
318+
@pytest.mark.asyncio
319+
async def test_async_cl_validator_get_block_proposer_duties(async_beacon):
320+
finality_checkpoint_response = await async_beacon.get_finality_checkpoint()
321+
_assert_valid_response(finality_checkpoint_response)
322+
323+
finality_checkpoint = finality_checkpoint_response["data"]
324+
epoch = finality_checkpoint["finalized"]["epoch"]
325+
326+
response = await async_beacon.get_block_proposer_duties(epoch)
327+
_assert_valid_response(response)
328+
329+
330+
@pytest.mark.asyncio
331+
async def test_async_cl_validator_get_sync_committee_duties(async_beacon):
332+
finality_checkpoint_response = await async_beacon.get_finality_checkpoint()
333+
_assert_valid_response(finality_checkpoint_response)
334+
335+
finality_checkpoint = finality_checkpoint_response["data"]
336+
epoch = finality_checkpoint["finalized"]["epoch"]
337+
338+
validators_response = await async_beacon.get_validators()
339+
_assert_valid_response(validators_response)
340+
341+
validators = validators_response["data"]
342+
random_validator = validators[randint(0, len(validators))]
343+
random_validator_index = random_validator["index"]
344+
345+
response = await async_beacon.get_sync_committee_duties(
346+
epoch, [random_validator_index]
347+
)
348+
_assert_valid_response(response)
349+
350+
351+
# Rewards endpoint tests:
352+
353+
354+
@pytest.mark.asyncio
355+
async def test_async_cl_validator_get_attestations_rewards(async_beacon):
356+
finality_checkpoint_response = await async_beacon.get_finality_checkpoint()
357+
_assert_valid_response(finality_checkpoint_response)
358+
359+
finality_checkpoint = finality_checkpoint_response["data"]
360+
epoch = finality_checkpoint["finalized"]["epoch"]
361+
362+
validators_response = await async_beacon.get_validators()
363+
_assert_valid_response(validators_response)
364+
365+
validators = validators_response["data"]
366+
random_validator = validators[randint(0, len(validators))]
367+
random_validator_index = random_validator["index"]
368+
369+
response = await async_beacon.get_attestations_rewards(
370+
epoch, [random_validator_index]
371+
)
372+
_assert_valid_response(response)

tests/beacon/test_beacon.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ def test_cl_node_get_peer(beacon):
214214
_assert_valid_response(response)
215215

216216

217+
def test_cl_node_get_peer_count(beacon):
218+
response = beacon.get_peer_count()
219+
_assert_valid_response(response)
220+
221+
217222
def test_cl_node_get_health(beacon):
218223
response = beacon.get_health()
219224
assert isinstance(response, int)
@@ -239,3 +244,74 @@ def test_cl_node_get_blob_sidecars(beacon):
239244
# test with indices
240245
with_indices = beacon.get_blob_sidecars("head", [0, 1])
241246
_assert_valid_response(with_indices)
247+
248+
249+
# Validator endpoint tests:
250+
251+
252+
def test_cl_validator_get_attester_duties(beacon):
253+
finality_checkpoint_response = beacon.get_finality_checkpoint()
254+
_assert_valid_response(finality_checkpoint_response)
255+
256+
finality_checkpoint = finality_checkpoint_response["data"]
257+
epoch = finality_checkpoint["finalized"]["epoch"]
258+
259+
validators_response = beacon.get_validators()
260+
_assert_valid_response(validators_response)
261+
262+
validators = validators_response["data"]
263+
random_validator = validators[randint(0, len(validators))]
264+
random_validator_index = random_validator["index"]
265+
266+
response = beacon.get_attester_duties(epoch, [random_validator_index])
267+
_assert_valid_response(response)
268+
269+
270+
def test_cl_validator_get_block_proposer_duties(beacon):
271+
finality_checkpoint_response = beacon.get_finality_checkpoint()
272+
_assert_valid_response(finality_checkpoint_response)
273+
274+
finality_checkpoint = finality_checkpoint_response["data"]
275+
epoch = finality_checkpoint["finalized"]["epoch"]
276+
277+
response = beacon.get_block_proposer_duties(epoch)
278+
_assert_valid_response(response)
279+
280+
281+
def test_cl_validator_get_sync_committee_duties(beacon):
282+
finality_checkpoint_response = beacon.get_finality_checkpoint()
283+
_assert_valid_response(finality_checkpoint_response)
284+
285+
finality_checkpoint = finality_checkpoint_response["data"]
286+
epoch = finality_checkpoint["finalized"]["epoch"]
287+
288+
validators_response = beacon.get_validators()
289+
_assert_valid_response(validators_response)
290+
291+
validators = validators_response["data"]
292+
random_validator = validators[randint(0, len(validators))]
293+
random_validator_index = random_validator["index"]
294+
295+
response = beacon.get_sync_committee_duties(epoch, [random_validator_index])
296+
_assert_valid_response(response)
297+
298+
299+
# Rewards endpoint tests:
300+
301+
302+
def test_cl_validator_get_attestations_rewards(beacon):
303+
finality_checkpoint_response = beacon.get_finality_checkpoint()
304+
_assert_valid_response(finality_checkpoint_response)
305+
306+
finality_checkpoint = finality_checkpoint_response["data"]
307+
epoch = finality_checkpoint["finalized"]["epoch"]
308+
309+
validators_response = beacon.get_validators()
310+
_assert_valid_response(validators_response)
311+
312+
validators = validators_response["data"]
313+
random_validator = validators[randint(0, len(validators))]
314+
random_validator_index = random_validator["index"]
315+
316+
response = beacon.get_attestations_rewards(epoch, [random_validator_index])
317+
_assert_valid_response(response)

web3/_utils/http_session_manager.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ def json_make_get_request(
117117
response.raise_for_status()
118118
return response.json()
119119

120+
def json_make_post_request(
121+
self, endpoint_uri: URI, *args: Any, **kwargs: Any
122+
) -> Dict[str, Any]:
123+
response = self.get_response_from_post_request(endpoint_uri, *args, **kwargs)
124+
response.raise_for_status()
125+
return response.json()
126+
120127
def get_response_from_post_request(
121128
self, endpoint_uri: URI, *args: Any, **kwargs: Any
122129
) -> requests.Response:
@@ -298,6 +305,15 @@ async def async_make_post_request(
298305
response.raise_for_status()
299306
return await response.read()
300307

308+
async def async_json_make_post_request(
309+
self, endpoint_uri: URI, *args: Any, **kwargs: Any
310+
) -> Dict[str, Any]:
311+
response = await self.async_get_response_from_post_request(
312+
endpoint_uri, *args, **kwargs
313+
)
314+
response.raise_for_status()
315+
return await response.json()
316+
301317
async def _async_close_evicted_sessions(
302318
self, timeout: float, evicted_sessions: List[ClientSession]
303319
) -> None:

web3/beacon/api_endpoints.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@
5959
GET_NODE_IDENTITY = "/eth/v1/node/identity"
6060
GET_PEERS = "/eth/v1/node/peers"
6161
GET_PEER = "/eth/v1/node/peers/{0}"
62+
GET_PEER_COUNT = "/eth/v1/node/peer_count"
6263
GET_HEALTH = "/eth/v1/node/health"
6364
GET_VERSION = "/eth/v1/node/version"
6465
GET_SYNCING = "/eth/v1/node/syncing"
66+
67+
# [ VALIDATOR endpoints ]
68+
69+
GET_ATTESTER_DUTIES = "/eth/v1/validator/duties/attester/{0}"
70+
GET_BLOCK_PROPOSERS_DUTIES = "/eth/v1/validator/duties/proposer/{0}"
71+
GET_SYNC_COMMITTEE_DUTIES = "/eth/v1/validator/duties/sync/{0}"
72+
73+
# [ REWARDS endpoints ]
74+
GET_ATTESTATIONS_REWARDS = "/eth/v1/beacon/rewards/attestations/{0}"

web3/beacon/async_beacon.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Dict,
44
List,
55
Optional,
6+
Union,
67
)
78

89
from aiohttp import (
@@ -18,6 +19,8 @@
1819
)
1920
from web3.beacon.api_endpoints import (
2021
GET_ATTESTATIONS,
22+
GET_ATTESTATIONS_REWARDS,
23+
GET_ATTESTER_DUTIES,
2124
GET_ATTESTER_SLASHINGS,
2225
GET_BEACON_HEADS,
2326
GET_BEACON_STATE,
@@ -27,6 +30,7 @@
2730
GET_BLOCK_ATTESTATIONS,
2831
GET_BLOCK_HEADER,
2932
GET_BLOCK_HEADERS,
33+
GET_BLOCK_PROPOSERS_DUTIES,
3034
GET_BLOCK_ROOT,
3135
GET_BLS_TO_EXECUTION_CHANGES,
3236
GET_DEPOSIT_CONTRACT,
@@ -45,10 +49,12 @@
4549
GET_LIGHT_CLIENT_UPDATES,
4650
GET_NODE_IDENTITY,
4751
GET_PEER,
52+
GET_PEER_COUNT,
4853
GET_PEERS,
4954
GET_PROPOSER_SLASHINGS,
5055
GET_REWARDS,
5156
GET_SPEC,
57+
GET_SYNC_COMMITTEE_DUTIES,
5258
GET_SYNCING,
5359
GET_VALIDATOR,
5460
GET_VALIDATOR_BALANCES,
@@ -78,6 +84,14 @@ async def _async_make_get_request(
7884
uri, params=params, timeout=ClientTimeout(self.request_timeout)
7985
)
8086

87+
async def _async_make_post_request(
88+
self, endpoint_uri: str, body: Union[List[str], Dict[str, Any]]
89+
) -> Dict[str, Any]:
90+
uri = URI(self.base_url + endpoint_uri)
91+
return await self._request_session_manager.async_json_make_post_request(
92+
uri, json=body, timeout=self.request_timeout
93+
)
94+
8195
# [ BEACON endpoints ]
8296

8397
# states
@@ -216,6 +230,9 @@ async def get_peers(self) -> Dict[str, Any]:
216230
async def get_peer(self, peer_id: str) -> Dict[str, Any]:
217231
return await self._async_make_get_request(GET_PEER.format(peer_id))
218232

233+
async def get_peer_count(self) -> Dict[str, Any]:
234+
return await self._async_make_get_request(GET_PEER_COUNT)
235+
219236
async def get_health(self) -> int:
220237
url = URI(self.base_url + GET_HEALTH)
221238
response = (
@@ -239,3 +256,33 @@ async def get_blob_sidecars(
239256
GET_BLOB_SIDECARS.format(block_id),
240257
params=indices_param,
241258
)
259+
260+
# [ VALIDATOR endpoints ]
261+
262+
async def get_attester_duties(
263+
self, epoch: str, validator_indices: List[str]
264+
) -> Dict[str, Any]:
265+
return await self._async_make_post_request(
266+
GET_ATTESTER_DUTIES.format(epoch), validator_indices
267+
)
268+
269+
async def get_block_proposer_duties(self, epoch: str) -> Dict[str, Any]:
270+
return await self._async_make_get_request(
271+
GET_BLOCK_PROPOSERS_DUTIES.format(epoch)
272+
)
273+
274+
async def get_sync_committee_duties(
275+
self, epoch: str, validator_indices: List[str]
276+
) -> Dict[str, Any]:
277+
return await self._async_make_post_request(
278+
GET_SYNC_COMMITTEE_DUTIES.format(epoch), validator_indices
279+
)
280+
281+
# [ REWARDS endpoints ]
282+
283+
async def get_attestations_rewards(
284+
self, epoch: str, validator_indices: List[str]
285+
) -> Dict[str, Any]:
286+
return await self._async_make_post_request(
287+
GET_ATTESTATIONS_REWARDS.format(epoch), validator_indices
288+
)

0 commit comments

Comments
 (0)