Skip to content
This repository has been archived by the owner on Jul 1, 2021. It is now read-only.

Hashablification #1394

Merged
merged 3 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eth2/beacon/attestation_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def validate_indexed_attestation(
f" but have {len(attesting_indices)} validators."
)

if attesting_indices != tuple(sorted(attesting_indices)):
if list(attesting_indices) != sorted(attesting_indices):
raise ValidationError(
f"Indices should be sorted; the attesting indices are not: {attesting_indices}."
)
Expand Down
41 changes: 24 additions & 17 deletions eth2/beacon/db/chain.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from abc import ABC, abstractmethod
import functools
from typing import Iterable, Optional, Tuple, Type, cast

from cytoolz import concat, first, sliding_window
Expand All @@ -8,6 +7,7 @@
from eth.validation import validate_word
from eth_typing import Hash32
from eth_utils import ValidationError, encode_hex, to_tuple
from lru import LRU
import ssz

from eth2.beacon.constants import ZERO_SIGNING_ROOT
Expand All @@ -28,6 +28,14 @@
from eth2.beacon.typing import Epoch, HashTreeRoot, SigningRoot, Slot
from eth2.configs import Eth2GenesisConfig

# When performing a chain sync (either fast or regular modes), we'll very often need to look
# up recent blocks to validate the chain, and decoding their SSZ representation is
# relatively expensive so we cache that here, but use a small cache because we *should* only
# be looking up recent blocks. We cache by root instead of ssz representation as ssz
# representation is not unique if different length configs are considered
state_cache = LRU(128)
block_cache = LRU(128)


class AttestationKey(ssz.Serializable):
fields = [("block_root", ssz.sedes.bytes32), ("index", ssz.sedes.uint8)]
Expand Down Expand Up @@ -368,13 +376,20 @@ def _get_block_by_root(
Raise BlockNotFound if it is not present in the db.
"""
validate_word(block_root, title="block root")

if block_root in block_cache and block_root in db:
return block_cache[block_root]

try:
block_ssz = db[block_root]
except KeyError:
raise BlockNotFound(
"No block with signing root {0} found".format(encode_hex(block_root))
)
return _decode_block(block_ssz, block_class)

block = ssz.decode(block_ssz, block_class)
block_cache[block_root] = block
return block

def get_slot_by_root(self, block_root: SigningRoot) -> Slot:
"""
Expand Down Expand Up @@ -737,11 +752,17 @@ def _get_state_by_root(
Raises StateNotFound if it is not present in the db.
"""
# TODO: validate_state_root
if state_root in state_cache and state_root in db:
return state_cache[state_root]

try:
state_ssz = db[state_root]
except KeyError:
raise StateNotFound(f"No state with root {encode_hex(state_root)} found")
return _decode_state(state_ssz, state_class)

state = ssz.decode(state_ssz, state_class)
state_cache[state] = state
return state

def persist_state(self, state: BeaconState) -> None:
"""
Expand Down Expand Up @@ -911,17 +932,3 @@ def get(self, key: bytes) -> bytes:
Return the value for the given key or a KeyError if it doesn't exist in the database.
"""
return self.db[key]


# When performing a chain sync (either fast or regular modes), we'll very often need to look
# up recent blocks to validate the chain, and decoding their SSZ representation is
# relatively expensive so we cache that here, but use a small cache because we *should* only
# be looking up recent blocks.
@functools.lru_cache(128)
def _decode_block(block_ssz: bytes, sedes: Type[BaseBeaconBlock]) -> BaseBeaconBlock:
return ssz.decode(block_ssz, sedes=sedes)


@functools.lru_cache(128)
def _decode_state(state_ssz: bytes, state_class: Type[BeaconState]) -> BeaconState:
return ssz.decode(state_ssz, sedes=state_class)
10 changes: 6 additions & 4 deletions eth2/beacon/deposit_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def process_deposit(
# needs to be done here because while the deposit contract will never
# create an invalid Merkle branch, it may admit an invalid deposit
# object, and we need to be able to skip over it
state = state.copy(eth1_deposit_index=state.eth1_deposit_index + 1)
state = state.set("eth1_deposit_index", state.eth1_deposit_index + 1)

pubkey = deposit.data.pubkey
amount = deposit.data.amount
Expand All @@ -72,9 +72,11 @@ def process_deposit(
pubkey, withdrawal_credentials, amount, config
)

return state.copy(
validators=state.validators + (validator,),
balances=state.balances + (amount,),
return state.mset(
"validators",
state.validators.append(validator),
"balances",
state.balances.append(amount),
)
else:
index = ValidatorIndex(validator_pubkeys.index(pubkey))
Expand Down
20 changes: 6 additions & 14 deletions eth2/beacon/epoch_processing_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from eth2._utils.bitfield import Bitfield, has_voted
from eth2._utils.numeric import integer_squareroot
from eth2._utils.tuple import update_tuple_item_with_fn
from eth2.beacon.committee_helpers import get_beacon_committee
from eth2.beacon.constants import BASE_REWARDS_PER_EPOCH
from eth2.beacon.exceptions import InvalidEpochError
Expand All @@ -25,22 +24,15 @@
def increase_balance(
state: BeaconState, index: ValidatorIndex, delta: Gwei
) -> BeaconState:
return state.copy(
balances=update_tuple_item_with_fn(
state.balances, index, lambda balance, *_: Gwei(balance + delta)
)
)
return state.transform(("balances", index), lambda balance: Gwei(balance + delta))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transfrom looks good!



def decrease_balance(
state: BeaconState, index: ValidatorIndex, delta: Gwei
) -> BeaconState:
return state.copy(
balances=update_tuple_item_with_fn(
state.balances,
index,
lambda balance, *_: Gwei(0) if delta > balance else Gwei(balance - delta),
)
return state.transform(
("balances", index),
lambda balance: Gwei(0) if delta > balance else Gwei(balance - delta),
)


Expand All @@ -51,7 +43,7 @@ def get_attesting_indices(
config: CommitteeConfig,
) -> Set[ValidatorIndex]:
"""
Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``.
Return the attesting indices corresponding to ``attestation_data`` and ``bitfield``.
"""
committee = get_beacon_committee(
state, attestation_data.slot, attestation_data.index, config
Expand All @@ -66,7 +58,7 @@ def get_indexed_attestation(
state, attestation.data, attestation.aggregation_bits, config
)

return IndexedAttestation(
return IndexedAttestation.create(
attesting_indices=sorted(attesting_indices),
data=attestation.data,
signature=attestation.signature,
Expand Down
5 changes: 3 additions & 2 deletions eth2/beacon/fork_choice/lmd_ghost.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import Any, Dict, Optional, Tuple, Type
from typing import Any, Dict, Optional, Tuple, Type, cast

from eth.constants import ZERO_HASH32
from eth_typing import Hash32
Expand Down Expand Up @@ -57,7 +57,8 @@ def deserialize(cls, data: bytes) -> "LMDGHOSTScore":
score = ssz.decode(
data, sedes=ssz.sedes.Vector(ssz.sedes.uint256, LMD_GHOST_SCORE_DATA_LENGTH)
)
return cls(score)
score_tuple = cast(Tuple[Gwei, int], tuple(score))
return cls(score_tuple)

@classmethod
def from_genesis(
Expand Down
47 changes: 28 additions & 19 deletions eth2/beacon/genesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from eth_typing import Hash32
import ssz

from eth2.beacon.constants import DEPOSIT_CONTRACT_TREE_DEPTH, SECONDS_PER_DAY
from eth2.beacon.constants import (
DEPOSIT_CONTRACT_TREE_DEPTH,
SECONDS_PER_DAY,
ZERO_HASH32,
ZERO_SIGNING_ROOT,
)
from eth2.beacon.deposit_helpers import process_deposit
from eth2.beacon.helpers import get_active_validator_indices
from eth2.beacon.types.block_headers import BeaconBlockHeader
Expand All @@ -13,15 +18,15 @@
from eth2.beacon.types.eth1_data import Eth1Data
from eth2.beacon.types.states import BeaconState
from eth2.beacon.types.validators import calculate_effective_balance
from eth2.beacon.typing import Timestamp, ValidatorIndex
from eth2.beacon.typing import Gwei, Timestamp, ValidatorIndex
from eth2.beacon.validator_status_helpers import activate_validator
from eth2.configs import Eth2Config


def is_genesis_trigger(
deposits: Sequence[Deposit], timestamp: int, config: Eth2Config
) -> bool:
state = BeaconState(config=config)
state = BeaconState.create(config=config)

for deposit in deposits:
state = process_deposit(state, deposit, config)
Expand All @@ -47,27 +52,28 @@ def initialize_beacon_state_from_eth1(
deposits: Sequence[Deposit],
config: Eth2Config
) -> BeaconState:
state = BeaconState(
state = BeaconState.create(
genesis_time=_genesis_time_from_eth1_timestamp(eth1_timestamp),
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)),
latest_block_header=BeaconBlockHeader(
body_root=BeaconBlockBody().hash_tree_root
eth1_data=Eth1Data.create(
block_hash=eth1_block_hash, deposit_count=len(deposits)
),
latest_block_header=BeaconBlockHeader.create(
body_root=BeaconBlockBody.create().hash_tree_root
),
block_roots=(ZERO_SIGNING_ROOT,) * config.SLOTS_PER_HISTORICAL_ROOT,
state_roots=(ZERO_HASH32,) * config.SLOTS_PER_HISTORICAL_ROOT,
randao_mixes=(eth1_block_hash,) * config.EPOCHS_PER_HISTORICAL_VECTOR,
slashings=(Gwei(0),) * config.EPOCHS_PER_SLASHINGS_VECTOR,
config=config,
)

# Process genesis deposits
for index, deposit in enumerate(deposits):
deposit_data_list = tuple(deposit.data for deposit in deposits[: index + 1])
state = state.copy(
eth1_data=state.eth1_data.copy(
deposit_root=ssz.get_hash_tree_root(
deposit_data_list,
ssz.List(DepositData, 2 ** DEPOSIT_CONTRACT_TREE_DEPTH),
)
)
deposit_root = ssz.get_hash_tree_root(
deposit_data_list, ssz.List(DepositData, 2 ** DEPOSIT_CONTRACT_TREE_DEPTH)
)
state = state.transform(("eth1_data", "deposit_root"), deposit_root)
state = process_deposit(state=state, deposit=deposit, config=config)

# Process genesis activations
Expand All @@ -76,13 +82,16 @@ def initialize_beacon_state_from_eth1(
balance = state.balances[validator_index]
effective_balance = calculate_effective_balance(balance, config)

state = state.update_validator_with_fn(
validator_index, lambda v, *_: v.copy(effective_balance=effective_balance)
state = state.transform(
("validators", validator_index, "effective_balance"), effective_balance
)

if effective_balance == config.MAX_EFFECTIVE_BALANCE:
state = state.update_validator_with_fn(
validator_index, activate_validator, config.GENESIS_EPOCH
activated_validator = activate_validator(
state.validators[validator_index], config.GENESIS_EPOCH
)
state = state.transform(
("validators", validator_index), activated_validator
)

return state
Expand All @@ -104,4 +113,4 @@ def is_valid_genesis_state(state: BeaconState, config: Eth2Config) -> bool:
def get_genesis_block(
genesis_state_root: Hash32, block_class: Type[BaseBeaconBlock]
) -> BaseBeaconBlock:
return block_class(state_root=genesis_state_root)
return block_class.create(state_root=genesis_state_root)
2 changes: 1 addition & 1 deletion eth2/beacon/state_machines/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,6 @@ def import_block(
state, block=block, check_proposer_signature=check_proposer_signature
)

block = block.copy(state_root=state.hash_tree_root)
block = block.set("state_root", state.hash_tree_root)

return state, block
20 changes: 9 additions & 11 deletions eth2/beacon/state_machines/forks/serenity/block_processing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from eth2._utils.hash import hash_eth2
from eth2._utils.numeric import bitwise_xor
from eth2._utils.tuple import update_tuple_item
from eth2.beacon.committee_helpers import get_beacon_proposer_index
from eth2.beacon.helpers import get_randao_mix
from eth2.beacon.state_machines.forks.serenity.block_validation import (
Expand Down Expand Up @@ -35,14 +34,15 @@ def process_block_header(
state, block, committee_config=CommitteeConfig(config)
)

return state.copy(
latest_block_header=BeaconBlockHeader(
return state.set(
"latest_block_header",
BeaconBlockHeader.create(
slot=block.slot,
parent_root=block.parent_root,
# `state_root` is zeroed and overwritten in the next `process_slot` call
body_root=block.body.hash_tree_root,
# `signature` is zeroed
)
),
)


Expand Down Expand Up @@ -73,19 +73,15 @@ def process_randao(
hash_eth2(block.body.randao_reveal),
)

return state.copy(
randao_mixes=update_tuple_item(
state.randao_mixes, randao_mix_index, new_randao_mix
)
)
return state.transform(("randao_mixes", randao_mix_index), new_randao_mix)


def process_eth1_data(
state: BeaconState, block: BaseBeaconBlock, config: Eth2Config
) -> BeaconState:
body = block.body

new_eth1_data_votes = state.eth1_data_votes + (body.eth1_data,)
new_eth1_data_votes = state.eth1_data_votes.append(body.eth1_data)

new_eth1_data = state.eth1_data
if (
Expand All @@ -94,7 +90,9 @@ def process_eth1_data(
):
new_eth1_data = body.eth1_data

return state.copy(eth1_data=new_eth1_data, eth1_data_votes=new_eth1_data_votes)
return state.mset(
"eth1_data", new_eth1_data, "eth1_data_votes", new_eth1_data_votes
)


def process_block(
Expand Down
Loading