Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: optimize hook module read in warp apply process by using provide… #5541

Open
wants to merge 5 commits into
base: refactor/consolidate-warp-route-updates
Choose a base branch
from

Conversation

mshojaei-txfusion
Copy link
Collaborator

@mshojaei-txfusion mshojaei-txfusion commented Feb 21, 2025

Description

This PR simplifies the hook module read functionality by removing the EvmHookReader class and its associated complexity. The changes include:

  1. Removal of the EvmHookReader import and instance in EvmHookModule
  2. Simplification of the read() method to directly return the config
  3. Reordering of config update in the update() method to ensure proper comparison

These changes make the code more maintainable and reduce unnecessary complexity while maintaining the same functionality.

Drive-by changes

  • Reordered the config update in the update() method to happen after normalization comparison

Related issues

Backward compatibility

Yes - These changes are backward compatible. The functionality remains the same, we're just simplifying the implementation. No infrastructure implications as this is a refactoring of the internal SDK code.

Testing

Manual - The changes have been tested manually to ensure the hook module read and update functionality works as expected. The changes are minimal and focused on internal implementation details.

Copy link

changeset-bot bot commented Feb 21, 2025

🦋 Changeset detected

Latest commit: 4f09729

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@hyperlane-xyz/sdk Minor
@hyperlane-xyz/cli Minor
@hyperlane-xyz/helloworld Minor
@hyperlane-xyz/infra Minor
@hyperlane-xyz/widgets Minor
@hyperlane-xyz/ccip-server Minor
@hyperlane-xyz/github-proxy Minor
@hyperlane-xyz/utils Minor
@hyperlane-xyz/core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@mshojaei-txfusion mshojaei-txfusion changed the base branch from refactor/consolidate-warp-route-updates to main February 21, 2025 14:18
@mshojaei-txfusion mshojaei-txfusion changed the base branch from main to refactor/consolidate-warp-route-updates February 21, 2025 14:39
@mshojaei-txfusion mshojaei-txfusion changed the base branch from refactor/consolidate-warp-route-updates to main February 21, 2025 15:08
@mshojaei-txfusion mshojaei-txfusion changed the base branch from main to refactor/consolidate-warp-route-updates February 21, 2025 17:04
ltyu
ltyu previously approved these changes Feb 26, 2025
Copy link
Contributor

@ltyu ltyu left a comment

Choose a reason for hiding this comment

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

good progress

