Skip to content

Commit

Permalink
Merge pull request #1640 from hwwhww/on_startup_4
Browse files Browse the repository at this point in the history
Routine for processing deposits
  • Loading branch information
hwwhww authored Dec 21, 2018
2 parents 8491513 + c74560f commit d82b10a
Show file tree
Hide file tree
Showing 11 changed files with 596 additions and 49 deletions.
52 changes: 31 additions & 21 deletions eth/_utils/bls.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,22 @@ def privtopub(k: int) -> int:


def verify(message: bytes, pubkey: int, signature: bytes, domain: int) -> bool:
final_exponentiation = final_exponentiate(
pairing(FQP_point_to_FQ2_point(decompress_G2(signature)), G1, False) *
pairing(
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
neg(decompress_G1(pubkey)),
False
try:
final_exponentiation = final_exponentiate(
pairing(
FQP_point_to_FQ2_point(decompress_G2(signature)),
G1,
final_exponentiate=False,
) *
pairing(
FQP_point_to_FQ2_point(hash_to_G2(message, domain)),
neg(decompress_G1(pubkey)),
final_exponentiate=False,
)
)
)
return final_exponentiation == FQ12.one()
return final_exponentiation == FQ12.one()
except (ValidationError, ValueError, AssertionError):
return False


def aggregate_signatures(signatures: Sequence[bytes]) -> Tuple[int, int]:
Expand Down Expand Up @@ -208,16 +215,19 @@ def verify_multiple(pubkeys: Sequence[int],
)
)

o = FQ12([1] + [0] * 11)
for m_pubs in set(messages):
# aggregate the pubs
group_pub = Z1
for i in range(len_msgs):
if messages[i] == m_pubs:
group_pub = add(group_pub, decompress_G1(pubkeys[i]))

o *= pairing(hash_to_G2(m_pubs, domain), group_pub, False)
o *= pairing(decompress_G2(signature), neg(G1), False)

final_exponentiation = final_exponentiate(o)
return final_exponentiation == FQ12.one()
try:
o = FQ12([1] + [0] * 11)
for m_pubs in set(messages):
# aggregate the pubs
group_pub = Z1
for i in range(len_msgs):
if messages[i] == m_pubs:
group_pub = add(group_pub, decompress_G1(pubkeys[i]))

o *= pairing(hash_to_G2(m_pubs, domain), group_pub, final_exponentiate=False)
o *= pairing(decompress_G2(signature), neg(G1), final_exponentiate=False)

final_exponentiation = final_exponentiate(o)
return final_exponentiation == FQ12.one()
except (ValidationError, ValueError, AssertionError):
return False
159 changes: 159 additions & 0 deletions eth/beacon/deposit_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
from typing import (
Sequence,
Tuple,
)

from eth_typing import (
Hash32,
)
from eth_utils import (
ValidationError,
)

from eth._utils import bls

from eth.beacon.constants import (
EMPTY_SIGNATURE,
)
from eth.beacon.enums import (
SignatureDomain,
)
from eth.beacon.exceptions import (
MinEmptyValidatorIndexNotFound,
)
from eth.beacon.types.deposit_input import DepositInput
from eth.beacon.types.states import BeaconState
from eth.beacon.types.validator_records import ValidatorRecord
from eth.beacon.helpers import (
get_domain,
)


def get_min_empty_validator_index(validators: Sequence[ValidatorRecord],
current_slot: int,
zero_balance_validator_ttl: int) -> int:
for index, validator in enumerate(validators):
is_empty = (
validator.balance == 0 and
validator.latest_status_change_slot + zero_balance_validator_ttl <= current_slot
)
if is_empty:
return index
raise MinEmptyValidatorIndexNotFound()


def validate_proof_of_possession(state: BeaconState,
pubkey: int,
proof_of_possession: bytes,
withdrawal_credentials: Hash32,
randao_commitment: Hash32) -> None:
deposit_input = DepositInput(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
proof_of_possession=EMPTY_SIGNATURE,
)

is_valid_signature = bls.verify(
pubkey=pubkey,
# TODO: change to hash_tree_root(deposit_input) when we have SSZ tree hashing
message=deposit_input.root,
signature=proof_of_possession,
domain=get_domain(
state.fork_data,
state.slot,
SignatureDomain.DOMAIN_DEPOSIT,
),
)

if not is_valid_signature:
raise ValidationError(
"BLS signature verification error"
)


def add_pending_validator(state: BeaconState,
validator: ValidatorRecord,
zero_balance_validator_ttl: int) -> Tuple[BeaconState, int]:
"""
Add a validator to the existing minimum empty validator index or
append to ``validator_registry``.
"""
# Check if there's empty validator index in `validator_registry`
try:
index = get_min_empty_validator_index(
state.validator_registry,
state.slot,
zero_balance_validator_ttl,
)
except MinEmptyValidatorIndexNotFound:
index = None

# Append to the validator_registry
validator_registry = state.validator_registry + (validator,)
state = state.copy(
validator_registry=validator_registry,
)
index = len(state.validator_registry) - 1
else:
# Use the empty validator index
state = state.update_validator(index, validator)

return state, index


def process_deposit(*,
state: BeaconState,
pubkey: int,
deposit: int,
proof_of_possession: bytes,
withdrawal_credentials: Hash32,
randao_commitment: Hash32,
zero_balance_validator_ttl: int) -> Tuple[BeaconState, int]:
"""
Process a deposit from Ethereum 1.0.
"""
validate_proof_of_possession(
state,
pubkey,
proof_of_possession,
withdrawal_credentials,
randao_commitment,
)

validator_pubkeys = tuple(v.pubkey for v in state.validator_registry)
if pubkey not in validator_pubkeys:
validator = ValidatorRecord.get_pending_validator(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
balance=deposit,
latest_status_change_slot=state.slot,
)

state, index = add_pending_validator(
state,
validator,
zero_balance_validator_ttl,
)
else:
# Top-up - increase balance by deposit
index = validator_pubkeys.index(pubkey)
validator = state.validator_registry[index]

if validator.withdrawal_credentials != withdrawal_credentials:
raise ValidationError(
"`withdrawal_credentials` are incorrect:\n"
"\texpected: %s, found: %s" % (
validator.withdrawal_credentials,
validator.withdrawal_credentials,
)
)

# Update validator's balance and state
validator = validator.copy(
balance=validator.balance + deposit,
)
state = state.update_validator(index, validator)

return state, index
10 changes: 10 additions & 0 deletions eth/beacon/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from eth.exceptions import (
PyEVMError,
)


class MinEmptyValidatorIndexNotFound(PyEVMError):
"""
No empty slot in the validator registry
"""
pass
28 changes: 11 additions & 17 deletions eth/beacon/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,13 @@
get_bitfield_length,
has_voted,
)
from eth.beacon._utils.hash import (
hash_eth2,
)
from eth._utils.numeric import (
clamp,
)

from eth.beacon.block_committees_info import (
BlockCommitteesInfo,
)
from eth.beacon.types.shard_committees import (
ShardCommittee,
)
from eth.beacon.block_committees_info import BlockCommitteesInfo
from eth.beacon.types.shard_committees import ShardCommittee
from eth.beacon.types.validator_registry_delta_block import ValidatorRegistryDeltaBlock
from eth.beacon._utils.random import (
shuffle,
split,
Expand Down Expand Up @@ -364,19 +358,19 @@ def get_effective_balance(validator: 'ValidatorRecord', max_deposit: int) -> int


def get_new_validator_registry_delta_chain_tip(current_validator_registry_delta_chain_tip: Hash32,
index: int,
validator_index: int,
pubkey: int,
flag: int) -> Hash32:
"""
Compute the next hash in the validator registry delta hash chain.
"""
return hash_eth2(
current_validator_registry_delta_chain_tip +
flag.to_bytes(1, 'big') +
index.to_bytes(3, 'big') +
# TODO: currently, we use 256-bit pubkey which is different form the spec
pubkey.to_bytes(32, 'big')
)
# TODO: switch to SSZ tree hashing
return ValidatorRegistryDeltaBlock(
latest_registry_delta_root=current_validator_registry_delta_chain_tip,
validator_index=validator_index,
pubkey=pubkey,
flag=flag,
).root


def get_fork_version(fork_data: 'ForkData',
Expand Down
21 changes: 15 additions & 6 deletions eth/beacon/types/deposit_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
hash32,
uint384,
)
from eth.beacon._utils.hash import hash_eth2


class DepositInput(rlp.Serializable):
Expand All @@ -23,12 +24,12 @@ class DepositInput(rlp.Serializable):
fields = [
# BLS pubkey
('pubkey', uint384),
# BLS proof of possession (a BLS signature)
('proof_of_possession', CountableList(uint384)),
# Withdrawal credentials
('withdrawal_credentials', hash32),
# Initial RANDAO commitment
('randao_commitment', hash32),
# BLS proof of possession (a BLS signature)
('proof_of_possession', CountableList(uint384)),
]

def __init__(self,
Expand All @@ -37,8 +38,16 @@ def __init__(self,
randao_commitment: Hash32,
proof_of_possession: Sequence[int]=(0, 0)) -> None:
super().__init__(
pubkey,
proof_of_possession,
withdrawal_credentials,
randao_commitment,
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
proof_of_possession=proof_of_possession,
)

_root = None

@property
def root(self) -> Hash32:
if self._root is None:
self._root = hash_eth2(rlp.encode(self))
return self._root
10 changes: 10 additions & 0 deletions eth/beacon/types/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,13 @@ def num_validators(self) -> int:
@property
def num_crosslinks(self) -> int:
return len(self.latest_crosslinks)

def update_validator(self,
validator_index: int,
validator: ValidatorRecord) -> 'BeaconState':
validator_registry = list(self.validator_registry)
validator_registry[validator_index] = validator
updated_state = self.copy(
validator_registry=tuple(validator_registry),
)
return updated_state
21 changes: 21 additions & 0 deletions eth/beacon/types/validator_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,24 @@ def is_active(self) -> bool:
Returns ``True`` if the validator is active.
"""
return self.status in VALIDATOR_RECORD_ACTIVE_STATUSES

@classmethod
def get_pending_validator(cls,
pubkey: int,
withdrawal_credentials: Hash32,
randao_commitment: Hash32,
balance: int,
latest_status_change_slot: int) -> 'ValidatorRecord':
"""
Return a new pending ``ValidatorRecord`` with the given fields.
"""
return cls(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
randao_commitment=randao_commitment,
randao_layers=0,
balance=balance,
status=ValidatorStatusCode.PENDING_ACTIVATION,
latest_status_change_slot=latest_status_change_slot,
exit_count=0,
)
Loading

0 comments on commit d82b10a

Please sign in to comment.