Skip to content

Commit 9c93531

Browse files
authored
Merge pull request #1631 from djrtwo/attestation-validation
Attestation validation
2 parents 39f8d42 + d254d9c commit 9c93531

File tree

12 files changed

+847
-30
lines changed

12 files changed

+847
-30
lines changed

eth/beacon/state_machines/forks/serenity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
from eth.beacon.state_machines.base import BeaconStateMachine
77
from eth.beacon.state_machines.state_transitions import BaseStateTransition # noqa: F401
88

9+
from .configs import SERENITY_CONFIG
910
from .blocks import SerenityBeaconBlock
1011
from .states import SerenityBeaconState
1112
from .state_transitions import SerenityStateTransition
12-
from .configs import SERENITY_CONFIG
1313

1414

1515
class SerenityStateMachine(BeaconStateMachine):
Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,44 @@
11
from eth.beacon.types.blocks import BaseBeaconBlock
2+
from eth.beacon.types.pending_attestation_records import PendingAttestationRecord
23
from eth.beacon.types.states import BeaconState
34
from eth.beacon.state_machines.configs import BeaconConfig
45

6+
from .validation import (
7+
validate_serenity_attestation,
8+
)
9+
510

611
def process_attestations(state: BeaconState,
712
block: BaseBeaconBlock,
813
config: BeaconConfig) -> BeaconState:
9-
# TODO
10-
# It's just for demo!!!
14+
"""
15+
Implements 'per-block-processing.operations.attestations' portion of Phase 0 spec:
16+
https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestations-1
17+
18+
Validate the ``attestations`` contained within the ``block`` in the context of ``state``.
19+
If any invalid, throw ``ValidationError``.
20+
Otherwise, append an ``PendingAttestationRecords`` for each to ``latest_attestations``.
21+
Return resulting ``state``.
22+
"""
23+
for attestation in block.body.attestations:
24+
validate_serenity_attestation(
25+
state,
26+
attestation,
27+
config.EPOCH_LENGTH,
28+
config.MIN_ATTESTATION_INCLUSION_DELAY,
29+
)
30+
31+
# update_latest_attestations
32+
additional_pending_attestations = tuple(
33+
PendingAttestationRecord(
34+
data=attestation.data,
35+
participation_bitfield=attestation.participation_bitfield,
36+
custody_bitfield=attestation.custody_bitfield,
37+
slot_included=state.slot,
38+
)
39+
for attestation in block.body.attestations
40+
)
1141
state = state.copy(
12-
slot=config.ZERO_BALANCE_VALIDATOR_TTL,
42+
latest_attestations=state.latest_attestations + additional_pending_attestations,
1343
)
1444
return state

eth/beacon/state_machines/forks/serenity/state_transitions.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from eth_typing import (
2+
Hash32,
3+
)
4+
15
from eth.beacon.types.blocks import BaseBeaconBlock
26
from eth.beacon.types.states import BeaconState
37

@@ -16,21 +20,29 @@ def __init__(self, config: BeaconConfig):
1620
self.config = config
1721

1822
def apply_state_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
19-
state = self.per_slot_transition(state, block)
20-
state = self.per_block_transition(state, block)
21-
state = self.per_epoch_transition(state, block)
23+
while state.slot != block.slot:
24+
state = self.per_slot_transition(state, block.parent_root)
25+
if state.slot == block.slot:
26+
state = self.per_block_transition(state, block)
27+
if state.slot % self.config.EPOCH_LENGTH == 0:
28+
state = self.per_epoch_transition(state)
2229

2330
return state
2431

25-
def per_slot_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
32+
def per_slot_transition(self,
33+
state: BeaconState,
34+
previous_block_root: Hash32) -> BeaconState:
2635
# TODO
36+
state = state.copy(
37+
slot=state.slot + 1
38+
)
2739
return state
2840

2941
def per_block_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
3042
# TODO
3143
state = process_attestations(state, block, self.config)
3244
return state
3345

34-
def per_epoch_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
46+
def per_epoch_transition(self, state: BeaconState) -> BeaconState:
3547
# TODO
3648
return state
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
from eth_typing import (
2+
Hash32
3+
)
4+
from eth_utils import (
5+
ValidationError,
6+
)
7+
import rlp
8+
from typing import Type
9+
10+
from eth.constants import (
11+
ZERO_HASH32,
12+
)
13+
14+
from eth._utils import bls as bls
15+
from eth.beacon._utils.hash import (
16+
hash_eth2,
17+
)
18+
19+
from eth.beacon.enums import (
20+
SignatureDomain,
21+
)
22+
from eth.beacon.helpers import (
23+
get_attestation_participants,
24+
get_block_root,
25+
get_domain,
26+
)
27+
from eth.beacon.types.states import BeaconState # noqa: F401
28+
from eth.beacon.types.attestations import Attestation # noqa: F401
29+
from eth.beacon.types.attestation_data import AttestationData # noqa: F401
30+
31+
32+
#
33+
# Attestation validation
34+
#
35+
def validate_serenity_attestation(state: Type[BeaconState],
36+
attestation: Attestation,
37+
epoch_length: int,
38+
min_attestation_inclusion_delay: int) -> None:
39+
"""
40+
Validate the given ``attestation``.
41+
Raise ``ValidationError`` if it's invalid.
42+
"""
43+
44+
validate_serenity_attestation_slot(
45+
attestation.data,
46+
state.slot,
47+
epoch_length,
48+
min_attestation_inclusion_delay,
49+
)
50+
51+
validate_serenity_attestation_justified_slot(
52+
attestation.data,
53+
state.slot,
54+
state.previous_justified_slot,
55+
state.justified_slot,
56+
epoch_length,
57+
)
58+
59+
validate_serenity_attestation_justified_block_root(
60+
attestation.data,
61+
justified_block_root=get_block_root(
62+
state.latest_block_roots,
63+
current_slot=state.slot,
64+
slot=attestation.data.justified_slot,
65+
),
66+
)
67+
68+
validate_serenity_attestation_latest_crosslink_root(
69+
attestation.data,
70+
latest_crosslink_root=state.latest_crosslinks[attestation.data.shard].shard_block_root,
71+
)
72+
73+
validate_serenity_attestation_shard_block_root(attestation.data)
74+
75+
validate_serenity_attestation_aggregate_signature(
76+
state,
77+
attestation,
78+
epoch_length,
79+
)
80+
81+
82+
def validate_serenity_attestation_slot(attestation_data: AttestationData,
83+
current_slot: int,
84+
epoch_length: int,
85+
min_attestation_inclusion_delay: int) -> None:
86+
"""
87+
Validate ``slot`` field of ``attestation_data``.
88+
Raise ``ValidationError`` if it's invalid.
89+
"""
90+
if attestation_data.slot + min_attestation_inclusion_delay > current_slot:
91+
raise ValidationError(
92+
"Attestation slot plus min inclusion delay is too high:\n"
93+
"\tFound: %s (%s + %s), Needed less than or equal to %s" %
94+
(
95+
attestation_data.slot + min_attestation_inclusion_delay,
96+
attestation_data.slot,
97+
min_attestation_inclusion_delay,
98+
current_slot,
99+
)
100+
)
101+
if attestation_data.slot + epoch_length < current_slot:
102+
raise ValidationError(
103+
"Attestation slot plus epoch length is too low:\n"
104+
"\tFound: %s (%s + %s), Needed greater than or equal to: %s" %
105+
(
106+
attestation_data.slot + epoch_length,
107+
attestation_data.slot,
108+
epoch_length,
109+
current_slot,
110+
)
111+
)
112+
113+
114+
def validate_serenity_attestation_justified_slot(attestation_data: AttestationData,
115+
current_slot: int,
116+
previous_justified_slot: int,
117+
justified_slot: int,
118+
epoch_length: int) -> None:
119+
"""
120+
Validate ``justified_slot`` field of ``attestation_data``.
121+
Raise ``ValidationError`` if it's invalid.
122+
"""
123+
if attestation_data.slot >= current_slot - (current_slot % epoch_length):
124+
if attestation_data.justified_slot != justified_slot:
125+
raise ValidationError(
126+
"Attestation ``slot`` is after recent epoch transition but attestation"
127+
"``justified_slot`` is not targeting the ``justified_slot``:\n"
128+
"\tFound: %s, Expected %s" %
129+
(attestation_data.justified_slot, justified_slot)
130+
)
131+
else:
132+
if attestation_data.justified_slot != previous_justified_slot:
133+
raise ValidationError(
134+
"Attestation ``slot`` is before recent epoch transition but attestation"
135+
"``justified_slot`` is not targeting the ``previous_justified_slot:\n"
136+
"\tFound: %s, Expected %s" %
137+
(attestation_data.justified_slot, previous_justified_slot)
138+
)
139+
140+
141+
def validate_serenity_attestation_justified_block_root(attestation_data: AttestationData,
142+
justified_block_root: Hash32) -> None:
143+
"""
144+
Validate ``justified_block_root`` field of ``attestation_data``.
145+
Raise ``ValidationError`` if it's invalid.
146+
"""
147+
if attestation_data.justified_block_root != justified_block_root:
148+
raise ValidationError(
149+
"Attestation ``justified_block_root`` is not equal to the "
150+
"``justified_block_root`` at the ``justified_slot``:\n"
151+
"\tFound: %s, Expected %s at slot %s" %
152+
(
153+
attestation_data.justified_block_root,
154+
justified_block_root,
155+
attestation_data.justified_slot,
156+
)
157+
)
158+
159+
160+
def validate_serenity_attestation_latest_crosslink_root(attestation_data: AttestationData,
161+
latest_crosslink_root: Hash32) -> None:
162+
"""
163+
Validate that either the attestation ``latest_crosslink_root`` or ``shard_block_root``
164+
field of ``attestation_data`` is the provided ``latest_crosslink_root``.
165+
Raise ``ValidationError`` if it's invalid.
166+
"""
167+
acceptable_shard_block_roots = {
168+
attestation_data.latest_crosslink_root,
169+
attestation_data.shard_block_root,
170+
}
171+
if latest_crosslink_root not in acceptable_shard_block_roots:
172+
raise ValidationError(
173+
"Neither the attestation ``latest_crosslink_root`` nor the attestation "
174+
"``shard_block_root`` are equal to the ``latest_crosslink_root``.\n"
175+
"\tFound: %s and %s, Expected %s" %
176+
(
177+
attestation_data.latest_crosslink_root,
178+
attestation_data.shard_block_root,
179+
latest_crosslink_root,
180+
)
181+
)
182+
183+
184+
def validate_serenity_attestation_shard_block_root(attestation_data: AttestationData) -> None:
185+
"""
186+
Validate ``shard_block_root`` field of `attestation_data`.
187+
Raise ``ValidationError`` if it's invalid.
188+
189+
Note: This is the Phase 0 version of ``shard_block_root`` validation.
190+
This is a built-in stub and will be changed in phase 1.
191+
"""
192+
if attestation_data.shard_block_root != ZERO_HASH32:
193+
raise ValidationError(
194+
"Attestation ``shard_block_root`` is not ZERO_HASH32.\n"
195+
"\tFound: %s, Expected %s" %
196+
(
197+
attestation_data.shard_block_root,
198+
ZERO_HASH32,
199+
)
200+
)
201+
202+
203+
def validate_serenity_attestation_aggregate_signature(state: Type[BeaconState],
204+
attestation: Attestation,
205+
epoch_length: int) -> None:
206+
"""
207+
Validate ``aggregate_signature`` field of ``attestation``.
208+
Raise ``ValidationError`` if it's invalid.
209+
210+
Note: This is the phase 0 version of `aggregate_signature`` validation.
211+
All proof of custody bits are assumed to be 0 within the signed data.
212+
This will change to reflect real proof of custody bits in the Phase 1.
213+
"""
214+
participant_indices = get_attestation_participants(
215+
state=state,
216+
slot=attestation.data.slot,
217+
shard=attestation.data.shard,
218+
participation_bitfield=attestation.participation_bitfield,
219+
epoch_length=epoch_length,
220+
)
221+
222+
pubkeys = tuple(
223+
state.validator_registry[validator_index].pubkey
224+
for validator_index in participant_indices
225+
)
226+
group_public_key = bls.aggregate_pubkeys(pubkeys)
227+
228+
# TODO: change to tree hashing when we have SSZ
229+
# TODO: Replace with AttestationAndCustodyBit data structure
230+
message = hash_eth2(
231+
rlp.encode(attestation.data) +
232+
(0).to_bytes(1, "big")
233+
)
234+
235+
is_valid_signature = bls.verify(
236+
message=message,
237+
pubkey=group_public_key,
238+
signature=attestation.aggregate_signature,
239+
domain=get_domain(
240+
fork_data=state.fork_data,
241+
slot=attestation.data.slot,
242+
domain_type=SignatureDomain.DOMAIN_ATTESTATION,
243+
),
244+
245+
)
246+
if not is_valid_signature:
247+
raise ValidationError(
248+
"Attestation ``aggregate_signature`` is invalid."
249+
)

eth/beacon/state_machines/state_transitions.py

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

6+
from eth_typing import (
7+
Hash32,
8+
)
69
from eth._utils.datatypes import (
710
Configurable,
811
)
@@ -24,13 +27,15 @@ def apply_state_transition(self, state: BeaconState, block: BaseBeaconBlock) ->
2427
pass
2528

2629
@abstractmethod
27-
def per_slot_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
30+
def per_slot_transition(self,
31+
state: BeaconState,
32+
previous_block_root: Hash32) -> BeaconState:
2833
pass
2934

3035
@abstractmethod
3136
def per_block_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
3237
pass
3338

3439
@abstractmethod
35-
def per_epoch_transition(self, state: BeaconState, block: BaseBeaconBlock) -> BeaconState:
40+
def per_epoch_transition(self, state: BeaconState) -> BeaconState:
3641
pass

0 commit comments

Comments
 (0)