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(appchains): IBC packets #187

Closed

Conversation

MaxMustermann2
Copy link
Contributor

@MaxMustermann2 MaxMustermann2 commented Sep 10, 2024

Summary by CodeRabbit

  • New Features

    • Introduced a new module for appchain coordination, enhancing the app's ability to manage interconnected chains.
    • Added new messages and structures for improved communication between coordinator and subscriber chains.
    • Enhanced error handling capabilities with new error variables.
  • Bug Fixes

    • Resolved issues related to channel operations and message handling in the appchain.
  • Documentation

    • Updated comments and documentation to reflect changes in the appchain's structure and functionality.
  • Refactor

    • Refactored code for better readability and maintainability, including the use of utility functions for chain ID processing.
  • Tests

    • Added new test cases to validate functionalities related to appchain coordination and AVS operations.

x/avs handles the assetID validation
- Anyone can create a subchain via a simple transaction. The genesis
  file is available at the end of the current epoch.
- If no operators opt in by that time, it is dropped.
- If the state is available, it is queried via CLI and added to the
  genesis file of the subchain.
- The genesis file, with data from other modules, starts the chain once
  enough vote power is live
The AVS module now validates the assetIDs passed to it
negative duration is also not supported
The wrapped consensus key object has utility in the subscriber module as
well, and, may have some utility in the coordinator module. Hence, it is
better located in a `types` subpackage.
Copy link
Contributor

coderabbitai bot commented Sep 10, 2024

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes in this pull request introduce a comprehensive update to the appchain coordination module within the Exocore application. Key modifications include the addition of a CoordinatorKeeper for managing appchain coordination, new Protobuf message definitions, and various utility functions. The updates also involve restructuring existing modules and enhancing error handling, event types, and key management. Several files have been modified to streamline operations related to IBC communication and subscriber chains, reflecting a significant evolution in the appchain's architecture.

Changes

File Path Summary of Changes
app/app.go Introduced CoordinatorKeeper, updated ModuleBasics, and integrated for IBC communication.
precompiles/avs/avs_test.go Removed duplicate imports and added new test cases for AVS functionalities.
proto/exocore/appchain/common/v1/common.proto Added new message SubscriberValidator and Cosmos-specific imports.
proto/exocore/appchain/common/v1/wire.proto Defined new messages and enumerations for communication between chains.
proto/exocore/appchain/coordinator/v1/coordinator.proto Added new message types for managing validator changes.
proto/exocore/appchain/subscriber/v1/genesis.proto Updated GenesisState with new fields and introduced MaturingVSCPacket.
testutil/utils.go Updated chainIDWithoutRevision assignment to use utils package.
utils/utils.go Added new utility functions for various operations.
x/appchain/common/types/errors.go Defined new error variables for channel operations.
x/appchain/common/types/events.go Introduced new constants for event handling.
x/appchain/common/types/expected_keepers.go Added new interfaces for IBC-related keepers.
x/appchain/common/types/keys.go Introduced new constants for key management.
x/appchain/common/types/types.go Added new functions for enhanced types functionality.
x/appchain/common/types/utils.go Introduced SendIBCPacket function for IBC packet handling.
x/appchain/coordinator/keeper/*.go Multiple new files and methods for managing connections, slashing, timeouts, and validators.
x/appchain/coordinator/module.go Restructured module package and updated functionality.
x/appchain/subscriber/keeper/*.go New methods for managing connections and distribution channels.
x/dogfood/keeper/*.go Refactored to use utils package for chain ID handling and validation.
x/operator/keeper/*.go Introduced new methods and refactored existing ones to utilize utils for chain ID processing.

Possibly related PRs

  • fix(unjail): fix the issue regarding unjail failed #184: The changes in this PR involve modifications to how the chain ID is processed, specifically switching from avstypes.ChainIDWithoutRevision to utils.ChainIDWithoutRevision, which aligns with the main PR's focus on enhancing chain ID handling in various components, including the introduction of new utility functions for managing chain IDs.

Suggested labels

Type: Build

Suggested reviewers

  • trestinlsd
  • mikebraver
  • bwhour
  • leonz789
  • cloud8little

Poem

In the land of code where rabbits play,
New modules hop in, brightening the day.
Coordinators dance, with packets in tow,
IBC whispers, as changes do flow.
With utils in hand, we leap and we bound,
In this appchain garden, new wonders are found! 🐇✨


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. (Experiment)
  • @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.

Comment on lines +176 to +178
for _, update := range m {
out = append(out, update)
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
frac, err := sdk.NewDecFromStr(k.GetSubscriberParams(ctx).SubscriberRedistributionFraction)
if err != nil {
// should not happen since we validated this in the params
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
if err != nil {
// It is the common behavior in cosmos-sdk to panic if SendCoinsFromModuleToModule
// returns error.
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
if err != nil {
// It is the common behavior in cosmos-sdk to panic if SendCoinsFromModuleToModule
// returns error.
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
x/appchain/subscriber/keeper/validators.go Fixed Show fixed Hide fixed
if err != nil {
// This should never happen as the pubkey is assumed
// to be stored correctly in ApplyCCValidatorChanges.
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
if err != nil {
// This should never happen as the pubkey is assumed
// to be stored correctly in ApplyCCValidatorChanges.
panic(err)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
"height", height, "vscID", vscID,
)

am.keeper.TrackHistoricalInfo(ctx)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

path flow from Begin/EndBlock to a panic call
path flow from Begin/EndBlock to a panic call
// EndBlock contains the logic that is automatically triggered at the end of each block
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
// send rewards to coordinator
am.keeper.EndBlockSendRewards(ctx)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

path flow from Begin/EndBlock to a panic call
path flow from Begin/EndBlock to a panic call
path flow from Begin/EndBlock to a panic call
x/appchain/subscriber/module.go Fixed Show fixed Hide fixed
@github-actions github-actions bot removed the C:Types label Oct 13, 2024
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.

Caution

Inline review comments failed to post

🛑 Comments failed to post (83)
x/appchain/coordinator/keeper/distribution.go (1)

11-14: 🛠️ Refactor suggestion

Consider adding error handling and improving readability.

The implementation is concise, but it makes some assumptions that may not always hold true. Consider the following improvements:

  1. Add error handling to account for cases where the module account might not exist.
  2. Split the chained method calls for better readability.

Here's a suggested implementation:

func (k Keeper) GetSubscriberRewardsPoolAddressStr(ctx sdk.Context) (string, error) {
    moduleAcc := k.accountKeeper.GetModuleAccount(ctx, types.SubscriberRewardsPool)
    if moduleAcc == nil {
        return "", fmt.Errorf("subscriber rewards pool module account not found")
    }
    return moduleAcc.GetAddress().String(), nil
}

This version handles potential errors and improves readability by breaking down the chained calls.

x/appchain/common/types/keys.go (1)

7-7: 🛠️ Refactor suggestion

Consider a more flexible approach to versioning.

The current version is hardcoded as ModuleName + "-1". For better maintainability and easier updates, consider using a separate version constant or environment variable.

Example:

const (
    ModuleVersion = "1"
    Version       = ModuleName + "-" + ModuleVersion
)

This approach allows for easier version updates in the future.

x/appchain/subscriber/keeper/ibc_client.go (1)

11-21: 🛠️ Refactor suggestion

Consider adding input validation for portID and channelID.

While the current implementation looks good, it might be beneficial to add checks for empty portID and channelID at the beginning of the function. This can help catch potential issues earlier and provide more specific error messages.

Here's a suggested improvement:

 func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string) error {
+	if portID == "" {
+		return errorsmod.Wrap(channeltypes.ErrInvalidPort, "port ID cannot be empty")
+	}
+	if channelID == "" {
+		return errorsmod.Wrap(channeltypes.ErrInvalidChannel, "channel ID cannot be empty")
+	}
 	capName := host.ChannelCapabilityPath(portID, channelID)
 	chanCap, ok := k.scopedKeeper.GetCapability(ctx, capName)
 	if !ok {
📝 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.

func (k Keeper) ChanCloseInit(ctx sdk.Context, portID, channelID string) error {
	if portID == "" {
		return errorsmod.Wrap(channeltypes.ErrInvalidPort, "port ID cannot be empty")
	}
	if channelID == "" {
		return errorsmod.Wrap(channeltypes.ErrInvalidChannel, "channel ID cannot be empty")
	}
	capName := host.ChannelCapabilityPath(portID, channelID)
	chanCap, ok := k.scopedKeeper.GetCapability(ctx, capName)
	if !ok {
		return errorsmod.Wrapf(
			channeltypes.ErrChannelCapabilityNotFound,
			"could not retrieve channel capability at: %s", capName,
		)
	}
	return k.channelKeeper.ChanCloseInit(ctx, portID, channelID, chanCap)
}
x/feedistribution/keeper/allocation.go (1)

7-7: 💡 Codebase verification

Issue Detected: Incomplete Removal of avstypes

The avstypes package is still referenced in multiple files across the codebase. Removing its import in x/feedistribution/keeper/allocation.go without addressing these references may cause build failures and functionality issues.

Files with remaining avstypes references:

  • x/operator/types/genesis.go
  • x/feedistribution/keeper/hooks_test.go
  • x/feedistribution/keeper/allocation.go
  • x/operator/keeper/grpc_query.go
  • x/operator/keeper/slash_test.go
  • x/dogfood/keeper/genesis.go
  • (and others as listed in the shell script output)

Please review the removal of avstypes and ensure all dependencies are appropriately handled.

🔗 Analysis chain

LGTM: Import changes look good.

The addition of the utils import and removal of avstypes suggest a refactoring of utility functions. This change appears to improve code organization.

To ensure this change doesn't introduce any issues, please run the following script to check for any remaining references to avstypes:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to avstypes in the codebase

# Test: Search for avstypes usage
rg --type go 'avstypes\.' 

Length of output: 4499

x/operator/types/keys.go (1)

209-216: 💡 Codebase verification

Optimization Requires Additional Changes

The proposed optimization to use the chainID without its length may introduce issues because existing functions rely on the chainID length for key parsing.

Affected Areas:

  • ParseKeyForOperatorKeyRemoval in x/operator/types/keys.go expects the key to include the chainID length.
  • Multiple usages in x/operator/keeper/consensus_keys.go depend on the current key structure.

Please ensure all dependent functions and key handling logic are updated accordingly before implementing this optimization.

🔗 Analysis chain

LGTM: Consistent implementation with added optimization note.

The changes align with previous updates, using utils.AppendMany and utils.ChainIDWithLenKey for consistency and improved readability.

Regarding the TODO comment about potentially using the chainID without its length:

This optimization could indeed be beneficial, as it would reduce key size and simplify the key structure. However, we need to verify if this change would affect any existing queries or iterations over these keys.

To investigate this, let's run the following script:

This script will help us understand if removing the chainID length from the key would impact any existing code. Based on the results, we can decide whether to proceed with the optimization or keep the current implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any code that might rely on the current key structure with chainID length

# Search for usages of KeyForOperatorKeyRemovalForChainID
echo "Usages of KeyForOperatorKeyRemovalForChainID:"
rg "KeyForOperatorKeyRemovalForChainID" --type go

# Search for any key parsing or iteration logic related to operator key removal
echo "\nRelated key parsing or iteration logic:"
rg "BytePrefixForOperatorKeyRemovalForChainID" --type go -A 10

Length of output: 4330

x/appchain/subscriber/keeper/distribution.go (1)

22-29: 🛠️ Refactor suggestion

Enhance error message with channel identifiers

Including srcPort and srcChan in the error message will provide more context when a channel is not found, aiding in debugging.

Apply this diff to improve the error message:

 return []string{}, errorsmod.Wrapf(commontypes.ErrChannelNotFound,
-    "cannot get connection hops from non-existent channel")
+    "cannot get connection hops from non-existent channel %s:%s", srcPort, srcChan)
📝 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.

func (k Keeper) GetConnectionHops(ctx sdk.Context, srcPort, srcChan string) ([]string, error) {
	ch, found := k.channelKeeper.GetChannel(ctx, srcPort, srcChan)
	if !found {
		return []string{}, errorsmod.Wrapf(commontypes.ErrChannelNotFound,
			"cannot get connection hops from non-existent channel %s:%s", srcPort, srcChan)
	}
	return ch.ConnectionHops, nil
}
x/appchain/common/types/validator.go (2)

21-23: ⚠️ Potential issue

Inconsistent field name casing: Pubkey should be PubKey

For consistency with Go naming conventions and the rest of the code, consider renaming the field Pubkey to PubKey.

Apply this diff to correct the field name:

 return SubscriberValidator{
     ConsAddress: address,
     Power:       power,
-    Pubkey:      pkAny,
+    PubKey:      pkAny,
 }

Ensure that all references to this field are updated accordingly throughout the codebase.

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

		ConsAddress: address,
		Power:       power,
		PubKey:      pkAny,

36-37: ⚠️ Potential issue

Add nil check for Pubkey before accessing GetCachedValue()

To prevent a potential nil pointer dereference, consider adding a check to ensure ocv.Pubkey is not nil before calling GetCachedValue().

Apply this diff to include the nil check:

 func (ocv SubscriberValidator) ConsPubKey() (cryptotypes.PubKey, error) {
+    if ocv.Pubkey == nil {
+        return nil, errorsmod.Wrap(sdkerrors.ErrInvalidType, "Pubkey is nil")
+    }
     pk, ok := ocv.Pubkey.GetCachedValue().(cryptotypes.PubKey)
     if !ok {
         return nil, errorsmod.Wrapf(
             sdkerrors.ErrInvalidType,
             "expecting cryptotypes.PubKey, got %T",
             pk,
         )
     }
     return pk, 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.

func (ocv SubscriberValidator) ConsPubKey() (cryptotypes.PubKey, error) {
    if ocv.Pubkey == nil {
        return nil, errorsmod.Wrap(sdkerrors.ErrInvalidType, "Pubkey is nil")
    }
	pk, ok := ocv.Pubkey.GetCachedValue().(cryptotypes.PubKey)
	if !ok {
        return nil, errorsmod.Wrapf(
            sdkerrors.ErrInvalidType,
            "expecting cryptotypes.PubKey, got %T",
            pk,
        )
    }
    return pk, nil
}
x/appchain/coordinator/keeper/height.go (2)

16-21: ⚠️ Potential issue

Handle nil values to prevent panic when retrieving height

In GetHeightForChainVscID, if the key does not exist in the store, store.Get(key) returns nil. Passing nil to sdk.BigEndianToUint64 can cause a panic. It's important to check if the key exists before attempting to decode the value.

Consider modifying the function as follows:

func (k Keeper) GetHeightForChainVscID(ctx sdk.Context, chainID string, vscID uint64) uint64 {
    store := ctx.KVStore(k.storeKey)
    key := types.HeightToChainVscIDKey(chainID, vscID)
    if !store.Has(key) {
        return 0
    }
    return sdk.BigEndianToUint64(store.Get(key))
}

24-28: ⚠️ Potential issue

Ensure safe retrieval of VscID to avoid potential panics

Similarly, in GetVscIDForChain, if the key does not exist, store.Get(key) will return nil, which may lead to a panic when decoded. Adding a check for the key's existence prevents this issue.

Here's the suggested modification:

func (k Keeper) GetVscIDForChain(ctx sdk.Context, chainID string) uint64 {
    store := ctx.KVStore(k.storeKey)
    key := types.VscIDForChainKey(chainID)
    if !store.Has(key) {
        return 0
    }
    return sdk.BigEndianToUint64(store.Get(key))
}
x/appchain/common/types/types.go (2)

13-25: ⚠️ Potential issue

Ensure safe conversion of res to string to avoid invalid UTF-8 encoding

Converting arbitrary byte slices to strings using string(res) may result in invalid UTF-8 sequences, leading to logging errors or unexpected behavior. To prevent this, consider encoding res using a safe method before logging.

Apply this diff to safely log the result:

 func NewResultAcknowledgementWithLog(ctx sdk.Context, res []byte) channeltypes.Acknowledgement {
 	if len(res) > maxLogSize {
 		ctx.Logger().Info(
 			"IBC ResultAcknowledgement constructed",
 			"res_size", len(res),
-			"res_preview", string(res[:maxLogSize]),
+			"res_preview", hex.EncodeToString(res[:maxLogSize]),
 		)
 	} else {
 		ctx.Logger().Info(
 			"IBC ResultAcknowledgement constructed",
 			"res_size", len(res),
-			"res", string(res),
+			"res", hex.EncodeToString(res),
 		)
 	}
 	return channeltypes.NewResultAcknowledgement(res)
 }

Additionally, add the necessary import statement at the top of the file:

import (
	"encoding/hex"
	// other imports...
)

This change uses hex.EncodeToString to convert the byte slices to a hexadecimal string representation, ensuring that the logged data is valid UTF-8 and preventing potential encoding issues.


48-49: ⚠️ Potential issue

Correct the function comment to match NewVscMaturedPacketData

The function comment incorrectly refers to NewVscPacketData instead of NewVscMaturedPacketData. This inconsistency may cause confusion.

Apply this diff to correct the comment:

-// NewVscPacketData creates a new VscMaturedPacketData instance.
+// NewVscMaturedPacketData creates a new VscMaturedPacketData instance.
📝 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.

// NewVscMaturedPacketData creates a new VscMaturedPacketData instance.
func NewVscMaturedPacketData(
proto/exocore/appchain/subscriber/v1/genesis.proto (1)

36-37: ⚠️ Potential issue

Adjust field definition formatting for maturity_time.

The field options for maturity_time are placed on a new line, which may lead to parsing errors with some protobuf compilers. It's standard practice to keep field options on the same line as the field declaration.

Apply this diff to correct the formatting:

- google.protobuf.Timestamp maturity_time = 2
-     [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ];
+ google.protobuf.Timestamp maturity_time = 2 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ];
📝 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.

  google.protobuf.Timestamp maturity_time = 2 [ (gogoproto.stdtime) = true, (gogoproto.nullable) = false ];
x/appchain/subscriber/keeper/genesis.go (1)

28-29: 🛠️ Refactor suggestion

Evaluate the use of panic for error handling during initialization

While panicking on critical initialization failures ensures that the application doesn't proceed in an invalid state, consider whether returning errors might offer better flexibility. This allows for graceful degradation or retries in certain deployment scenarios.

For example, you could modify the error handling as:

if err := k.portKeeper.BindPort(ctx, commontypes.SubscriberPortID); err != nil {
	return nil, fmt.Errorf("could not claim port capability: %v", err)
}

...

if err != nil {
	return nil, fmt.Errorf("could not create client for coordinator chain: %v", err)
}

Also applies to: 36-37

x/appchain/coordinator/keeper/impl_epochs_hooks.go (4)

48-48: ⚠️ Potential issue

Handle errors returned by SendQueuedValidatorUpdates

Any errors from SendQueuedValidatorUpdates should be handled to ensure that queued validator updates are sent successfully.

Apply this diff to handle the error:

-	wrapper.keeper.SendQueuedValidatorUpdates(ctx, epoch)
+	if err := wrapper.keeper.SendQueuedValidatorUpdates(ctx, epoch); err != nil {
+		// Handle the error appropriately, e.g., log it
+		ctx.Logger().Error("Failed to send queued validator updates", "epoch", epoch, "error", err)
+	}
📝 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.

	if err := wrapper.keeper.SendQueuedValidatorUpdates(ctx, epoch); err != nil {
		// Handle the error appropriately, e.g., log it
		ctx.Logger().Error("Failed to send queued validator updates", "epoch", epoch, "error", err)
	}

40-40: ⚠️ Potential issue

Handle errors returned by RemoveTimedoutSubscribers

The function RemoveTimedoutSubscribers may produce an error that is not being handled. Proper error handling ensures robustness and easier debugging.

Apply this diff to handle the error:

-	wrapper.keeper.RemoveTimedoutSubscribers(ctx, identifier, epoch)
+	if err := wrapper.keeper.RemoveTimedoutSubscribers(ctx, identifier, epoch); err != nil {
+		// Handle the error appropriately, e.g., log it
+		ctx.Logger().Error("Failed to remove timed-out subscribers", "error", err)
+	}
📝 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.

	if err := wrapper.keeper.RemoveTimedoutSubscribers(ctx, identifier, epoch); err != nil {
		// Handle the error appropriately, e.g., log it
		ctx.Logger().Error("Failed to remove timed-out subscribers", "error", err)
	}

28-28: ⚠️ Potential issue

Handle errors returned by ActivateScheduledChains

The function ActivateScheduledChains may return an error that is currently not being handled. Ignoring potential errors can lead to unexpected behavior or silent failures. It's important to capture and handle errors appropriately.

Apply this diff to handle the error:

-	wrapper.keeper.ActivateScheduledChains(ctx, identifier, epoch)
+	if err := wrapper.keeper.ActivateScheduledChains(ctx, identifier, epoch); err != nil {
+		// Handle the error appropriately, e.g., log it
+		ctx.Logger().Error("Failed to activate scheduled chains", "error", err)
+		// Depending on the severity, consider taking additional action
+	}
📝 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.

	if err := wrapper.keeper.ActivateScheduledChains(ctx, identifier, epoch); err != nil {
		// Handle the error appropriately, e.g., log it
		ctx.Logger().Error("Failed to activate scheduled chains", "error", err)
		// Depending on the severity, consider taking additional action
	}

45-45: ⚠️ Potential issue

Handle errors returned by QueueValidatorUpdatesForEpochID

Ignoring errors from QueueValidatorUpdatesForEpochID may lead to unprocessed validator updates. Ensure errors are captured and handled.

Apply this diff to handle the error:

-	wrapper.keeper.QueueValidatorUpdatesForEpochID(ctx, identifier, epoch)
+	if err := wrapper.keeper.QueueValidatorUpdatesForEpochID(ctx, identifier, epoch); err != nil {
+		// Handle the error appropriately, e.g., log it
+		ctx.Logger().Error("Failed to queue validator updates for epoch", "epoch", epoch, "error", err)
+	}
📝 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.

	if err := wrapper.keeper.QueueValidatorUpdatesForEpochID(ctx, identifier, epoch); err != nil {
		// Handle the error appropriately, e.g., log it
		ctx.Logger().Error("Failed to queue validator updates for epoch", "epoch", epoch, "error", err)
	}
x/appchain/coordinator/types/expected_keepers.go (1)

58-61: 🛠️ Refactor suggestion

Consider using more specific types instead of []byte in DelegationKeeper methods

In the DelegationKeeper interface, the methods IncrementUndelegationHoldCount and DecrementUndelegationHoldCount accept a []byte parameter. Using a more specific type (e.g., sdk.AccAddress or a defined identifier type) could improve type safety and code readability.

x/appchain/subscriber/keeper/connection.go (1)

12-86: 🛠️ Refactor suggestion

Add unit tests for new keeper methods

The newly added methods in the keeper are crucial for managing interactions with the coordinator chain. To ensure their correct functionality and prevent future regressions, please consider adding unit tests for these methods.

Would you like assistance in creating unit tests for these methods?

x/appchain/coordinator/keeper/impl_operator_hooks.go (2)

56-78: 🛠️ Refactor suggestion

Refactor duplicated logic to reduce code repetition

Similarly, the method AfterOperatorKeyRemovalInitiated contains logic that duplicates code found in AfterOperatorKeyReplaced. Refactoring to use the shared helper function improves consistency and maintainability.

Apply this diff to refactor the code:

 func (h OperatorHooksWrapper) AfterOperatorKeyRemovalInitiated(
     ctx sdk.Context, _ sdk.AccAddress, chainID string, key exocoretypes.WrappedConsKey,
 ) {
-    if chainID != utils.ChainIDWithoutRevision(ctx.ChainID()) {
-        consAddr := key.ToConsAddr()
-        _, found := h.keeper.GetSubscriberValidatorForChain(ctx, chainID, consAddr)
-        if found {
-            // schedule this consensus address for pruning at the maturity of the packet containing this vscID that will
-            // go out at the end of this epoch.
-            nextVscID := h.keeper.GetVscIDForChain(ctx, chainID) + 1
-            h.keeper.AppendConsAddrToPrune(ctx, chainID, nextVscID, consAddr)
-            // reverse lookup
-            h.keeper.SetMaturityVscIDForChainIDConsAddr(ctx, chainID, consAddr, nextVscID)
-        } else {
-            // delete the reverse lookup of old cons addr + chain id -> operator addr, since it was never an active
-            // validator.
-            h.keeper.operatorKeeper.DeleteOperatorAddressForChainIDAndConsAddr(
-                ctx, chainID, consAddr,
-            )
-        }
-    }
+    consAddr := key.ToConsAddr()
+    h.handleOperatorKeyChange(ctx, chainID, consAddr)
 }
📝 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.

// AfterOperatorKeyRemovalInitiated is the implementation of the operator hooks.
func (h OperatorHooksWrapper) AfterOperatorKeyRemovalInitiated(
	ctx sdk.Context, _ sdk.AccAddress, chainID string, key exocoretypes.WrappedConsKey,
) {
	consAddr := key.ToConsAddr()
	h.handleOperatorKeyChange(ctx, chainID, consAddr)
}

30-54: 🛠️ Refactor suggestion

Refactor duplicated logic to reduce code repetition

The logic within AfterOperatorKeyReplaced (lines 36-53) is nearly identical to that in AfterOperatorKeyRemovalInitiated. Consider extracting this shared logic into a separate helper function to avoid code duplication and enhance maintainability.

Apply this diff to refactor the code:

+func (h OperatorHooksWrapper) handleOperatorKeyChange(
+    ctx sdk.Context, chainID string, consAddr sdk.ConsAddress,
+) {
+    if chainID != utils.ChainIDWithoutRevision(ctx.ChainID()) {
+        _, found := h.keeper.GetSubscriberValidatorForChain(ctx, chainID, consAddr)
+        if found {
+            // Schedule this consensus address for pruning at the maturity of the packet containing this vscID
+            // that will go out at the end of this epoch.
+            nextVscID := h.keeper.GetVscIDForChain(ctx, chainID) + 1
+            h.keeper.AppendConsAddrToPrune(ctx, chainID, nextVscID, consAddr)
+            // Reverse lookup
+            h.keeper.SetMaturityVscIDForChainIDConsAddr(ctx, chainID, consAddr, nextVscID)
+        } else {
+            // Delete the reverse lookup of old cons addr + chain id -> operator addr, since it was never an active validator.
+            h.keeper.operatorKeeper.DeleteOperatorAddressForChainIDAndConsAddr(ctx, chainID, consAddr)
+        }
+    }
+}
+
 func (h OperatorHooksWrapper) AfterOperatorKeyReplaced(
     ctx sdk.Context, _ sdk.AccAddress,
     oldKey exocoretypes.WrappedConsKey, _ exocoretypes.WrappedConsKey,
     chainID string,
 ) {
-    if chainID != utils.ChainIDWithoutRevision(ctx.ChainID()) {
-        consAddr := oldKey.ToConsAddr()
-        _, found := h.keeper.GetSubscriberValidatorForChain(ctx, chainID, consAddr)
-        if found {
-            // schedule this consensus address for pruning at the maturity of the packet containing this vscID that will
-            // go out at the end of this epoch.
-            nextVscID := h.keeper.GetVscIDForChain(ctx, chainID) + 1
-            h.keeper.AppendConsAddrToPrune(ctx, chainID, nextVscID, consAddr)
-            // reverse lookup
-            h.keeper.SetMaturityVscIDForChainIDConsAddr(ctx, chainID, consAddr, nextVscID)
-        } else {
-            // delete the reverse lookup of old cons addr + chain id -> operator addr, since it was never an active
-            // validator.
-            h.keeper.operatorKeeper.DeleteOperatorAddressForChainIDAndConsAddr(
-                ctx, chainID, consAddr,
-            )
-        }
-    }
+    consAddr := oldKey.ToConsAddr()
+    h.handleOperatorKeyChange(ctx, chainID, consAddr)
 }
📝 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.

// AfterOperatorKeyReplaced is the implementation of the operator hooks.
func (h OperatorHooksWrapper) handleOperatorKeyChange(
    ctx sdk.Context, chainID string, consAddr sdk.ConsAddress,
) {
    if chainID != utils.ChainIDWithoutRevision(ctx.ChainID()) {
        _, found := h.keeper.GetSubscriberValidatorForChain(ctx, chainID, consAddr)
        if found {
            // Schedule this consensus address for pruning at the maturity of the packet containing this vscID
            // that will go out at the end of this epoch.
            nextVscID := h.keeper.GetVscIDForChain(ctx, chainID) + 1
            h.keeper.AppendConsAddrToPrune(ctx, chainID, nextVscID, consAddr)
            // Reverse lookup
            h.keeper.SetMaturityVscIDForChainIDConsAddr(ctx, chainID, consAddr, nextVscID)
        } else {
            // Delete the reverse lookup of old cons addr + chain id -> operator addr, since it was never an active validator.
            h.keeper.operatorKeeper.DeleteOperatorAddressForChainIDAndConsAddr(ctx, chainID, consAddr)
        }
    }
}

func (h OperatorHooksWrapper) AfterOperatorKeyReplaced(
    ctx sdk.Context, _ sdk.AccAddress,
    oldKey exocoretypes.WrappedConsKey, _ exocoretypes.WrappedConsKey,
    chainID string,
) {
    consAddr := oldKey.ToConsAddr()
    h.handleOperatorKeyChange(ctx, chainID, consAddr)
}
x/appchain/coordinator/keeper/validators.go (1)

78-83: ⚠️ Potential issue

Handle potential nil value when retrieving MaxValidators to prevent panic

In the GetMaxValidatorsForChain function, if the MaxValidators key does not exist in the store, bz will be nil. Passing nil to sdk.BigEndianToUint64(bz) can cause a panic because it expects an 8-byte slice. To ensure robustness and prevent unexpected panics, it's safer to check if bz is not nil before attempting the conversion.

Consider adding a check for bz == nil and returning a default value or handling the situation appropriately.

Apply this diff to fix the issue:

 func (k Keeper) GetMaxValidatorsForChain(
 	ctx sdk.Context, chainID string,
 ) uint32 {
 	store := ctx.KVStore(k.storeKey)
 	key := types.MaxValidatorsKey(chainID)
 	bz := store.Get(key)
+	if bz == nil {
+		// Return a default value, for example, 0
+		return 0
+	}
 	// #nosec G115 // we stored it, we trust it
 	return uint32(sdk.BigEndianToUint64(bz))
 }
📝 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.

func (k Keeper) GetMaxValidatorsForChain(
	ctx sdk.Context, chainID string,
) uint32 {
	store := ctx.KVStore(k.storeKey)
	key := types.MaxValidatorsKey(chainID)
	bz := store.Get(key)
	if bz == nil {
		// Return a default value, for example, 0
		return 0
	}
	// #nosec G115 // we stored it, we trust it
	return uint32(sdk.BigEndianToUint64(bz))
}
x/appchain/coordinator/keeper/identifiers.go (2)

67-79: ⚠️ Potential issue

Ensure dynamic handling of prefix length when slicing keys

Similar to GetAllChainsWithClients, in the GetAllChainsWithChannels method, you're slicing the key using iterator.Key()[1:]. Consider using the dynamic length of the prefix to enhance code robustness.

Apply this diff:

 func (k Keeper) GetAllChainsWithChannels(ctx sdk.Context) []string {
 	store := ctx.KVStore(k.storeKey)
-	iterator := sdk.KVStorePrefixIterator(store, []byte{types.ChannelForChainBytePrefix})
+	prefix := []byte{types.ChannelForChainBytePrefix}
+	iterator := sdk.KVStorePrefixIterator(store, prefix)
 	defer iterator.Close()

 	var chains []string
 	for ; iterator.Valid(); iterator.Next() {
-		chainID := string(iterator.Key()[1:])
+		chainID := string(iterator.Key()[len(prefix):])
 		chains = append(chains, chainID)
 	}

 	return chains
 }
📝 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.

func (k Keeper) GetAllChainsWithChannels(ctx sdk.Context) []string {
	store := ctx.KVStore(k.storeKey)
	prefix := []byte{types.ChannelForChainBytePrefix}
	iterator := sdk.KVStorePrefixIterator(store, prefix)
	defer iterator.Close()

	var chains []string
	for ; iterator.Valid(); iterator.Next() {
		chainID := string(iterator.Key()[len(prefix):])
		chains = append(chains, chainID)
	}

	return chains
}

35-47: ⚠️ Potential issue

Ensure dynamic handling of prefix length when slicing keys

In the GetAllChainsWithClients method, you are slicing the key using iterator.Key()[1:] to extract the chainID. This assumes that the prefix length is exactly one byte. To make the code more robust and maintainable, consider using the length of the prefix dynamically.

Apply this diff to enhance robustness:

 func (k Keeper) GetAllChainsWithClients(ctx sdk.Context) []string {
 	store := ctx.KVStore(k.storeKey)
-	iterator := sdk.KVStorePrefixIterator(store, []byte{types.ClientForChainBytePrefix})
+	prefix := []byte{types.ClientForChainBytePrefix}
+	iterator := sdk.KVStorePrefixIterator(store, prefix)
 	defer iterator.Close()

 	var chains []string
 	for ; iterator.Valid(); iterator.Next() {
-		chainID := string(iterator.Key()[1:])
+		chainID := string(iterator.Key()[len(prefix):])
 		chains = append(chains, chainID)
 	}

 	return chains
 }
📝 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.

func (k Keeper) GetAllChainsWithClients(ctx sdk.Context) []string {
	store := ctx.KVStore(k.storeKey)
	prefix := []byte{types.ClientForChainBytePrefix}
	iterator := sdk.KVStorePrefixIterator(store, prefix)
	defer iterator.Close()

	var chains []string
	for ; iterator.Valid(); iterator.Next() {
		chainID := string(iterator.Key()[len(prefix):])
		chains = append(chains, chainID)
	}

	return chains
}
x/appchain/subscriber/keeper/queue.go (4)

80-81: 🛠️ Refactor suggestion

Avoid repetition of comments about key prefixes.

The same note about the correct prefix is repeated. To adhere to the DRY (Don't Repeat Yourself) principle, consider consolidating this information.

You could extract common comments into a single location or improve documentation to reduce redundancy.


38-43: 🛠️ Refactor suggestion

Streamline packet retrieval in GetPendingPackets.

Since GetPendingPackets is primarily a wrapper around GetAllPendingPacketsWithIdx, consider optimizing to remove unnecessary intermediate steps.

You could modify GetAllPendingPacketsWithIdx to accept a parameter that determines whether indexes are needed, consolidating the logic.


85-90: 🛠️ Refactor suggestion

Optimize key deletion in DeleteAllPendingDataPackets.

Collecting all keys before deletion may consume unnecessary memory. You can delete keys during iteration to improve efficiency.

Apply this diff to delete keys while iterating:

iterator := sdk.KVStorePrefixIterator(store, []byte{types.PendingDataPacketsBytePrefix})
defer iterator.Close()
-keysToDel := [][]byte{}
for ; iterator.Valid(); iterator.Next() {
-	keysToDel = append(keysToDel, iterator.Key())
+	store.Delete(iterator.Key())
}
-for _, key := range keysToDel {
-	store.Delete(key)
-}

This approach reduces memory usage by eliminating the need to store all keys before deleting them.

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

	iterator := sdk.KVStorePrefixIterator(store, []byte{types.PendingDataPacketsBytePrefix})
	defer iterator.Close()
	for ; iterator.Valid(); iterator.Next() {
		store.Delete(iterator.Key())
	}

58-58: ⚠️ Potential issue

Handle potential unmarshalling errors.

Using k.cdc.MustUnmarshal(bz, &packet) may cause a panic if the data is corrupted. It's safer to use k.cdc.Unmarshal and handle any errors gracefully.

Apply this diff to handle unmarshalling errors:

for ; iterator.Valid(); iterator.Next() {
	var packet commontypes.SubscriberPacketData
	bz := iterator.Value()
-	k.cdc.MustUnmarshal(bz, &packet)
+	if err := k.cdc.Unmarshal(bz, &packet); err != nil {
+		// Handle the error, e.g., skip this packet or log the error
+		continue
+	}
	packetWithIdx := types.SubscriberPacketDataWithIdx{
		SubscriberPacketData: packet,
		Idx: sdk.BigEndianToUint64(iterator.Key()[1:]),
	}
	packets = append(packets, packetWithIdx)
}

Committable suggestion was skipped due to low confidence.

x/appchain/coordinator/keeper/impl_delegation_hooks.go (3)

56-59: ⚠️ Potential issue

Address the TODO regarding operators in the process of opting out

There's a TODO indicating that the current implementation doesn't handle operators who are in the process of opting out:

Ensure that this scenario is properly handled to avoid inconsistencies in undelegation timing and operator status.

Would you like me to assist in implementing the necessary changes or open a GitHub issue to track this task?


74-91: 🛠️ Refactor suggestion

Refactor logic for determining 'nextVscID' to improve readability

The current logic for setting nextVscID can be refactored for clarity:

Consider restructuring the conditional statements to reduce nesting and improve code readability.

Here's a suggested refactor:

 			if wrapper.keeper.operatorKeeper.IsOperatorRemovingKeyFromChainID(
 				ctx, operator, chainID,
 			) {
 				nextVscID = wrapper.keeper.GetMaturityVscIDForChainIDConsAddr(ctx, chainID, wrappedKey.ToConsAddr())
 				if nextVscID == 0 {
 					logger.Error(
 						"undelegation maturity epoch not set",
 						"operator", operator,
 						"chainID", chainID,
 						"consAddr", wrappedKey.ToConsAddr(),
 						"recordKey", fmt.Sprintf("%x", recordKey),
 					)
 					// move on to the next chainID
 					continue
 				}
 			} else {
-				nextVscID = wrapper.keeper.GetVscIDForChain(ctx, chainID) + 1
+			}
+			if nextVscID == 0 {
+				nextVscID = wrapper.keeper.GetVscIDForChain(ctx, chainID) + 1
 			}

This reduces nesting and clearly separates the logic for setting nextVscID.


94-96: ⚠️ Potential issue

Ensure consistent error handling when incrementing undelegation hold count

In previous error scenarios, the function logs the error and continues, but here it returns the error directly:

Consider maintaining consistent error handling throughout the function. Decide whether to return errors or handle them internally, and apply that approach consistently.

proto/exocore/appchain/common/v1/wire.proto (1)

5-5: ⚠️ Potential issue

Fix import statement: 'cosmos/staking/v1beta1/staking.proto' file does not exist

The static analysis tool indicates that the file 'cosmos/staking/v1beta1/staking.proto' cannot be found. Please verify that the file exists at the specified location or update the import path accordingly. This is crucial since the Infraction enum from this file is used in the SlashPacketData message.

🧰 Tools
🪛 buf

5-5: import "cosmos/staking/v1beta1/staking.proto": file does not exist

(COMPILE)

x/appchain/coordinator/keeper/keeper.go (2)

80-84: ⚠️ Potential issue

Handle Potential Nil Return in GetPort Method

In the GetPort method:

store := ctx.KVStore(k.storeKey)
return string(store.Get(types.PortKey()))

If store.Get(types.PortKey()) returns nil (e.g., when the port ID has not been set), converting nil to a string results in an empty string. This might lead to unexpected behavior downstream. Consider handling the nil case explicitly:

  • Return an error if the port ID is not set.
  • Provide a default value or document that an empty string indicates an unset port.

100-117: 🛠️ Refactor suggestion

Automate Field Validation in mustValidateFields

The mustValidateFields method manually checks each field for nil:

utils.PanicIfNil(k.storeKey, "storeKey")
// ...additional checks for each field

This approach requires updating the method every time a new field is added to the Keeper struct, which may result in missed validations if overlooked.

Consider automating the validation process to ensure all fields are checked without manual intervention. Possible solutions include:

  • Using reflection: Iterate over the struct fields to check for nil values. This can be done carefully to address linter concerns.
  • Code generation: Employ a code generation tool to produce the mustValidateFields method, ensuring it stays up-to-date with the struct fields.
  • Constructor enforcement: Modify the NewKeeper function to enforce field initialization, potentially reducing the need for separate validation.
x/appchain/coordinator/keeper/unbonding.go (3)

61-62: ⚠️ Potential issue

Check for nil value before converting to uint64

In GetMaturityVscIDForChainIDConsAddr, if the key does not exist, store.Get(key) will return nil. Passing nil to sdk.BigEndianToUint64 will cause a panic. Consider checking if bz is nil before attempting the conversion.

Apply this diff to ensure safe conversion:

 bz := store.Get(key)
+if bz == nil {
+    return 0
+}
 return sdk.BigEndianToUint64(bz)
📝 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.

	bz := store.Get(key)
	if bz == nil {
		return 0
	}
	return sdk.BigEndianToUint64(bz)

26-27: ⚠️ Potential issue

Handle nil value when unmarshalling consensus addresses

In GetConsAddrsToPrune, if the key does not exist in the store, store.Get(key) will return nil. Passing nil to k.cdc.MustUnmarshal will cause a panic. Please add a nil check before unmarshalling to prevent potential runtime errors.

Apply this diff to handle the nil case safely:

 key := types.ConsAddrsToPruneKey(chainID, vscID)
-k.cdc.MustUnmarshal(store.Get(key), &res)
+bz := store.Get(key)
+if bz == nil {
+    return res
+}
+k.cdc.MustUnmarshal(bz, &res)
📝 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.

	key := types.ConsAddrsToPruneKey(chainID, vscID)
	bz := store.Get(key)
	if bz == nil {
		return res
	}
	k.cdc.MustUnmarshal(bz, &res)
	return res

92-93: ⚠️ Potential issue

Prevent panic by handling nil value during unmarshalling

In GetUndelegationsToRelease, similar to the previous functions, store.Get(key) may return nil if the key is not found. This will cause k.cdc.MustUnmarshal to panic. Adding a nil check before unmarshalling will prevent this issue.

Apply this diff to handle the potential nil value:

 key := types.UndelegationsToReleaseKey(chainID, vscID)
-k.cdc.MustUnmarshal(store.Get(key), &res)
+bz := store.Get(key)
+if bz == nil {
+    return res
+}
+k.cdc.MustUnmarshal(bz, &res)
📝 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.

	key := types.UndelegationsToReleaseKey(chainID, vscID)
	bz := store.Get(key)
	if bz == nil {
		return res
	}
	k.cdc.MustUnmarshal(bz, &res)
	return res
x/appchain/subscriber/types/keys.go (1)

94-96: ⚠️ Potential issue

Ensure consistent use of ConsAddress conversion

In SubscriberValidatorKey, the address is appended directly:

return append([]byte{SubscriberValidatorBytePrefix}, address...)

In contrast, OutstandingDowntimeKey uses consAddress.Bytes():

return append([]byte{OutstandingDowntimeBytePrefix}, consAddress.Bytes()...)

For consistency and to prevent potential issues, consider using address.Bytes() in SubscriberValidatorKey if sdk.ConsAddress supports the Bytes() method.

Apply this diff if applicable:

 func SubscriberValidatorKey(address sdk.ConsAddress) []byte {
-    return append([]byte{SubscriberValidatorBytePrefix}, address...)
+    return append([]byte{SubscriberValidatorBytePrefix}, address.Bytes()...)
 }
x/appchain/subscriber/keeper/impl_sdk.go (3)

129-135: ⚠️ Potential issue

Avoid using panic in unimplemented methods

The Delegation method panics when called. Returning nil or an error is safer and prevents the application from crashing unexpectedly if this method is called.

Apply this diff to handle the method safely:

-func (k Keeper) Delegation(
-	sdk.Context,
-	sdk.AccAddress,
-	sdk.ValAddress,
-) stakingtypes.DelegationI {
-	panic("unimplemented on this keeper")
+func (k Keeper) Delegation(
+	ctx sdk.Context,
+	delAddr sdk.AccAddress,
+	valAddr sdk.ValAddress,
+) stakingtypes.DelegationI {
+	return 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.

func (k Keeper) Delegation(
	ctx sdk.Context,
	delAddr sdk.AccAddress,
	valAddr sdk.ValAddress,
) stakingtypes.DelegationI {
	return nil
}

48-50: ⚠️ Potential issue

Avoid using panic in unimplemented methods

The Validator method panics when called, which can cause the application to crash unexpectedly if this method is invoked. Consider returning nil or an appropriate error to handle the unimplemented method more gracefully.

Apply this diff to handle the method safely:

-func (k Keeper) Validator(sdk.Context, sdk.ValAddress) stakingtypes.ValidatorI {
-	panic("unimplemented on this keeper")
+func (k Keeper) Validator(ctx sdk.Context, addr sdk.ValAddress) stakingtypes.ValidatorI {
+	return 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.

func (k Keeper) Validator(ctx sdk.Context, addr sdk.ValAddress) stakingtypes.ValidatorI {
	return nil
}

67-80: ⚠️ Potential issue

Clarify the usage of the Slash method

The Slash method is implemented but the comment indicates it "should not be called anywhere." If this method must exist to satisfy an interface, consider documenting its intended usage more clearly or returning immediately with a no-op to prevent unintended behavior.

Apply this diff to clarify the method's usage:

-func (k Keeper) Slash(
-	ctx sdk.Context,
-	addr sdk.ConsAddress,
-	infractionHeight, power int64,
-	slashFactor sdk.Dec,
-) math.Int {
-	return k.SlashWithInfractionReason(
-		ctx, addr, infractionHeight,
-		power, slashFactor,
-		stakingtypes.Infraction_INFRACTION_UNSPECIFIED,
-	)
+func (k Keeper) Slash(
+	ctx sdk.Context,
+	addr sdk.ConsAddress,
+	infractionHeight, power int64,
+	slashFactor sdk.Dec,
+) math.Int {
+	// This method should not be called; add logging or handle appropriately.
+	return math.ZeroInt()
}
📝 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.

// Calls SlashWithInfractionReason with Infraction_INFRACTION_UNSPECIFIED.
// It should not be called anywhere.
func (k Keeper) Slash(
	ctx sdk.Context,
	addr sdk.ConsAddress,
	infractionHeight, power int64,
	slashFactor sdk.Dec,
) math.Int {
	// This method should not be called; add logging or handle appropriately.
	return math.ZeroInt()
}
x/appchain/coordinator/keeper/timeout.go (3)

121-129: ⚠️ Potential issue

Avoid potential panic in GetFirstVscTimeout by handling errors during unmarshaling

In the loop, if the unmarshaling fails due to corrupted data, it could cause a panic. It would be prudent to handle the error to maintain application stability.

Suggested modification:

 if iterator.Valid() {
     _, _, err := types.ParseVscTimeoutKey(iterator.Key())
     if err != nil {
         return timeout, false
     }
     bz := iterator.Value()
-    k.cdc.MustUnmarshal(bz, &timeout)
+    err = k.cdc.Unmarshal(bz, &timeout)
+    if err != nil {
+        return timeout, false // Handle the error appropriately
+    }
     return timeout, 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.

	if iterator.Valid() {
		_, _, err := types.ParseVscTimeoutKey(iterator.Key())
		if err != nil {
			return timeout, false
		}
		bz := iterator.Value()
		err = k.cdc.Unmarshal(bz, &timeout)
		if err != nil {
			return timeout, false // Handle the error appropriately
		}
		return timeout, true
	}

68-79: ⚠️ Potential issue

Prevent potential panic in GetChainInitTimeout by handling unmarshal errors

Using k.cdc.MustUnmarshal can cause a panic if the stored data is corrupted or improperly formatted. To enhance robustness, consider using k.cdc.Unmarshal and handling the error accordingly.

Apply this diff to handle the unmarshal error gracefully:

 if bz == nil {
     return epoch, false
 }
-k.cdc.MustUnmarshal(bz, &epoch)
+err := k.cdc.Unmarshal(bz, &epoch)
+if err != nil {
+    return epoch, false // Handle the error as appropriate
+}
 return epoch, 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.

// GetChainInitTimeout returns the epoch by the end of which the chain must be initialized.
func (k Keeper) GetChainInitTimeout(
	ctx sdk.Context, chainID string,
) (epoch epochstypes.Epoch, found bool) {
	store := ctx.KVStore(k.storeKey)
	bz := store.Get(types.ChainInitTimeoutKey(chainID))
	if bz == nil {
		return epoch, false
	}
	err := k.cdc.Unmarshal(bz, &epoch)
	if err != nil {
		return epoch, false // Handle the error as appropriate
	}
	return epoch, true
}

96-107: ⚠️ Potential issue

Ensure safe unmarshaling in GetVscTimeout to prevent panics

Similar to GetChainInitTimeout, using MustUnmarshal here might lead to a panic if the data is invalid. It's safer to use Unmarshal and handle potential errors.

Here’s the suggested change:

 if bz == nil {
     return timeout, false
 }
-k.cdc.MustUnmarshal(bz, &timeout)
+err := k.cdc.Unmarshal(bz, &timeout)
+if err != nil {
+    return timeout, false // Handle the error as appropriate
+}
 return timeout, 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.

// GetVscTimeout returns the epoch by the end of which a response to a VSC must be received.
func (k Keeper) GetVscTimeout(
	ctx sdk.Context, chainID string, vscID uint64,
) (timeout epochstypes.Epoch, found bool) {
	store := ctx.KVStore(k.storeKey)
	bz := store.Get(types.VscTimeoutKey(chainID, vscID))
	if bz == nil {
		return timeout, false
	}
	err := k.cdc.Unmarshal(bz, &timeout)
	if err != nil {
		return timeout, false // Handle the error as appropriate
	}
	return timeout, true
}
x/appchain/coordinator/keeper/connection.go (1)

95-135: 🛠️ Refactor suggestion

Adjust log level for missing timeout in SetSubscriberChain

In the SetSubscriberChain function, when the initialization timeout is not found for a chain (lines 133-135), an error is logged:

k.Logger(ctx).Error("timeout not found for chain", "chainID", chainID)

If the absence of a timeout is a possible and expected condition, consider changing the log level to Warn or Info to prevent unnecessary error logging.

x/appchain/coordinator/keeper/slash.go (1)

57-57: 🛠️ Refactor suggestion

Remove unnecessary AVS existence check

According to our previous learning, in HandleSlashPacket, it's unnecessary to check if the AVS exists because the packet must be received over a channel belonging to a chain known to the coordinator module, which mandatorily registers the AVS. The call to k.avsKeeper.IsAVSByChainID is redundant.

Consider directly retrieving the avsAddress without checking for existence.

-	_, avsAddress := k.avsKeeper.IsAVSByChainID(ctx, chainID)
+	avsAddress := k.avsKeeper.GetAVSAddress(ctx, chainID)

Ensure that GetAVSAddress is implemented to retrieve the AVS address associated with the chainID.

Committable suggestion was skipped due to low confidence.

x/appchain/coordinator/keeper/relay.go (3)

98-123: ⚠️ Potential issue

Implement the HandleVscMaturedPacket function

The HandleVscMaturedPacket function currently contains only commented-out code and lacks an implementation. This function is critical for processing VSC matured packets, managing undelegations, and pruning consensus keys.

Would you like assistance in implementing this function or opening a GitHub issue to ensure this task is tracked and completed?


149-166: ⚠️ Potential issue

Inconsistent channel identifiers in OnTimeoutPacket

In the OnTimeoutPacket function, there are inconsistencies with the use of packet.SourceChannel and packet.DestinationChannel:

  • At line 150, you retrieve the chain using packet.SourceChannel.
  • At line 153, you log packet.SourceChannel as the "channelID".
  • At line 156, the error message references packet.DestinationChannel.

To maintain consistency and accuracy, ensure that the same channel identifier is used throughout the function.

Apply the following diff to correct the inconsistencies:

- return types.ErrUnknownSubscriberChannelID.Wrapf(
-     "ack packet on %s", packet.DestinationChannel,
- )
+ return types.ErrUnknownSubscriberChannelID.Wrapf(
+     "ack packet on %s", packet.SourceChannel,
+ )
📝 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.

func (k Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) error {
	chainID, found := k.GetChainForChannel(ctx, packet.SourceChannel)
	if !found {
		k.Logger(ctx).Error(
			"packet timeout, unknown channel",
			"channelID", packet.SourceChannel,
		)
		return types.ErrUnknownSubscriberChannelID.Wrapf(
			"ack packet on %s", packet.SourceChannel,
		)
	}
	// stop chain and release unbondings
	k.Logger(ctx).Info(
		"packet timeout, removing the subscriber",
		"chainID", chainID,
	)
	return k.StopSubscriberChain(ctx, chainID, false)
}

131-145: ⚠️ Potential issue

Inconsistent use of packet channel identifiers in OnAcknowledgementPacket

There is inconsistent use of packet.SourceChannel and packet.DestinationChannel within the OnAcknowledgementPacket function:

  • At line 134, you log packet.SourceChannel as the "channelID".
  • At line 137, you call GetChainForChannel with packet.DestinationChannel.
  • At line 140, the error message references packet.DestinationChannel.

Ensure consistent use of either SourceChannel or DestinationChannel to prevent confusion and potential errors.

Apply the following diff to correct the inconsistencies:

- k.Logger(ctx).Error(
-     "recv ErrorAcknowledgement",
-     "channelID", packet.SourceChannel,
-     "error", err,
- )
+ k.Logger(ctx).Error(
+     "recv ErrorAcknowledgement",
+     "channelID", packet.DestinationChannel,
+     "error", err,
+ )
- if chainID, ok := k.GetChainForChannel(ctx, packet.DestinationChannel); ok {
+ if chainID, ok := k.GetChainForChannel(ctx, packet.SourceChannel); ok {
- return types.ErrUnknownSubscriberChannelID.Wrapf(
-     "ack packet on %s", packet.DestinationChannel,
- )
+ return types.ErrUnknownSubscriberChannelID.Wrapf(
+     "ack packet on %s", packet.SourceChannel,
+ )
📝 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.

	if err := ack.GetError(); err != "" {
		k.Logger(ctx).Error(
			"recv ErrorAcknowledgement",
			"channelID", packet.DestinationChannel,
			"error", err,
		)
		if chainID, ok := k.GetChainForChannel(ctx, packet.SourceChannel); ok {
			return k.StopSubscriberChain(ctx, chainID, false)
		}
		return types.ErrUnknownSubscriberChannelID.Wrapf(
			"ack packet on %s", packet.SourceChannel,
		)
	}
	return nil
}
x/appchain/subscriber/keeper/keeper.go (5)

18-29: 🛠️ Refactor suggestion

Consider reducing dependencies in the Keeper struct

The Keeper struct now includes multiple dependencies, which increases coupling and can affect maintainability. Assess whether all these dependencies are necessary for the Keeper. If possible, refactor to minimize dependencies or group related interfaces.


97-101: ⚠️ Potential issue

Enhance error context in ClaimCapability

In ClaimCapability, if an error occurs while claiming the capability, consider adding contextual information to the error message. This will aid in debugging and provide clearer logs.

Apply this diff to enhance error handling:

func (k Keeper) ClaimCapability(
	ctx sdk.Context,
	cap *capabilitytypes.Capability,
	name string,
) error {
-	return k.scopedKeeper.ClaimCapability(ctx, cap, name)
+	if err := k.scopedKeeper.ClaimCapability(ctx, cap, name); err != nil {
+		return fmt.Errorf("failed to claim capability %s: %w", name, err)
+	}
+	return 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.

	cap *capabilitytypes.Capability,
	name string,
) error {
	if err := k.scopedKeeper.ClaimCapability(ctx, cap, name); err != nil {
		return fmt.Errorf("failed to claim capability %s: %w", name, err)
	}
	return nil
}

70-72: ⚠️ Potential issue

Handle potential nil return value from store.Get

In GetPort, if the key types.PortKey() does not exist in the store, store.Get will return nil. Converting nil to a string results in an empty string. Ensure this behavior is intentional. If not, handle the nil case explicitly to avoid unexpected results.

Apply this diff to handle nil values:

func (k Keeper) GetPort(ctx sdk.Context) string {
	store := ctx.KVStore(k.storeKey)
-	return string(store.Get(types.PortKey()))
+	bz := store.Get(types.PortKey())
+	if bz == nil {
+		return ""
+	}
+	return string(bz)
}
📝 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.

func (k Keeper) GetPort(ctx sdk.Context) string {
	store := ctx.KVStore(k.storeKey)
	bz := store.Get(types.PortKey())
	if bz == nil {
		return ""
	}
	return string(bz)
}

190-194: 🛠️ Refactor suggestion

Validate the value set in SetOutstandingDowntime

In SetOutstandingDowntime, a byte value {1} is stored to represent the outstanding downtime flag. Confirm that this convention is consistently used throughout the code and that retrieving this flag handles the byte correctly. Consider defining a constant for the flag value for clarity.

Apply this diff to use a constant:

+const OutstandingDowntimeValue = byte(1)

func (k Keeper) SetOutstandingDowntime(
	ctx sdk.Context, consAddress sdk.ConsAddress,
) {
	store := ctx.KVStore(k.storeKey)
-	store.Set(types.OutstandingDowntimeKey(consAddress), []byte{1})
+	store.Set(types.OutstandingDowntimeKey(consAddress), []byte{OutstandingDowntimeValue})
}

Committable suggestion was skipped due to low confidence.


88-91: ⚠️ Potential issue

Check for nil capability when binding port

In BindPort, after calling k.portKeeper.BindPort, the returned capability might be nil if the port binding fails. It's important to check for a nil capability before proceeding to claim it to prevent potential nil pointer dereferences.

Apply this diff to handle a nil capability:

func (k Keeper) BindPort(ctx sdk.Context, portID string) error {
	capability := k.portKeeper.BindPort(ctx, portID)
+	if capability == nil {
+		return fmt.Errorf("failed to bind port: %s", portID)
+	}
	return k.ClaimCapability(ctx, capability, host.PortPath(portID))
}
📝 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.

func (k Keeper) BindPort(ctx sdk.Context, portID string) error {
	capability := k.portKeeper.BindPort(ctx, portID)
	if capability == nil {
		return fmt.Errorf("failed to bind port: %s", portID)
	}
	return k.ClaimCapability(ctx, capability, host.PortPath(portID))
}
x/appchain/subscriber/module.go (2)

166-171: ⚠️ Potential issue

Handle potential errors when retrieving the coordinator channel

The code retrieves the coordinator channel using GetCoordinatorChannel, but it only checks if the channel is found. Consider handling scenarios where the retrieval might fail or return unexpected results to ensure robustness.


190-199: ⚠️ Potential issue

Ensure error handling for packet transmission functions

The functions EndBlockSendRewards, QueueVscMaturedPackets, and SendPackets are called without checking for errors. To prevent silent failures and improve reliability, handle any errors returned by these functions.

Consider applying the following changes to handle potential errors:

-am.keeper.EndBlockSendRewards(ctx)
-am.keeper.QueueVscMaturedPackets(ctx)
-am.keeper.SendPackets(ctx)
+if err := am.keeper.EndBlockSendRewards(ctx); err != nil {
+    am.keeper.Logger(ctx).Error("Failed to send rewards at end block", "error", err)
+}
+if err := am.keeper.QueueVscMaturedPackets(ctx); err != nil {
+    am.keeper.Logger(ctx).Error("Failed to queue VSC matured packets", "error", err)
+}
+if err := am.keeper.SendPackets(ctx); err != nil {
+    am.keeper.Logger(ctx).Error("Failed to send packets", "error", err)
+}

Committable suggestion was skipped due to low confidence.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 191-191: Panic in BeginBock or EndBlock consensus methods
path flow from Begin/EndBlock to a panic call
path flow from Begin/EndBlock to a panic call
path flow from Begin/EndBlock to a panic call

x/appchain/subscriber/keeper/rewards.go (5)

22-22: 🛠️ Refactor suggestion

Consider handling the error instead of just logging it

In EndBlockSendRewards, when SendRewardsToCoordinator returns an error, the function logs the error but continues execution. Depending on the application's requirements, it might be more appropriate to handle the error or return it to ensure that issues are addressed promptly.


70-73: ⚠️ Potential issue

Avoid panicking on errors from SendCoinsFromModuleToModule

Panicking during consensus-related operations can cause a chain halt. Instead of panicking when SendCoinsFromModuleToModule fails, handle the error to maintain chain stability.

Apply this diff to handle the error gracefully:

 if err != nil {
-    // It is the common behavior in cosmos-sdk to panic if SendCoinsFromModuleToModule
-    // returns error.
-    panic(err)
+    k.Logger(ctx).Error("failed to send coins for subscriber redistribution", "error", err)
+    return
 }
📝 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.

		if err != nil {
			k.Logger(ctx).Error("failed to send coins for subscriber redistribution", "error", err)
			return
		}
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 72-72: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt


101-102: ⚠️ Potential issue

Handle nil value when retrieving last reward transmission height

The Get method might return nil if the key doesn't exist, which could cause BigEndianToUint64 to panic. Add a check to handle the nil case and set a default value.

Apply this diff:

 bz := store.Get(types.LastRewardTransmissionHeightKey())
+if bz == nil {
+    return 0 // or an appropriate default value
+}
 return int64(sdk.BigEndianToUint64(bz))
📝 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.

	bz := store.Get(types.LastRewardTransmissionHeightKey())
	if bz == nil {
		return 0 // or an appropriate default value
	}
	return int64(sdk.BigEndianToUint64(bz))
}

50-53: ⚠️ Potential issue

Handle parameter parsing errors without panicking

When parsing SubscriberRedistributionFraction, the code panics if there is an error. Panicking in consensus-related methods can cause a chain halt. Instead, handle the error gracefully to enhance robustness.

Apply this diff to handle the error without panicking:

 frac, err := sdk.NewDecFromStr(k.GetSubscriberParams(ctx).SubscriberRedistributionFraction)
 if err != nil {
-    // should not happen since we validated this in the params
-    panic(err)
+    k.Logger(ctx).Error("failed to parse SubscriberRedistributionFraction", "error", err)
+    return
 }
📝 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.

	frac, err := sdk.NewDecFromStr(k.GetSubscriberParams(ctx).SubscriberRedistributionFraction)
	if err != nil {
		k.Logger(ctx).Error("failed to parse SubscriberRedistributionFraction", "error", err)
		return
	}
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 53-53: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt


75-82: ⚠️ Potential issue

Consistently handle errors without panicking

Similar to the previous comment, avoid panicking when sending remaining tokens to the coordinator module fails. Consistent error handling enhances the resilience of the application.

Apply this diff:

 if err != nil {
-    // It is the common behavior in cosmos-sdk to panic if SendCoinsFromModuleToModule
-    // returns error.
-    panic(err)
+    k.Logger(ctx).Error("failed to send remaining tokens to coordinator module", "error", err)
+    return
 }
📝 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.

	remainingTokens := fpTokens.Sub(subsRedistrTokens...)
	err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName,
		types.SubscriberToSendToCoordinatorName, remainingTokens)
	if err != nil {
		k.Logger(ctx).Error("failed to send remaining tokens to coordinator module", "error", err)
		return
	}
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 81-81: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt

x/appchain/subscriber/keeper/validators.go (2)

226-227: ⚠️ Potential issue

Avoid Panics in Consensus Methods to Prevent Chain Halt

Using panic in consensus methods like BeginBlock or EndBlock can cause the entire blockchain to halt if an unexpected error occurs. Instead of panicking, consider handling the error gracefully and logging the error. This ensures the node can continue operating or at least fail without affecting the entire network.

Apply this fix to handle errors without panicking:

At lines 226-227:

- panic(err)
+ k.Logger(ctx).Error("failed to get consensus public key", "error", err)
+ return

At lines 232-233:

- panic(err)
+ k.Logger(ctx).Error("failed to create new validator", "error", err)
+ return

Also applies to: 232-233

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 226-226: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt


205-212: 🛠️ Refactor suggestion

Optimize Pruning Logic in Historical Info Tracking

The current pruning loop iterates from ctx.BlockHeight() - numHistoricalEntries down to 0, checking each height for historical info to delete. This could be inefficient for large block heights. Consider optimizing by calculating the minimum height to keep and directly deleting entries below that height without iteration.

Apply this refactored logic:

 func (k Keeper) TrackHistoricalInfo(ctx sdk.Context) {
     numHistoricalEntries := int64(k.GetParams(ctx).HistoricalEntries)
+    if numHistoricalEntries == 0 {
+        return
+    }
 
+    minHeight := ctx.BlockHeight() - numHistoricalEntries + 1
+    iterator := sdk.KVStorePrefixIterator(k.storeKey, types.HistoricalInfoPrefix)
+    defer iterator.Close()
+    for ; iterator.Valid(); iterator.Next() {
+        hiHeight := types.GetHeightFromHistoricalInfoKey(iterator.Key())
+        if hiHeight < minHeight {
+            k.DeleteHistoricalInfo(ctx, hiHeight)
+        }
+    }
 
     // Existing logic to store new historical info...
 }

Committable suggestion was skipped due to low confidence.

x/appchain/coordinator/module_ibc.go (1)

85-92: 🛠️ Refactor suggestion

Implement version negotiation for enhanced compatibility

Currently, OnChanOpenTry strictly checks that counterpartyVersion matches commontypes.Version. Implementing a version negotiation strategy would improve flexibility and allow compatibility with counterparties using different versions.

Apply this diff to facilitate version negotiation:

-	if counterpartyVersion != commontypes.Version {
+	// Accept any counterparty version for negotiation purposes
+	if err := im.keeper.ValidateCounterpartyVersion(counterpartyVersion); err != nil {
 		return "", errorsmod.Wrapf(
 			commontypes.ErrInvalidVersion,
 			"invalid counterparty version: got: %s, expected %s",
 			counterpartyVersion,
 			commontypes.Version,
 		)
 	}

And define the ValidateCounterpartyVersion method accordingly in your keeper.

Committable suggestion was skipped due to low confidence.

utils/utils.go (2)

288-292: ⚠️ Potential issue

Avoid Using panic in Library Code

The PanicIfNil function uses panic to handle a nil input. In library code, it's recommended to return an error instead of panicking, to allow the caller to handle the error gracefully.

Apply this diff to return an error instead:

-func PanicIfNil(x interface{}, msg string) {
-    if x == nil {
-        panic("zero or nil value for " + msg)
-    }
-}
+func ErrorIfNil(x interface{}, msg string) error {
+    if x == nil {
+        return fmt.Errorf("zero or nil value for %s", msg)
+    }
+    return nil
+}

Don't forget to import the "fmt" package if it's not already imported.

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

func ErrorIfNil(x interface{}, msg string) error {
	if x == nil {
		return fmt.Errorf("zero or nil value for %s", msg)
	}
	return nil
}

248-254: 🛠️ Refactor suggestion

Optimize Slice Allocation in AppendMany Function

In the AppendMany function, preallocating the capacity of the output slice out can improve performance by reducing the number of memory allocations during the append operations.

Apply this diff to preallocate the slice:

 func AppendMany(byteses ...[]byte) (out []byte) {
+    var totalLen int
+    for _, bytes := range byteses {
+        totalLen += len(bytes)
+    }
+    out = make([]byte, 0, totalLen)
     for _, bytes := range byteses {
         out = append(out, bytes...)
     }
     return out
 }
📝 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.

// AppendMany appends a variable number of byte slices together
func AppendMany(byteses ...[]byte) (out []byte) {
    var totalLen int
    for _, bytes := range byteses {
        totalLen += len(bytes)
    }
    out = make([]byte, 0, totalLen)
    for _, bytes := range byteses {
        out = append(out, bytes...)
    }
    return out
}
x/appchain/coordinator/keeper/validator_set_update.go (4)

87-88: ⚠️ Potential issue

Review loop termination condition

In the loop starting at line 82, the condition if power < 1 { break } will exit the loop when a validator has a power less than 1. This could skip subsequent validators with non-zero power if the list isn't sorted by power in descending order. Ensure that the validators are appropriately sorted before this loop, or adjust the condition to avoid unintentionally skipping valid validators.


216-219: ⚠️ Potential issue

Avoid shadowing the err variable

In the SendVscPacketsToChain function, the err variable is re-declared within the if block at line 216, which shadows the outer err variable. This can lead to confusion and potential bugs.

Suggested fix:

Avoid re-declaring err by using = instead of :=:

-            err := k.StopSubscriberChain(ctx, chainID, true)
+            err = k.StopSubscriberChain(ctx, chainID, true)
             if err != nil {
                 panic(fmt.Errorf("subscriber chain failed to stop: %w", err))
             }
📝 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.

			err = k.StopSubscriberChain(ctx, chainID, true)
			if err != nil {
				panic(fmt.Errorf("subscriber chain failed to stop: %w", err))
			}

216-220: 🛠️ Refactor suggestion

Consider handling errors without panicking

When k.StopSubscriberChain returns an error, the code panics. Panicking can halt the entire application, which might not be desirable in a production environment. Consider handling the error gracefully to maintain system stability.

Suggested change:

Handle the error without panicking:

             err = k.StopSubscriberChain(ctx, chainID, true)
             if err != nil {
-                panic(fmt.Errorf("subscriber chain failed to stop: %w", err))
+                k.Logger(ctx).Error(
+                    "failed to stop subscriber chain",
+                    "chainID", chainID,
+                    "error", err,
+                )
+                // Additional error handling as necessary
                 return
             }
📝 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.

			err := k.StopSubscriberChain(ctx, chainID, true)
			if err != nil {
				k.Logger(ctx).Error(
					"failed to stop subscriber chain",
					"chainID", chainID,
					"error", err,
				)
				// Additional error handling as necessary
				return
			}
			return

242-245: ⚠️ Potential issue

Handle nil values when unmarshaling

In the GetPendingVscPackets function, if the value retrieved from the store is nil, calling k.cdc.MustUnmarshal(value, &data) will panic. Add a check for nil before attempting to unmarshal to prevent potential crashes.

Suggested fix:

     value := store.Get(key)
+    if value == nil {
+        return types.ValidatorSetChangePackets{}
+    }
     k.cdc.MustUnmarshal(value, &data)
📝 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.

	key := types.ChainIDToVscPacketsKey(chainID)
	value := store.Get(key)
	if value == nil {
		return types.ValidatorSetChangePackets{}
	}
	k.cdc.MustUnmarshal(value, &data)
	return data
x/appchain/coordinator/types/keys.go (4)

133-133: ⚠️ Potential issue

Complete the incomplete comment

The comment on line 133 is incomplete: // safe to do, since.... Please complete the comment to provide clarity on why this operation is safe.


187-193: ⚠️ Potential issue

Inconsistent key construction using chainID

In SubscriberValidatorKey, the chainID is processed using utils.ChainIDWithLenKey(chainID), which includes the length of the chainID in the key. However, in other key functions like ChannelForChainKey and ChainForChannelKey, the chainID is appended directly without including its length. This inconsistency might lead to issues in key generation and retrieval.

Consider standardizing the key construction method for chainID across all functions to ensure consistency.


140-140: ⚠️ Potential issue

Remove informal language from comment

The comment // PortKey returns the key for the port (hello Harry Potter!) contains informal language. It's recommended to keep comments professional and focused on the code functionality.

Update the comment to:

-// PortKey returns the key for the port (hello Harry Potter!)
+// PortKey returns the key for the port
📝 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.

// PortKey returns the key for the port

5-5: ⚠️ Potential issue

Remove redundant import alias for 'fmt' package

The import statement fmt "fmt" unnecessarily aliases the fmt package to itself. In Go, this is redundant and can be simplified by directly importing "fmt" without an alias.

Apply this diff to remove the unnecessary alias:

-import fmt "fmt"
+import "fmt"
📝 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.

	"fmt"
x/appchain/subscriber/keeper/relay.go (2)

26-35: ⚠️ Potential issue

Return an error acknowledgement instead of nil when receiving a VSC packet on a non-coordinator channel.

In the OnRecvVSCPacket method, when a VSC packet is received on a non-coordinator channel, the function logs an error and returns nil. Returning nil may not be appropriate, as the caller might expect an acknowledgement to handle the error properly. Consider returning an error acknowledgement to signal the issue.

Apply this diff to return an error acknowledgement:

 func (k Keeper) OnRecvVSCPacket(
     ctx sdk.Context,
     packet channeltypes.Packet,
     data commontypes.ValidatorSetChangePacketData,
 ) exported.Acknowledgement {
     coordinatorChannel, found := k.GetCoordinatorChannel(ctx)
     if found && packet.SourceChannel != coordinatorChannel {
         // should never happen
         k.Logger(ctx).Error(
             "received VSCPacket on non-coordinator channel",
             "source channel", packet.SourceChannel,
             "coordinator channel", coordinatorChannel,
         )
-        return nil
+        return channeltypes.NewErrorAcknowledgement(fmt.Sprintf(
+            "received VSCPacket on non-coordinator channel: expected %s, got %s",
+            coordinatorChannel, packet.SourceChannel,
+        ))
     }
     if !found {
         // first message on channel
📝 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.

	coordinatorChannel, found := k.GetCoordinatorChannel(ctx)
	if found && packet.SourceChannel != coordinatorChannel {
		// should never happen
		k.Logger(ctx).Error(
			"received VSCPacket on non-coordinator channel",
			"source channel", packet.SourceChannel,
			"coordinator channel", coordinatorChannel,
		)
		return channeltypes.NewErrorAcknowledgement(fmt.Sprintf(
			"received VSCPacket on non-coordinator channel: expected %s, got %s",
			coordinatorChannel, packet.SourceChannel,
		))
	}

252-283: 🛠️ Refactor suggestion

Continue processing remaining packets after an error occurs in SendPackets.

In the SendPackets method, if an error occurs while sending a packet, the function returns immediately, skipping the processing of remaining packets. This could prevent successfully sending other pending packets. Consider modifying the error handling to log the error and continue processing the rest of the packets. Additionally, if the IBC client is expired, it may be appropriate to break out of the loop.

Apply this diff to adjust the error handling:

     for i := range pending {
         p := pending[i]
         // Send packet over IBC
         err := commontypes.SendIBCPacket(
             ctx,
             k.scopedKeeper,
             k.channelKeeper,
             channelID,                    // source channel id
             commontypes.SubscriberPortID, // source port id
             commontypes.ModuleCdc.MustMarshalJSON(&p.SubscriberPacketData),
             timeoutPeriod,
         )
         if err != nil {
             if clienttypes.ErrClientNotActive.Is(err) {
                 // IBC client is expired!
                 // leave the packet data stored to be sent once the client is upgraded
                 k.Logger(ctx).Info(
                     "IBC client is expired, cannot send IBC packet; leaving packet data stored:",
                     "type", p.Type.String(),
                 )
-                return
+                break
             }
             // Not able to send packet over IBC!
             // Leave the packet data stored for the sent to be retried in the next block.
             // Note that if VSCMaturedPackets are not sent for long enough, the coordinator
             // will remove the subscriber anyway.
             k.Logger(ctx).Error(
                 "cannot send IBC packet; leaving packet data stored:",
                 "type", p.Type.String(), "err", err.Error(),
             )
-            return
+            continue
         }
         if p.Type == commontypes.VscMaturedPacket {
             id := p.GetVscMaturedPacketData().ValsetUpdateID
📝 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.

	for i := range pending {
		p := pending[i]
		// Send packet over IBC
		err := commontypes.SendIBCPacket(
			ctx,
			k.scopedKeeper,
			k.channelKeeper,
			channelID,                    // source channel id
			commontypes.SubscriberPortID, // source port id
			commontypes.ModuleCdc.MustMarshalJSON(&p.SubscriberPacketData),
			timeoutPeriod,
		)
		if err != nil {
			if clienttypes.ErrClientNotActive.Is(err) {
				// IBC client is expired!
				// leave the packet data stored to be sent once the client is upgraded
				k.Logger(ctx).Info(
					"IBC client is expired, cannot send IBC packet; leaving packet data stored:",
					"type", p.Type.String(),
				)
				break
			}
			// Not able to send packet over IBC!
			// Leave the packet data stored for the sent to be retried in the next block.
			// Note that if VSCMaturedPackets are not sent for long enough, the coordinator
			// will remove the subscriber anyway.
			k.Logger(ctx).Error(
				"cannot send IBC packet; leaving packet data stored:",
				"type", p.Type.String(), "err", err.Error(),
			)
			continue
		}
		if p.Type == commontypes.VscMaturedPacket {
			id := p.GetVscMaturedPacketData().ValsetUpdateID
x/appchain/subscriber/module_ibc.go (5)

241-258: 🛠️ Refactor suggestion

Ensure consistent error handling in OnChanCloseInit

The OnChanCloseInit method permits relayers to close duplicate channels but returns an error for other cases. To maintain consistency and clarity, consider refining the error messages or adjusting the conditions under which errors are returned.

Review the logic to ensure that valid channel closure requests are handled appropriately and provide more descriptive error messages where necessary.


225-239: ⚠️ Potential issue

Reconsider error handling in OnChanOpenConfirm method

The OnChanOpenConfirm method currently returns an error indicating that the channel handshake must be initiated by the subscriber chain. However, since the subscriber chain initiated the handshake, this method should proceed without error upon confirmation from the counterparty.

Modify the method to allow successful channel confirmation:

func (im IBCModule) OnChanOpenConfirm(
	ctx sdk.Context,
	_ string,
	_ string,
) error {
	im.keeper.Logger(ctx).Debug(
		"OnChanOpenConfirm",
	)
-	return errorsmod.Wrap(
-		commontypes.ErrInvalidChannelFlow,
-		"channel handshake must be initiated by subscriber chain",
-	)
+	return 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.

// OnChanOpenConfirm implements the IBCModule interface
func (im IBCModule) OnChanOpenConfirm(
	ctx sdk.Context,
	_ string,
	_ string,
) error {
	im.keeper.Logger(ctx).Debug(
		"OnChanOpenConfirm",
	)

	return nil
}

285-290: ⚠️ Potential issue

Include error details in packet unmarshal acknowledgement

When unmarshalling packet data fails, the error acknowledgement does not include the underlying error details. Including the error information can aid in debugging and provide clarity on the failure cause.

Update the error acknowledgement to include error details:

if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
-	errAck := commontypes.NewErrorAcknowledgementWithLog(
-		ctx, fmt.Errorf("cannot unmarshal packet data"),
-	)
+	errAck := commontypes.NewErrorAcknowledgementWithLog(
+		ctx, fmt.Errorf("cannot unmarshal packet data: %v", err),
+	)
	ack = &errAck
} else {
📝 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.

	if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
		errAck := commontypes.NewErrorAcknowledgementWithLog(
			ctx, fmt.Errorf("cannot unmarshal packet data: %v", err),
		)
		ack = &errAck
	} else {

174-176: ⚠️ Potential issue

Validate coordinator fee pool address before setting it

Before setting the CoordinatorFeePoolAddr, it's crucial to validate that the address provided is a valid Bech32 address. This prevents potential issues arising from malformed or malicious data.

Add address validation:

// This address is not required to be supplied at the time of chain registration.
// Rather, it is set later by the coordinator chain.
+if _, err := sdk.AccAddressFromBech32(md.CoordinatorFeePoolAddr); err != nil {
+	return errorsmod.Wrapf(
+		sdkerrors.ErrInvalidAddress,
+		"invalid coordinator fee pool address: %v",
+		err,
+	)
+}
im.keeper.SetCoordinatorFeePoolAddrStr(ctx, md.CoordinatorFeePoolAddr)
📝 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.

	// This address is not required to be supplied at the time of chain registration.
	// Rather, it is set later by the coordinator chain.
	if _, err := sdk.AccAddressFromBech32(md.CoordinatorFeePoolAddr); err != nil {
		return errorsmod.Wrapf(
			sdkerrors.ErrInvalidAddress,
			"invalid coordinator fee pool address: %v",
			err,
		)
	}
	im.keeper.SetCoordinatorFeePoolAddrStr(ctx, md.CoordinatorFeePoolAddr)

198-205: ⚠️ Potential issue

Specify a valid signer for MsgChannelOpenInit

The MsgChannelOpenInit message requires a valid signer for authentication. Currently, the signer is set to an empty string, which may lead to issues during message handling.

Provide a valid signer, such as the module's account address:

distrTransferMsg := channeltypes.NewMsgChannelOpenInit(
	transfertypes.PortID,
	transfertypes.Version,
	channeltypes.UNORDERED,
	connHops,
	transfertypes.PortID,
-	"", // signer unused
+	im.keeper.GetModuleAddress().String(), // specify the module account as signer
)

Ensure that GetModuleAddress() returns the appropriate address for signing.

Committable suggestion was skipped due to low confidence.

x/appchain/coordinator/keeper/ibc_client.go (1)

27-27: ⚠️ Potential issue

Inconsistent usage of subscriber and subscriber.ChainID in logging statements

In the logging statements at lines 27, 35, and 46, there is inconsistency in how subscriber or subscriber.ChainID is used:

  • Line 27: "chainID", subscriber.ChainID
  • Lines 35 and 46: "chainID", subscriber

To maintain consistency and ensure clarity in the logs, consider always using subscriber.ChainID when logging the chain ID.

Apply this diff to fix the inconsistency:

// In lines 35 and 46, update subscriber to subscriber.ChainID
-    "chainID", subscriber,
+    "chainID", subscriber.ChainID,

Also applies to: 35-35, 46-46

@MaxMustermann2 MaxMustermann2 marked this pull request as draft October 22, 2024 04:24
Copy link

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days-before-close if no further activity occurs.

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.

1 participant