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

feat: check eth events indexed in range #12728

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

akaladarshi
Copy link
Contributor

Related Issues

#12709

Proposed Changes

  • check if the events within ranged is indexed

@akaladarshi akaladarshi marked this pull request as ready for review November 27, 2024 12:00
Comment on lines 286 to 293
startCid, err := si.getTipsetKeyCidByHeight(ctx, f.MinHeight)
if err != nil {
if errors.Is(err, ErrNotFound) {
// Null round for the start of the range is acceptable
return nil
}
return xerrors.Errorf("failed to get tipset key cid for start height: %w", err)
}
Copy link
Member

Choose a reason for hiding this comment

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

In the ErrNotFound case, I'd like it to increment by 1 until it finds one <= MaxHeight that's not a null round.

In the special case where there are only null rounds between MinHeight and MaxHeight, pretend that it's indexed and don't return an error, they'll just get no results.

So you'll need a loop here, and a local copy of f.MinHeight to increment.

}

if exists, err := si.isTipsetIndexed(ctx, startCid); err != nil || !exists {
return xerrors.Errorf("start tipset not indexed: %w", err)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return xerrors.Errorf("start tipset not indexed: %w", err)
return xerrors.Errorf("filter start tipset is not indexed: %w", err)

return xerrors.Errorf("start tipset not indexed: %w", err)
}
if exists, err := si.isTipsetIndexed(ctx, endCid); err != nil || !exists {
return xerrors.Errorf("end tipset not indexed: %w", err)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return xerrors.Errorf("end tipset not indexed: %w", err)
return xerrors.Errorf("filter end tipset is not indexed: %w", err)

if head == nil || f.MaxHeight > head.Height() {
return xerrors.Errorf("range end is in the future: %w", err)
}
return ErrNotFound // End is unindexed
Copy link
Member

Choose a reason for hiding this comment

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

We're back in the null round case, I think that we should walk backward to find the first non-null lower than MaxHeight. Basically the same as the approach outlined above for MinHeight but in the other direction. You may find a way to abstract the algorithm to make it a reusable function, or it may be easier to just write it all out.

Basically: let's contract the range if the edges contain null rounds, walk inward until we find a min and max that are not null rounds. Be careful not to iterate beyond the other bound (don't increment from Min beyond the Max value, and don't decrement from Max beyond the Min value). The special case is when there are only null rounds between them, don't error, just return. There's another special case of contracting to min==max, you could easily do an if block with startCid.Equals(endCid) around the second isTipsetIndexed call to deal with that (although it's not critical since it'd just mean two queries for the same thing if it wasn't handled).

@BigLep
Copy link
Member

BigLep commented Dec 9, 2024

@akaladarshi : I assume you'll re-request review when it's ready.


return xerrors.Errorf("failed to get tipset key cid by height: %w", err)
// Find the first non-null round in the range
startCid, err := si.findFirstNonNullRound(ctx, &minHeight, maxHeight)
Copy link
Member

Choose a reason for hiding this comment

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

The use of the pointer to update it here is clever, but a little too non-obvious from a maintenance perspective, there's no obvious notice here it gets altered. I think you'd be better off returning a new minHeight along with the cid. You'll have to var startCid, endCid cid.Cid and not use := for these calls though.

Copy link
Member

Choose a reason for hiding this comment

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

[]byte sorry, not cid.Cid

which is another point - not being a CID, let's be careful with naming of these things, startKeyBytes perhaps?

Comment on lines +267 to +268
// If all rounds are null, consider the range valid
if endCid == nil {
Copy link
Member

@rvagg rvagg Dec 11, 2024

Choose a reason for hiding this comment

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

oo, interesting case, you should never get here, right, since we do the walk for startCid first? If you can't come up with a plausible way to get here then I think we should error here and say something about this being "unexpected" (we want someone to report this error if they ever stumble upon it)

Comment on lines +285 to +296
func (si *SqliteIndexer) checkTipsetByKeyCid(ctx context.Context, tipsetKeyCid []byte, height abi.ChainEpoch) error {
exists, err := si.isTipsetIndexed(ctx, tipsetKeyCid)
if err != nil {
return xerrors.Errorf("failed to check if tipset at height %d is indexed: %w", height, err)
}

if exists {
return nil // null round
}

return ErrNotFound // tipset is not indexed
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm not really sure the height is necessary here, but the main thing I want to do is clarify what this is for. How about we rename this one checkTipsetIndexedStatus since you've dropped it form its parent

Suggested change
func (si *SqliteIndexer) checkTipsetByKeyCid(ctx context.Context, tipsetKeyCid []byte, height abi.ChainEpoch) error {
exists, err := si.isTipsetIndexed(ctx, tipsetKeyCid)
if err != nil {
return xerrors.Errorf("failed to check if tipset at height %d is indexed: %w", height, err)
}
if exists {
return nil // null round
}
return ErrNotFound // tipset is not indexed
}
func (si *SqliteIndexer) checkTipsetIndexedStatus(ctx context.Context, tipsetKeyCid []byte, height abi.ChainEpoch) error {
exists, err := si.isTipsetIndexed(ctx, tipsetKeyCid)
if err != nil {
return xerrors.Errorf("failed to check if tipset at height %d is indexed: %w", height, err)
} else if exists {
return nil // has been indexed
}
return ErrNotFound
}

Comment on lines -246 to -255
switch {
case f.TipsetCid != cid.Undef:
tipsetKeyCid = f.TipsetCid.Bytes()
case f.MinHeight >= 0 && f.MinHeight == f.MaxHeight:
tipsetKeyCid, err = si.getTipsetKeyCidByHeight(ctx, f.MinHeight)
if err != nil {
if err == ErrNotFound {
// this means that this is a null round and there exist no events for this epoch
return nil
}
Copy link
Member

Choose a reason for hiding this comment

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

you've lost all of this logic in the process of refactoring, so we have a couple of problems now:

  1. We're not testing the case where the filter specifies a specific TipsetCid - which we can do directly without needing to look at the range (case 1 above)
  2. We're not taking the shortcut path for the case where the filter specifies a min==max, that one is easy because it's just one tipset to test (case 2 above)
  3. Your new code just jumps straight in and assumes that f.MinHeight and f.MaxHeight are useful, but that may not be the case. They're allowed to have -1 values, which signifies that neither have been specified (we do that in
    func parseBlockRange(heaviest abi.ChainEpoch, fromBlock, toBlock *string, maxRange abi.ChainEpoch) (minHeight abi.ChainEpoch, maxHeight abi.ChainEpoch, err error) {
    ). Which is why the >= 0 check existed in the original case.

So I think you'll need to restore some form of the original switch to accommodate those two cases above and then for the third case where f.MinHeight >= 0 && f.MaxHeight >= 0, and maybe && f.MinHeight < f.MaxHeight just for good measure, just to be defensive about possible inputs.

Comment on lines +301 to +308
cid, err := si.getTipsetKeyCidByHeight(ctx, height)
if err == nil {
*minHeight = height // Update the minHeight to the found height
return cid, nil
}
if !errors.Is(err, ErrNotFound) {
return nil, xerrors.Errorf("failed to get tipset key cid for height %d: %w", height, err)
}
Copy link
Member

Choose a reason for hiding this comment

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

Let's switch this for readability, since programmers are used to seeing errors handled first (but not always, just the majority of the time), we introduce a cognitive hiccup when we switch things around; but if we keep it close to the expected flow then it's easier to skim

Suggested change
cid, err := si.getTipsetKeyCidByHeight(ctx, height)
if err == nil {
*minHeight = height // Update the minHeight to the found height
return cid, nil
}
if !errors.Is(err, ErrNotFound) {
return nil, xerrors.Errorf("failed to get tipset key cid for height %d: %w", height, err)
}
cid, err := si.getTipsetKeyCidByHeight(ctx, height)
if err != nil {
if !errors.Is(err, ErrNotFound) {
return nil, xerrors.Errorf("failed to get tipset key cid for height %d: %w", height, err)
}
// else null round, keep searching
continue
}
*minHeight = height // Update the minHeight to the found height
return cid, nil

@rvagg
Copy link
Member

rvagg commented Dec 11, 2024

Good so far, nice to be checking those bounds in a sophisticated way now. See notes inline; some minor suggestions and one larger one about the missing functionality checking all the variations of a filter that got lost in the refactor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ⌨️ In Progress
Development

Successfully merging this pull request may close these issues.

3 participants