Skip to content
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

Ignore some untimely attestations #12387

Merged
merged 21 commits into from
May 12, 2023
Merged

Conversation

potuz
Copy link
Contributor

@potuz potuz commented May 12, 2023

When we receive an attestation for an old target, a node needs to regenerate the beacon state at that time in order to validate the attestation. Performing several of these replays at the same time is what stresses the nodes' CPUs and bloats memory usage. A node has a cache of seen attestation targets designed to address this situation. With the increase in validator sizes and the number of this untimely attestation, this cache quickly filled and forced us to replay several times in the same state. This PR implements a heuristic such that when a node receives many unviable checkpoint attestations, a node will simply ignore them if the target of the attestation is known to be old and not have been a checkpoint in any chain known to our node

This PR also made a change to validate aggregated p2p pipeline to not process slots for state used to verify selection proof and signature

@potuz potuz force-pushed the ignore_old_attestations branch from f81436a to 3dc041f Compare May 12, 2023 02:39
return nil, errors.Wrap(err, "could not compute epoch start")
}
cachedState = transition.NextSlotState(c.Root, slot)
if cachedState != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

cachedState, err := s.checkpointStateCache.StateByCheckpoint(c) has a check
!cachedState.IsNil() on line 33. is this cachedState different as we don't need that IsNil check?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's safe to add !cachedState.IsNil() check here

// If the attestation is recent and canonical we can use the head state to compute the shuffling.
headEpoch := slots.ToEpoch(s.HeadSlot())
if c.Epoch == headEpoch {
targetSlot, err := s.cfg.ForkChoiceStore.Slot([32]byte(c.Root))
Copy link
Contributor

Choose a reason for hiding this comment

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

should there be a check for cfg or ForkChoice initialized

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so, this is in the blockchain package and forkchoice starts up before in the node.

Comment on lines -70 to -74
{
name: "no pre state for attestations's target block",
a: util.HydrateAttestation(&ethpb.Attestation{Data: &ethpb.AttestationData{Target: &ethpb.Checkpoint{Root: BlkWithOutStateRoot[:]}}}),
wantedErr: "could not get pre state for epoch 0",
},
Copy link
Member

Choose a reason for hiding this comment

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

Note I removed this test. This becomes an impossible scenario with the addition of getting head state read-only

@@ -152,20 +151,6 @@ func (s *Service) validateAggregatedAtt(ctx context.Context, signed *ethpb.Signe
return pubsub.ValidationIgnore, err
}

attSlot := signed.Message.Aggregate.Data.Slot
Copy link
Member

Choose a reason for hiding this comment

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

This change is quite irrelevant but it's worth noting to the audience:
Processing slots is unnecessary here

ss, err := slots.EpochStart(target.Epoch)
if err != nil {
return nil, err
}
if err := slots.ValidateClock(ss, uint64(s.genesisTime.Unix())); err != nil {
return nil, err
}
s.cfg.ForkChoiceStore.RLock()
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be outside of getAttPreState instead of inside?
getAttPreState is used in OnAttestation which don't have the same locks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OnAttestation path is called only from UpdateHead that grabs the lock.

@terencechain terencechain force-pushed the ignore_old_attestations branch from 2e6a9d5 to 8dd8373 Compare May 12, 2023 15:50
@terencechain terencechain marked this pull request as ready for review May 12, 2023 15:50
@terencechain terencechain requested a review from a team as a code owner May 12, 2023 15:50
Copy link
Member

@terencechain terencechain left a comment

Choose a reason for hiding this comment

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

Lgtm

@prylabs-bulldozer prylabs-bulldozer bot merged commit b991780 into develop May 12, 2023
@prylabs-bulldozer prylabs-bulldozer bot deleted the ignore_old_attestations branch May 12, 2023 16:55
potuz added a commit that referenced this pull request May 12, 2023
* Ignore some untimely attestations

* correct child slot check

* consider tips as viable for checkpoints

* deal with canonical blocks

* forkchoice unit tests

* blockchain readonly beacon state

* Ignore some untimely attestations

* correct child slot check

* consider tips as viable for checkpoints

* deal with canonical blocks

* forkchoice unit tests

* blockchain readonly beacon state

* Fix AttestationTargetState mock

* Fix ineffectual assignment lint

* Fix blockchain tests

* Fix build

* Add Nil check

* add comment on lock

---------

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
Co-authored-by: Preston Van Loon <pvanloon@offchainlabs.com>
@@ -380,6 +381,14 @@ func (s *Service) InForkchoice(root [32]byte) bool {
return s.cfg.ForkChoiceStore.HasNode(root)
}

// IsViableForkCheckpoint returns whether the given checkpoint is a checkpoint in any
// chain known to forkchoice
func (s *Service) IsViableForCheckpoint(cp *forkchoicetypes.Checkpoint) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not seeing that this function is called from anywhere

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just to ensure s.cfg.ForkChoiceStore.RLock() is grabbed if it this accessor is used in the future? I did check that the locks are acquired correctly in the other call path for s.cfg.ForkChoiceStore.IsViableForCheckpoint(cp). The only other call path I see is UpdateHead->processAttestations->receiveAttestationNoPubsub->OnAttestation->getAttPreState)

Copy link
Contributor

Choose a reason for hiding this comment

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

the lock is acquired in UpdateHead FYI

Copy link
Member

Choose a reason for hiding this comment

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

+1. I think it can be removed

@@ -380,6 +381,14 @@ func (s *Service) InForkchoice(root [32]byte) bool {
return s.cfg.ForkChoiceStore.HasNode(root)
}

// IsViableForkCheckpoint returns whether the given checkpoint is a checkpoint in any
// chain known to forkchoice
func (s *Service) IsViableForCheckpoint(cp *forkchoicetypes.Checkpoint) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just to ensure s.cfg.ForkChoiceStore.RLock() is grabbed if it this accessor is used in the future? I did check that the locks are acquired correctly in the other call path for s.cfg.ForkChoiceStore.IsViableForCheckpoint(cp). The only other call path I see is UpdateHead->processAttestations->receiveAttestationNoPubsub->OnAttestation->getAttPreState)

@@ -380,6 +381,14 @@ func (s *Service) InForkchoice(root [32]byte) bool {
return s.cfg.ForkChoiceStore.HasNode(root)
}

// IsViableForkCheckpoint returns whether the given checkpoint is a checkpoint in any
// chain known to forkchoice
func (s *Service) IsViableForCheckpoint(cp *forkchoicetypes.Checkpoint) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the lock is acquired in UpdateHead FYI

if node.slot > epochStart {
return false, nil
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I might remove this space for OCD unless it is to separate these conditionals into groups but def not a big deal

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi David yeah this function was just mostly for future reference that locking of forlchoice is tricky within the Blockchain package but any external user has to use the service one that is self locking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants