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

fix(warden): (re)introduce prophet #1163

Merged
merged 2 commits into from
Jan 15, 2025
Merged

fix(warden): (re)introduce prophet #1163

merged 2 commits into from
Jan 15, 2025

Conversation

Pitasi
Copy link
Contributor

@Pitasi Pitasi commented Jan 13, 2025

Reintroduces #1141 that was bugged in scenarios with 2+ validators.

The fix is at 02a2392. The vemanager already unwraps the vemanager.VoteExtensions before invoking Slinky's VerifyVoteExtension, but Slinky's VerifyVoteExtension was using the WardenSlinkyCodec that's trying to unwrap vemanager.VoteExtensions again.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced the Prophet package, a task runner for Futures and Votes
    • Added deduplication mechanism for request handling
    • Implemented asynchronous task management with vote extension support
    • Created a vote extension manager for aggregating multiple extensions
  • Dependency Updates

    • Updated golang-lru library to version 2.0.7
    • Replaced older version of LRU cache with new implementation
  • Improvements

    • Enhanced error handling and logging in async transaction processing
    • Added support for filtering and managing pending futures
    • Implemented robust vote extension verification mechanisms
  • Testing

    • Added unit tests for transaction trimming and injection functions
    • Introduced mock handler for testing asynchronous operations

The vemanager already takes care of unwrapping the vote extension before
calling the verify vote extension handler.
@Pitasi Pitasi requested a review from mn13 January 13, 2025 10:30
Copy link
Contributor

coderabbitai bot commented Jan 13, 2025

📝 Walkthrough

Walkthrough

The pull request introduces a comprehensive implementation of the prophet package, a task runner for futures and votes in the Warden Protocol. It establishes a robust asynchronous computation framework with deduplication, handler registration, and execution mechanisms. The changes span multiple components, including core package implementation, protocol buffer definitions, and integration with the Warden application's async module. The implementation supports task execution, verification, and vote extension handling across different system components.

Changes

