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: allow saving version 0 #1002

Merged
merged 9 commits into from
Nov 4, 2024
Merged

feat: allow saving version 0 #1002

merged 9 commits into from
Nov 4, 2024

Conversation

kocubinski
Copy link
Member

@kocubinski kocubinski commented Oct 31, 2024

This patch makes SetInitialVersion(0) calls valid, which previously would error during SaveVersion. This is particularly useful from server/v2 which processes and saves a Genesis block at version initialHeight - 1, which is 0 in the default case.

Unrelated to above is some clean up; legacydump is a compiled binary file and shouldn't be committed. An amd64 binary is presently in master, which doesn't run on e.g. arm64. Instead of this, removed the binary and updated the Makefile and Github action to build where required (by tests).

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Enhanced version management capabilities in the MutableTree structure.
    • Improved error handling and validation in the nodeDB structure.
    • Added tracking for initial version status in the Options configuration.
    • New test cases added for comprehensive coverage of MutableTree functionality.
    • Introduced a build step for the legacydump binary in the CI process.
  • Bug Fixes

    • Adjusted return value in version retrieval to better indicate absence of versions.
  • Documentation

    • Updated method signatures to reflect changes in version handling and error management.
    • Revised documentation to clarify updates in the DeleteVersionsFrom method.
    • Added entries to .gitignore to exclude unnecessary files and directories.

@kocubinski kocubinski requested a review from a team as a code owner October 31, 2024 15:19
Copy link

coderabbitai bot commented Oct 31, 2024

Walkthrough

The pull request introduces modifications to the MutableTree, nodeDB, Options, and related test files. Key changes include the addition of a boolean field initialVersionSet in the MutableTree and Options structs to manage the initial version setting. The getLatestVersion method's signature in nodeDB is updated to return a boolean indicating success, enhancing error handling and version management capabilities. Additionally, various test files are updated to reflect these changes, ensuring robust testing of the new functionality.

Changes

File Change Summary
mutable_tree.go - Added field: initialVersionSet bool in MutableTree struct.
- Updated WorkingVersion method to utilize initialVersionSet.
- Modified SetInitialVersion to set initialVersionSet to true.
- Adjusted LoadVersion logic for version comparisons.
- Reset initialVersionSet in SaveVersion method.
nodedb.go - Changed return value of getLatestVersion from (int64, error) to (bool, int64, error).
- Enhanced error handling in SetFastStorageVersionToBatch and deleteVersion methods.
options.go - Added field: initialVersionSet bool in Options struct.
- Updated InitialVersionOption function to set initialVersionSet to true.
immutable_tree.go - Updated isLatestTreeVersion method to ignore the first return value from getLatestVersion.
iterator_test.go - Enhanced error assertions for iterator creation scenarios.
- Updated tests to validate error handling for nil parameters.
migrate_test.go - Updated TestDeleteVersions to ignore the first return value from getLatestVersion.
mutable_tree_test.go - Added new test: TestMutableTree_InitialVersionZero.
- Modified existing tests to reflect new method signatures.
nodedb_test.go - Updated tests to ignore the first return value from getLatestVersion.
tree_random_test.go - Adjusted tests to ignore the first return value from getLatestVersion.
.github/workflows/ci.yml - Added a new step to build the legacydump binary before running tests.
.gitignore - Added entries to ignore various temporary and test-related files and directories.
Makefile - Introduced a new target for building legacydump and updated the test target dependencies.
docs/node/nodedb.md - Updated DeleteVersionsFrom method to ignore the first return value from getLatestVersion.

Possibly related PRs

Suggested reviewers

  • cool-develope
  • tac0turtle

🐰 In the garden where changes bloom,
A tree now knows its initial room.
With versions set and logic tight,
The nodes will dance in pure delight.
So hop along, let’s celebrate,
For better trees, we now await! 🌱


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ec511e4 and fbd5df5.

📒 Files selected for processing (1)
  • mutable_tree.go (10 hunks)
🔇 Additional comments (5)
mutable_tree.go (5)

43-43: LGTM: Clean field addition and initialization

The new initialVersionSet field is properly added to the MutableTree struct and correctly initialized in the constructor through options.

Also applies to: 66-66


458-467: LGTM: Improved version handling logic

The changes enhance error handling by properly checking version existence before proceeding. This aligns with the PR objective of allowing version 0 while maintaining data integrity.


876-876: ⚠️ Potential issue

Add mutex protection for thread safety

Similar to the SaveVersion method, this state modification should be protected by the mutex. Consider wrapping the entire method in a mutex lock.

Apply this diff to add mutex protection:

 func (tree *MutableTree) SetInitialVersion(version uint64) {
+    tree.mtx.Lock()
+    defer tree.mtx.Unlock()
     tree.ndb.opts.InitialVersion = version
     tree.initialVersionSet = true
 }

Likely invalid or redundant comment.


155-157: LGTM: Proper version handling logic

The changes correctly implement the version 0 support by checking initialVersionSet flag before applying the initial version. This is the core change that enables the feature requested in the PR.

✅ Verification successful

Based on the search results, I can now generate a final response:

Version handling validation is consistent across the codebase

The changes in version handling are safe and consistent with the rest of the codebase:

  • All version validations enforce version > 0 through explicit checks (version <= 0)
  • The new initialVersionSet flag properly handles the special case for version 1 without breaking existing version validation rules
  • The version handling in MutableTree maintains proper validation across different operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify version handling consistency
rg -B 2 -A 2 "version.*== 0|version.*<= 0" --type go

Length of output: 834


Script:

#!/bin/bash
# Let's search for version-related validations and initializations
ast-grep --pattern 'if $_ <= 0 { $$$_ }'

# Also search for version-related error messages
rg "version.*must be" --type go -B 2 -A 2

# And check how version is handled in the mutable tree
ast-grep --pattern 'func ($_ *MutableTree) $$$_ version $$$_ { $$$_ }'

Length of output: 4287


711-711: Consider adding mutex protection

The initialVersionSet flag is modified without synchronization. While the method itself isn't meant to be concurrent, it's safer to protect this state change with the existing mtx mutex to prevent any potential race conditions.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
mutable_tree.go (2)

464-468: Improve error handling in LoadVersion

The changes to version comparison logic are correct, but the error handling could be more descriptive:

-if latestVersion >= 0 && latestVersion < targetVersion {
+if latestVersion < targetVersion {
   return latestVersion, fmt.Errorf(
-    "wanted to load target %d but only found up to %d",
+    "cannot load target version %d as latest available version is %d",
     targetVersion, latestVersion)
 }

Line range hint 1-1000: Add documentation for version 0 handling

The changes enable saving version 0, which is a significant behavioral change. This should be documented in the package documentation or in relevant method comments to make it clear that version 0 is now supported.

Add the following documentation to the package:

// Version 0 is now supported as a valid version number. When SetInitialVersion(0)
// is called, the tree will use 0 as the initial version instead of defaulting to 1.
// This is particularly useful for processing Genesis blocks where version 0 represents
// the initial state.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e99f035 and b7bfb3a.

📒 Files selected for processing (2)
  • mutable_tree.go (4 hunks)
  • nodedb.go (1 hunks)
🔇 Additional comments (3)
mutable_tree.go (2)

43-43: LGTM: New field addition is well-placed

The initialVersionSet boolean field is appropriately added to the MutableTree struct to track the initial version setting state.


150-154: Verify the version calculation logic

The modification to WorkingVersion() introduces a special case for version 0, but there are some concerns:

  1. The condition version == 1 && tree.initialVersionSet means we only apply the initial version when the current version is 0 (since WorkingVersion adds 1).
  2. After setting the version, initialVersionSet is reset to false, which means subsequent calls will not use the initial version.

This implementation could lead to inconsistencies if SaveVersion() is called multiple times before committing.

✅ Verification successful

Based on the code analysis, I can now provide a definitive response about the version calculation logic:

The version calculation in WorkingVersion() is correct and safe

The implementation in WorkingVersion() is designed to handle the initial version setting in a specific way:

  1. The initialVersionSet flag is only set once through SetInitialVersion() and is cleared after first use
  2. The special case only applies when version == 1 (i.e., before first save) and initialVersionSet is true
  3. After applying the initial version, initialVersionSet is set to false, ensuring it's only used once
  4. Multiple calls to SaveVersion() are safe as subsequent WorkingVersion() calls will increment normally

The test coverage confirms this behavior:

  • Tests verify correct version progression with initial version setting
  • No test failures related to version inconsistencies with multiple saves
  • The implementation is used extensively throughout the codebase without issues
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential version handling issues in tests
rg -A 5 "SetInitialVersion.*0" 
rg -A 5 "SaveVersion.*WorkingVersion"

Length of output: 72


Script:

#!/bin/bash
# Let's search for version-related tests and implementation
rg -A 10 "func.*WorkingVersion"
rg -A 10 "SaveVersion.*\("
rg -A 10 "initialVersionSet"

Length of output: 89150

nodedb.go (1)

841-841: Ensure all callers of getLatestVersion() handle -1 as the 'no version' indicator

Changing the return value from 0 to -1 in getLatestVersion() when no versions exist may affect functions that rely on this method. It's crucial to verify that all callers properly handle -1 as the indicator of no existing versions to prevent potential errors or unexpected behavior.

Run the following script to identify all usages of getLatestVersion() and ensure they handle the -1 return value correctly:

✅ Verification successful

Let me check how these callers handle the return value.


Let me check how the -1 value is used in the codebase.


Let me check how the return value from getLatestVersion is specifically used in the calling code.


Based on the analysis of the code, I can now provide the final response:

The change from 0 to -1 as 'no version' indicator is safe and properly handled

All callers of getLatestVersion() use the return value in one of these safe ways:

  • Compare it with other versions for range checks (e.g., firstVersion <= version && version <= latestVersion)
  • Error handling through the accompanying error return value
  • Version existence checks that properly handle any non-positive values
  • Test assertions that verify specific version numbers

The change from 0 to -1 as the 'no version' indicator is actually safer as it distinguishes between "no versions exist" and "version 0 exists", making the API more explicit.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all callers of getLatestVersion() handle a return value of -1 appropriately.

# Find all instances where getLatestVersion() is called
rg 'getLatestVersion\(' -t go

# For each occurrence, check if the returned value is compared to -1
# Example: Search for comparisons like 'if version == -1' or proper handling in error cases

Length of output: 1431


Script:

#!/bin/bash
# Get context around each getLatestVersion call to see error handling
rg 'getLatestVersion\(' -t go -B 3 -A 3

Length of output: 7153


Script:

#!/bin/bash
# Search for comparisons with -1 and version checks
rg 'version.*[=<>!].*-1|version.*==.*-1|-1.*==.*version|-1.*[=<>!].*version' -t go

# Also get the implementation of key methods that might handle version numbers
ast-grep --pattern 'func $_($$$) {
  $$$
  if $_ == -1 {
    $$$
  }
  $$$
}'

Length of output: 1199


Script:

#!/bin/bash
# Get more context around the usage of getLatestVersion results
rg 'latestVersion.*:=.*getLatestVersion\(' -B 5 -A 10 -t go

Length of output: 13783

Copy link

@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.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
options.go (1)

90-91: Add documentation for the new field.

The initialVersionSet field serves an important purpose in distinguishing between default and explicitly set initial versions. Please add documentation to explain its role.

 	AsyncPruning bool

+	// initialVersionSet tracks whether InitialVersion was explicitly set.
+	// This helps distinguish between default version 0 and explicitly set version 0.
 	initialVersionSet bool
mutable_tree.go (1)

465-469: Consider improving error message for version 0 case

While the logic correctly handles version 0, the error message when latestVersion < targetVersion might be confusing for version 0. Consider clarifying the error message when the target version is 0.

-    return latestVersion, fmt.Errorf("wanted to load target %d but only found up to %d", targetVersion, latestVersion)
+    return latestVersion, fmt.Errorf("wanted to load target %d but only found versions up to %d", targetVersion, latestVersion)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b7bfb3a and 35f6e94.

📒 Files selected for processing (2)
  • mutable_tree.go (5 hunks)
  • options.go (2 hunks)
🔇 Additional comments (6)
options.go (2)

109-111: LGTM! Implementation correctly tracks explicit version setting.

The modification properly sets the initialVersionSet flag when an initial version is specified, supporting the PR's objective of allowing version 0 to be explicitly set.


Line range hint 90-111: Verify consistent handling of initialVersionSet.

Let's verify that the new flag is consistently checked wherever initial version validation occurs.

✅ Verification successful

Let me gather more specific information about how the initialVersionSet flag is used.


Let me check one final aspect - the SaveVersion method implementation to understand how versions are handled.


Based on the gathered information, I can now provide the final response:

initialVersionSet flag is properly implemented and consistently handled

The verification shows the initialVersionSet flag is correctly implemented with proper safeguards:

  • Flag is consistently propagated from Options to MutableTree
  • Version handling in SaveVersion properly respects the initial version setting
  • Clear logic exists for resetting the flag after initial use
  • Implementation properly handles version=1 case with initial version override

The changes are safe and maintain consistency across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for initial version related code to ensure consistent handling
echo "Searching for initial version checks..."
rg -l "InitialVersion" --type go

echo "\nSearching for version validation logic..."
rg "SetInitialVersion|SaveVersion.*0|version.*== 0" --type go

Length of output: 881


Script:

#!/bin/bash
# Check usage of initialVersionSet flag
echo "Searching for initialVersionSet usage..."
rg "initialVersionSet" --type go -A 3

# Check SaveVersion implementation
echo "\nChecking SaveVersion implementation..."
ast-grep --pattern 'func ($tree *MutableTree) SaveVersion() ([]byte, int64, error) {
  $$$
}'

Length of output: 1198


Script:

#!/bin/bash
# Check SaveVersion and version handling
echo "Checking SaveVersion and version handling..."
rg "func.*SaveVersion" --type go -A 10

# Also check version validation in tests
echo "\nChecking version validation in tests..."
rg "TestSaveVersion|TestInitialVersion" --type go -A 5

Length of output: 3625

mutable_tree.go (4)

43-43: LGTM: Clean addition of version initialization tracking

The addition of initialVersionSet field and its initialization in the constructor provides a clean way to track version initialization state.

Also applies to: 66-66


151-155: LGTM: Proper handling of initial version

The WorkingVersion method now correctly handles the initial version case and properly resets the flag after use, preventing any potential reuse issues.


877-877: ⚠️ Potential issue

Add validation for initial version change

The method should validate that the initial version is not being changed after the tree has been used to prevent potential inconsistencies.

 func (tree *MutableTree) SetInitialVersion(version uint64) {
+    if tree.version != 0 {
+        panic("initial version can only be set on a new tree")
+    }
     tree.ndb.opts.InitialVersion = version
     tree.initialVersionSet = true
 }

Likely invalid or redundant comment.


151-155: Verify version 0 handling across codebase

Let's verify that version 0 handling is consistent across the codebase and that SetInitialVersion is used correctly.

✅ Verification successful

Let me gather more context about the version handling in mutable_tree.go to understand the complete picture.


Based on the gathered information, I can now provide the final response:

Version handling is consistent and correctly implemented

The version handling implementation is consistent across the codebase:

  • Version 0 is properly handled as a special case in MutableTree
  • SetInitialVersion is correctly used to set custom initial versions, with proper state tracking via initialVersionSet
  • Version validation ensures versions are always > 0 (see node.go validation)
  • The version increment logic in WorkingVersion() correctly handles both normal increments and initial version cases
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for SetInitialVersion usage and version 0 handling
echo "Checking SetInitialVersion usage:"
rg "SetInitialVersion\(" -A 5

echo "Checking version 0 handling:"
rg "version.*==.*0|version.*<=.*0|version.*>=.*0" -A 5

Length of output: 2336


Script:

#!/bin/bash
# Check version handling in mutable_tree.go
echo "Checking version handling in mutable_tree.go:"
rg "version.*=|version.*<|version.*>" mutable_tree.go -A 3

echo -e "\nChecking WorkingVersion implementation:"
ast-grep --pattern 'func $_WorkingVersion() $_{ $$$}'

Length of output: 2431

Copy link

@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.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (4)
tree_random_test.go (1)

Line range hint 1-424: Consider adding test coverage for version 0 handling

While the changes correctly adapt to the new getLatestVersion signature, consider adding specific test cases to verify the new functionality of allowing version 0 to be saved. This would help ensure the feature works as intended and prevent regressions.

Would you like me to help generate additional test cases specifically for version 0 handling?

mutable_tree_test.go (1)

1485-1496: LGTM: Good test coverage for version 0 support.

The test effectively verifies the new functionality of saving version 0. However, consider enhancing it with additional assertions:

  1. Verify the saved key-value pair can be retrieved
  2. Test that subsequent versions (1, 2, etc.) work correctly after initial version 0
  3. Add error cases to ensure proper validation

Consider adding these test cases:

// Test retrieval
val, err := tree.Get([]byte("hello"))
require.NoError(t, err)
require.Equal(t, []byte("world"), val)

// Test subsequent versions
_, err = tree.Set([]byte("hello2"), []byte("world2"))
require.NoError(t, err)

_, version, err = tree.SaveVersion()
require.NoError(t, err)
require.Equal(t, int64(1), version)
nodedb.go (2)

308-312: Consider removing commented-out code

The code from lines 309-312 is commented out. If it's no longer needed, consider removing it to keep the codebase clean.


Line range hint 735-744: Ensure proper handling of the boolean return value from getLatestVersion

In getFirstVersion, the boolean return value from getLatestVersion is ignored. Please confirm that this is acceptable and that the function behaves correctly even when the boolean indicates failure.

Consider adjusting the code to handle the boolean value:

+	ok, latestVersion, err := ndb.getLatestVersion()
	if err != nil {
		return 0, err
	}
