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

zoneconcierge: proper initialisation for chain info #285

Merged
merged 4 commits into from
Jan 24, 2023

Conversation

SebastianElvis
Copy link
Member

Fixes an improvement found by @gusin13

This PR introduces the proper initialisation for chain info.

Previously, upon querying GetChainInfo, if a chain info does not exist, then ZoneConcierge returns an empty chain info rather than an error. This is not proper since the function has to give a clear message on whether a chain info exists or not. For example, one side effect of this is that the chain info API returns 200 even if the chain info does not exist.

This PR introduces the initialisation that works as follows. Upon a header via IBC

  • if chain info does not exist, initialise an empty one
  • if canonical header, then update the latest header to this one and increment counter
  • if fork header, then update the fork header as before

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.

Good stuff. I'm not entirely onboard with the new errors introduced here and the way they are handled, added corresponding comments

if err != nil {
// chain info has not been initialised yet
// this can only be a programming error
panic(err)
Copy link
Member

Choose a reason for hiding this comment

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

Here we panic since not having a ChainInfo can only be a programming error. However, on the previous if condition, we return an error. Why don't we panic there as well or return an error?

Copy link
Member

Choose a reason for hiding this comment

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

Given that this is a helper method and it doesn't have a complete view of the entire logic, I would avoid panicking in this function as the reader does not have all the context.

Copy link
Member Author

@SebastianElvis SebastianElvis Jan 23, 2023

Choose a reason for hiding this comment

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

Good catch! After a second thought, maybe let's return an error here. It will be the AfterHeaderWithValidCommit hook's responsibility to panic.

chainInfo := k.GetChainInfo(ctx, chainID)
chainInfo, err := k.GetChainInfo(ctx, chainID)
if err != nil {
return sdkerrors.Wrapf(types.ErrChainInfoNotFound, "cannot insert fork header when chain info is not initialized")
Copy link
Member

Choose a reason for hiding this comment

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

In updateLatestHeader we panic. What's the difference here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Have updated the PR to return errors in tryToUpdateLatestForkHeader and updateLatestHeader consistently 👍


// initialise chain info if not exist
chainInfo, err := h.k.GetChainInfo(ctx, indexedHeader.ChainId)
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

What if there's another kind of error related to GetChainInfo? I suggest that GetChainInfo returns nil if no chain info exists (or we implement a method ChainInfoExists) and we create that in this case. In the future, if an extra error is created this might spill over here and we panic for no reason.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I have changed the PR so that we initialise chain info only when the error is of type ErrEpochChainInfoNotFound, and panic directly if the error is other types.

I suggest that GetChainInfo returns nil if no chain info exists (or we implement a method ChainInfoExists) and we create that in this case.

I remember we had a discussion on this and the conclusion was that we need to have proper initialisation of chain info and give the invoker a clear message if the chain info does not exist. This was part of the motivation of this PR actually.

return nil, fmt.Errorf("chainID is empty")
}
// ensure chain info has not been initialised yet
if _, err := k.GetChainInfo(ctx, chainID); err == nil {
Copy link
Member

Choose a reason for hiding this comment

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

The GetChainInfo returning an error is not a good condition for identifying whether a Chain Info exists imo. I would have it return nil if no such info exists.

Copy link
Member Author

Choose a reason for hiding this comment

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

Like above, I updated the PR to distinguish errors by using sdkerrors.IsOf.

If other errors occur maybe AfterHeaderWithValidCommit will need to do other corresponding things, e.g., panic, rather than just returning nil. Wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

It took me a while to get around why we return an error under err==nil, so, I would suggest we don't use k.GetChainInfo() here. Instead, we could have a k.HasChainInfo() method that wraps store.Has(). k.GetChainInfo() can reuse k.HashChainInfo() and calling k.HasChainInfo() would be more efficient as it does not need to read the database and unmarshal the object.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep that's a good idea. Fixed 👍

Headers: []*types.IndexedHeader{},
},
}
return nil, 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.

Why return an error here? I don't think it's an error if no chain info exists.

Copy link
Member Author

Choose a reason for hiding this comment

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

It was not an error before this PR. This PR aims at introducing initialisation of chain info, such that the invokers of GetChainInfo gets a clear message whether the chain checkpoints to Babylon or not. For example, without this PR, the chain_info API always returns status code 200 even if the chain does not checkpoint to Babylon. Instead in this case, the status code should be 500.

@SebastianElvis
Copy link
Member Author

Thanks Vitalis for the great comments! I have addressed all of them. Since Vitalis is offline this week, @gitferry could you please have a look at this PR? Thanks!

Copy link
Contributor

@gitferry gitferry left a comment

Choose a reason for hiding this comment

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

Great work! Left a few suggestions:

return nil, fmt.Errorf("chainID is empty")
}
// ensure chain info has not been initialised yet
if _, err := k.GetChainInfo(ctx, chainID); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

It took me a while to get around why we return an error under err==nil, so, I would suggest we don't use k.GetChainInfo() here. Instead, we could have a k.HasChainInfo() method that wraps store.Has(). k.GetChainInfo() can reuse k.HashChainInfo() and calling k.HasChainInfo() would be more efficient as it does not need to read the database and unmarshal the object.

Comment on lines 66 to 67
// chain info has not been initialised yet
return err
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// chain info has not been initialised yet
return err
return fmt.Errorf("failed to get chain info of %s: %w", chainID, err)

chainInfo := k.GetChainInfo(ctx, chainID)
chainInfo, err := k.GetChainInfo(ctx, chainID)
if err != nil {
// chain info does not exist yet, nothing to record
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be better if we add a log here? Otherwise, we would loose this information

// chain info does not exist yet, initialise chain info for this chain
chainInfo, err = h.k.InitChainInfo(ctx, indexedHeader.ChainId)
if err != nil {
panic(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
panic(err)
panic(fmt.Errorf("failed to initialize chain info of %s: %w", indexedHeader.ChainId, err)

panic(err)
}
} else {
panic(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
panic(err)
panic(fmt.Errorf("failed to get chain info of %s: %w", indexedHeader.ChainId, err)

@SebastianElvis SebastianElvis merged commit 42e3744 into dev Jan 24, 2023
@SebastianElvis SebastianElvis deleted the zoneconcierge-chaininfo-err branch January 24, 2023 00:48
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