File/Path Change Summary
CHANGELOG.md Added first version of prophet module with task runner for Futures and Votes
go.mod Updated LRU library dependency from v1.0.2 to v2.0.7
prophet/* New package implementing task runner with deduplication, handler management, and execution logic
proto/warden/async/v1beta1/ve.proto Added message types for async transactions and vote extensions
proto/warden/vemanager/vemanager.proto Introduced VoteExtensions message for handling multiple byte arrays
warden/app/app.go Integrated prophet package, registered echo handler
warden/x/async/keeper/* Enhanced async module with ABCI handlers, future management, and vote extension processing

Sequence Diagram

sequenceDiagram
    participant Client
    participant Prophet
    participant Registry
    participant Handler
    participant Executor

    Client->>Prophet: AddFuture
    Prophet->>Registry: GetHandler
    Registry-->>Prophet: Return Handler
    Prophet->>Executor: Execute Future
    Executor->>Handler: Execute
    Handler-->>Executor: Return Result
    Executor-->>Prophet: Completed Future
    Prophet->>Client: Future Result Available
Loading

This sequence diagram illustrates the high-level flow of future execution within the prophet package, showing how futures are added, processed, and completed through the system's components.

Finishing Touches

  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

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: 9

🧹 Nitpick comments (27)
warden/app/vemanager/vemanager.go (5)

30-31: Correct grammatical error in comment

The comment contains a grammatical error. Replace "it's" with "is" for clarity.

Apply this diff to correct the comment:

-// CONTRACT: changing the order of registration it's a consensus breaking
+// CONTRACT: changing the order of registration is a consensus breaking

58-61: Add context to marshaling error

When w.Marshal() fails, returning the error directly might make debugging difficult. Consider adding context to the error message.

Apply this diff to enhance the error handling:

-    return nil, err
+    return nil, fmt.Errorf("failed to marshal VoteExtensions: %w", err)

17-21: Use plural naming for handler slices

For clarity, consider renaming the handler slice fields to their plural forms to indicate that they contain multiple handlers.

Apply this diff to rename the fields:

 type VoteExtensionManager struct {
-    extendVoteHandler          []sdk.ExtendVoteHandler
-    verifyVoteExtensionHandler []sdk.VerifyVoteExtensionHandler
-    prepareProposalHandler     []sdk.PrepareProposalHandler
+    extendVoteHandlers          []sdk.ExtendVoteHandler
+    verifyVoteExtensionHandlers []sdk.VerifyVoteExtensionHandler
+    prepareProposalHandlers     []sdk.PrepareProposalHandler
 }

Ensure that all references to these fields are updated accordingly throughout the code.


14-16: Enhance the documentation for VoteExtensionManager

Consider providing more detailed documentation for the VoteExtensionManager struct to explain its role and how it affects consensus.

Expanding the comment will improve maintainability and help other developers understand the importance of this component.


7-12: Group imports according to standard conventions

To improve readability, group the import statements into standard library packages, third-party packages, and project-specific packages.

Apply this diff to reorganize the imports:

 import (
-    fmt "fmt"
-
-    cometabci "github.com/cometbft/cometbft/abci/types"
-    sdk "github.com/cosmos/cosmos-sdk/types"
+    "fmt"
+
+    sdk "github.com/cosmos/cosmos-sdk/types"
+    cometabci "github.com/cometbft/cometbft/abci/types"
 )
prophet/handlers.go (4)

23-26: Use descriptive variable names for clarity

Consider renaming the variable s to handler to improve code readability and adhere to the naming conventions recommended in the Uber Go Style Guide.

Apply this diff to rename the variable:

 func Execute(ctx context.Context, f Future) (FutureResult, error) {
-	s := getHandler(f.Handler)
+	handler := getHandler(f.Handler)
 	if handler == nil {
-		return FutureResult{}, fmt.Errorf("no future handler registered for %s", f.Handler)
+		return FutureResult{}, fmt.Errorf("no future handler registered for %s", f.Handler)
 	}

28-28: Avoid shadowing the log variable

The declaration log := slog.With(...) shadows the package-level log import. To prevent confusion and enhance clarity, consider renaming the local variable to something like logger.

Apply this diff to rename the variable:

-	log := slog.With("task", "Execute", "future", f.ID, "handler", f.Handler)
+	logger := slog.With("task", "Execute", "future", f.ID, "handler", f.Handler)

47-50: Use descriptive variable names for clarity

Similar to the previous suggestion, rename the variable s to handler in the Verify function for better readability.

Apply this diff to rename the variable:

 func Verify(ctx context.Context, f FutureResult) error {
-	s := getHandler(f.Handler)
+	handler := getHandler(f.Handler)
 	if handler == nil {
-		return fmt.Errorf("no future handler registered for %s", f.Handler)
+		return fmt.Errorf("no future handler registered for %s", f.Handler)
 	}

52-52: Avoid shadowing the log variable

The declaration log := slog.With(...) shadows the package-level log import. Renaming the local variable will prevent confusion.

Apply this diff:

-	log := slog.With("task", "Verify", "future", f.ID, "handler", f.Handler)
+	logger := slog.With("task", "Verify", "future", f.ID, "handler", f.Handler)
prophet/exec.go (4)

17-38: Pass context to ExecFutures for proper cancellation and timeout handling

Currently, context.TODO() is used inside the goroutine, which is intended for code that needs to be updated. It's recommended to pass a context.Context to ExecFutures and use it within the goroutine to handle cancellations and timeouts appropriately.

Apply this diff to modify the function signature and usage:

-func ExecFutures(r FutureReader, w FutureResultWriter) error {
+func ExecFutures(ctx context.Context, r FutureReader, w FutureResultWriter) error {

 	log := slog.With("process", "exec_futures")

 	go func() {
 		for future := range r.Read() {
-			log := log.With("future", future.ID)
+			logger := log.With("future", future.ID)

-			log.Debug("running future")
-			output, err := Execute(context.TODO(), future)
+			logger.Debug("running future")
+			output, err := Execute(ctx, future)
 			if err != nil {
-				log.Error("failed to run future", "err", err)
+				logger.Error("failed to run future", "err", err)
 				continue
 			}
 			err = w.Write(output)
 			if err != nil {
-				log.Error("failed to write future result", "err", err)
+				logger.Error("failed to write future result", "err", err)
 				continue
 			}
 		}
 	}()

Ensure to update any calls to ExecFutures to pass the appropriate context.


50-69: Pass context to ExecVotes for proper cancellation and timeout handling

Similar to the previous suggestion, modify ExecVotes to accept a context.Context and use it within the goroutine instead of context.TODO().

Apply this diff:

-func ExecVotes(r FutureResultReader, w VoteWriter) error {
+func ExecVotes(ctx context.Context, r FutureResultReader, w VoteWriter) error {

 	log := slog.With("process", "exec_votes")

 	go func() {
 		for proposal := range r.Read() {
-			plog := log.With("future", proposal.ID)
+			logger := log.With("future", proposal.ID)

-			plog.Debug("verifying future proposal")
-			err := Verify(context.TODO(), proposal)
+			logger.Debug("verifying future proposal")
+			err := Verify(ctx, proposal)
 			if err := w.Write(Vote{
 				ID:  proposal.ID,
 				Err: err,
 			}); err != nil {
-				plog.Error("failed to write vote", "err", err)
+				logger.Error("failed to write vote", "err", err)
 				continue
 			}
 		}
 	}()

Update any calls to ExecVotes to pass the appropriate context.


25-26: Rename variable log to avoid shadowing and improve clarity

The local variable log shadows the package-level log import. Consider renaming it to logger to enhance clarity.

Apply this diff:

 			log := log.With("future", future.ID)
-			log.Debug("running future")
+			logger := log.With("future", future.ID)
+			logger.Debug("running future")

55-57: Rename variable plog to logger for consistency

Using consistent variable names improves readability. Rename plog to logger.

Apply this diff:

-			plog := log.With("future", proposal.ID)
-			plog.Debug("verifying future proposal")
+			logger := log.With("future", proposal.ID)
+			logger.Debug("verifying future proposal")
prophet/prophet.go (3)

105-111: Avoid silent dropping of items in queues

Dropping items when the queue is full can lead to data loss or inconsistent states. Evaluate if a bounded buffer is appropriate, or if backpressure mechanisms or blocking calls are better suited for this use case.

Consider using an unbuffered channel or implementing a proper backpressure strategy to ensure that no items are lost.


28-30: Check for potential errors from newS function

When initializing resultsWriter and votesWriter, errors from newS are checked, but since newS only returns nil or an error from lru.New, which generally doesn't fail with the provided parameters, consider handling the error differently or adding context to potential failures.

Ensure that any error from newS is meaningful and, if unlikely to fail, consider simplifying error handling.


133-137: Avoid empty error returns in Write method

The Write method for the set s currently always returns nil. If there's no possibility of an error, consider removing the error return to simplify the interface.

Modify the method signature:

-func (s *s[T]) Write(value T) error {
+func (s *s[T]) Write(value T) {
 	id := value.getID()
 	s.l.Add(id, value)
-	return nil
 }

Update any calls to Write accordingly.

warden/x/async/keeper/abci.go (2)

25-25: Consider making the pending futures limit configurable

In the EndBlocker method, the limit 10 in k.futures.PendingFutures(ctx, 10) is hardcoded. For better flexibility and maintainability, consider defining this limit as a constant or making it a configurable parameter.

Apply this diff to define a constant for the limit:

+	const pendingFuturesLimit = 10
-	futures, err := k.futures.PendingFutures(ctx, 10)
+	futures, err := k.futures.PendingFutures(ctx, pendingFuturesLimit)

191-205: Ensure proper error logging in processVE method

In the processVE method, errors are returned immediately upon failure in adding future results or setting future votes. Consider logging these errors before returning to improve observability and ease of debugging.

Apply this diff to log errors before returning:

 		if err := k.AddFutureResult(ctx, r.FutureId, fromAddr, r.Output); err != nil {
+			ctx.Logger().Error("failed to add future result", "error", err)
 			return fmt.Errorf("failed to add future result: %w", err)
 		}
 		if err := k.SetFutureVote(ctx, vote.FutureId, fromAddr, vote.Vote); err != nil {
+			ctx.Logger().Error("failed to set task vote", "error", err)
 			return fmt.Errorf("failed to set task vote: %w", err)
 		}
prophet/handlers/echo/echo.go (1)

21-26: Consider adding error context.

While the error message is clear, consider using errors.New or fmt.Errorf with more context about the mismatch.

-		return fmt.Errorf("input and output do not match")
+		return fmt.Errorf("echo verification failed: input %x does not match output %x", input, output)
prophet/registry.go (1)

5-8: Consider dependency injection over global state.

Global state can make testing difficult and create tight coupling. Consider making the registry an injectable dependency.

prophet/future.go (1)

4-11: Consider adding input validation.

The Future struct should validate its fields, especially ensuring non-empty Handler and valid Input.

+// NewFuture creates a new Future with validation
+func NewFuture(id uint64, handler string, input []byte) (*Future, error) {
+	if handler == "" {
+		return nil, errors.New("handler cannot be empty")
+	}
+	if input == nil {
+		return nil, errors.New("input cannot be nil")
+	}
+	return &Future{
+		ID:      id,
+		Handler: handler,
+		Input:   input,
+	}, nil
+}
prophet/dedup.go (2)

7-7: Make LRU cache size configurable.

Consider making dedupLRUSize configurable through environment variables or configuration file.


57-63: Add error documentation.

Document the possible error cases in the function comment.

+// newDedupFutureReader creates a new deduplicated future reader.
+// It returns an error if the LRU cache initialization fails.
 func newDedupFutureReader(r FutureReader) (*dedupFutureReader, error) {
warden/testutil/keeper/async.go (1)

41-41: Consider using a mock prophet.P instead of nil

Passing nil for the new prophet.P parameter could mask potential issues in tests. Consider creating a mock implementation or using a test instance of prophet.P to ensure proper testing of the interaction between the keeper and prophet package.

warden/x/async/keeper/futures.go (1)

90-116: Optimize PendingFutures implementation

Consider these optimizations:

  1. Early return when limit is 0
  2. Pre-check if there are any pending futures before allocation
 func (k *FutureKeeper) PendingFutures(ctx context.Context, limit int) ([]types.Future, error) {
+    if limit <= 0 {
+        return nil, nil
+    }
+
+    hasAny, err := k.pendingFutures.Has(ctx, 0)
+    if err != nil {
+        return nil, err
+    }
+    if !hasAny {
+        return nil, nil
+    }
+
     it, err := k.pendingFutures.IterateRaw(ctx, nil, nil, collections.OrderAscending)
warden/app/app.go (2)

248-252: Consider moving handler registration after prophet initialization.

The echo handler is registered before the prophet instance is created. While this works due to the static nature of registration, it would be clearer to move the registration after prophet initialization for better code organization and to prevent potential issues if registration becomes dynamic in the future.

-prophet.Register("echo", echo.Handler{})
-prophetP, err := prophet.New()
-if err != nil {
-    panic(fmt.Errorf("failed to create prophet: %w", err))
-}
+prophetP, err := prophet.New()
+if err != nil {
+    panic(fmt.Errorf("failed to create prophet: %w", err))
+}
+prophet.Register("echo", echo.Handler{})

407-409: Consider graceful shutdown for prophet.

The prophet is started but there's no visible shutdown mechanism. Consider implementing a cleanup in the app's shutdown path.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0785e4 and 02a2392.

⛔ Files ignored due to path filters (4)
  • api/warden/async/v1beta1/ve.pulsar.go is excluded by !api/**
  • api/warden/vemanager/vemanager.pulsar.go is excluded by !api/**
  • warden/app/vemanager/vemanager.pb.go is excluded by !**/*.pb.go
  • warden/x/async/types/v1beta1/ve.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (22)
  • CHANGELOG.md (1 hunks)
  • go.mod (1 hunks)
  • prophet/dedup.go (1 hunks)
  • prophet/doc.go (1 hunks)
  • prophet/exec.go (1 hunks)
  • prophet/future.go (1 hunks)
  • prophet/handlers.go (1 hunks)
  • prophet/handlers/echo/echo.go (1 hunks)
  • prophet/prophet.go (1 hunks)
  • prophet/registry.go (1 hunks)
  • proto/warden/async/v1beta1/ve.proto (1 hunks)
  • proto/warden/vemanager/vemanager.proto (1 hunks)
  • warden/app/app.go (6 hunks)
  • warden/app/oracle.go (7 hunks)
  • warden/app/vemanager/vemanager.go (1 hunks)
  • warden/testutil/keeper/async.go (1 hunks)
  • warden/x/async/keeper/abci.go (1 hunks)
  • warden/x/async/keeper/abci_test.go (1 hunks)
  • warden/x/async/keeper/futures.go (5 hunks)
  • warden/x/async/keeper/keeper.go (5 hunks)
  • warden/x/async/module/module.go (3 hunks)
  • warden/x/async/types/v1beta1/future.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • prophet/doc.go
🧰 Additional context used
📓 Path-based instructions (18)
warden/x/async/types/v1beta1/future.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/x/async/module/module.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

prophet/registry.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/testutil/keeper/async.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

prophet/handlers/echo/echo.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

CHANGELOG.md (1)

Pattern **/*.md: "Assess the documentation for misspellings, grammatical errors, missing documentation and correctness"

prophet/handlers.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/app/app.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/x/async/keeper/futures.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/x/async/keeper/abci_test.go (2)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.


Pattern **/*_test.go: "Assess the unit test code assessing sufficient code coverage for the changes associated in the pull request"

prophet/future.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

prophet/exec.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

prophet/prophet.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

prophet/dedup.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/app/vemanager/vemanager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/x/async/keeper/keeper.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/x/async/keeper/abci.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

warden/app/oracle.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

🪛 buf (1.47.2)
proto/warden/vemanager/vemanager.proto

3-3: Files with package "warden.vemanager" must be within a directory "warden/vemanager" relative to root but were in directory "proto/warden/vemanager".

(PACKAGE_DIRECTORY_MATCH)

proto/warden/async/v1beta1/ve.proto

5-5: import "gogoproto/gogo.proto": file does not exist

(COMPILE)

🔇 Additional comments (18)
warden/app/vemanager/vemanager.go (1)

46-61: Verify the definition and implementation of VoteExtensions

The VoteExtensions struct is used but not defined in this file. Ensure that it is properly defined elsewhere in the codebase with Marshal and Unmarshal methods implemented.

Please confirm that VoteExtensions is correctly defined and that marshaling and unmarshaling operations function as expected.

warden/x/async/keeper/abci.go (2)

31-36: Handle potential errors from k.p.AddFuture

If k.p.AddFuture can return an error, consider adding error handling within the loop to catch and manage any potential issues when adding futures.

Does k.p.AddFuture return an error? If so, update the code to handle it appropriately.


236-249: Efficient trimming of excess bytes in trimExcessBytes function

The trimExcessBytes function effectively trims transactions to respect the maximum size limit, ensuring block size constraints are maintained without including oversized transactions.

warden/app/oracle.go (2)

219-228: Effective combination of proposal handlers

The combineProcessProposal and combinePreBlocker functions correctly combine the Slinky and Async handlers, ensuring that both handlers are executed in sequence and their responses are appropriately managed.


279-299: Proper implementation of WardenSlinkyCodec

The WardenSlinkyCodec correctly wraps the existing slinkyCodec to support the custom vote extension format (vemanager.VoteExtensions). The Decode method properly handles unmarshalling and error checking before delegating to the wrapped codec.

