-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Separate type for unaggregated network attestations #14659
base: develop
Are you sure you want to change the base?
Conversation
# Conflicts: # proto/prysm/v1alpha1/electra.ssz.go
att.AggregationBits = bitfield.NewBitlist(1) | ||
committeeIndex, err := att.GetCommitteeIndex() | ||
require.NoError(t, err) | ||
_, result, err = service.validateBitLength(ctx, s, att.Data.Slot, committeeIndex, att.AggregationBits) | ||
require.ErrorContains(t, "wanted participants bitfield length 4, got: 1", err) | ||
assert.Equal(t, pubsub.ValidationReject, result) | ||
|
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 this test deleted?
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.
Because I removed the helper function. We still test this, but it's done in https://github.com/prysmaticlabs/prysm/pull/14659/files#diff-f1d6b0b8f81c14d3cf2ef27e17d4c4e7e9b1ad1f8fc3b5a66d9bdf2eeb615853 now.
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.
never mind, I will revert this change
|
||
preState, err := s.cfg.chain.AttestationTargetState(ctx, data.Target) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return pubsub.ValidationIgnore, err | ||
} | ||
committeeIndex, err := att.GetCommitteeIndex() | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return pubsub.ValidationIgnore, err | ||
} | ||
committee, err := helpers.BeaconCommitteeFromState(ctx, preState, att.GetData().Slot, committeeIndex) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return pubsub.ValidationIgnore, err | ||
} | ||
|
||
var singleAtt *eth.SingleAttestation | ||
if att.Version() >= version.Electra { | ||
singleAtt, ok = att.(*eth.SingleAttestation) | ||
if !ok { | ||
return pubsub.ValidationIgnore, fmt.Errorf("attestation has wrong type (expected %T, got %T)", ð.SingleAttestation{}, att) | ||
} | ||
att = singleAtt.ToAttestationElectra(committee) | ||
} | ||
|
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.
Is there a DOS vector for calling AttestationTargetState
and BeaconCommitteeFromState
this early?
name: "nil signature", | ||
attestation: ðpb.AttestationElectra{ | ||
AggregationBits: testhelpers.FillByteSlice(4, 74), | ||
Data: ðpb.AttestationData{ | ||
Source: ðpb.Checkpoint{}, | ||
Target: ðpb.Checkpoint{}, | ||
}, | ||
CommitteeBits: testhelpers.FillByteSlice(8, 83), | ||
}, | ||
expectedErrorMessage: "attestation signature can't be nil", |
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 nil signature test removed?
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.
I removed it because we had two functions in the codebase checking if an attestation is nil. One is helpers.ValidateNilAttestation
and the other one is validateNilAttestation
on the VC side. The only difference between them was signature validation which is missing in the helpers
package. I decided it's better to just have a single implementation and I expect the one in helpers
to be more accurate. I am not exactly sure what happens though when the BN receives an attestation without a signature if the function in helpers
doesn't fail (most likely signature validation fails in some other place).
if !ok { | ||
return pubsub.ValidationIgnore, fmt.Errorf("attestation has wrong type (expected %T, got %T)", ð.SingleAttestation{}, att) | ||
} | ||
att = singleAtt.ToAttestationElectra(committee) |
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 is the biggest additional performance penalty as far as I can see. For a node that subscribe to several subnets. Is this a concern? We will call this 10-30k times per slot
@@ -71,6 +70,32 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p | |||
if data.Slot == 0 { | |||
return pubsub.ValidationIgnore, nil | |||
} | |||
|
|||
preState, err := s.cfg.chain.AttestationTargetState(ctx, data.Target) |
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 are we calling this here ? There must be strong justification for
- fetching a state
- fetching the committee from it
As these are additional bottlenecks on our highest traffic gossip 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.
Also why is this being called before the current gossip checks
|
||
committee, result, err := s.validateBitLength(ctx, bs, aggregate.GetData().Slot, committeeIndex, aggregate.GetAggregationBits()) |
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 was this removed ? Its effectively the same as what has been added in
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.
you're right, I will change it back
committee, result, err := s.validateBitLength(ctx, bs, a.GetData().Slot, committeeIndex, a.GetAggregationBits()) | ||
if result != pubsub.ValidationAccept { | ||
return result, err | ||
committee, err := helpers.BeaconCommitteeFromState(ctx, bs, a.GetData().Slot, committeeIndex) |
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.
Same here, why is validateBitLength
removed
@@ -115,6 +104,48 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p | |||
} | |||
} | |||
|
|||
if !s.cfg.chain.InForkchoice(bytesutil.ToBytes32(data.BeaconBlockRoot)) { |
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.
Executing all these methods before we have validated data.BeaconBlockRoot
is wrong. As we might have not processed the block yet (and it has to be inserted into our pending queue)
return pubsub.ValidationIgnore, blockchain.ErrNotDescendantOfFinalized | ||
} | ||
|
||
if err = s.cfg.chain.VerifyLmdFfgConsistency(ctx, att); err != nil { |
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.
Same here and for the rest of the methods that follow it. If it is difficult enough, you should just stream the attestations after
@@ -141,23 +162,25 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(ctx context.Context, p | |||
tracing.AnnotateError(span, blockchain.ErrNotDescendantOfFinalized) | |||
return pubsub.ValidationIgnore, blockchain.ErrNotDescendantOfFinalized | |||
} | |||
if err := s.cfg.chain.VerifyLmdFfgConsistency(ctx, att); err != nil { |
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 check is performed now after you are grabbing a full state and computing a beacon committee shuffling, this opens up the door for a malicious peer to kill the node with a crafted attestation.
ctx, span := trace.StartSpan(ctx, "AttesterServer.ProposeAttestationElectra") | ||
defer span.End() | ||
|
||
committeeIndex, err := att.GetCommitteeIndex() | ||
targetState, err := vs.AttestationStateFetcher.AttestationTargetState(ctx, att.Data.Target) |
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.
Any concerns with this line: for every attestation we propose, we need to retrieve the target state. What are the implications of this?
# Conflicts: # CHANGELOG.md # beacon-chain/rpc/eth/beacon/handlers_pool.go # beacon-chain/rpc/prysm/v1alpha1/validator/attester.go # beacon-chain/rpc/prysm/v1alpha1/validator/server.go # beacon-chain/rpc/service.go # proto/prysm/v1alpha1/attestation.go
log.WithError(err).Error("could not save attestation") | ||
} | ||
} else if att.IsAggregated() { |
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.
single attestation can't be aggregated
What type of PR is this?
Feature
What does this PR do? Why is it needed?
Implements ethereum/consensus-specs#3900
The above consensus change introduces a new
SingleAttestation
type for unaggregated attestations.AttestationElectra
will still be used for aggregated atts. Using the single version in all of our code would be a huge undertaking because many components, such as the attestation pool and attestation caches, require attestations to be of the old format. Modifying these components to work both with pre-Electra attestations and the single attestation is risky and too much work for Pectra.The main reason why
SingleAttestation
was introduced is to prevent a DoS attack, which means it's main value is at the level of gossip. Once we pass gossip validation checks, there is no downside to using theAttestationElectra
for unaggregated attestations too. This allows us to leave most attestation-related code untouched. To have the best of both worlds, we convert the single attestation to an Electra attestation as quickly as possible and use the latter from that point on.When converting from
SingleAttestation
toElectraAttestation
we need to find the index within the committee of theattester_index
from the single attestation, becauseattester_index
is the validator index within the whole state. To give an example:The way it's done is by using the
AttestationStateFetcher
to get the attestation's target state, then get the appropriate committee from that state and lastly callToAttestationElectra
on theSingleAttestation
, passing the committee to the function.Other notes for review
Acknowledgements