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

Make iaviewer usage more human friendly #719

Merged
merged 3 commits into from
Oct 30, 2023
Merged

Make iaviewer usage more human friendly #719

merged 3 commits into from
Oct 30, 2023

Conversation

roy-dydx
Copy link
Contributor

Changelist

  • Migrate cli to use cobra
  • Add data-all command to list iavl tree for all modules
  • Unmarshal proto values and output human readable strings if they are in the registry

Test Plan

  • Manually tried all commands on a db from localnet

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2023

Walkthrough

The changes introduce a significant enhancement to the iaviewer CLI tool, adding new commands for data retrieval and improving command-line parsing. The update also includes a new function for fetching all prefixes for iavl trees and a new protoUnmarshaller function for unmarshalling byte slices into protobuf messages. A global variable unmarshallerRegistry has been added to map module prefix names to inner registry maps.

Changes

File Summary
protocol/scripts/iaviewer/iaviewer.go The main function is refactored to use the cobra library for command-line parsing. Four new commands (data-all, data, shape, versions) are added for data, shape, and version retrieval from an iavl tree. A new function GetAllPrefixes is introduced to fetch all prefixes for iavl trees.
protocol/scripts/iaviewer/unmarshaller.go A new function protoUnmarshaller is added to unmarshal byte slices into protobuf messages. A global variable unmarshallerRegistry is introduced to map module prefix names to inner registry maps, which map prefixes of keys in the IAVL key-value store to functions that can unmarshal the corresponding values.

Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • If you reply to a review comment from CodeRabbit, the bot will automatically respond.
  • To engage with CodeRabbit bot directly around the specific lines of code in the PR, mention @coderabbitai in your review comment
  • Note: Review comments are made on code diffs or files, not on the PR overview.
  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai help to get help.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.

Note: For conversation with the bot, please use the review comments on code diffs or files.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.json

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 0ba123e and 32881de.
Files selected for processing (2)
  • protocol/scripts/iaviewer/iaviewer.go (4 hunks)
  • protocol/scripts/iaviewer/unmarshaller.go (1 hunks)
Additional comments: 12
protocol/scripts/iaviewer/unmarshaller.go (2)
  • 1-9: The imports look fine. Ensure that all the imported packages are being used in the code.

  • 21-28: The unmarshallerRegistry is a map of maps, storing message names and corresponding unmarshaller functions. This is a good approach for organizing the unmarshallers. However, ensure that the keys used in the map are consistent and accurate across the codebase.

protocol/scripts/iaviewer/iaviewer.go (10)
  • 9-16: The import section has been updated to include the cobra library for command-line parsing and the regexp package for regular expressions. The os and strconv packages are no longer imported, as they are not used in the new code.

  • 27-41: The main function has been refactored to use the cobra library for command-line parsing. The rootCmd variable is defined as a cobra.Command and is executed in the main function. The init function adds four new commands to rootCmd: DataAllCmd, DataCmd, ShapeCmd, and VersionsCmd.

  • 43-56: Three new functions have been added to add flags to a cobra.Command: AddPathFlag, AddPrefixFlag, and AddVersionFlag. These functions add a "path", "prefix", and "version" flag to the command, respectively.

  • 57-87: The DataAllCmd function has been added. This function returns a cobra.Command that retrieves data from all IAVL trees in the LevelDB directory specified by the "path" flag. If the "version" flag is provided, it retrieves data from the specified version of the trees; otherwise, it retrieves data from the latest version.

  • 89-118: The DataCmd function has been added. This function returns a cobra.Command that retrieves data from a specific IAVL tree in the LevelDB directory specified by the "path" flag and the "prefix" flag. If the "version" flag is provided, it retrieves data from the specified version of the tree; otherwise, it retrieves data from the latest version.

  • 120-146: The ShapeCmd function has been added. This function returns a cobra.Command that retrieves the shape of a specific IAVL tree in the LevelDB directory specified by the "path" flag and the "prefix" flag. If the "version" flag is provided, it retrieves the shape of the specified version of the tree; otherwise, it retrieves the shape of the latest version.

  • 148-173: The VersionsCmd function has been added. This function returns a cobra.Command that lists the versions of a specific IAVL tree in the LevelDB directory specified by the "path" flag and the "prefix" flag. If the "version" flag is provided, it retrieves the versions of the specified version of the tree; otherwise, it retrieves the versions of the latest version.

  • 204-227: The GetAllPrefixes function has been added. This function fetches all prefixes for IAVL trees in the DB by iterating through all keys and looking for prefixes that match what is expected from modules.

  • 249-258: > Note: This review was outside of the patch, so it was mapped to the patch with the greatest overlap. Original lines [253-267]

The ReadTree function has been updated to accept a dbm.DB and a uint64 for the version instead of a directory string and an integer. The function now prints the version of the tree being read.

  • 269-292: The PrintTree function has been updated to print the data of a specific IAVL tree. It now accepts a *iavl.MutableTree and a prefix string as arguments. The function prints the keys, hashed values, and human-readable strings of the protobuf messages if they are in the registry. It also prints the hash and size of the tree.

Comment on lines +11 to +19
func protoUnmarshaller[M codec.ProtoMarshaler](b []byte) string {
cdc := app.GetEncodingConfig().Codec
var m M
// TODO: avoid reflection?
msgType := reflect.TypeOf(m).Elem()
m = reflect.New(msgType).Interface().(M)
cdc.MustUnmarshal(b, m)
return m.String()
}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 27, 2023

Choose a reason for hiding this comment

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

The protoUnmarshaller function uses reflection to create a new instance of the type M and then unmarshals the byte slice into it. While this is a valid approach, reflection can be slow and error-prone. If the type M does not implement the codec.ProtoMarshaler interface, this function will panic at runtime. Consider adding error handling to make the function more robust.

11:
func protoUnmarshaller[M codec.ProtoMarshaler](b []byte) (string, error) {
12:
	cdc := app.GetEncodingConfig().Codec
13:
	var m M
14:
	// TODO: avoid reflection?
15:
	msgType := reflect.TypeOf(m).Elem()
16:
	m = reflect.New(msgType).Interface().(M)
17:
	err := cdc.Unmarshal(b, m)
18:
	if err != nil {
19:
		return "", err
20:
	}
21:
	return m.String(), nil
22:
}

Commitable suggestion (Beta)
Suggested change
func protoUnmarshaller[M codec.ProtoMarshaler](b []byte) string {
cdc := app.GetEncodingConfig().Codec
var m M
// TODO: avoid reflection?
msgType := reflect.TypeOf(m).Elem()
m = reflect.New(msgType).Interface().(M)
cdc.MustUnmarshal(b, m)
return m.String()
}
func protoUnmarshaller[M codec.ProtoMarshaler](b []byte) (string, error) {
cdc := app.GetEncodingConfig().Codec
var m M
// TODO: avoid reflection?
msgType := reflect.TypeOf(m).Elem()
m = reflect.New(msgType).Interface().(M)
err := cdc.Unmarshal(b, m)
if err != nil {
return "", err
}
return m.String(), nil
}

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 want this to panic for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

@roy-dydx Understood. If you want the function to panic in case of an error during unmarshalling, you can continue using MustUnmarshal. This will indeed cause a panic if the unmarshalling fails. Please feel free to reach out if you need further assistance or have any other requests.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 32881de and 5c7a4a8.
Files selected for processing (1)
  • protocol/scripts/iaviewer/unmarshaller.go (1 hunks)
Additional comments: 2
protocol/scripts/iaviewer/unmarshaller.go (2)
  • 11-19: The previous comment about adding error handling to the protoUnmarshaller function is still valid. The function currently uses MustUnmarshal which will panic if unmarshalling fails. It would be better to use Unmarshal and return an error if it fails. This will make the function more robust and prevent unexpected panics.

  • 21-32: The unmarshallerRegistry map is a good way to associate prefixes with unmarshalling functions. However, it's important to ensure that the prefixes used are unique and do not overlap. If they do, it could lead to unexpected behavior. Consider adding checks to ensure that the prefixes are unique and do not overlap.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 5c7a4a8 and 9e9f22f.
Files selected for processing (1)
  • protocol/scripts/iaviewer/iaviewer.go (4 hunks)
