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: support multiple chain ids in FinalizedChainsInfo #365

Merged
merged 31 commits into from
May 2, 2023

Conversation

gusin13
Copy link
Contributor

@gusin13 gusin13 commented Apr 27, 2023

Description

This PR is part of the optimizations being done to improve babylon-api performance.

FinalizedChainInfo rpc query is called multiple times by the explorer apis. I have replaced this method with FinalizedChainsInfo which will accept a list of chain ids so we can fetch info of multiple cz's at once.

Testing

Have added relevant fuzz tests and cli query

@gusin13 gusin13 marked this pull request as ready for review April 28, 2023 05:25
Copy link
Member

@SebastianElvis SebastianElvis left a comment

Choose a reason for hiding this comment

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

Nice!

Comment on lines 204 to 216
encountered := map[string]bool{}
for _, chainID := range req.ChainIds {
if len(chainID) == 0 {
return nil, status.Error(codes.InvalidArgument, "chain ID cannot be empty")
}

// check for duplicates and return error on first duplicate found
if encountered[chainID] {
return nil, status.Errorf(codes.InvalidArgument, "duplicate chain ID %s", chainID)
} else {
encountered[chainID] = true
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can wrap L204-216 as a function for checking duplication/empty value in a list of strings. This might be useful in a number of range queries

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, fixed.

@gusin13 gusin13 force-pushed the BM-603-api-add-new-rpc-queries-finalized branch from 4c848d8 to 69a3775 Compare April 28, 2023 08:37
@gusin13 gusin13 changed the base branch from cz-rpc-chains-info to dev April 28, 2023 08:37
Copy link
Member

@vitsalis vitsalis left a comment

Choose a reason for hiding this comment

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

Nice work! Some suggestions:

test/e2e/configurer/chain/queries.go Outdated Show resolved Hide resolved
q.Add(parameters[i], parameters[i+1])
}
req.URL.RawQuery = q.Encode()
if queryParams != nil {
Copy link
Member

Choose a reason for hiding this comment

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

I suggest that in the case that we do not want to pass any parameters, we do not pass nil, but instead pass an empty queryParams object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure fixed

if err != nil {
// skip if the finalised chain info is not found
if errors.Is(err, types.ErrEpochChainInfoNotFound) {
k.Logger(ctx).Error("finalised chain info not found", "chain ID", chainID)
Copy link
Member

Choose a reason for hiding this comment

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

No need to produce an error log for an invalid request. Could lead to someone hogging the logs by sending invalid requests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure fixed

finalizedEpoch, chainInfo, err := k.GetLastFinalizedChainInfo(ctx, chainID)
if err != nil {
// skip if the finalised chain info is not found
if errors.Is(err, types.ErrEpochChainInfoNotFound) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't like checking for particular errors as this might lead to inconsistencies if something minor is changed. I suggest we add a method FinalizedChainInfoExists or something similar, to check whether we don't need to add anything for this chain.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure fixed


resp.RawCheckpoint = rawCheckpoint.Ckpt
exists, err := k.HasFinalizedChainInfo(ctx, chainID)
Copy link
Member

Choose a reason for hiding this comment

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

The HasFinalizedChainInfo method retrieves the latest finalized epoch each time. This is a storage read operation and is quite costly, especially given that this value won't change in-between iterations. I suggest we retrieve this value outside of this loop and instead of using the HasFinalizedChainInfo(ctx, chainID) function which is non-performant, we use a function EpochChainInfoExists(ctx, epochNum, chainID), which is similar to this method but only looks for existence.

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 am not sure how we will only check for existence as we have to perform this check inside that function which will involve a storage read.

@@ -20,6 +21,23 @@ func (k Keeper) GetEpochChainInfo(ctx sdk.Context, chainID string, epochNumber u
return &chainInfo, nil
}

// HasFinalizedChainInfo checks if chain info exists for a given chain ID in the last finalised epoch
func (k Keeper) HasFinalizedChainInfo(ctx sdk.Context, chainID string) (bool, error) {
Copy link
Member

Choose a reason for hiding this comment

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

This method is not that performant and does more than it should. I suggest we remove this and use something similar to this method to check for existence given a chain ID and epoch.

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 am not sure how we will only check for existence as we have to perform this check inside that function which will involve a storage read.

}
// return if chain IDs contain duplicates or empty strings
if err := bbntypes.CheckForDuplicatesAndEmptyStrings(req.ChainIds); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
Copy link
Member

Choose a reason for hiding this comment

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

The errors from the above function are not specific and helpful for the user. For example, "Duplicate string" might not lead the user to understand that they should not provide duplicate chain IDs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, as CheckForDuplicatesAndEmptyStrings is a generic util and can be used in any module I have wrapped the error from the caller side instead. Have fixed it now..

@@ -20,15 +21,16 @@ func (k Keeper) GetEpochChainInfo(ctx sdk.Context, chainID string, epochNumber u
return &chainInfo, nil
}

// EpochChainInfoExists checks if the latest chain info exists of a given epoch for a given chain ID
func (k Keeper) EpochChainInfoExists(ctx sdk.Context, chainID string, epochNumber uint64) bool {
Copy link
Member

Choose a reason for hiding this comment

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

This method can also be used on the first lines of the GetEpochChainInfo function above in order to not have duplicated logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure fixed

return 0, nil, err
}

func (k Keeper) GetLastFinalizedChainInfo(ctx sdk.Context, chainID string, finalizedEpoch uint64) (uint64, *types.ChainInfo, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Few things:

  1. Why do we return a uint64 corresponding to the finalized epoch if this is available from chainInfo.LatestHeader.BabylonEpoch?
  2. This method seems exactly the same as GetEpochChainInfo, with the main difference that if the epoch parameter is different than the chainInfo.LatestHeader.BabylonEpoch, then we call it again with the epoch parameter set to chainInfo.LatestHeader.BabylonEpoch. So two questions:
    a. Why do we need to call GetEpochChainInfo again with the correct epoch? Does anything actually change on the chainInfo object?
    b. Maybe we can have a single method. This particular function does not need to be aware that it is retrieving something finalized as it just retrieves the data given to it in the epoch parameter. I would argue we don't actually need this method, especially if (a) doesn't change anything. If we do indeed need it, let's remove any reference to finalized as this function does not do anything finalized specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, fixed in my last commits.

x/zoneconcierge/keeper/grpc_query.go Outdated Show resolved Hide resolved
@gusin13 gusin13 merged commit 73f57be into dev May 2, 2023
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.

4 participants