Skip to content

Commit 639bf15

Browse files
committed
General refactoring
1 parent 82c1d08 commit 639bf15

File tree

8 files changed

+349
-236
lines changed

8 files changed

+349
-236
lines changed

src/dvspec/consensus.py

Lines changed: 8 additions & 55 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
"""
@@ -76,7 +29,7 @@ def consensus_is_valid_attestation_data(slashing_db: SlashingDB,
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)
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)
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/eth_node_interface.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import random
2+
from eth2spec.phase0.mainnet import (
3+
MAX_COMMITTEES_PER_SLOT,
4+
SLOTS_PER_EPOCH,
5+
TARGET_COMMITTEE_SIZE,
6+
Attestation,
7+
AttestationData,
8+
BeaconBlock,
9+
Checkpoint,
10+
compute_epoch_at_slot,
11+
compute_start_slot_at_epoch,
12+
)
13+
14+
from dvspec.utils.types import (
15+
AttestationDuty,
16+
BLSPubkey,
17+
BLSSignature,
18+
Bytes32,
19+
CommitteeIndex,
20+
Epoch,
21+
List,
22+
ProposerDuty,
23+
Slot,
24+
ValidatorIndex,
25+
)
26+
from dvspec.spec import (
27+
State,
28+
)
29+
30+
VALIDATOR_SET_SIZE = SLOTS_PER_EPOCH
31+
32+
"""
33+
Ethereum node interface methods
34+
"""
35+
36+
37+
def bn_get_attestation_duties_for_epoch(validator_indices: List[ValidatorIndex], epoch: Epoch) -> List[AttestationDuty]:
38+
attestation_duties = []
39+
for validator_index in validator_indices:
40+
start_slot_at_epoch = compute_start_slot_at_epoch(epoch)
41+
attestation_slot = start_slot_at_epoch + random.randrange(SLOTS_PER_EPOCH)
42+
attestation_duty = AttestationDuty(pubkey=BLSPubkey(0x00),
43+
validator_index=validator_index,
44+
committee_index=random.randrange(MAX_COMMITTEES_PER_SLOT),
45+
committee_length=TARGET_COMMITTEE_SIZE,
46+
committees_at_slot=MAX_COMMITTEES_PER_SLOT,
47+
validator_committee_index=random.randrange(TARGET_COMMITTEE_SIZE),
48+
slot=attestation_slot)
49+
attestation_duties.append(attestation_duty)
50+
return attestation_duties
51+
52+
53+
def bn_produce_attestation_data(slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
54+
attestation_data = AttestationData(slot=slot,
55+
index=committee_index,
56+
source=Checkpoint(epoch=min(compute_epoch_at_slot(slot) - 1, 0)),
57+
target=Checkpoint(epoch=compute_epoch_at_slot(slot)))
58+
return attestation_data
59+
60+
61+
def bn_submit_attestation(attestation: Attestation) -> None:
62+
pass
63+
64+
65+
def bn_get_proposer_duties_for_epoch(epoch: Epoch) -> List[ProposerDuty]:
66+
proposer_duties = []
67+
validator_indices = [x for x in range(VALIDATOR_SET_SIZE)]
68+
random.shuffle(validator_indices)
69+
for i in range(SLOTS_PER_EPOCH):
70+
proposer_duties.append(ProposerDuty(pubkey=BLSPubkey(0x00), validator_index=validator_indices[i], slot=i))
71+
return proposer_duties
72+
73+
74+
def bn_produce_block(slot: Slot, randao_reveal: BLSSignature, graffiti: Bytes32) -> BeaconBlock:
75+
block = BeaconBlock()
76+
block.slot = slot
77+
block.body.randao_reveal = randao_reveal
78+
block.body.graffiti = graffiti
79+
return block
80+
81+
82+
"""
83+
Helpers for Ethereum node interaface methods
84+
"""
85+
86+
87+
def fill_attestation_duties_with_val_index(state: State, attestation_duties: List[AttestationDuty]) -> List[AttestationDuty]:
88+
val_index_to_pubkey = {}
89+
for dv in state.distributed_validators:
90+
val_index_to_pubkey[dv.validator_identity.index] = dv.validator_identity.pubkey
91+
for att_duty in attestation_duties:
92+
att_duty.pubkey = val_index_to_pubkey[att_duty.validator_index]
93+
return attestation_duties
94+
95+
96+
97+
def fill_proposer_duties_with_val_index(state: State, proposer_duties: List[ProposerDuty]) -> List[ProposerDuty]:
98+
val_index_to_pubkey = {}
99+
for dv in state.distributed_validators:
100+
val_index_to_pubkey[dv.validator_identity.index] = dv.validator_identity.pubkey
101+
for pro_duty in proposer_duties:
102+
pro_duty.pubkey = val_index_to_pubkey[pro_duty.validator_index]
103+
return proposer_duties

0 commit comments

Comments
 (0)