+	if !ok {
+		return 0, fmt.Errorf("no versions exist in the database")
+	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 35f6e94 and 81b4a52.

📒 Files selected for processing (8)
  • immutable_tree.go (1 hunks)
  • iterator_test.go (2 hunks)
  • migrate_test.go (1 hunks)
  • mutable_tree.go (11 hunks)
  • mutable_tree_test.go (4 hunks)
  • nodedb.go (8 hunks)
  • nodedb_test.go (2 hunks)
  • tree_random_test.go (3 hunks)
🔇 Additional comments (16)
migrate_test.go (2)

180-180: LGTM: Method signature update handled correctly.

The change correctly adapts to the updated getLatestVersion method signature by capturing the additional boolean return value. The test's logic remains intact, properly verifying that the latest version matches the target version after loading for overwriting.


Line range hint 1-391: Consider adding test coverage for saving version 0.

Given that this PR's objective is to allow saving version 0 and this file contains version-related migration tests, it would be beneficial to add test coverage specifically verifying:

  1. The ability to save version 0
  2. The migration behavior when version 0 is involved
  3. The interaction between version 0 and legacy versions

Let's check if there's any existing test coverage for version 0:

Would you like me to help draft the additional test cases?

iterator_test.go (2)

185-187: LGTM: Correct handling of the updated getLatestVersion signature

The code properly handles the new return signature of getLatestVersion() and correctly manages error checking.


256-258: LGTM: Consistent handling of getLatestVersion signature

The code maintains consistency with other changes in handling the updated getLatestVersion() signature.

nodedb_test.go (2)

90-91: LGTM: Successfully adapted to new getLatestVersion signature

The change correctly handles the updated method signature while maintaining the test's original purpose of verifying storage version setting.


407-408: LGTM: Successfully adapted to new getLatestVersion signature

The change correctly handles the updated method signature while maintaining the test's original purpose of verifying deadlock prevention during version deletion.

tree_random_test.go (3)

286-286: LGTM: Correctly updated to handle new getLatestVersion signature

The code properly adapts to the new signature by discarding the boolean success indicator, while maintaining the existing version check logic.


350-350: LGTM: Properly handles updated getLatestVersion signature

The code correctly adapts to the new method signature while maintaining the existing version comparison logic.


367-367: LGTM: Successfully updated to new getLatestVersion signature

The code appropriately handles the new method signature while preserving the existing version comparison functionality.

mutable_tree.go (3)

43-43: LGTM: Field addition and initialization are correct

The new initialVersionSet boolean field is properly added to the MutableTree struct and correctly initialized in the constructor.

Also applies to: 66-66


155-159: LGTM: WorkingVersion logic handles initial version correctly

The logic properly handles setting the initial version when version is 1 and initialVersionSet is true, with appropriate flag reset.


Line range hint 459-473: LGTM: LoadVersion changes improve version validation

The changes enhance version validation by:

  • Properly checking latest version existence
  • Improving initial version validation
  • Adding appropriate error handling for version mismatches
mutable_tree_test.go (3)

772-773: LGTM: Proper persistence check added.

The addition of SaveVersion call ensures that fast storage changes are properly persisted.


912-912: LGTM: Properly updated method call signature.

The change correctly updates the getLatestVersion call to handle its new return signature.


1006-1006: LGTM: Consistent method signature update.

The change maintains consistency with other getLatestVersion call updates.

nodedb.go (1)

Line range hint 805-847: Verify that all return paths in getLatestVersion correctly handle the new boolean return value

The getLatestVersion method now returns a boolean indicating success. Please ensure that all return statements correctly reflect whether a valid latest version was found, and that callers handle the boolean and version values appropriately.

immutable_tree.go Show resolved Hide resolved
@@ -871,6 +879,7 @@ func (tree *MutableTree) saveFastNodeRemovals() error {
// and is otherwise ignored.
func (tree *MutableTree) SetInitialVersion(version uint64) {
tree.ndb.opts.InitialVersion = version
tree.initialVersionSet = true
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider adding mutex protection for thread safety

The SetInitialVersion method modifies shared state (initialVersionSet) without synchronization. Consider protecting this operation with the existing mtx mutex to prevent potential race conditions in concurrent usage scenarios.

Apply this diff to add mutex protection:

 func (tree *MutableTree) SetInitialVersion(version uint64) {
+    tree.mtx.Lock()
+    defer tree.mtx.Unlock()
     tree.ndb.opts.InitialVersion = version
     tree.initialVersionSet = true
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tree.initialVersionSet = true
func (tree *MutableTree) SetInitialVersion(version uint64) {
tree.mtx.Lock()
defer tree.mtx.Unlock()
tree.ndb.opts.InitialVersion = version
tree.initialVersionSet = true
}

mutable_tree.go Outdated Show resolved Hide resolved
nodedb.go Show resolved Hide resolved
nodedb.go Show resolved Hide resolved
nodedb.go Show resolved Hide resolved
nodedb.go Show resolved Hide resolved
Copy link

@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.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
mutable_tree.go (2)

Line range hint 459-614: Consider adding error documentation

The LoadVersion function has been modified to handle version 0, but the changes to error conditions and version handling should be documented in the function's comments to make the behavior clear to callers.

-// Returns the version number of the specific version found
+// Returns the version number of the specific version found.
+// If targetVersion is 0 or negative, it loads the latest version.
+// Returns ErrVersionDoesNotExist if the version does not exist,
+// or if there are no versions and targetVersion is positive.
 func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) {

155-159: Consider documenting version 0 behavior change

The changes to allow version 0 represent a significant behavioral change that could affect systems interacting with the IAVL tree. Consider:

  1. Adding documentation about version 0 support in the package documentation
  2. Updating integration tests to cover version 0 scenarios
  3. Adding a migration guide for systems that might assume versions start from 1

This change might require coordination with dependent systems to ensure they handle version 0 correctly.

Also applies to: 881-881

nodedb.go (2)

Line range hint 334-342: Handle case where no versions exist

The boolean return value from getLatestVersion is ignored. When no versions exist, the function continues with version 0, which could lead to unexpected behavior.

-    _, latestVersion, err := ndb.getLatestVersion()
+    ok, latestVersion, err := ndb.getLatestVersion()
     if err != nil {
         // TODO: should be true or false as default? (removed panic here)
         return false, err
     }
+    if !ok {
+        return false, nil
+    }
     if versions[1] != strconv.Itoa(int(latestVersion)) {
         return true, nil
     }

Line range hint 730-742: Handle case where no versions exist in getFirstVersion

The boolean return value from getLatestVersion is ignored. This could lead to incorrect behavior when determining the first version in an empty database.

-    _, latestVersion, err := ndb.getLatestVersion()
+    ok, latestVersion, err := ndb.getLatestVersion()
     if err != nil {
         return 0, err
     }
+    if !ok {
+        return 0, nil
+    }
     for firstVersion < latestVersion {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 81b4a52 and f37eaef.

📒 Files selected for processing (2)
  • mutable_tree.go (9 hunks)
  • nodedb.go (7 hunks)
🔇 Additional comments (5)
mutable_tree.go (4)

43-43: LGTM: New field addition

The initialVersionSet boolean field is appropriately added to track the initial version setting state.


66-66: LGTM: Field initialization

The initialVersionSet field is correctly initialized from options in the constructor.


881-881: ⚠️ Potential issue

Add thread safety protection

The SetInitialVersion method modifies shared state (initialVersionSet) without synchronization. This could lead to race conditions in concurrent usage scenarios.

Apply this diff to add mutex protection:

 func (tree *MutableTree) SetInitialVersion(version uint64) {
+    tree.mtx.Lock()
+    defer tree.mtx.Unlock()
     tree.ndb.opts.InitialVersion = version
     tree.initialVersionSet = true
 }

155-159: Verify the version handling logic

The changes to WorkingVersion() look correct, allowing version 0 to be used when initialVersionSet is true. However, we should verify that this change doesn't affect existing version handling in the codebase.

✅ Verification successful

Based on the analysis of the version handling in the codebase, I can now generate the final response:

Version handling change is safe and well-contained

The change to WorkingVersion() is safe because:

  • The version 0 handling is only used when initialVersionSet is true, which is a controlled state
  • All version comparisons in the codebase handle version 0 appropriately:
    • Version 0 checks are rare and mostly defensive (e.g., node.go:474 checks version <= 0)
    • Most version comparisons are for range checks or version tracking
    • No assumptions about version numbers being strictly positive
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any hardcoded version comparisons that might be affected
# by allowing version 0

# Look for version comparisons that might need adjustment
rg -n "version [><=]" --type go

# Look for explicit version 0 checks
rg -n "version.*== 0" --type go
rg -n "version.*!= 0" --type go

Length of output: 5490


Script:

#!/bin/bash
# Let's analyze the version handling in mutable_tree.go more thoroughly
# to understand the impact of allowing version 0

# Check for version initialization and assignments
ast-grep --pattern 'version = $_'

# Check for WorkingVersion method implementation
ast-grep --pattern 'func (tree *$_) WorkingVersion() int64 {
  $$$
}'

# Look for tests related to version handling
rg -n "TestWork.*Version|Test.*Version.*" --type go

# Check for any version-related constants or special values
rg -n "const.*[Vv]ersion|[Vv]ersion.*=" --type go

Length of output: 42414

nodedb.go (1)

Line range hint 800-842: LGTM: Improved version handling with explicit success indicator

The change to return a boolean success indicator along with the version is a good improvement. It makes the API more explicit about whether a version was found, rather than relying on potentially ambiguous version numbers.

Copy link
Member

@tac0turtle tac0turtle left a comment

Choose a reason for hiding this comment

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

utACK

@tac0turtle
Copy link
Member

this needs to be backported to v1.3

Copy link

@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.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (4)
.github/workflows/ci.yml (1)

Line range hint 13-13: Invalid Go version specified

The specified Go version "1.23" does not exist yet (latest stable version is 1.21). This will cause the workflow to fail.

- go-version: "1.23" # The Go version to download (if necessary) and use.
+ go-version: "1.21" # The Go version to download (if necessary) and use.
Makefile (1)

27-29: Add clean target for legacydump binary.

The new target correctly builds the legacydump binary. However, to maintain a clean repository state, we should add a clean target to remove the generated binary.

Add the following clean target:

+clean:
+	rm -f cmd/legacydump/legacydump
+.PHONY: clean
docs/node/nodedb.md (1)

22-22: Documentation should explain the new return value.

The getLatestVersion method signature has been updated to include a boolean return value, but its purpose isn't documented. Consider adding a comment explaining what this boolean indicates for better API documentation.

Example addition:

// DeleteVersionsFrom permanently deletes all tree versions from the given version upwards.
func (ndb *nodeDB) DeleteVersionsFrom(fromVersion int64) error {
+	// getLatestVersion returns (exists bool, version int64, err error)
	_, latest, err := ndb.getLatestVersion()
🧰 Tools
🪛 Markdownlint

22-22: Column: 1
Hard tabs

(MD010, no-hard-tabs)

mutable_tree.go (1)

43-43: Add documentation for the new field.

Consider adding a comment explaining the purpose of initialVersionSet field and its relationship with version management.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f37eaef and ec511e4.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml (1 hunks)
  • .gitignore (1 hunks)
  • Makefile (1 hunks)
  • docs/node/nodedb.md (1 hunks)
  • mutable_tree.go (10 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🧰 Additional context used
🪛 Markdownlint
docs/node/nodedb.md

22-22: Column: 1
Hard tabs

(MD010, no-hard-tabs)

🔇 Additional comments (5)
docs/node/nodedb.md (1)

22-22: LGTM: Formatting is correct for Go code examples.

The indentation using tabs is appropriate for Go code examples in documentation.

🧰 Tools
🪛 Markdownlint

22-22: Column: 1
Hard tabs

(MD010, no-hard-tabs)

mutable_tree.go (4)

155-158: LGTM: Version handling logic is correct.

The modification properly handles the initial version setting while maintaining backward compatibility.


881-881: Previous thread safety comment is still applicable.


716-716: Verify initialVersionSet reset behavior.

The reset of initialVersionSet looks correct, but we should verify that this doesn't affect subsequent operations when the same initial version needs to be reused.

#!/bin/bash
# Search for tests that verify initial version behavior
rg -n "SetInitialVersion.*SaveVersion" --type go -B 2 -A 2

458-461: Verify version handling consistency across the codebase.

The changes to version handling look correct, but we should verify that other parts of the codebase handle version 0 correctly.

Also applies to: 471-474

✅ Verification successful

Let's search for version-related patterns more broadly to understand how versions are handled across the codebase.


Based on the extensive code analysis, I can now provide a definitive response regarding version handling consistency:

Version handling is consistent and safe across the codebase

The codebase shows consistent and careful handling of versions through:

  1. Proper version validation before operations:

    • Version existence checks before loading/accessing versions
    • Proper error handling for non-existent versions
    • Validation of version ranges during deletion operations
  2. Atomic version management:

    • Version 0 has special handling as an initial state
    • Version increments are handled consistently during saves
    • Version tracking is maintained properly across tree operations
  3. Safe version deletion:

    • Checks for active version readers before deletion
    • Proper cleanup of orphaned nodes
    • Maintains version integrity during deletion operations

The changes to handle version 0 in the reviewed code segment align with the existing version handling patterns throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for version comparisons and validations
rg -n "version [<=>]= 0" --type go -B 2 -A 2

# Search for explicit version 0 checks
rg -n "version == 0" --type go -B 2 -A 2

Length of output: 193


Script:

#!/bin/bash
# Search for version handling patterns with broader context
rg -n "Version" --type go -B 3 -A 3

# Search for version-related function calls and checks
ast-grep --pattern 'if $_ == 0 { $$$ }'

# Search for version validation in function signatures
ast-grep --pattern 'func $_($_, version int64) $_'

Length of output: 310128

@@ -25,6 +25,7 @@ jobs:
# integer overflow).
- name: test & coverage report creation
run: |
cd cmd/legacydump && go build -o legacydump main.go && cd ../..
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve the binary build step

The current approach of changing directories to build the binary has several issues:

  1. Could leave the working directory in wrong state if build fails
  2. Creates the binary in the source directory
  3. Uses multiple commands where one would suffice
- cd cmd/legacydump && go build -o legacydump main.go && cd ../..
+ go build -o /tmp/legacydump ./cmd/legacydump

Additionally, consider adding a cleanup step in a post block to ensure the binary is removed after tests:

    post:
      - name: Cleanup
        if: always()
        run: rm -f /tmp/legacydump

Comment on lines +30 to +32
test: cmd/legacydump/legacydump
@echo "--> Running go test"
@go test ./... $(LDFLAGS) -v
@go test ./... $(LDFLAGS)
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Removing --short flag will significantly impact test execution time

The codebase contains several time-intensive tests that are specifically skipped in short mode:

  • Concurrent operation tests in mutable_tree_test.go that are explicitly skipped in short mode
  • Large-scale tests with significant iterations:
    • Tests with 1000 random key/value entries per version
    • Performance test with 1,000,000 iterations
    • Batch tests with 10MB of data (1000 entries of 10KB each)
    • Basic tests with 10,000 iterations

Removing the --short flag will cause these tests to run, substantially increasing the CI pipeline duration. Consider keeping a separate make target for comprehensive testing (current test target) while maintaining the test-short target for faster CI runs.

🔗 Analysis chain

Verify test execution time impact.

The removal of the --short flag from go test means all tests will run, including long-running ones. While this provides better coverage, it might significantly increase the CI/CD pipeline duration.

Let's check the test execution time impact:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Compare test execution time with and without --short flag

echo "Running tests with --short flag..."
time go test ./... --short >/dev/null

echo -e "\nRunning tests without --short flag..."
time go test ./... >/dev/null

Length of output: 433


Script:

#!/bin/bash
# Let's check for test files and analyze their content for long-running tests
# Look for test files with potential time-consuming operations

# Search for test files containing sleep, timeout, or other time-related patterns
echo "=== Test files with potential long-running operations ==="
rg -l --type go "time\.(Sleep|After|Tick)|context\.WithTimeout" ".*_test\.go$"

echo -e "\n=== Test files with skip conditions based on short flag ==="
rg -l --type go "testing\.Short\(\)" ".*_test\.go$"

echo -e "\n=== Detailed context of tests using Short() flag ==="
rg -B2 -A2 --type go "testing\.Short\(\)" ".*_test\.go$"

Length of output: 733


Script:

#!/bin/bash
# Let's try a different approach to find test files
echo "=== Finding Go test files ==="
fd "_test\.go$"

echo -e "\n=== Checking Makefile history for test-related changes ==="
git log -p -- Makefile | grep -A 5 -B 5 "go test"

Length of output: 3363


Script:

#!/bin/bash
# Let's analyze the test files for time-consuming operations and short flag usage
echo "=== Checking test files for time-consuming operations ==="
rg -C3 "time\.(Sleep|After|Tick)|context\.WithTimeout" basic_test.go batch_test.go tree_test.go mutable_tree_test.go

echo -e "\n=== Checking test files for testing.Short() usage ==="
rg -C3 "testing\.Short\(\)" basic_test.go batch_test.go tree_test.go mutable_tree_test.go

echo -e "\n=== Checking for large test data or iterations ==="
rg -C3 "for.+range|for.+:=.+;.+;" basic_test.go batch_test.go tree_test.go mutable_tree_test.go | rg "1000|10000"

Length of output: 2610

@tac0turtle tac0turtle merged commit 256952e into master Nov 4, 2024
7 of 8 checks passed
@tac0turtle tac0turtle deleted the kocu/allow-version0 branch November 4, 2024 13:45
@tac0turtle
Copy link
Member

@Mergifyio backport release/v1.3.x

Copy link
Contributor

mergify bot commented Nov 4, 2024

backport release/v1.3.x

✅ Backports have been created

mergify bot pushed a commit that referenced this pull request Nov 4, 2024
tac0turtle pushed a commit that referenced this pull request Nov 4, 2024
Co-authored-by: Matt Kocubinski <mkocubinski@gmail.com>
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.

3 participants