Skip to content

Commit d82b10a

Browse files
authored
Merge pull request #1640 from hwwhww/on_startup_4
Routine for processing deposits
2 parents 8491513 + c74560f commit d82b10a

File tree

11 files changed

+596
-49
lines changed

11 files changed

+596
-49
lines changed

eth/_utils/bls.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,22 @@ 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(
176+
FQP_point_to_FQ2_point(decompress_G2(signature)),
177+
G1,
178+
final_exponentiate=False,
179+
) *
180+
pairing(
181+
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
182+
neg(decompress_G1(pubkey)),
183+
final_exponentiate=False,
184+
)
179185
)
180-
)
181-
return final_exponentiation == FQ12.one()
186+
return final_exponentiation == FQ12.one()
187+
except (ValidationError, ValueError, AssertionError):
188+
return False
182189

183190

184191
def aggregate_signatures(signatures: Sequence[bytes]) -> Tuple[int, int]:
@@ -208,16 +215,19 @@ def verify_multiple(pubkeys: Sequence[int],
208215
)
209216
)
210217

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()
218+
try:
219+
o = FQ12([1] + [0] * 11)
220+
for m_pubs in set(messages):
221+
# aggregate the pubs
222+
group_pub = Z1
223+
for i in range(len_msgs):
224+
if messages[i] == m_pubs:
225+
group_pub = add(group_pub, decompress_G1(pubkeys[i]))
226+
227+
o *= pairing(hash_to_G2(m_pubs, domain), group_pub, final_exponentiate=False)
228+
o *= pairing(decompress_G2(signature), neg(G1), final_exponentiate=False)
229+
230+
final_exponentiation = final_exponentiate(o)
231+
return final_exponentiation == FQ12.one()
232+
except (ValidationError, ValueError, AssertionError):
233+
return False

eth/beacon/deposit_helpers.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
from typing import (
2+
Sequence,
3+
Tuple,
4+
)
5+
6+
from eth_typing import (
7+
Hash32,
8+
)
9+
from eth_utils import (
10+
ValidationError,
11+
)
12+
13+
from eth._utils import bls
14+
15+
from eth.beacon.constants import (
16+
EMPTY_SIGNATURE,
17+
)
18+
from eth.beacon.enums import (
19+
SignatureDomain,
20+
)
21+
from eth.beacon.exceptions import (
22+
MinEmptyValidatorIndexNotFound,
23+
)
24+
from eth.beacon.types.deposit_input import DepositInput
25+
from eth.beacon.types.states import BeaconState
26+
from eth.beacon.types.validator_records import ValidatorRecord
27+
from eth.beacon.helpers import (
28+
get_domain,
29+
)
30+
31+
32+
def get_min_empty_validator_index(validators: Sequence[ValidatorRecord],
33+
current_slot: int,
34+
zero_balance_validator_ttl: int) -> int:
35+
for index, validator in enumerate(validators):
36+
is_empty = (
37+
validator.balance == 0 and
38+
validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot
39+
)
40+
if is_empty:
41+
return index
42+
raise MinEmptyValidatorIndexNotFound()
43+
44+
45+
def validate_proof_of_possession(state: BeaconState,
46+
pubkey: int,
47+
proof_of_possession: bytes,
48+
withdrawal_credentials: Hash32,
49+
randao_commitment: Hash32) -> None:
50+
deposit_input = DepositInput(
51+
pubkey=pubkey,
52+
withdrawal_credentials=withdrawal_credentials,
53+
randao_commitment=randao_commitment,
54+
proof_of_possession=EMPTY_SIGNATURE,
55+
)
56+
57+
is_valid_signature = bls.verify(
58+
pubkey=pubkey,
59+
# TODO: change to hash_tree_root(deposit_input) when we have SSZ tree hashing
60+
message=deposit_input.root,
61+
signature=proof_of_possession,
62+
domain=get_domain(
63+
state.fork_data,
64+
state.slot,
65+
SignatureDomain.DOMAIN_DEPOSIT,
66+
),
67+
)
68+
69+
if not is_valid_signature:
70+
raise ValidationError(
71+
"BLS signature verification error"
72+
)
73+
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+
# Append to the validator_registry
93+
validator_registry = state.validator_registry + (validator,)
94+
state = state.copy(
95+
validator_registry=validator_registry,
96+
)
97+
index = len(state.validator_registry) - 1
98+
else:
99+
# Use the empty validator index
100+
state = state.update_validator(index, validator)
101+
102+
return state, index
103+
104+
105+
def process_deposit(*,
106+
state: BeaconState,
107+
pubkey: int,
108+
deposit: int,
109+
proof_of_possession: bytes,
110+
withdrawal_credentials: Hash32,
111+
randao_commitment: Hash32,
112+
zero_balance_validator_ttl: int) -> Tuple[BeaconState, int]:
113+
"""
114+
Process a deposit from Ethereum 1.0.
115+
"""
116+
validate_proof_of_possession(
117+
state,
118+
pubkey,
119+
proof_of_possession,
120+
withdrawal_credentials,
121+
randao_commitment,
122+
)
123+
124+
validator_pubkeys = tuple(v.pubkey for v in state.validator_registry)
125+
if pubkey not in validator_pubkeys:
126+
validator = ValidatorRecord.get_pending_validator(
127+
pubkey=pubkey,
128+
withdrawal_credentials=withdrawal_credentials,
129+
randao_commitment=randao_commitment,
130+
balance=deposit,
131+
latest_status_change_slot=state.slot,
132+
)
133+
134+
state, index = add_pending_validator(
135+
state,
136+
validator,
137+
zero_balance_validator_ttl,
138+
)
139+
else:
140+
# Top-up - increase balance by deposit
141+
index = validator_pubkeys.index(pubkey)
142+
validator = state.validator_registry[index]
143+
144+
if validator.withdrawal_credentials != withdrawal_credentials:
145+
raise ValidationError(
146+
"`withdrawal_credentials` are incorrect:\n"
147+
"\texpected: %s, found: %s" % (
148+
validator.withdrawal_credentials,
149+
validator.withdrawal_credentials,
150+
)
151+
)
152+
153+
# Update validator's balance and state
154+
validator = validator.copy(
155+
balance=validator.balance + deposit,
156+
)
157+
state = state.update_validator(index, validator)
158+
159+
return state, index

eth/beacon/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from eth.exceptions import (
2+
PyEVMError,
3+
)
4+
5+
6+
class MinEmptyValidatorIndexNotFound(PyEVMError):
7+
"""
8+
No empty slot in the validator registry
9+
"""
10+
pass

eth/beacon/helpers.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,13 @@
2020
get_bitfield_length,
2121
has_voted,
2222
)
23-
from eth.beacon._utils.hash import (
24-
hash_eth2,
25-
)
2623
from eth._utils.numeric import (
2724
clamp,
2825
)
2926

30-
from eth.beacon.block_committees_info import (
31-
BlockCommitteesInfo,
32-
)
33-
from eth.beacon.types.shard_committees import (
34-
ShardCommittee,
35-
)
27+
from eth.beacon.block_committees_info import BlockCommitteesInfo
28+
from eth.beacon.types.shard_committees import ShardCommittee
29+
from eth.beacon.types.validator_registry_delta_block import ValidatorRegistryDeltaBlock
3630
from eth.beacon._utils.random import (
3731
shuffle,
3832
split,
@@ -364,19 +358,19 @@ def get_effective_balance(validator: 'ValidatorRecord', max_deposit: int) -> int
364358

365359

366360
def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32,
367-
index: int,
361+
validator_index: int,
368362
pubkey: int,
369363
flag: int) -> Hash32:
370364
"""
371365
Compute the next hash in the validator registry delta hash chain.
372366
"""
373-
return hash_eth2(
374-
current_validator_registry_delta_chain_tip +
375-
flag.to_bytes(1, 'big') +
376-
index.to_bytes(3, 'big') +
377-
# TODO: currently, we use 256-bit pubkey which is different form the spec
378-
pubkey.to_bytes(32, 'big')
379-
)
367+
# TODO: switch to SSZ tree hashing
368+
return ValidatorRegistryDeltaBlock(
369+
latest_registry_delta_root=current_validator_registry_delta_chain_tip,
370+
validator_index=validator_index,
371+
pubkey=pubkey,
372+
flag=flag,
373+
).root
380374

381375

382376
def get_fork_version(fork_data: 'ForkData',

eth/beacon/types/deposit_input.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
hash32,
1515
uint384,
1616
)
17+
from eth.beacon._utils.hash import hash_eth2
1718

1819

1920
class DepositInput(rlp.Serializable):
@@ -23,12 +24,12 @@ class DepositInput(rlp.Serializable):
2324
fields = [
2425
# BLS pubkey
2526
('pubkey', uint384),
26-
# BLS proof of possession (a BLS signature)
27-
('proof_of_possession', CountableList(uint384)),
2827
# Withdrawal credentials
2928
('withdrawal_credentials', hash32),
3029
# Initial RANDAO commitment
3130
('randao_commitment', hash32),
31+
# BLS proof of possession (a BLS signature)
32+
('proof_of_possession', CountableList(uint384)),
3233
]
3334

3435
def __init__(self,
@@ -37,8 +38,16 @@ def __init__(self,
3738
randao_commitment: Hash32,
3839
proof_of_possession: Sequence[int]=(0, 0)) -> None:
3940
super().__init__(
40-
pubkey,
41-
proof_of_possession,
42-
withdrawal_credentials,
43-
randao_commitment,
41+
pubkey=pubkey,
42+
withdrawal_credentials=withdrawal_credentials,
43+
randao_commitment=randao_commitment,
44+
proof_of_possession=proof_of_possession,
4445
)
46+
47+
_root = None
48+
49+
@property
50+
def root(self) -> Hash32:
51+
if self._root is None:
52+
self._root = hash_eth2(rlp.encode(self))
53+
return self._root

eth/beacon/types/states.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,13 @@ def num_validators(self) -> int:
157157
@property
158158
def num_crosslinks(self) -> int:
159159
return len(self.latest_crosslinks)
160+
161+
def update_validator(self,
162+
validator_index: int,
163+
validator: ValidatorRecord) -> 'BeaconState':
164+
validator_registry = list(self.validator_registry)
165+
validator_registry[validator_index] = validator
166+
updated_state = self.copy(
167+
validator_registry=tuple(validator_registry),
168+
)
169+
return updated_state

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+
)

0 commit comments

Comments
 (0)