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

Do not HTR the state when checking for optimistic mode #12143

Merged
merged 35 commits into from
Mar 20, 2023
Merged

Conversation

rkapka
Copy link
Contributor

@rkapka rkapka commented Mar 16, 2023

What type of PR is this?

Optimization

What does this PR do? Why is it needed?

Several users complained about out-of-memory issues happening when issuing API requests. This is most likely because of how we check for optimistic status. Currently we calculate the state root every time inside IsOptimistic, which is very inefficient. In tghis PR we move to an algorithm which does not calculate HTR on the state. The algorithm was designed by @potuz

@rkapka rkapka changed the title initial impl Do not HTR the state when checking for optimistic mode Mar 16, 2023
return false, errors.Wrapf(err, "could not get block roots for slot %d", slotNumber)
}
if len(roots) == 0 {
// TODO: What to do here?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what to do in this case

Copy link
Contributor

Choose a reason for hiding this comment

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

my suggestion above removes this problem

if st.Slot() == headSt.Slot() {
return optimisticModeFetcher.IsOptimistic(ctx)
}
r, err := st.LatestBlockHeader().HashTreeRoot()
Copy link
Member

Choose a reason for hiding this comment

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

doesn't work this way for the reasons we have discussed earlier.

Copy link
Contributor

Choose a reason for hiding this comment

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

you do not need to do this HTR. All you care is if the block is canonical or not. Here the cheapest thing to do is to pass database and use database.BlockRootsBySlot or BlocksBySlot

if !optimistic {
return false, nil
}
headSt, err := stateFetcher.State(ctx, []byte("head"))
Copy link
Contributor

Choose a reason for hiding this comment

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

please use chainInfo.HeadSlot()

if primitives.Slot(slotNumber) == headSt.Slot() {
return optimisticModeFetcher.IsOptimistic(ctx)
}
finalizedSlot, err := slots.EpochStart(headSt.FinalizedCheckpoint().Epoch)
Copy link
Contributor

Choose a reason for hiding this comment

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

please use chainInfo.FinalizedCheckpt() and move this check before the head check above.

if primitives.Slot(slotNumber) <= finalizedSlot {
return false, nil
}
_, roots, err := database.BlockRootsBySlot(ctx, primitives.Slot(slotNumber))
Copy link
Contributor

Choose a reason for hiding this comment

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

You can avoid this database lookup in this case by using chainInfo.ForkChoicer().AncestorRoot(ctx, headRoot, slot) and then checking for optimistic status of that root. Be careful with the forkchoice 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.

Be careful with the forkchoice locks.

Do I need to explicitly do anything?

return false, errors.Wrapf(err, "could not get block roots for slot %d", slotNumber)
}
if len(roots) == 0 {
// TODO: What to do here?
Copy link
Contributor

Choose a reason for hiding this comment

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

my suggestion above removes this problem

Comment on lines 127 to 137
for _, r := range roots {
canonical, err := chainInfo.IsCanonical(ctx, r)
if err != nil {
return false, errors.Wrapf(err, "could not check canonical status")
}
if canonical {
return optimisticModeFetcher.IsOptimisticForRoot(ctx, r)
}
}
// No block is canonical, return true
return true, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

My suggestion above removes this block.

) (bool, error) {
st, err := stateFetcher.State(ctx, stateId)
if err != nil {
return false, errors.Wrap(err, "could not fetch justified state")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this error message?

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 forgot to change this when extracting the function

Comment on lines 153 to 159
headSt, err := stateFetcher.State(ctx, []byte("head"))
if err != nil {
return false, errors.Wrap(err, "could not fetch head state")
}
if st.Slot() == headSt.Slot() {
return optimisticModeFetcher.IsOptimistic(ctx)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

you are grabbing a state only to check that the incoming id has the same state slot. You can use if you want chainInfo.HeadSlot() but also you should move this check (which is very unlikely) down the path.

if st.Slot() == headSt.Slot() {
return optimisticModeFetcher.IsOptimistic(ctx)
}
r, err := st.LatestBlockHeader().HashTreeRoot()
Copy link
Contributor

Choose a reason for hiding this comment

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

you do not need to do this HTR. All you care is if the block is canonical or not. Here the cheapest thing to do is to pass database and use database.BlockRootsBySlot or BlocksBySlot

}
if len(roots) == 0 {
// TODO: What to do here?
return false, errors.New("no block")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We now have this issue here @potuz

@rkapka rkapka marked this pull request as ready for review March 17, 2023 10:39
@rkapka rkapka requested a review from a team as a code owner March 17, 2023 10:39
if err != nil {
return false, errors.Wrap(err, "could not get head state's finalized slot")
}
if primitives.Slot(slotNumber) <= finalizedSlot {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move this check above the head one.

}
r, err := chainInfo.ForkChoicer().AncestorRoot(ctx, bytesutil.ToBytes32(stateId), primitives.Slot(slotNumber))
if err != nil {
return false, errors.Wrap(err, "could not get ancestor root")
Copy link
Contributor

Choose a reason for hiding this comment

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

on errors is probably better to return true instead of false.

if primitives.Slot(slotNumber) <= finalizedSlot {
return false, nil
}
r, err := chainInfo.ForkChoicer().AncestorRoot(ctx, bytesutil.ToBytes32(stateId), primitives.Slot(slotNumber))
Copy link
Contributor

Choose a reason for hiding this comment

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

how is stateId a block root here? you need to pass the headroot here.

@rkapka rkapka force-pushed the is-optimistic-htr branch from 86a639c to fd5da7f Compare March 17, 2023 15:39
return true, errors.Wrapf(err, "could not get block roots for slot %d", st.Slot())
}
if len(roots) == 0 {
return false, errors.New("no blocks returned from the database")
Copy link
Contributor

Choose a reason for hiding this comment

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

better to return true here

Comment on lines 152 to 173
for _, r := range roots {
canonical, err := chainInfo.IsCanonical(ctx, r)
if err != nil {
return true, errors.Wrapf(err, "could not check canonical status")
}
if canonical {
optimistic, err := optimisticModeFetcher.IsOptimistic(ctx)
if err != nil {
return true, errors.Wrap(err, "could not check optimistic status")
}
if !optimistic {
return false, nil
}
finalizedSlot, err := slots.EpochStart(chainInfo.FinalizedCheckpt().Epoch)
if err != nil {
return true, errors.Wrap(err, "could not get head state's finalized slot")
}
if st.Slot() <= finalizedSlot {
return false, nil
}
return true, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This block does not check what you want. Suppose you get roots = [r1, r2] with r1 canonical and r2 not, both past the finalized version. The node is not optimistic. In this situation you would return false.

Now suppose that the block with root r2 has stateRoot precisely the state you queried for. In this case you should have returned true.

What you need to do here is to requests the blocks, not the roots. And then check if the stateRoot matches the given id, and in that case (only in that case) check if it is canonical or not, etc...

@rkapka rkapka force-pushed the is-optimistic-htr branch from ff3a8bd to c6c866f Compare March 17, 2023 18:20
@rkapka rkapka force-pushed the is-optimistic-htr branch from c6c866f to 1a4b3ef Compare March 17, 2023 18:22
potuz
potuz previously approved these changes Mar 18, 2023
@rkapka rkapka force-pushed the is-optimistic-htr branch from 289d164 to 1090deb Compare March 18, 2023 16:50
Comment on lines +90 to +96
optimistic, err := optimisticModeFetcher.IsOptimistic(ctx)
if err != nil {
return true, errors.Wrap(err, "could not check optimistic status")
}
if !optimistic {
return false, nil
}
Copy link
Member

Choose a reason for hiding this comment

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

Why not move this to before strconv.ParseUint(stateIdString, 10, 64)?

Copy link
Member

Choose a reason for hiding this comment

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

Very smart check btw

Comment on lines 116 to 119
r, err := chainInfo.ForkChoicer().AncestorRoot(ctx, bytesutil.ToBytes32(headRoot), primitives.Slot(slotNumber))
if err != nil {
return true, errors.Wrap(err, "could not get ancestor root")
}
Copy link
Member

Choose a reason for hiding this comment

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

This is very smart

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is Potuz's idea

case "head":
return optimisticModeFetcher.IsOptimistic(ctx)
case "genesis", "finalized":
return false, nil
Copy link
Member

Choose a reason for hiding this comment

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

Why wouldn't we check finalized?

fcp := chainInfo.FinalizedJustifiedCheckpoint()
		if fcp == nil {
			return true, errors.New("received nil finalized checkpoint")
		}
		return optimisticModeFetcher.IsOptimisticForRoot(ctx, bytesutil.ToBytes32(fcp.Root))

Copy link
Contributor Author

@rkapka rkapka Mar 18, 2023

Choose a reason for hiding this comment

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

finalized block (which corresponds to finalized state) can't be optimistic, as said on Slack

terencechain
terencechain previously approved these changes Mar 18, 2023
@rkapka rkapka added the Blocked Blocked by research or external factors label Mar 20, 2023
@rkapka
Copy link
Contributor Author

rkapka commented Mar 20, 2023

Blocked by #12162

terencechain
terencechain previously approved these changes Mar 20, 2023
@potuz potuz removed the Blocked Blocked by research or external factors label Mar 20, 2023
@potuz potuz merged commit 0cb46eb into develop Mar 20, 2023
@delete-merged-branch delete-merged-branch bot deleted the is-optimistic-htr branch March 20, 2023 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Api related tasks Ready For Review A pull request ready for code review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants