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

Commit

Permalink
Merge pull request #1311 from hwwhww/aggregation_strategy
Browse files Browse the repository at this point in the history
Basic aggregation strategy
  • Loading branch information
hwwhww committed Dec 18, 2019
2 parents 8eca0b1 + 01b78f6 commit c04c294
Show file tree
Hide file tree
Showing 21 changed files with 1,210 additions and 235 deletions.
52 changes: 51 additions & 1 deletion eth2/beacon/operations/attestation_pool.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
from typing import Tuple

from eth_utils import ValidationError

from eth2.beacon.state_machines.forks.serenity.block_validation import (
validate_attestation_slot,
)
from eth2.beacon.types.attestations import Attestation
from eth2.beacon.typing import CommitteeIndex, SigningRoot, Slot
from eth2.configs import Eth2Config

from .pool import OperationPool


def is_valid_slot(
attestation: Attestation, current_slot: Slot, config: Eth2Config
) -> bool:
try:
validate_attestation_slot(
attestation.data.slot,
current_slot,
config.SLOTS_PER_EPOCH,
config.MIN_ATTESTATION_INCLUSION_DELAY,
)
except ValidationError:
return False
else:
return True


class AttestationPool(OperationPool[Attestation]):
pass
def get_valid_attestation_by_current_slot(
self, slot: Slot, config: Eth2Config
) -> Tuple[Attestation, ...]:
return tuple(
filter(
lambda attestation: is_valid_slot(attestation, slot, config),
self._pool_storage.values(),
)
)

def get_acceptable_attestations(
self,
slot: Slot,
committee_index: CommitteeIndex,
beacon_block_root: SigningRoot,
) -> Tuple[Attestation, ...]:
return tuple(
filter(
lambda attestation: (
beacon_block_root == attestation.data.beacon_block_root
and slot == attestation.data.slot
and committee_index == attestation.data.index
),
self._pool_storage.values(),
)
)
199 changes: 199 additions & 0 deletions eth2/beacon/scripts/quickstart_state/keygen_100_validators.yaml

Large diffs are not rendered by default.

136 changes: 134 additions & 2 deletions eth2/beacon/tools/builder/aggregator.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
from typing import Sequence

from eth_typing import BLSSignature
from eth_utils import ValidationError
from ssz import get_hash_tree_root, uint64

from eth2._utils.bls import bls
from eth2._utils.hash import hash_eth2
from eth2.beacon.attestation_helpers import (
validate_indexed_attestation_aggregate_signature,
)
from eth2.beacon.committee_helpers import get_beacon_committee
from eth2.beacon.epoch_processing_helpers import (
get_attesting_indices,
get_indexed_attestation,
)
from eth2.beacon.helpers import compute_epoch_at_slot, get_domain
from eth2.beacon.signature_domain import SignatureDomain
from eth2.beacon.types.aggregate_and_proof import AggregateAndProof
from eth2.beacon.types.attestations import Attestation
from eth2.beacon.types.states import BeaconState
from eth2.beacon.typing import CommitteeIndex, Slot
from eth2.beacon.typing import Bitfield, CommitteeIndex, Slot
from eth2.configs import CommitteeConfig

# TODO: TARGET_AGGREGATORS_PER_COMMITTEE is not in Eth2Config now.
TARGET_AGGREGATORS_PER_COMMITTEE = 16


def slot_signature(
def get_slot_signature(
state: BeaconState, slot: Slot, privkey: int, config: CommitteeConfig
) -> BLSSignature:
"""
Expand Down Expand Up @@ -54,3 +66,123 @@ def is_aggregator(
committee = get_beacon_committee(state, slot, index, config)
modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE)
return int.from_bytes(hash_eth2(signature)[0:8], byteorder="little") % modulo == 0


def get_aggregate_from_valid_committee_attestations(
attestations: Sequence[Attestation]
) -> Attestation:
"""
Return the aggregate attestation.
The given attestations SHOULD have the same `data: AttestationData` and are valid.
"""
signatures = [attestation.signature for attestation in attestations]
aggregate_signature = bls.aggregate_signatures(signatures)

all_aggregation_bits = [
attestation.aggregation_bits for attestation in attestations
]
aggregation_bits = tuple(map(any, zip(*all_aggregation_bits)))

assert len(attestations) > 0

return Attestation.create(
data=attestations[0].data,
aggregation_bits=Bitfield(aggregation_bits),
signature=aggregate_signature,
)


#
# Validation
#


def validate_aggregate_and_proof(
state: BeaconState,
aggregate_and_proof: AggregateAndProof,
attestation_propagation_slot_range: int,
config: CommitteeConfig,
) -> None:
"""
Validate aggregate_and_proof
Reference: https://github.com/ethereum/eth2.0-specs/blob/master/specs/networking/p2p-interface.md#global-topics # noqa: E501
"""
attestation = aggregate_and_proof.aggregate

validate_attestation_propagation_slot_range(
state, attestation, attestation_propagation_slot_range
)

attesting_indices = get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, config
)
if aggregate_and_proof.aggregator_index not in attesting_indices:
raise ValidationError(
f"The aggregator index ({aggregate_and_proof.aggregator_index}) is not within"
f" the aggregate's committee {attesting_indices}"
)

if not is_aggregator(
state,
attestation.data.slot,
attestation.data.index,
aggregate_and_proof.selection_proof,
config,
):
raise ValidationError(
f"The given validator {aggregate_and_proof.aggregator_index}"
" is not a selected aggregator"
)

validate_aggregator_proof(state, aggregate_and_proof, config)

validate_attestation_signature(state, attestation, config)


def validate_attestation_propagation_slot_range(
state: BeaconState,
attestation: Attestation,
attestation_propagation_slot_range: int,
) -> None:
if (
attestation.data.slot + attestation_propagation_slot_range < state.slot
or attestation.data.slot > state.slot
):
raise ValidationError(
"attestation.data.slot should be within the last"
" {attestation_propagation_slot_range} slots. Got"
f" attestationdata.slot={attestation.data.slot},"
f" current slot={state.slot}"
)


def validate_aggregator_proof(
state: BeaconState, aggregate_and_proof: AggregateAndProof, config: CommitteeConfig
) -> None:
slot = aggregate_and_proof.aggregate.data.slot
pubkey = state.validators[aggregate_and_proof.aggregator_index].pubkey
domain = get_domain(
state,
SignatureDomain.DOMAIN_BEACON_ATTESTER,
config.SLOTS_PER_EPOCH,
message_epoch=compute_epoch_at_slot(slot, config.SLOTS_PER_EPOCH),
)
message_hash = get_hash_tree_root(slot, sedes=uint64)

bls.validate(
message_hash=message_hash,
pubkey=pubkey,
signature=aggregate_and_proof.selection_proof,
domain=domain,
)


def validate_attestation_signature(
state: BeaconState, attestation: Attestation, config: CommitteeConfig
) -> None:
indexed_attestation = get_indexed_attestation(state, attestation, config)
validate_indexed_attestation_aggregate_signature(
state, indexed_attestation, config.SLOTS_PER_EPOCH
)
3 changes: 3 additions & 0 deletions eth2/beacon/tools/builder/proposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ def create_block_on_state(
parent_block=parent_block, block_params=FromBlockParams(slot=slot)
)

# MAX_ATTESTATIONS
attestations = attestations[: config.MAX_ATTESTATIONS]

# TODO: Add more operations
randao_reveal = _generate_randao_reveal(privkey, slot, state, config)
eth1_data = state.eth1_data
Expand Down
6 changes: 5 additions & 1 deletion eth2/beacon/tools/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ def _create(
is disabled.
"""
override_lengths(cls.config)
if "num_validators" in kwargs:
num_validators = kwargs["num_validators"]
else:
num_validators = cls.num_validators

if kwargs["genesis_state"] is None:
keymap = mk_keymap_of_size(cls.num_validators)
keymap = mk_keymap_of_size(num_validators)
genesis_state, genesis_block = create_mock_genesis(
config=cls.config,
pubkeys=tuple(keymap.keys()),
Expand Down
16 changes: 9 additions & 7 deletions eth2/beacon/types/aggregate_and_proof.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,29 @@
class AggregateAndProof(HashableContainer):

fields = [
("index", uint64),
("selection_proof", bytes96),
("aggregator_index", uint64),
("aggregate", Attestation),
("selection_proof", bytes96),
]

@classmethod
def create(
cls: Type[TAggregateAndProof],
index: ValidatorIndex = default_validator_index,
selection_proof: BLSSignature = EMPTY_SIGNATURE,
aggregator_index: ValidatorIndex = default_validator_index,
aggregate: Attestation = default_attestation,
selection_proof: BLSSignature = EMPTY_SIGNATURE,
) -> TAggregateAndProof:
return super().create(
index=index, selection_proof=selection_proof, aggregate=aggregate
aggregator_index=aggregator_index,
aggregate=aggregate,
selection_proof=selection_proof,
)

def __str__(self) -> str:
return (
f"index={self.index},"
f" selection_proof={humanize_hash(self.selection_proof)},"
f"aggregator_index={self.aggregator_index},"
f" aggregate={self.aggregate},"
f" selection_proof={humanize_hash(self.selection_proof)},"
)


Expand Down
18 changes: 17 additions & 1 deletion eth2/beacon/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,27 @@ def __str__(self) -> str:
SigningRoot = NewType("SigningRoot", Hash32)


#
# Networkinig
#

# CommitteeIndex % ATTESTATION_SUBNET_COUNT
SubnetId = NewType("SubnetId", int)


#
# Helpers
#


class FromBlockParams(NamedTuple):
slot: Slot = None


# defaults to emulate "zero types"
#
# Defaults to emulate "zero types"
#

default_slot = Slot(0)
default_epoch = Epoch(0)
default_committee_index = CommitteeIndex(0)
Expand Down
Loading

0 comments on commit c04c294

Please sign in to comment.