-
Notifications
You must be signed in to change notification settings - Fork 977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EIP-7549: Move committee index outside Attestation #3559
Conversation
Had a quick scan. So far I agree it was a flawed design in phase0. 😨 For EIP-7549, a note that it seems troublesome to have both |
Wouldn’t it be easier to retain the index field and allow for attestations from epochs before the fork to have it set to non-zero value? I don’t see a big inefficiency in having 8 legacy bytes per attestation (1kb at max per block) neither from chain nor from the network perspective. |
The Attestation struct gets a new index in EIP7549 so it's a breaking change from pre-EIP7549. AttestationData structs can be made backwards compatible. Actually we may want to make |
Haven't looked into solutions, but one annoying redundancy is that the target epoch always must match the attestation slot, which pointlessly must be verified everywhere - the target epoch could be removed without any information loss. |
Definitely I agree here. But I would like to contain the scope creep for EIP-7549. The impact of reducing a the number of signatures required to verify casper FFG is much more significant than compared to tens of byte / message network bandwidth optimisation |
i think it's definitely desirable to be able to include attester slashings from before the fork, otherwise we end up in a situation where we have a period of time where you're likely to be able to get away with things.. although i guess the slashing would just have to be converted to the new format? |
I think it's good to move the committee index out of the signing root, but is the benefit of this improvement worthy compared to the cost of implementing this? I think it would be reasonable to do it if we hadn't launched the beacon chain yet, but, since we had, we would have to worry about the fork boundary and that is the cost that we have to bear. |
The benefit is massive for constrained verifiers of the Casper FFG. Reduces the count of pairing necessary to verify by 64x. This alone can accelerate how early we see fully trust-less bridges on Ethereum secured by full consensus significantly.
When launching the beacon chain this property was necessary in the fully sharded roadmap. So we must bite the bullet to earn the benefits on the roadmap change towards danksharding. |
This is not the case. For an attestation to be slashable on any chain (which you want if there are two competing chains), you must explicitly note the target epoch because you don't necessarily have the head block info to do the lookup on a competing chain. Additionally, for the attestation to be incentivized on a chain in which that head does not exist (e.g. short re-org), the target must be noted explicitly |
Why is this? if you have the attestation slot you do have already the target checkpoint's epoch. This is independent of any chain or any headroot. |
https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#attestations
|
Apologies -- my rebuttal is removing the entire target checkpoint -- (epoch, root) -- rather than just the epoch. I do believe you can remove the epoch (as it is a function of slot) but that you cannot remove the root and adequately incentivize or slash on chains that don't necessarily contained the signed head |
This is not what I expected to happen in this EIP. I suspected that instead, you would extend the bitfield and allow for full aggregation of the sub-committees from a slot into a single message (fully virtualizing the committee index). Rather than hoisting the committee-index into the Attestation rather than data. Instead here, these still show up on chain as different messages but when doing verification, you can aggregate further? Is that the idea? |
That would be more data efficient but require a more complex change.
Yes, you can do the bitfield extension on the verifier side without having to change the protocol itself. |
Makes sense. Isolating the change like this seems reasonable |
Potential approach to a more efficient on chain aggregation: class Attestation(Container):
aggregation_bits: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_COMMITTEES_PER_SLOT] # [Modified in EIP7549]
data: AttestationData
committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] # [New in EIP7549]
signature: BLSSignature
def get_committee_indexes(attestation: Attestation) -> List[CommitteeIndex]:
return [CommitteeIndex(index) for bit, index in enumerate(commitee_bits) if bit]
...
# process_attestation checks
assert len(attestation.aggregation_bits) == len([b for b in attestation.committee_bits if b])
assert len(attestation.aggregation_bits) > 0
for index in get_committee_indexes(attestation):
assert data.index < get_committee_count_per_slot(state, data.target.epoch)
committee = get_beacon_committee(state, data.slot, index)
assert len(attestation.aggregation_bits[index]) == len(committee)
...
# attestation_aggregation topic checks
assert len(attestation.aggregation_bits) == len([b for b in attestation.committee_bits if b])
assert len(attestation.aggregation_bits) == 1
…
# Reduce a limit on a number of on chain attestation
MAX_ATTESTATIONS = 8 This approach makes proposer responsible for a final aggregation which is simply packing aggreation bits into a list, setting corresponding bits in the committee bitvector and aggregating signatures. This approach allows to re-use the same struct for on chain and network attestations. |
I like this technique @mkalinin. It still seems very contained/simple and is certainly a more complete solution |
I do like @mkalinin's approach as well. Is |
@mkalinin approach is pretty neat I agree with including it. How does this change affect the data complexity of AttersterSlashings? When slashing a small fraction of validators it can be more expensive if you only learn of large aggregates. For a large fraction, you would expect to have many offenders in the same signature, but I haven't thought about it in depth. Also without a very low |
I like the idea of having a single bitfield per slot. The problem about it is that without compression it would become inefficient especially for aggregating subnets where only 1/64th of validators are presented in a bitfield. If we can agree that for any existing use case this inefficiency won’t hurt because say data compression (e.g. snappy) is always used then a single bitfield would make more sense than a list of them. |
Good point! The worst case slashing scenario will have 64 times data complexity increase comparing to what we have today:
So to alleviate the data complexity a slasher will have to keep attestations in a network format — supporting this does seem to not require any changes, and seems to be feasible for accidental equivocations due to node’s misconfiguration. In the case of an attack with mass equivocations the data complexity doesn’t seem to increase in general, but with the proposed change it can result in a block size spikes vs gradual slashings inclusion as it would happen with an existing on chain format — this is where we can expect Note that all the above numbers are calculated with |
Do you mean class Attestation(Container):
aggregation_bits: Vector[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_COMMITTEES_PER_SLOT] # [Modified in EIP7549]
data: AttestationData
signature: BLSSignature or class Attestation(Container):
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] # [Modified in EIP7549]
data: AttestationData
signature: BLSSignature |
@wemeetagain doesn't matter too much. Either way I don't think we should include bits for all committees in each attestation |
60f10ab
to
38f269c
Compare
I have updated the spec to incorporate @mkalinin ideas allowing for efficient block packing of attestations. Rationale for adjusting the preset values can be found here https://hackmd.io/@n0ble/eip7549_onchain_aggregates I have also updated the deprecation strategy of Now the PR should be in good shape to be merged and iterate further in other PRs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me 👍
Okay to me 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
```python | ||
class Attestation(Container): | ||
aggregation_bits: List[Bitlist[MAX_VALIDATORS_PER_COMMITTEE], MAX_COMMITTEES_PER_SLOT] # [Modified in EIP7549] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the nested lists here? this is a deeply inefficient encoding which should be computable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ie we should be able to flatten this to a single list which would save on merkle tree size / hashing time, data size, redundancy in encoding etc etc - important for lots of reasons given the hundreds of thousands of attestations we have to process
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]
will make the uncompressed size of Attestation outside of block (unaggregated + aggregated) very big. This List has exactly 1 element except when included in a blog after which it's likely to be fully populated. With your suggestion the Bitlist will be 99% zeros while on p2p topics. Snappy will eat those zeroes, but still.
SSZ serialization involves an extra offset for the List wrapper. SSZ merkleization will have ~8 extra depth which is indeed inefficient but not sure if it's that bad.
EDIT: Ok I'm onboard with the change 👍 slightly more complex parsing logic but overall a simplification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This begs the question: should we use a simpler wrapper for spreading attestations (vs the ones that end up in the block) on the single-attestation topics?
Something like class SingleAttestation(container): (attester_index: ValidatorIndex, data: AttestationData, sig: Signature)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a developer I like such cleaner distinction but it has marginal benefit and does not justify adding a new container IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The optimized container only shaves a few bytes, won't have a meaningful impact on bandwidth nor CPU, you still have to verify a BLS sig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well apart from bandwidth and compression time, it also shaves hashing for the message id - true, not the heaviest thing, but still appears when profiling when I look at it (specially on subscribe-all-subnets
nodes).
We can "internally" reduce the memory footprint that an extra List
indirection adds, for the purpose of building an attestation pool, but there are these observable aspects to it which prevent it from being done across the board.
I would not suggest this if we weren't changing Attestation already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, notably, this would enforce the rule that the gossip topic should have only one signatory, simplifying the gossip validation rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw, Attestation
data structure in attestation subnets keeps open optimisation path where every attesting node publishes a single aggregate instead of multiple single attestations. Although, it does require either core protocol or network layer changes but switching to SingleAttestation
would take a step back from this path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keeps open optimisation path where every attesting
this is a bridge to cross when it's time to cross it, I suspect - ie one reason we don't allow it already is because it's hard to guarantee disjoint attestation sets like that - the single attestation is fundamentally more simple to reason about and it already exists as a "concept" in the protocol, even if it doesn't have its own container.
As a complement to ethereum#3787, this PR introduces a `SingleAttestation` type used for network propagation only. In Electra, the on-chain attestation format introduced in [EIP-7549](ethereum#3559) presents several difficulties - not only are the new fields to be interpreted differently during network processing and onchain which adds complexity in clients, they also introduce inefficiency both in hash computation and bandwidth. The new type puts the validator and committee indices directly in the attestation type, this simplifying processing and increasing security. * placing the validator index directly in the attestation allows verifying the signature without computing a shuffling - this closes a loophole where clients either must drop attestations or risk being overwhelmed by shuffling computations during attestation verification * the simpler "structure" of the attestation saves several hash calls during processing (a single-item List has significant hashing overhead compared to a field) * we save a few bytes here and there - we can also put stricter bounds on message size on the attestation topic because `SingleAttestation` is now fixed-size * the ambiguity of interpreting the `attestation_bits` list indices which became contextual under EIP-7549 is removed Because this change only affects the network encoding (and not block contents), the implementation impact on clients should be minimal.
data = attestation.data | ||
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) | ||
assert data.target.epoch == compute_epoch_at_slot(data.slot) | ||
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the max age removed here? (state.slot <= data.slot + SLOTS_PER_EPOCH
)
https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#attestations
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is initially removed in #3360
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, thanks for the pointer! Have missed the Deneb specs while reviewing.
Implements https://eips.ethereum.org/EIPS/eip-7549
Motivation
This proposal aims to make Casper FFG clients more efficient by reducing the average number of pairings needed to verify consensus rules. While all types of clients can benefit from this EIP, ZK circuits proving Casper FFG consensus are likely to have to most impact.
On a beacon chain network with at least 262144 active indexes it’s necessary to verify a minimum of ceil(32*64 * 2/3) = 1366 attestations to reach a 2/3 threshold. Participants cast two votes at once: LMD GHOST vote and Casper-FFG vote. However, the Attestation message contains three elements:
Signing over the 3rd item causes tuples of equal votes to produce different signing roots. If the committee index is moved outside of the Attestation message the minimum number of attestations to verify to reach a 2/3 threshold is reduced to ceil(32 * 2/3) = 22 (a factor of 62).
Deprecation strategy
The index field in AttestationData can be deprecated by:
This PR implements the first for simplicity, but open discussion on the rest