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

Add migration function for stateful order expirations #1890

Merged
merged 4 commits into from
Jul 18, 2024

Conversation

roy-dydx
Copy link
Contributor

@roy-dydx roy-dydx commented Jul 10, 2024

Changelist

  • Add a migration to the new stateful order format. Use it in the upgrade handler when it is available.

Test Plan

  • Added test

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 the PR has breaking postgres changes to the indexer add the indexer-postgres-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.

Summary by CodeRabbit

  • New Features

    • Added functionality to migrate order expiration states from slices to individual keys.
    • Introduced a deprecated function to sort and set order IDs in state for testing purposes.
  • Bug Fixes

    • Ensured iterators are properly closed in the RemoveExpiredStatefulOrders function to prevent resource leaks.
  • Tests

    • Added tests to validate the migration of order expiration states and the handling of multiple time slices.

Copy link
Contributor

coderabbitai bot commented Jul 10, 2024

Walkthrough

The changes introduce functions for migrating order expiration state from time-based slices to individual keys within the protocol's clob keeper. These updates ensure better state management and include improvements like closing iterators to avoid resource leaks. Additionally, tests have been introduced to validate these migrations, ensuring that the new functionality works correctly under various scenarios.

Changes

File Path Change Summary
protocol/x/clob/keeper/stateful_order_state.go Added a defer it.Close() statement; introduced UnsafeMigrateOrderExpirationState and LegacySetStatefulOrdersTimeSliceInState.
protocol/x/clob/keeper/stateful_order_state_test.go Added tests TestMigrateOrderExpirationState and TestUnsafeMigrateOrderExpirationState.
protocol/x/clob/keeper/migration.go Introduced functions for migrating order expiration state and setting sorted order IDs in state.
protocol/x/clob/keeper/migration_test.go Added tests for validating the UnsafeMigrateOrderExpirationState function.
protocol/x/clob/keeper/stateful_order_expiration_migration.go Added UnsafeMigrateOrderExpirationState and LegacySetStatefulOrdersTimeSliceInState functions.
protocol/x/clob/keeper/stateful_order_expiration_migration_test.go Added tests for UnsafeMigrateOrderExpirationState function in time-sensitive context.

Poem

In the code's vast digital space,
Orders now find a new place,
From time-bound slices they move,
To keys that improve the groove.
Tests ensure they stay in grace,
Code shines with a cleaner face.
🌟🐇🚀


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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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 as 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.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

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

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between db2dcfd and 7d01d55.

Files selected for processing (2)
  • protocol/x/clob/keeper/stateful_order_state.go (2 hunks)
  • protocol/x/clob/keeper/stateful_order_state_test.go (1 hunks)
Additional comments not posted (4)
protocol/x/clob/keeper/stateful_order_state.go (3)

233-233: Approved: Proper resource management with defer it.Close().

The addition of defer it.Close() ensures that the iterator is properly closed, preventing potential resource leaks.


459-478: Approved: New migration function MigrateOrderExpirationState.

The MigrateOrderExpirationState function correctly migrates order expiration state from time slices to individual keys. Proper resource management and error handling are included.


480-503: Approved: New deprecated function LegacySetStatefulOrdersTimeSliceInState.

The LegacySetStatefulOrdersTimeSliceInState function correctly sets sorted order IDs in the legacy format. This function is intended for testing the migration function.

protocol/x/clob/keeper/stateful_order_state_test.go (1)

1076-1130: Approved: New test function TestMigrateOrderExpirationState.

The TestMigrateOrderExpirationState function correctly verifies the migration of order expiration state. It sets up different time slices, performs the migration, and ensures the expected order IDs match the actual ones. The test also ensures the old store is empty after migration.

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.

Actionable comments posted: 1

Outside diff range, codebase verification and nitpick comments (1)
protocol/x/clob/keeper/stateful_order_state_test.go (1)

1076-1076: Add a function-level comment.

Add a comment describing the purpose of this test function.

// TestMigrateOrderExpirationState verifies the migration of order expiration states.
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 7d01d55 and 29acd69.

Files selected for processing (2)
  • protocol/x/clob/keeper/stateful_order_state.go (2 hunks)
  • protocol/x/clob/keeper/stateful_order_state_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • protocol/x/clob/keeper/stateful_order_state.go
Additional comments not posted (2)
protocol/x/clob/keeper/stateful_order_state_test.go (2)

1103-1103: Check the context initialization.

Ensure that keepertest.NewClobKeepersTestContext properly initializes the context for the test.


1109-1109: Ensure successful migration.

Verify that MigrateOrderExpirationState successfully migrates the order expiration states.

Comment on lines 1115 to 1117
it := oldStore.Iterator(nil, nil)
require.False(t, it.Valid())
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 11, 2024

Choose a reason for hiding this comment

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

Close the iterator.

Ensure that the iterator is properly closed to prevent resource leaks.

-  require.False(t, it.Valid())
+  require.False(t, it.Valid())
+  it.Close()
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
it := oldStore.Iterator(nil, nil)
require.False(t, it.Valid())
it := oldStore.Iterator(nil, nil)
require.False(t, it.Valid())
it.Close()

Copy link
Contributor

@teddyding teddyding Jul 11, 2024

Choose a reason for hiding this comment

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

Nit: Close the iterator as suggested

Copy link
Contributor

Choose a reason for hiding this comment

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

@teddyding, thanks for confirming the change to close the iterator.

require.False(t, it.Valid())

for goodTilTime, expectedOrderIds := range tc.timeSlicesToOrderIds {
orderIds := ks.ClobKeeper.GetStatefulOrderIdExpirations(ks.Ctx, goodTilTime)
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, what's the ordering on this returned orderId list?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In sorted key order since it's done by store iteration. Ends up being sorted by serialized order id which can get weird since it's a serialized proto. But order doesn't matter for us here and we plan to migrate orderId.ToStateKey() in the future.

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.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 29acd69 and c88bbc8.

Files selected for processing (2)
  • protocol/x/clob/keeper/stateful_order_state.go (2 hunks)
  • protocol/x/clob/keeper/stateful_order_state_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • protocol/x/clob/keeper/stateful_order_state_test.go
Additional comments not posted (2)
protocol/x/clob/keeper/stateful_order_state.go (2)

233-233: Good practice: Properly closing the iterator.

The addition of defer it.Close() ensures that the iterator is properly closed, preventing potential resource leaks. This is a good practice.


484-507: Approved: Deprecated function for testing migration.

The LegacySetStatefulOrdersTimeSliceInState function correctly sorts the order IDs before writing them to state and is appropriately marked as deprecated for testing purposes.

Comment on lines 459 to 482
// MigrateOrderExpirationState migrates order expiration state from slices based on time to individual keys.
// Deprecated: Only intended for use in the v5.2 upgrade handler.
func (k Keeper) MigrateOrderExpirationState(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)

prefixStore := prefix.NewStore(
store,
[]byte(types.LegacyStatefulOrdersTimeSlicePrefix), //nolint:staticcheck
)
it := prefixStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
time, err := sdk.ParseTimeBytes(it.Key())
if err != nil {
panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
}
var orders types.StatefulOrderTimeSliceValue
k.cdc.MustUnmarshal(it.Value(), &orders)
for _, orderId := range orders.OrderIds {
k.AddStatefulOrderIdExpiration(ctx, time, orderId)
}
prefixStore.Delete(it.Key())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Approved with a suggestion: Handle panics for better traceability.

The MigrateOrderExpirationState function properly migrates order expiration state and ensures data integrity by panicking on malformed times. However, for better traceability, consider catching the panic and logging the error before re-panicking.

for ; it.Valid(); it.Next() {
	time, err := sdk.ParseTimeBytes(it.Key())
	if err != nil {
		log.Error("Migration failed due to malformed time", "time", it.Key(), "error", err)
		panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
	}
	// existing code
}

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.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between c88bbc8 and ce1fc58.

Files selected for processing (2)
  • protocol/x/clob/keeper/stateful_order_state.go (2 hunks)
  • protocol/x/clob/keeper/stateful_order_state_test.go (1 hunks)
Additional comments not posted (2)
protocol/x/clob/keeper/stateful_order_state.go (1)

485-508: LGTM!

The function correctly sorts the order IDs before storing them in state.

protocol/x/clob/keeper/stateful_order_state_test.go (1)

1076-1131: LGTM!

The test function comprehensively verifies the migration of order expiration state.

Comment on lines 459 to 483
// UnsafeMigrateOrderExpirationState migrates order expiration state from slices based on time to
// individual keys.
// Deprecated: Only intended for use in the v5.2 upgrade handler.
func (k Keeper) UnsafeMigrateOrderExpirationState(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)

prefixStore := prefix.NewStore(
store,
[]byte(types.LegacyStatefulOrdersTimeSlicePrefix), //nolint:staticcheck
)
it := prefixStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
time, err := sdk.ParseTimeBytes(it.Key())
if err != nil {
panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
}
var orders types.StatefulOrderTimeSliceValue
k.cdc.MustUnmarshal(it.Value(), &orders)
for _, orderId := range orders.OrderIds {
k.AddStatefulOrderIdExpiration(ctx, time, orderId)
}
prefixStore.Delete(it.Key())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add logging before panicking for better traceability.

Consider logging the error before panicking to improve traceability during migration.

for ; it.Valid(); it.Next() {
	time, err := sdk.ParseTimeBytes(it.Key())
	if err != nil {
		log.Error("Migration failed due to malformed time", "time", it.Key(), "error", err)
		panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
	}
	// existing code
}

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.

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between ce1fc58 and 4f4539c.

Files selected for processing (3)
  • protocol/x/clob/keeper/migration.go (1 hunks)
  • protocol/x/clob/keeper/migration_test.go (1 hunks)
  • protocol/x/clob/keeper/stateful_order_state.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • protocol/x/clob/keeper/stateful_order_state.go
Additional comments not posted (3)
protocol/x/clob/keeper/migration.go (2)

12-36: LGTM! The function is well-structured and handles errors appropriately.

The UnsafeMigrateOrderExpirationState function migrates order expiration state from slices to individual keys. The use of a defer statement ensures the iterator is closed properly, and error handling is appropriately managed.


38-61: LGTM! The function is well-structured and ensures data integrity.

The LegacySetStatefulOrdersTimeSliceInState function sorts the order IDs and ensures there are no duplicates before storing them. The function is marked as deprecated and is only intended for testing purposes.

protocol/x/clob/keeper/migration_test.go (1)

16-71: LGTM! The test function is well-structured and covers different scenarios.

The TestUnsafeMigrateOrderExpirationState function sets up the initial state, runs the migration, and verifies the results. It covers different scenarios, including multiple time slices and no time slices, ensuring comprehensive test coverage.

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.

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 4f4539c and 3d1ea87.

Files selected for processing (2)
  • protocol/x/clob/keeper/stateful_order_expiration_migration.go (1 hunks)
  • protocol/x/clob/keeper/stateful_order_expiration_migration_test.go (1 hunks)
Additional comments not posted (5)
protocol/x/clob/keeper/stateful_order_expiration_migration.go (2)

15-35: LGTM!

The function is well-structured and closes the iterator to avoid resource leaks.


41-61: LGTM!

The function is well-structured and correctly performs the necessary operations.

protocol/x/clob/keeper/stateful_order_expiration_migration_test.go (3)

20-34: LGTM!

The test case is comprehensive and correctly validates the migration functionality with multiple time slices.


35-37: LGTM!

The test case is comprehensive and correctly validates the migration functionality with no time slices.


16-70: LGTM!

The test cases are well-structured and correctly validate the migration functionality.

Comment on lines +15 to +35
func (k Keeper) UnsafeMigrateOrderExpirationState(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)

prefixStore := prefix.NewStore(
store,
[]byte(types.LegacyStatefulOrdersTimeSlicePrefix), //nolint:staticcheck
)
it := prefixStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
time, err := sdk.ParseTimeBytes(it.Key())
if err != nil {
panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
}
var orders types.StatefulOrderTimeSliceValue
k.cdc.MustUnmarshal(it.Value(), &orders)
for _, orderId := range orders.OrderIds {
k.AddStatefulOrderIdExpiration(ctx, time, orderId)
}
prefixStore.Delete(it.Key())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider replacing panic with proper error handling.

Using panic for error handling can cause the entire application to terminate abruptly. Consider returning an error or using a more graceful error-handling mechanism.

-  if err != nil {
-    panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
-  }
+  if err != nil {
+    return fmt.Errorf("migration failed due to malformed time: %s", it.Key())
+  }
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
func (k Keeper) UnsafeMigrateOrderExpirationState(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
prefixStore := prefix.NewStore(
store,
[]byte(types.LegacyStatefulOrdersTimeSlicePrefix), //nolint:staticcheck
)
it := prefixStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
time, err := sdk.ParseTimeBytes(it.Key())
if err != nil {
panic(fmt.Sprintf("migration failed due to malformed time: %s", it.Key()))
}
var orders types.StatefulOrderTimeSliceValue
k.cdc.MustUnmarshal(it.Value(), &orders)
for _, orderId := range orders.OrderIds {
k.AddStatefulOrderIdExpiration(ctx, time, orderId)
}
prefixStore.Delete(it.Key())
}
func (k Keeper) UnsafeMigrateOrderExpirationState(ctx sdk.Context) error {
store := ctx.KVStore(k.storeKey)
prefixStore := prefix.NewStore(
store,
[]byte(types.LegacyStatefulOrdersTimeSlicePrefix), //nolint:staticcheck
)
it := prefixStore.Iterator(nil, nil)
defer it.Close()
for ; it.Valid(); it.Next() {
time, err := sdk.ParseTimeBytes(it.Key())
if err != nil {
return fmt.Errorf("migration failed due to malformed time: %s", it.Key())
}
var orders types.StatefulOrderTimeSliceValue
k.cdc.MustUnmarshal(it.Value(), &orders)
for _, orderId := range orders.OrderIds {
k.AddStatefulOrderIdExpiration(ctx, time, orderId)
}
prefixStore.Delete(it.Key())
}
return nil
}

@tqin7 tqin7 merged commit 6da7590 into main Jul 18, 2024
17 checks passed
@tqin7 tqin7 deleted the roy/statefulexpmigration branch July 18, 2024 19:31
mergify bot pushed a commit that referenced this pull request Jul 18, 2024
Co-authored-by: Tian Qin <tian@dydx.exchange>
(cherry picked from commit 6da7590)
tqin7 pushed a commit that referenced this pull request Jul 18, 2024
#1945)

Co-authored-by: roy-dydx <133032749+roy-dydx@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants