Skip to content

Commit d0f3929

Browse files
committed
PR feedback
1 parent ea0c27d commit d0f3929

File tree

6 files changed

+212
-79
lines changed

6 files changed

+212
-79
lines changed

eth/_utils/bls.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,18 @@ def privtopub(k: int) -> int:
170170

171171

172172
def verify(message: bytes, pubkey: int, signature: bytes, domain: int) -> bool:
173-
final_exponentiation = final_exponentiate(
174-
pairing(FQP_point_to_FQ2_point(decompress_G2(signature)), G1, False) *
175-
pairing(
176-
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
177-
neg(decompress_G1(pubkey)),
178-
False
173+
try:
174+
final_exponentiation = final_exponentiate(
175+
pairing(FQP_point_to_FQ2_point(decompress_G2(signature)), G1, False) *
176+
pairing(
177+
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
178+
neg(decompress_G1(pubkey)),
179+
False
180+
)
179181
)
180-
)
181-
return final_exponentiation == FQ12.one()
182+
return final_exponentiation == FQ12.one()
183+
except (ValidationError, ValueError, AssertionError):
184+
return False
182185

183186

184187
def aggregate_signatures(signatures: Sequence[bytes]) -> Tuple[int, int]:
@@ -208,16 +211,19 @@ def verify_multiple(pubkeys: Sequence[int],
208211
)
209212
)
210213

211-
o = FQ12([1] + [0] * 11)
212-
for m_pubs in set(messages):
213-
# aggregate the pubs
214-
group_pub = Z1
215-
for i in range(len_msgs):
216-
if messages[i] == m_pubs:
217-
group_pub = add(group_pub, decompress_G1(pubkeys[i]))
218-
219-
o *= pairing(hash_to_G2(m_pubs, domain), group_pub, False)
220-
o *= pairing(decompress_G2(signature), neg(G1), False)
221-
222-
final_exponentiation = final_exponentiate(o)
223-
return final_exponentiation == FQ12.one()
214+
try:
215+
o = FQ12([1] + [0] * 11)
216+
for m_pubs in set(messages):
217+
# aggregate the pubs
218+
group_pub = Z1
219+
for i in range(len_msgs):
220+
if messages[i] == m_pubs:
221+
group_pub = add(group_pub, decompress_G1(pubkeys[i]))
222+
223+
o *= pairing(hash_to_G2(m_pubs, domain), group_pub, False)
224+
o *= pairing(decompress_G2(signature), neg(G1), False)
225+
226+
final_exponentiation = final_exponentiate(o)
227+
return final_exponentiation == FQ12.one()
228+
except (ValidationError, ValueError, AssertionError):
229+
return False

eth/beacon/deposit_helpers.py

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
)
1818
from eth.beacon.enums import (
1919
SignatureDomain,
20-
ValidatorStatusCode,
20+
)
21+
from eth.beacon.exceptions import (
22+
MinEmptyValidatorIndexNotFound,
2123
)
2224
from eth.beacon.types.deposit_input import DepositInput
2325
from eth.beacon.types.states import BeaconState
@@ -27,32 +29,32 @@
2729
)
2830

2931

30-
def min_empty_validator_index(validators: Sequence[ValidatorRecord],
31-
current_slot: int,
32-
zero_balance_validator_ttl: int) -> int:
32+
def get_min_empty_validator_index(validators: Sequence[ValidatorRecord],
33+
current_slot: int,
34+
zero_balance_validator_ttl: int) -> int:
3335
for index, validator in enumerate(validators):
3436
if (
3537
validator.balance == 0 and
3638
validator.latest_status_change_slot + zero_balance_validator_ttl <=
3739
current_slot
3840
):
3941
return index
40-
return None
42+
raise MinEmptyValidatorIndexNotFound()
4143

4244

4345
def validate_proof_of_possession(state: BeaconState,
4446
pubkey: int,
4547
proof_of_possession: bytes,
4648
withdrawal_credentials: Hash32,
47-
randao_commitment: Hash32) -> bool:
49+
randao_commitment: Hash32) -> None:
4850
deposit_input = DepositInput(
4951
pubkey=pubkey,
5052
withdrawal_credentials=withdrawal_credentials,
5153
randao_commitment=randao_commitment,
5254
proof_of_possession=EMPTY_SIGNATURE,
5355
)
5456

55-
if not bls.verify(
57+
is_valid_signature = bls.verify(
5658
pubkey=pubkey,
5759
# TODO: change to hash_tree_root(deposit_input) when we have SSZ tree hashing
5860
message=deposit_input.root,
@@ -61,16 +63,48 @@ def validate_proof_of_possession(state: BeaconState,
6163
state.fork_data,
6264
state.slot,
6365
SignatureDomain.DOMAIN_DEPOSIT,
64-
)
65-
):
66+
),
67+
)
68+
69+
if not is_valid_signature:
6670
raise ValidationError(
6771
"BLS signature verification error"
6872
)
6973

70-
return True
74+
75+
def add_pending_validator(state: BeaconState,
76+
validator: ValidatorRecord,
77+
zero_balance_validator_ttl: int) -> Tuple[BeaconState, int]:
78+
"""
79+
Add a validator to the existing minimum empty validator index or
80+
append to ``validator_registry``.
81+
"""
82+
# Check if there's empty validator index in `validator_registry`
83+
try:
84+
index = get_min_empty_validator_index(
85+
state.validator_registry,
86+
state.slot,
87+
zero_balance_validator_ttl,
88+
)
89+
except MinEmptyValidatorIndexNotFound:
90+
index = None
91+
92+
if index is None:
93+
# Append to the validator_registry
94+
validator_registry = state.validator_registry + (validator,)
95+
state = state.copy(
96+
validator_registry=validator_registry,
97+
)
98+
index = len(state.validator_registry) - 1
99+
else:
100+
# Use the empty validator index
101+
state = state.update_validator(index, validator)
102+
103+
return state, index
71104

72105

73-
def process_deposit(state: BeaconState,
106+
def process_deposit(*,
107+
state: BeaconState,
74108
pubkey: int,
75109
deposit: int,
76110
proof_of_possession: bytes,
@@ -90,35 +124,19 @@ def process_deposit(state: BeaconState,
90124

91125
validator_pubkeys = tuple([v.pubkey for v in state.validator_registry])
92126
if pubkey not in validator_pubkeys:
93-
# Add new validator
94-
validator = ValidatorRecord(
127+
validator = ValidatorRecord.get_pending_validator(
95128
pubkey=pubkey,
96129
withdrawal_credentials=withdrawal_credentials,
97130
randao_commitment=randao_commitment,
98-
randao_layers=0,
99131
balance=deposit,
100-
status=ValidatorStatusCode.PENDING_ACTIVATION,
101132
latest_status_change_slot=state.slot,
102-
exit_count=0,
103133
)
104134

105-
# Check if there's empty validator index
106-
index = min_empty_validator_index(
107-
state.validator_registry,
108-
state.slot,
135+
state, index = add_pending_validator(
136+
state,
137+
validator,
109138
zero_balance_validator_ttl,
110139
)
111-
if index is None:
112-
# Append to the validator_registry
113-
with state.build_changeset() as state_changeset:
114-
state_changeset.validator_registry = (
115-
state.validator_registry + (validator,)
116-
)
117-
state = state_changeset.commit()
118-
index = len(state.validator_registry) - 1
119-
else:
120-
# Use the empty validator index
121-
state = state.update_validator(index, validator)
122140
else:
123141
# Top-up - increase balance by deposit
124142
index = validator_pubkeys.index(pubkey)
@@ -134,9 +152,9 @@ def process_deposit(state: BeaconState,
134152
)
135153

136154
# Update validator's balance and state
137-
with validator.build_changeset() as validator_changeset:
138-
validator_changeset.balance = validator.balance + deposit
139-
validator = validator_changeset.commit()
155+
validator = validator.copy(
156+
balance=validator.balance + deposit,
157+
)
140158
state = state.update_validator(index, validator)
141159

142160
return state, index

eth/beacon/types/states.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ def num_crosslinks(self) -> int:
161161
def update_validator(self,
162162
validator_index: int,
163163
validator: ValidatorRecord) -> 'BeaconState':
164-
with self.build_changeset() as state_changeset:
165-
validator_registry = list(state_changeset.validator_registry)
166-
validator_registry[validator_index] = validator
167-
state_changeset.validator_registry = tuple(validator_registry)
168-
self = state_changeset.commit()
164+
validator_registry = list(self.validator_registry)
165+
validator_registry[validator_index] = validator
166+
self = self.copy(
167+
validator_registry=tuple(validator_registry),
168+
)
169169
return self

eth/beacon/types/validator_records.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,24 @@ def is_active(self) -> bool:
6868
Returns ``True`` if the validator is active.
6969
"""
7070
return self.status in VALIDATOR_RECORD_ACTIVE_STATUSES
71+
72+
@classmethod
73+
def get_pending_validator(cls,
74+
pubkey: int,
75+
withdrawal_credentials: Hash32,
76+
randao_commitment: Hash32,
77+
balance: int,
78+
latest_status_change_slot: int) -> 'ValidatorRecord':
79+
"""
80+
Return a new pending ``ValidatorRecord`` with the given fields.
81+
"""
82+
return cls(
83+
pubkey=pubkey,
84+
withdrawal_credentials=withdrawal_credentials,
85+
randao_commitment=randao_commitment,
86+
randao_layers=0,
87+
balance=balance,
88+
status=ValidatorStatusCode.PENDING_ACTIVATION,
89+
latest_status_change_slot=latest_status_change_slot,
90+
exit_count=0,
91+
)

tests/beacon/test_deposit_helpers.py

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,21 @@
1010
from eth.beacon.constants import (
1111
EMPTY_SIGNATURE,
1212
)
13+
from eth.beacon.deposit_helpers import (
14+
add_pending_validator,
15+
get_min_empty_validator_index,
16+
process_deposit,
17+
validate_proof_of_possession,
18+
)
1319
from eth.beacon.enums import (
1420
SignatureDomain,
1521
)
22+
from eth.beacon.exceptions import (
23+
MinEmptyValidatorIndexNotFound,
24+
)
1625
from eth.beacon.helpers import (
1726
get_domain,
1827
)
19-
from eth.beacon.deposit_helpers import (
20-
min_empty_validator_index,
21-
process_deposit,
22-
validate_proof_of_possession,
23-
)
2428
from eth.beacon.types.states import BeaconState
2529
from eth.beacon.types.deposit_input import DepositInput
2630
from eth.beacon.types.validator_records import ValidatorRecord
@@ -47,31 +51,89 @@ def make_deposit_input(pubkey, withdrawal_credentials, randao_commitment):
4751
"expected",
4852
(
4953
(0, 1, 1, 2, 0),
50-
(1, 1, 1, 2, None), # not (balance == 0)
51-
(0, 1, 1, 1, None), # not (validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot) # noqa: E501
54+
(1, 1, 1, 2, MinEmptyValidatorIndexNotFound()), # not (balance == 0)
55+
(0, 1, 1, 1, MinEmptyValidatorIndexNotFound()), # not (validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot) # noqa: E501
5256
),
5357
)
54-
def test_min_empty_validator_index(sample_validator_record_params,
55-
balance,
56-
latest_status_change_slot,
57-
zero_balance_validator_ttl,
58-
current_slot,
59-
expected):
58+
def test_get_min_empty_validator_index(sample_validator_record_params,
59+
balance,
60+
latest_status_change_slot,
61+
zero_balance_validator_ttl,
62+
current_slot,
63+
expected):
6064
validators = [
6165
ValidatorRecord(**sample_validator_record_params).copy(
6266
balance=balance,
6367
latest_status_change_slot=latest_status_change_slot,
6468
)
6569
for _ in range(10)
6670
]
71+
if isinstance(expected, Exception):
72+
with pytest.raises(MinEmptyValidatorIndexNotFound):
73+
get_min_empty_validator_index(
74+
validators=validators,
75+
current_slot=current_slot,
76+
zero_balance_validator_ttl=zero_balance_validator_ttl,
77+
)
78+
else:
79+
result = get_min_empty_validator_index(
80+
validators=validators,
81+
current_slot=current_slot,
82+
zero_balance_validator_ttl=zero_balance_validator_ttl,
83+
)
84+
assert result == expected
6785

68-
result = min_empty_validator_index(
69-
validators=validators,
70-
current_slot=current_slot,
71-
zero_balance_validator_ttl=zero_balance_validator_ttl,
86+
87+
@pytest.mark.parametrize(
88+
"validator_registry_len,"
89+
"min_empty_validator_index_result,"
90+
"expected_index",
91+
(
92+
(10, 1, 1),
93+
(10, 5, 5),
94+
(10, None, 10),
95+
),
96+
)
97+
def test_add_pending_validator(monkeypatch,
98+
sample_beacon_state_params,
99+
sample_validator_record_params,
100+
validator_registry_len,
101+
min_empty_validator_index_result,
102+
expected_index):
103+
from eth.beacon import deposit_helpers
104+
105+
def mock_get_min_empty_validator_index(validators,
106+
current_slot,
107+
zero_balance_validator_ttl):
108+
if min_empty_validator_index_result is None:
109+
raise MinEmptyValidatorIndexNotFound()
110+
else:
111+
return min_empty_validator_index_result
112+
113+
monkeypatch.setattr(
114+
deposit_helpers,
115+
'get_min_empty_validator_index',
116+
mock_get_min_empty_validator_index
72117
)
73118

74-
assert result == expected
119+
state = BeaconState(**sample_beacon_state_params).copy(
120+
validator_registry=[
121+
ValidatorRecord(**sample_validator_record_params).copy(
122+
balance=100,
123+
)
124+
for _ in range(validator_registry_len)
125+
]
126+
)
127+
validator = ValidatorRecord(**sample_validator_record_params).copy(
128+
balance=5566,
129+
)
130+
state, index = add_pending_validator(
131+
state,
132+
validator,
133+
zero_balance_validator_ttl=0, # it's for `get_min_empty_validator_index`
134+
)
135+
assert index == expected_index
136+
assert state.validator_registry[index] == validator
75137

76138

77139
@pytest.mark.parametrize(
@@ -102,7 +164,7 @@ def test_validate_proof_of_possession(sample_beacon_state_params, pubkeys, privk
102164
if expected is True:
103165
proof_of_possession = sign_proof_of_possession(deposit_input, privkey, domain)
104166

105-
assert validate_proof_of_possession(
167+
validate_proof_of_possession(
106168
state=state,
107169
pubkey=pubkey,
108170
proof_of_possession=proof_of_possession,

0 commit comments

Comments
 (0)