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

Basic aggregation strategy #1311

Merged
merged 8 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
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]
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we also assume that there's no overlap between attesters in each attestation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's okay to have overlapped aggregation_bits.

Copy link
Contributor

Choose a reason for hiding this comment

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

IIRC currently we don't support validating aggregated signature where there's duplicated signature being aggregated. I would suggest we add a note in docstring or add a check and logging if there's duplicated signature being aggregated. So that it would be easier to debug or less likely to make mistake.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. And I will split it into two AttestationPool: aggregated_attestation_pool and unaggregated_attestation_pool. Thanks! 👍

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