Additional comments: 7
protocol/scripts/iaviewer/iaviewer.go (7)
  • 28-31: The rootCmd is defined correctly and the SilenceUsage flag is set to true to prevent the usage message from being printed when an error occurs. This is a good practice for a cleaner CLI experience.

  • 34-38: The error handling in the main function is done correctly. If an error occurs during the execution of the rootCmd, it is printed to os.Stderr and the program exits with a non-zero status code.

  • 40-45: The init function is used to add commands to the rootCmd. This is a good practice as it ensures that the commands are added before the main function is executed.

  • 61-63: The AddPathFlag, AddPrefixFlag, and AddVersionFlag functions are used to add flags to the commands. This is a good practice as it makes the code more modular and easier to maintain.

  • 156-182: The DataAllCmd, DataCmd, ShapeCmd, and VersionsCmd functions are used to create the commands. These functions are well-structured and make good use of the cobra library's features. The error handling in the RunE functions is done correctly.

  • 260-266: The ReadTree function is used to load an iavl tree from a DB. If a prefix is provided, a new PrefixDB is created with the provided prefix. This is a good practice as it allows for more granular control over the data that is loaded from the DB.

  • 277-300: The PrintTree function is used to print the data of an iavl tree. The function iterates through the tree and prints each key and its corresponding value. The function also checks if there is a registered unmarshaller for the tree's prefix and uses it to unmarshal the value if there is one. This is a good practice as it allows for more detailed output.

Comment on lines +212 to +234
// GetAllPrefixes fetches all prefixes for iavl trees in the DB. There's no great way to do this,
// so we just iterate through all keys and look for prefixes that match what we expect from modules.
func GetAllPrefixes(db dbm.DB) []string {
it, err := db.Iterator(nil, nil)
if err != nil {
panic(err)
}
defer it.Close()
// Luckily for us, this iterator goes in alphabetical order
var prefixes []string
for ; it.Valid(); it.Next() {
k := it.Key()
matches := prefixRe.FindStringSubmatch(string(k))
if matches != nil {
if len(prefixes) == 0 || matches[1] != prefixes[len(prefixes)-1] {
prefixes = append(prefixes, matches[1])
}
}
}
if err := it.Error(); err != nil {
panic(err)
}
return prefixes
Copy link
Contributor

Choose a reason for hiding this comment

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

The GetAllPrefixes function is used to fetch all prefixes for iavl trees in the DB. The function iterates through all keys in the DB and looks for prefixes that match what is expected from modules. This function could potentially be optimized by using a more efficient method to fetch the prefixes, but without more context, it's hard to suggest a specific improvement.

msgType := reflect.TypeOf(m).Elem()
m = reflect.New(msgType).Interface().(M)
cdc.MustUnmarshal(b, m)
return m.String()
Copy link
Contributor

Choose a reason for hiding this comment

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

Q: should we return string here and use %s? or should we return m and use %+v?

not sure if %+v works

Copy link
Contributor Author

@roy-dydx roy-dydx Oct 30, 2023

Choose a reason for hiding this comment

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

The functions would have generic return value which results in different return values for each proto. We need all these functions to be the same signature so we can put them in a map like we're doing now.

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 use any?

not a big deal though

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then to use String() I'd need to type assert which would require another generic function. (or maybe reflection works?)

I don't know how %+v works, but I don't think it would work on interface{}.

cmd.Flags().Uint64("version", 0, "version of the tree (default: latest version)")
}

func DataAllCmd() *cobra.Command {
Copy link
Contributor

Choose a reason for hiding this comment

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

for my understanding - how did this work before prefix option was added? kind of surprised that this is not already supported

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't work. If you don't pass prefix in, it will assume that all the leveldb keys are unprefixed and try to interpret it as an iavl tree. But for dbs created by cosmos, all the keys are prefixed and not ready to be interpreted as iavl tree unless the prefix is removed. So you'd just find no values unless you pass in a prefix of a tree.

Copy link
Contributor

Choose a reason for hiding this comment

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

🤯

@roy-dydx roy-dydx merged commit 8261625 into main Oct 30, 2023
14 of 16 checks passed
@roy-dydx roy-dydx deleted the roy/iav branch October 30, 2023 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

2 participants