@@ -132,7 +129,7 @@ export class EvmHookModule extends HyperlaneModule<
public async read(): Promise<HookConfig> {
return typeof this.args.config === 'string'
? this.args.addresses.deployedHook
: this.reader.deriveHookConfig(this.args.addresses.deployedHook);
: { ...this.args.config };
Copy link
Contributor

Choose a reason for hiding this comment

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

we generally still want the Modules to derive onchain values. however, i do see your intention.

the challenge here is that we need some way to force update() to derive, but have another way to detect if this.args.config is an already derived config.

One way we can do this is by checking if the field address exists. This should mean that it's a DerivedHookConfig

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see, can you take another look and confirm I understood correctly?

@ltyu ltyu self-requested a review February 26, 2025 18:51
@ltyu ltyu dismissed their stale review February 26, 2025 18:51

Need changes

@mshojaei-txfusion mshojaei-txfusion changed the base branch from refactor/consolidate-warp-route-updates to main February 27, 2025 12:16
@mshojaei-txfusion mshojaei-txfusion changed the base branch from main to refactor/consolidate-warp-route-updates February 27, 2025 15:08
Copy link
Contributor

@ltyu ltyu left a comment

Choose a reason for hiding this comment

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

lgtm

'@hyperlane-xyz/sdk': minor
---

Optimize HookModule.read() by removing redundant hook config derivation
Copy link
Contributor

Choose a reason for hiding this comment

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

needs update?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updated

return typeof this.args.config === 'string'
? this.args.addresses.deployedHook
: this.reader.deriveHookConfig(this.args.addresses.deployedHook);
const { config, addresses } = this.args;
Copy link
Contributor

Choose a reason for hiding this comment

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

I know you didn't create this, but can you add some comments to both the class, and the update and read function so that consumers know how to think about the semantics and API? It's not super obvious to me why you are able to just skip the hook derivation. IIUC, its that under only some circumstances we pass the actual config in, and the module can just assume that as the result of the read? If that is the case, imo the api has to be made pretty clear, when I call .read, i would expect that the module actually reads. Probably worth adding tests here if they dont exist cc @ltyu

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@ltyu can you please first clarify this and then I write the comments?

Copy link
Contributor

Choose a reason for hiding this comment

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

The reason read() has this branching logic is because the Module was designed to allow

  1. initialization through the constructor, which is used when we have an existing deployed Hook config.
  2. initialization through create(), in this case, it is used to create a new Hook.

In both cases, we want to allow the caller to read(). The reason I suggested using the address is due to the first point. If a user initializes a config through the constructor, we can assume they have an existing deployed config i.e., a DerivedHookConfig. For the latter point, deployedHook address will be populated so we will derive from it.

alternatively, we can also populate this.args.config in the create() with a DerivedConfig to simplify read()

I'm not sure what the if (typeof config === 'string') is used for. perhaps @paulbalaji can clarify

Copy link
Contributor

@nambrot nambrot Mar 5, 2025

Choose a reason for hiding this comment

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

Ok, so just talked IRL with @ltyu and @paulbalaji, here is our current assessment:

There is a few references for config in AbstractHyperlaneModule and its children (i.e. Core, Hook, ISM, ICA). As far as I can tell:

  • It's mainly a convenience compiler enforcement for the read and update function usage of the config
  • In module.read, when the this.args.config is a string (i.e a desired hook or ISM that is passed as the contract address) we use that as in indication to skip reading. Afaict, we do so because we expect that it's not derivable and the user "intentionally" set it as such (like when they have a bespoke hook/ISM they want to use)
  • We do generally pass the constructor-level config as "the expected config" for the module, however, we the update and create functions already expect the config as an explicit argument.
  • In all module update functions, we always explicitly call read which tries to derive the config

All these observations lead me to the following conclusions:

  • We practically don't actually use the TConfig of the stored args: HyperlaneModuleParams
  • We pretty much always read module config even though the caller/constructor of the module already has it (or they don't, but module.read wouldn't give that to them either.
  • We primarily use the fact that both hook and ism config can be specified as the contract address.

What I would like to suggest us to do:

  • Confirm the above understanding
  • Remove the storing of the config and replace it with its current only intended usage in form of indicating whether or not to attempt reading the config or not
  • Change the function signature of module.update to allow the caller to pass in the current config as an optional argument (that like warp module has already read for hook/ism). Alternatively, allow consumers to explicitly set the current config in an instance method (vs. implicitly as a generic constructor argument)

More generally:

  • Be much more clear about how we use config and avoid config by itself (i.e. prefer to use targetConfig, actualConfig, onchainConfig etc.)

@mshojaei-txfusion discussing with the team right now, so consider this issue/PR blocked until we confirm the recommendation

Copy link
Contributor

Choose a reason for hiding this comment

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

Filing #5610 as somewhat related

// We need to normalize the current and target configs to compare.
const normalizedCurrentConfig = normalizeConfig(await this.read());
const normalizedTargetConfig = normalizeConfig(targetConfig);

// Update the config
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does moving this here make any difference? I feel like if we have logic that depends on the module state, then we should make that a lot more obvious

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As now we use this.args.config on read as well, we can't change it before read.
What do you exactly mean by "we should make that a lot more obvious"?

Copy link
Contributor

Choose a reason for hiding this comment

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

We should have clear semantics as to when instance variables (like this.args.config are populated and readable). In this case, it sounds like this.args.config is expected to be not usable until read happens, and that is not obvious at all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Review
Development

Successfully merging this pull request may close these issues.

3 participants