warden/x/async/types/v1beta1/future.go (1)

3-6: Correct implementation of IsValid method for FutureVoteType

The IsValid method accurately determines the validity of a FutureVoteType by checking if the value exists in the FutureVoteType_name map. This ensures only defined enum values are considered valid.

prophet/handlers/echo/echo.go (1)

13-15: LGTM! Good use of interface compliance check.

The implementation follows Go best practices with explicit interface compliance verification.

warden/x/async/keeper/keeper.go (2)

29-30: LGTM! Clean integration of prophet.P

The prophet.P field is properly added to the Keeper struct.


110-115: LGTM! Proper validation in SetFutureVote

The method properly validates the vote type before proceeding with the operation.

warden/x/async/keeper/futures.go (1)

19-19: LGTM! Clean integration of pendingFutures collection

The pendingFutures KeySet is properly integrated following the existing collections pattern.

Also applies to: 31-31, 37-37

warden/x/async/keeper/abci_test.go (2)

9-73: LGTM! Comprehensive test coverage for trimExcessBytes

Excellent test coverage with well-structured test cases covering edge cases and boundary conditions.


75-137: LGTM! Comprehensive test coverage for injectTx

Well-structured test cases with good coverage of edge cases and different insertion scenarios.

warden/x/async/module/module.go (1)

24-24: LGTM! Clean integration of the prophet package.

The changes properly integrate the prophet package by adding it as an optional dependency in ModuleInputs and passing it to the keeper.

Also applies to: 194-195, 217-217

proto/warden/vemanager/vemanager.proto (1)

7-12: LGTM! Well-documented message type.

The VoteExtensions message type is well-documented and correctly implements the requirement to distinguish between multiple extensions from different sources.

proto/warden/async/v1beta1/ve.proto (2)

19-34: LGTM! Well-structured vote extension messages.

The message types are well-organized and provide a clear structure for handling async vote extensions:

  • AsyncVoteExtension for containing results and votes
  • VEResultItem for future results
  • VEVoteItem for future votes

14-16: Address FIXME comment about data duplication.

The comment indicates potential space wastage due to duplication with slinky. This should be investigated and resolved.

Let's verify the duplication:

Would you like me to help analyze the duplication and propose a solution to optimize the block space usage?

go.mod (1)

62-62: LGTM: Dependency upgrade for LRU cache library.

The upgrade from github.com/hashicorp/golang-lru v1.0.2 to github.com/hashicorp/golang-lru/v2 v2.0.7 is appropriate for implementing the deduplication feature in the new prophet package.

CHANGELOG.md (1)

57-57: LGTM: Well-formatted changelog entry.

The entry follows the changelog guidelines and clearly documents the introduction of the "prophet" module with its key features.


var resps []*cometabci.ResponseVerifyVoteExtension
for i, ext := range w.Extensions {
handler := m.verifyVoteExtensionHandler[i]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent potential index out-of-range panic

Accessing m.verifyVoteExtensionHandler[i] without ensuring the index i is within bounds may cause a panic if the lengths are mismatched.

This issue will be resolved by checking for equality of lengths as suggested earlier.

warden/app/vemanager/vemanager.go Show resolved Hide resolved
// future will be executed in the background, the results can be retrieved by
// calling [P.Results].
func (p *P) AddFuture(future Future) {
p.futures.Add(future)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle potential error when adding futures to the queue

The Add method does not return an error, but it logs a warning if the queue is full and drops the item. This can lead to silent failures. Consider modifying Add to return an error, allowing the caller to handle it appropriately.

Modify the Add method to return an error:

-func (q *q[T]) Add(item T) {
+func (q *q[T]) Add(item T) error {
 	select {
 	case q.ch <- item:
+		return nil
 	default:
 		slog.Warn("q.Add: queue is full, dropped item", "item", item)
+		return fmt.Errorf("queue is full, item dropped")
 	}
 }

Update the AddFuture method to handle the error:

-func (p *P) AddFuture(future Future) {
-	p.futures.Add(future)
+func (p *P) AddFuture(future Future) error {
+	if err := p.futures.Add(future); err != nil {
+		return err
+	}
+	return nil
 }

Similarly, update AddFutureResult:

-func (p *P) AddFutureResult(proposal FutureResult) {
-	p.proposals.Add(proposal)
+func (p *P) AddFutureResult(proposal FutureResult) error {
+	if err := p.proposals.Add(proposal); err != nil {
+		return err
+	}
+	return nil
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +126 to +133
log.Error("failed to unmarshal async tx", "err", err)
// probably not an async tx?
// but slinky in this case rejects their proposal so maybe we
// should do the same?
return &cometabci.ResponseProcessProposal{
Status: cometabci.ResponseProcessProposal_ACCEPT,
}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Re-evaluate proposal acceptance on unmarshal failure

In the ProcessProposalHandler, when tx.Unmarshal(asyncTx) fails, the proposal is still accepted. Given the comment mentioning that Slinky rejects the proposal in such cases, consider rejecting the proposal to prevent processing invalid transactions.

Apply this diff to reject the proposal on unmarshal failure:

-	return &cometabci.ResponseProcessProposal{
-		Status: cometabci.ResponseProcessProposal_ACCEPT,
-	}, nil
+	return &cometabci.ResponseProcessProposal{
+		Status: cometabci.ResponseProcessProposal_REJECT,
+	}, nil
📝 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
log.Error("failed to unmarshal async tx", "err", err)
// probably not an async tx?
// but slinky in this case rejects their proposal so maybe we
// should do the same?
return &cometabci.ResponseProcessProposal{
Status: cometabci.ResponseProcessProposal_ACCEPT,
}, nil
}
log.Error("failed to unmarshal async tx", "err", err)
// probably not an async tx?
// but slinky in this case rejects their proposal so maybe we
// should do the same?
return &cometabci.ResponseProcessProposal{
Status: cometabci.ResponseProcessProposal_REJECT,
}, nil
}

Comment on lines +158 to +163
log.Error("failed to unmarshal async tx", "err", err, "tx", asyncTx)
// probably not an async tx?
// but slinky in this case rejects their proposal so maybe we
// should do the same?
return resp, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consistent error handling in PreBlocker

In the PreBlocker, when tx.Unmarshal(asyncTx) fails, the error is logged, but the function continues without rejecting the block or handling the invalid transaction. To maintain consistency and ensure invalid data does not affect the system, consider implementing proper error handling, such as rejecting the pre-block or skipping the invalid transaction.

Apply this diff to handle the error appropriately:

 			return resp, nil
+		}
+
+		// Consider rejecting or handling invalid transactions here
+		// Example:
+		// return resp, fmt.Errorf("invalid async transaction")
 		}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +25 to +29
func getHandler(name string) FutureHandler {
registry.rw.RLock()
defer registry.rw.RUnlock()
return registry.futures[name]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in getHandler.

The function should return an error when the handler is not found, rather than potentially returning nil.

-func getHandler(name string) FutureHandler {
+func getHandler(name string) (FutureHandler, error) {
 	registry.rw.RLock()
 	defer registry.rw.RUnlock()
-	return registry.futures[name]
+	handler, exists := registry.futures[name]
+	if !exists {
+		return nil, fmt.Errorf("handler %q not found", name)
+	}
+	return handler, nil
}
📝 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 getHandler(name string) FutureHandler {
registry.rw.RLock()
defer registry.rw.RUnlock()
return registry.futures[name]
}
func getHandler(name string) (FutureHandler, error) {
registry.rw.RLock()
defer registry.rw.RUnlock()
handler, exists := registry.futures[name]
if !exists {
return nil, fmt.Errorf("handler %q not found", name)
}
return handler, nil
}

Comment on lines +33 to +42
go func() {
defer close(out)
for req := range ch {
if c.Contains(req.getID()) {
continue
}
c.Add(req.getID(), struct{}{})
out <- req
}
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add context for graceful shutdown.

The goroutine should respect context cancellation for graceful shutdown.

-	go func() {
+	go func(ctx context.Context) {
 		defer close(out)
+		
+		select {
+		case <-ctx.Done():
+			return
+		default:
 		for req := range ch {
+			select {
+			case <-ctx.Done():
+				return
+			default:
 			if c.Contains(req.getID()) {
 				continue
 			}
 			c.Add(req.getID(), struct{}{})
 			out <- req
+			}
 		}
-	}()
+	}(ctx)

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +94 to +108
func (k Keeper) AddFutureResult(ctx context.Context, id uint64, submitter, output []byte) error {
if err := k.futures.SetResult(ctx, types.FutureResult{
Id: id,
Output: output,
Submitter: submitter,
}); err != nil {
return err
}

if err := k.SetFutureVote(ctx, id, submitter, types.FutureVoteType_VOTE_TYPE_VERIFIED); err != nil {
return err
}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Consider implementing store-level transaction handling in AddFutureResult

The review comment is valid. The method performs two separate atomic operations on different collections (results and votes), but there's no mechanism to ensure both operations succeed or fail together. If SetFutureVote fails after SetResult succeeds, it would leave the system in an inconsistent state where a result exists without a corresponding vote.

Consider:

  • Using store-level transaction handling to wrap both operations
  • Implementing a rollback mechanism for the first operation if the second fails
  • Or combining both pieces of data into a single atomic operation if possible
🔗 Analysis chain

Consider transaction handling in AddFutureResult

The method performs two operations (setting result and vote) that should be atomic. Consider handling the case where setting the vote fails after the result has been set.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any existing error handling patterns in the codebase
rg -A 5 "if err := .*; err != nil \{.*rollback|transaction"

Length of output: 71433


Script:

#!/bin/bash
# Check the implementation of SetResult and SetFutureVote
ast-grep --pattern 'func $_ SetResult($_, $_, $_) {
  $$$
}'

ast-grep --pattern 'func $_ SetFutureVote($_, $_, $_, $_) {
  $$$
}'

# Look for store patterns in the async module
rg -A 5 "store|Store" warden/x/async/

Length of output: 5345


Script:

#!/bin/bash
# Check futures implementation
rg -A 10 "type FutureKeeper" warden/x/async/

# Look for collections usage patterns
rg -A 5 "collections\." warden/x/async/

# Check if there's any transaction support
rg -A 5 "WithTransaction|BeginTransaction|Transaction" warden/x/async/

Length of output: 6407

@@ -0,0 +1,12 @@
syntax = "proto3";

package warden.vemanager;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix package directory structure.

The package warden.vemanager should be within a directory warden/vemanager relative to the proto root directory.

Move the proto file to the correct directory:

mkdir -p warden/vemanager
mv proto/warden/vemanager/vemanager.proto proto/warden/vemanager/
🧰 Tools
🪛 buf (1.47.2)

3-3: Files with package "warden.vemanager" must be within a directory "warden/vemanager" relative to root but were in directory "proto/warden/vemanager".

(PACKAGE_DIRECTORY_MATCH)

@Pitasi Pitasi merged commit 6232668 into main Jan 15, 2025
18 checks passed
@Pitasi Pitasi deleted the prophet-fix-devnet branch January 15, 2025 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants