Skip to content

Commit 4ce9185

Browse files
authored
Merge pull request #11 from ethereum/make-tests-executable
Make tests executable
2 parents 2ab4fec + 46eb2de commit 4ce9185

File tree

14 files changed

+453
-231
lines changed

14 files changed

+453
-231
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ venv_lint: venv_activate
2828
$(VENV_ACTIVATE) && flake8 --config=flake8.ini ./src ./tests && mypy --config-file mypy.ini ./src ./tests
2929

3030
venv_test: venv_activate
31-
@echo "TODO: implement tests"
31+
$(VENV_ACTIVATE) && ${VENV_NAME}/bin/python -m pytest tests/test.py
77.4 KB
Loading
78.3 KB
Loading

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ eth2spec==1.1.2
44
# dev requirements
55
mypy
66
flake8
7+
8+
# test requirements
9+
pytest

src/dvspec/consensus.py

Lines changed: 9 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,17 @@
1-
import eth2spec.phase0.mainnet as eth2spec
2-
3-
from .eth_node_interface import (
1+
from eth2spec.phase0.mainnet import (
42
AttestationData,
53
BeaconBlock,
64
)
5+
76
from .utils.types import (
87
AttestationDuty,
9-
BLSPubkey,
108
ProposerDuty,
119
SlashingDB,
1210
)
13-
14-
"""
15-
Helper Functions
16-
"""
17-
18-
19-
def is_slashable_attestation_data(slashing_db: SlashingDB,
20-
attestation_data: AttestationData, pubkey: BLSPubkey) -> bool:
21-
matching_slashing_db_data = [data for data in slashing_db.data if data.pubkey == pubkey]
22-
if matching_slashing_db_data == []:
23-
return True
24-
assert len(matching_slashing_db_data) == 1
25-
slashing_db_data = matching_slashing_db_data[0]
26-
# Check for EIP-3076 conditions:
27-
# https://eips.ethereum.org/EIPS/eip-3076#conditions
28-
if slashing_db_data.signed_attestations != []:
29-
min_target = min(attn.target_epoch for attn in slashing_db_data.signed_attestations)
30-
min_source = min(attn.source_epoch for attn in slashing_db_data.signed_attestations)
31-
if attestation_data.target.epoch <= min_target:
32-
return True
33-
if attestation_data.source.epoch < min_source:
34-
return True
35-
for past_attn in slashing_db_data.signed_attestations:
36-
past_attn_data = AttestationData(source=past_attn.source_epoch, target=past_attn.target_epoch)
37-
if eth2spec.is_slashable_attestation_data(past_attn_data, attestation_data):
38-
return True
39-
return False
40-
41-
42-
def is_slashable_block(slashing_db: SlashingDB, block: BeaconBlock, pubkey: BLSPubkey) -> bool:
43-
matching_slashing_db_data = [data for data in slashing_db.data if data.pubkey == pubkey]
44-
if matching_slashing_db_data == []:
45-
return False
46-
assert len(matching_slashing_db_data) == 1
47-
slashing_db_data = matching_slashing_db_data[0]
48-
# Check for EIP-3076 conditions:
49-
# https://eips.ethereum.org/EIPS/eip-3076#conditions
50-
if slashing_db_data.signed_blocks != []:
51-
min_block = slashing_db_data.signed_blocks[0]
52-
for b in slashing_db_data.signed_blocks[1:]:
53-
if b.slot < min_block.slot:
54-
min_block = b
55-
if block.slot < min_block.slot:
56-
return True
57-
for past_block in slashing_db_data.signed_blocks:
58-
if past_block.slot == block.slot:
59-
if past_block.signing_root != block.hash_tree_root():
60-
return True
61-
return False
11+
from .utils.helpers import (
12+
is_slashable_attestation_data,
13+
is_slashable_block,
14+
)
6215

6316

6417
"""
@@ -71,12 +24,12 @@ def consensus_is_valid_attestation_data(slashing_db: SlashingDB,
7124
"""Determines if the given attestation is valid for the attestation duty.
7225
"""
7326
assert attestation_data.slot == attestation_duty.slot
74-
assert attestation_data.committee_index == attestation_duty.committee_index
27+
assert attestation_data.index == attestation_duty.committee_index
7528
assert not is_slashable_attestation_data(slashing_db, attestation_data, attestation_duty.pubkey)
7629
return True
7730

7831

79-
def consensus_on_attestation(attestation_duty: AttestationDuty) -> AttestationData:
32+
def consensus_on_attestation(slashing_db: SlashingDB, attestation_duty: AttestationDuty) -> AttestationData:
8033
"""Consensus protocol between distributed validator nodes for attestation values.
8134
Returns the decided value.
8235
If this DV is the leader, it must use `bn_produce_attestation_data` for the proposed value.
@@ -95,7 +48,7 @@ def consensus_is_valid_block(slashing_db: SlashingDB, block: BeaconBlock, propos
9548
return True
9649

9750

98-
def consensus_on_block(proposer_duty: ProposerDuty) -> AttestationData:
51+
def consensus_on_block(slashing_db: SlashingDB, proposer_duty: ProposerDuty) -> AttestationData:
9952
"""Consensus protocol between distributed validator nodes for block values.
10053
Returns the decided value.
10154
If this DV is the leader, it must use `bn_produce_block` for the proposed value.

src/dvspec/eth_node_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def bn_submit_block(block: SignedBeaconBlock) -> None:
7676
- VC asks for its attestation, block proposal, or sync duties using the following methods:
7777
- https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getAttesterDuties
7878
- https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getProposerDuties
79-
- mhttps://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getSyncCommitteeDuties
79+
- https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/getSyncCommitteeDuties
8080
- VC asks for new attestation data, block, or sync duty using the following methods:
8181
- https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/produceAttestationData
8282
- https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/produceBlockV2

src/dvspec/spec.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22
from typing import (
33
List,
44
)
5+
from eth2spec.phase0.mainnet import (
6+
AttestationData,
7+
BeaconBlock,
8+
)
59

10+
from .utils.helpers import (
11+
get_slashing_db_data_for_pubkey,
12+
is_slashable_attestation_data,
13+
is_slashable_block,
14+
)
615
from .eth_node_interface import (
716
AttestationDuty,
817
ProposerDuty,
@@ -24,7 +33,9 @@
2433
from .utils.types import (
2534
BLSPubkey,
2635
SlashingDB,
27-
UInt64
36+
SlashingDBAttestation,
37+
SlashingDBBlock,
38+
ValidatorIndex
2839
)
2940

3041

@@ -35,7 +46,7 @@ class ValidatorIdentity:
3546
# Ethereum public key
3647
pubkey: BLSPubkey
3748
# Index of Ethereum validator
38-
index: UInt64
49+
index: ValidatorIndex
3950

4051

4152
@dataclass
@@ -47,7 +58,7 @@ class CoValidator:
4758
# Secret-shared public key
4859
pubkey: BLSPubkey
4960
# Index of the co-validator in the distributed validator protocol
50-
index: UInt64
61+
index: ValidatorIndex
5162

5263

5364
@dataclass
@@ -64,27 +75,52 @@ class State:
6475
distributed_validators: List[DistributedValidator]
6576

6677

67-
def serve_attestation_duty(attestation_duty: AttestationDuty) -> None:
78+
def update_attestation_slashing_db(slashing_db: SlashingDB,
79+
attestation_data: AttestationData, pubkey: BLSPubkey) -> None:
80+
"""Update slashing DB for the validator with pubkey with new attestation data.
81+
"""
82+
assert not is_slashable_attestation_data(slashing_db, attestation_data, pubkey)
83+
slashing_db_data = get_slashing_db_data_for_pubkey(slashing_db, pubkey)
84+
slashing_db_attestation = SlashingDBAttestation(source_epoch=attestation_data.source.epoch,
85+
target_epoch=attestation_data.target.epoch,
86+
signing_root=attestation_data.hash_tree_root())
87+
slashing_db_data.signed_attestations.append(slashing_db_attestation)
88+
89+
90+
def update_block_slashing_db(slashing_db: SlashingDB, block: BeaconBlock, pubkey: BLSPubkey) -> None:
91+
"""Update slashing DB for the validator with pubkey with new block.
92+
"""
93+
assert not is_slashable_block(slashing_db, block, pubkey)
94+
slashing_db_data = get_slashing_db_data_for_pubkey(slashing_db, pubkey)
95+
slashing_db_block = SlashingDBBlock(slot=block.slot,
96+
signing_root=block.hash_tree_root())
97+
slashing_db_data.signed_blocks.append(slashing_db_block)
98+
99+
100+
def serve_attestation_duty(slashing_db: SlashingDB, attestation_duty: AttestationDuty) -> None:
68101
"""
69102
Attestation Production Process:
70103
1. At the start of every epoch, get attestation duties for epoch+1 by running
71104
bn_get_attestation_duties_for_epoch(validator_indices, epoch+1)
72105
2. For each attestation_duty received in Step 1, schedule
73-
serve_attestation_duty(attestation_duty) at 1/3rd way through the slot
106+
serve_attestation_duty(slashing_db, attestation_duty) at 1/3rd way through the slot
74107
attestation_duty.slot
75108
See notes here:
76109
https://github.com/ethereum/beacon-APIs/blob/05c1bc142e1a3fb2a63c79098743776241341d08/validator-flow.md#attestation
77110
"""
111+
# TODO: Is lock on consensus the best way to do this? Does lock on slashing DB work?
78112
# Obtain lock on consensus_on_attestation here.
79113
# Only a single consensus_on_attestation instance should be
80114
# running at any given time
81-
attestation_data = consensus_on_attestation(attestation_duty)
115+
attestation_data = consensus_on_attestation(slashing_db, attestation_duty)
82116
# Release lock on consensus_on_attestation here.
117+
# Add attestation to slashing DB
118+
update_attestation_slashing_db(slashing_db, attestation_data, attestation_duty.pubkey)
83119
# Cache decided attestation data value to provide to VC
84120
cache_attestation_data_for_vc(attestation_data, attestation_duty)
85121

86122

87-
def serve_proposer_duty(proposer_duty: ProposerDuty) -> None:
123+
def serve_proposer_duty(slashing_db: SlashingDB, proposer_duty: ProposerDuty) -> None:
88124
""""
89125
Block Production Process:
90126
1. At the start of every epoch, get proposer duties for epoch+1 by running
@@ -94,11 +130,14 @@ def serve_proposer_duty(proposer_duty: ProposerDuty) -> None:
94130
See notes here:
95131
https://github.com/ethereum/beacon-APIs/blob/05c1bc142e1a3fb2a63c79098743776241341d08/validator-flow.md#block-proposing
96132
"""
133+
# TODO: Is lock on consensus the best way to do this? Does lock on slashing DB work?
97134
# Obtain lock on consensus_on_block here.
98135
# Only a single consensus_on_block instance should be
99136
# running at any given time
100-
block = consensus_on_block(proposer_duty)
137+
block = consensus_on_block(slashing_db, proposer_duty)
101138
# Release lock on consensus_on_block here.
139+
# Add block to slashing DB
140+
update_block_slashing_db(slashing_db, block, proposer_duty.pubkey)
102141
# Cache decided block value to provide to VC
103142
cache_block_for_vc(block, proposer_duty)
104143

src/dvspec/utils/helpers.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import eth2spec.phase0.mainnet as eth2spec
2+
from eth2spec.phase0.mainnet import (
3+
AttestationData,
4+
BeaconBlock,
5+
)
6+
7+
from .types import (
8+
BLSPubkey,
9+
SlashingDB,
10+
SlashingDBData,
11+
)
12+
13+
14+
"""
15+
Helper Functions
16+
"""
17+
18+
19+
def get_slashing_db_data_for_pubkey(slashing_db: SlashingDB, pubkey: BLSPubkey) -> SlashingDBData:
20+
"""Get SlashingDBData for the pubkey in the slashing_db.
21+
Returns empty SlashingDBData for the pubkey if matching entry is not found in slashing_db.
22+
"""
23+
matching_slashing_db_data = [data for data in slashing_db.data if data.pubkey == pubkey]
24+
if matching_slashing_db_data == []:
25+
# No matching SlashingDBData found. Returning empty SlashingDBData.
26+
return SlashingDBData(pubkey=pubkey, signed_blocks=[], signed_attestations=[])
27+
assert len(matching_slashing_db_data) == 1
28+
slashing_db_data = matching_slashing_db_data[0]
29+
return slashing_db_data
30+
31+
32+
def is_slashable_attestation_data(slashing_db: SlashingDB,
33+
attestation_data: AttestationData, pubkey: BLSPubkey) -> bool:
34+
"""Checks if the attestation data is slashable according to the slashing DB.
35+
"""
36+
slashing_db_data = get_slashing_db_data_for_pubkey(slashing_db, pubkey)
37+
# Check for EIP-3076 conditions:
38+
# https://eips.ethereum.org/EIPS/eip-3076#conditions
39+
if slashing_db_data.signed_attestations != []:
40+
min_target = min(attn.target_epoch for attn in slashing_db_data.signed_attestations)
41+
min_source = min(attn.source_epoch for attn in slashing_db_data.signed_attestations)
42+
if attestation_data.target.epoch <= min_target:
43+
return True
44+
if attestation_data.source.epoch < min_source:
45+
return True
46+
for past_attn in slashing_db_data.signed_attestations:
47+
past_attn_data = AttestationData(source=past_attn.source_epoch, target=past_attn.target_epoch)
48+
if eth2spec.is_slashable_attestation_data(past_attn_data, attestation_data):
49+
return True
50+
return False
51+
52+
53+
def is_slashable_block(slashing_db: SlashingDB, block: BeaconBlock, pubkey: BLSPubkey) -> bool:
54+
"""Checks if the block is slashable according to the slashing DB.
55+
"""
56+
slashing_db_data = get_slashing_db_data_for_pubkey(slashing_db, pubkey)
57+
# Check for EIP-3076 conditions:
58+
# https://eips.ethereum.org/EIPS/eip-3076#conditions
59+
if slashing_db_data.signed_blocks != []:
60+
min_block = slashing_db_data.signed_blocks[0]
61+
for b in slashing_db_data.signed_blocks[1:]:
62+
if b.slot < min_block.slot:
63+
min_block = b
64+
if block.slot < min_block.slot:
65+
return True
66+
for past_block in slashing_db_data.signed_blocks:
67+
if past_block.slot == block.slot:
68+
if past_block.signing_root != block.hash_tree_root():
69+
return True
70+
return False

tests/helpers/consensus.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from eth2spec.phase0.mainnet import (
2+
AttestationData,
3+
BeaconBlock,
4+
)
5+
from dvspec.utils.types import (
6+
BLSSignature,
7+
Bytes32,
8+
AttestationDuty,
9+
ProposerDuty,
10+
SlashingDB,
11+
)
12+
from dvspec.consensus import (
13+
consensus_is_valid_attestation_data,
14+
consensus_is_valid_block,
15+
)
16+
17+
from tests.helpers.eth_node_interface import (
18+
bn_produce_attestation_data,
19+
bn_produce_block,
20+
)
21+
22+
23+
"""
24+
Consensus Specification
25+
"""
26+
27+
28+
def consensus_on_attestation(slashing_db: SlashingDB, attestation_duty: AttestationDuty) -> AttestationData:
29+
"""Consensus protocol between distributed validator nodes for attestation values.
30+
Returns the decided value.
31+
If this DV is the leader, it must use `bn_produce_attestation_data` for the proposed value.
32+
The consensus protocol must use `consensus_is_valid_attestation_data` to determine
33+
validity of the proposed attestation value.
34+
"""
35+
# TODO: Use this method in tests instead of dvspec.consensus.consensus_on_attestation
36+
attestation_data = bn_produce_attestation_data(attestation_duty.slot, attestation_duty.committee_index)
37+
assert consensus_is_valid_attestation_data(slashing_db, attestation_data, attestation_duty)
38+
return attestation_data
39+
40+
41+
def consensus_on_block(slashing_db: SlashingDB, proposer_duty: ProposerDuty) -> BeaconBlock:
42+
"""Consensus protocol between distributed validator nodes for block values.
43+
Returns the decided value.
44+
If this DV is the leader, it must use `bn_produce_block` for the proposed value.
45+
The consensus protocol must use `consensus_is_valid_block` to determine
46+
validity of the proposed block value.
47+
"""
48+
# TODO: Use this method in tests instead of dvspec.consensus.consensus_on_block
49+
block = bn_produce_block(proposer_duty.slot, BLSSignature(b'0'*96), Bytes32(b'0'*32))
50+
assert consensus_is_valid_block(slashing_db, block, proposer_duty)
51+
return block

0 commit comments

Comments
 (0)