# Eth2 gossip-based attestation aggregation model
Attestation nets:
- Global net, aimed for "the selected validators"
- Topic name: `beacon_aggregate_and_proof`
- Subnets, aimed for aggregating attestations within your committee, and possibly shared with a few other committees.
- Topic name: `committee_index{subnet_id}_beacon_attestation`
## Model Params / setup
Model Params:
- `SLOTS_PER_EPOCH`
- `RANDOM_SUBNETS_PER_VALIDATOR`
- `ATTESTATION_PROPAGATION_SLOT_RANGE`
- `ACTIVE_VALIDATOR_COUNT`
- `COMMITTEE_COUNT`
- `ATTESTATION_SUBNET_COUNT`
- `PARTICIPATING_NETWORK_NODES`
Derived Params:
- committee size: `ACTIVE_VALIDATOR_COUNT / COMMITTEE_COUNT`
- subnet size (committees): `COMMITTEE_COUNT / ATTESTATION_SUBNET_COUNT`
TODO topology/randomness params:
- chain forking
- network topology
- latency, bandwidth
TODO: distribute `ACTIVE_VALIDATOR_COUNT` over `PARTICIPATING_NETWORK_NODES`, with random shuffling every epoch
----
# Simulation Model
## Split into roles
Create network of N nodes with some topology function.
Select M out of N nodes to be validating.
Distribute P "good" validators to the M validating nodes.
Distribute M-P "bad" validators to the remaining validating nodes.
For the N-M non-validating nodes, select Q "good" nodes: just do node work without any validator.
The remainder (N-M-Q) "bad" nodes will be misbehaving with their gossip.
## Per role
### For each validating node:
Subscribe to some subnets, as backbone for the network. For each validator, subscribe randomly to some subnets:
- Switch between every `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs
Subscribe to the subnets for the committees of its validators in time for the epoch.
TODO: see specs issue \#2523, small chance of shuffling problems
Propagate subnet attestations if
`get_strategy_state(attestation.data.slot, attestation.data.index)` can find a `strategy_state`
and they are valid: `if process_attestation(strategy_state, attestation)`
and follow the strategy. I.e. `if propagation_ok(strategy_state, attestation)`
For each validator, when it needs to attest: ask it to sign an attestation, and put it on the correct subnet
After receiving a sufficient amount of attestations, for each `strategy_state` (i.e. each managed committee):
- aggregate attestations for it `make_candidates(strategy_state, time_elapsed)` (just one in the standard case)
- filter out already propagated attestations
- if any, make the validator sign these
- Then repeat this process whenever a new attestation is received.
Additionally, run the above routine on a few `time_elapsed` in the round, to cover the case when no other attestations trigger making new candidates.
For each `strategy_state` (i.e. each managed committee), near the end of the round, if one of its validators is selected:
- get the aggregate: `make_aggregate(strategy_state)`, and put it on the global topic.
Propagate global attestations, if:
- attestation has not been seen
- TODO: global net honest nodes will have to track seen messages.
- attested block is valid
- TODO: model invalid blocks
- slot within propagation range
- selected validator is valid:
- part of attestation participants
- selected for the slot
- selection proof signature is valid
- The aggregate attestation itself is valid
*insert strategy*, executed per committee with validators of the node, interface:
- `new_round(strategy_state: AttestationStrategy, slot: Slot, index: CommitteeIndex)`
- `process_attestation(strategy_state: AttestationStrategy, incoming: Attestation) -> bool`: returns true if valid, and memorizes it if so.
- `make_candidates(strategy_state: AttestationStrategy, time_elapsed: int) -> []Attestation`
- `make_aggregate(strategy_state: AttestationStrategy) -> Attestation`
- `propagation_ok(strategy_state: AttestationStrategy, incoming: Attestation) -> bool`: returns true if it should be propagated
#### For each "good" validator:
- Sign attestations when the node asks for it
#### For each "bad" validator:
TODO: possible mistakes/bad behavior:
- Do not participate, i.e. no signing
- Participate naive, i.e. sign, even though it results in bad aggregation
- Participate incorrectly, i.e. wrong signing
### For each non-validating node:
#### For each "good" node
Subscribe to some subnets more persistently, similar to what to-be-activated validators would do.
- Switch between every `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs
Produce an attestation for the node head every epoch
TODO: just mock a set of random chain head roots every slot?
#### For each "bad" node
TODO: possible mistakes/bad behavior:
- Subscribe to some subnets, and quickly leave them again for others
- Drop valid messages
- Propagate/create bad messages
## Possible aggregation strategies
Helper, checks consensus validity:
```python
def is_valid_attestation(att: Attestation) -> bool:
...
```
### Naive
```python
@dataclass
def AttestationStrategy():
seen: Dict[Root, Attestation] = field(default_factory=dict)
```
```python
def process_attestation(strategy_state: AttestationStrategy, incoming: Attestation) -> bool:
if is_valid_attestation(incoming):
strategy_state.seen[hash_tree_root(incoming)] = incoming
return True
return False
```
```python
def make_candidates(strategy_state: AttestationStrategy, time_elapsed: int) -> []Attestation:
# do not aggregate anything, except that of local validators
local_atts = strategy_state.seen.values().filter(lambda att: any((loc in att.participants()) for loc in strategy_state.local_committee))
candidates = [aggregate(local_atts)]
candidates.extend(strategy_state.seen.values() - local_atts)
return candidates
```
```python
def make_aggregate(strategy_state: AttestationStrategy) -> Attestation:
# TODO: just combine seen attestations into some aggregate, even if unorganized/sparse.
return aggregate
```
```python
def propagation_ok(strategy_state: AttestationStrategy, incoming: Attestation, time_elapsed: int) -> bool:
return hash_tree_root(incoming) not in strategy_state.seen
```
### Partition and performance based
Based on this idea: https://notes.ethereum.org/9Ijj2RkuRiqQYB9NOfY_9Q
```python
@dataclass
def AttestationStrategy():
seen: Dict[Root, Attestation] = field(default_factory=dict)
# TODO: performance state for each bit
```
```python
def process_attestation(strategy_state: AttestationStrategy, incoming: Attestation) -> bool:
if is_valid_attestation(incoming):
strategy_state.seen[hash_tree_root(incoming)] = incoming
# TODO update performance states
return True
return False
```
```python
def make_candidates(strategy_state: AttestationStrategy, time_elapsed: int) -> []Attestation:
# based on the time closing in to some target, prefer a full attestation over a well-organized one.
# TODO: of seen attestations, group them based on what is acceptable to aggregate given the elapsed time.
# (adjusted based on previous aggregate performance, tracked in AttestationStrategy).
# TODO: aggregate each group, this will be the candidates
# TODO: store new unseen aggregates
# TODO: update performance of bits based on aggregates.
return candidates
```
```python
def make_aggregate(strategy_state: AttestationStrategy) -> Attestation:
# TODO: just combine seen attestations into some aggregate, even if unorganized/sparse.
# Start with the best-scoring ones. Should be fast, even with many half-aggregated inputs
return aggregate
```
```python
def propagation_ok(strategy_state: AttestationStrategy, incoming: Attestation, time_elapsed: int) -> bool:
# TODO: score the attestation based on:
# - partitioning of bits. Try make non-overlapping attestations
# - the bits it has, and if they have been seen already or not, and each of their previous performance.
threshold = # for the time elapsed, determine how eagerly we want to propagate it.
if score > current:
# - increase performance score of affected bits
return True
return False
```