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

[composable-controller] Enable handling of non-controller input #4943

Conversation

MajorLift
Copy link
Contributor

@MajorLift MajorLift commented Nov 19, 2024

References

Changelog

@metamask/composable-controller

Changed

  • Widen ComposableController controller option controllers and generic type parameter ChildControllers to accept all objects with a 'name' property. Passing in non-controller input no longer produces a runtime error.
    • Note that the composed state output and stateChange event subscription operation will exclude non-controller input.
    • Generic type parameter ComposableControllerState only accepts controllers with a 'state' property.
  • Widen input type for type guards isBaseController, isBaseControllerV1 from ControllerInstance to unknown.

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've highlighted breaking changes using the "BREAKING" category above as appropriate
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@MajorLift MajorLift added team-wallet-framework team-tiger Tiger team (for tech debt reduction + performance improvements) labels Nov 19, 2024
@MajorLift MajorLift self-assigned this Nov 19, 2024
@MajorLift
Copy link
Contributor Author

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "19.0.0-preview-bb2f1fdd",
  "@metamask-previews/address-book-controller": "6.0.1-preview-bb2f1fdd",
  "@metamask-previews/announcement-controller": "7.0.1-preview-bb2f1fdd",
  "@metamask-previews/approval-controller": "7.1.1-preview-bb2f1fdd",
  "@metamask-previews/assets-controllers": "44.0.0-preview-bb2f1fdd",
  "@metamask-previews/base-controller": "7.0.2-preview-bb2f1fdd",
  "@metamask-previews/build-utils": "3.0.1-preview-bb2f1fdd",
  "@metamask-previews/chain-controller": "0.1.3-preview-bb2f1fdd",
  "@metamask-previews/composable-controller": "9.0.1-preview-bb2f1fdd",
  "@metamask-previews/controller-utils": "11.4.3-preview-bb2f1fdd",
  "@metamask-previews/ens-controller": "15.0.0-preview-bb2f1fdd",
  "@metamask-previews/eth-json-rpc-provider": "4.1.6-preview-bb2f1fdd",
  "@metamask-previews/gas-fee-controller": "22.0.1-preview-bb2f1fdd",
  "@metamask-previews/json-rpc-engine": "10.0.1-preview-bb2f1fdd",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.5-preview-bb2f1fdd",
  "@metamask-previews/keyring-controller": "18.0.0-preview-bb2f1fdd",
  "@metamask-previews/logging-controller": "6.0.2-preview-bb2f1fdd",
  "@metamask-previews/message-manager": "11.0.1-preview-bb2f1fdd",
  "@metamask-previews/multichain": "0.0.0-preview-bb2f1fdd",
  "@metamask-previews/name-controller": "8.0.1-preview-bb2f1fdd",
  "@metamask-previews/network-controller": "22.0.2-preview-bb2f1fdd",
  "@metamask-previews/notification-controller": "7.0.0-preview-bb2f1fdd",
  "@metamask-previews/notification-services-controller": "0.13.0-preview-bb2f1fdd",
  "@metamask-previews/permission-controller": "11.0.3-preview-bb2f1fdd",
  "@metamask-previews/permission-log-controller": "3.0.1-preview-bb2f1fdd",
  "@metamask-previews/phishing-controller": "12.3.0-preview-bb2f1fdd",
  "@metamask-previews/polling-controller": "12.0.1-preview-bb2f1fdd",
  "@metamask-previews/preferences-controller": "14.0.0-preview-bb2f1fdd",
  "@metamask-previews/profile-sync-controller": "1.0.0-preview-bb2f1fdd",
  "@metamask-previews/queued-request-controller": "7.0.1-preview-bb2f1fdd",
  "@metamask-previews/rate-limit-controller": "6.0.1-preview-bb2f1fdd",
  "@metamask-previews/selected-network-controller": "19.0.0-preview-bb2f1fdd",
  "@metamask-previews/signature-controller": "22.0.0-preview-bb2f1fdd",
  "@metamask-previews/transaction-controller": "39.0.0-preview-bb2f1fdd",
  "@metamask-previews/user-operation-controller": "18.0.0-preview-bb2f1fdd"
}

@MajorLift
Copy link
Contributor Author

MajorLift commented Nov 19, 2024

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "19.0.0-preview-3d44b965",
  "@metamask-previews/address-book-controller": "6.0.1-preview-3d44b965",
  "@metamask-previews/announcement-controller": "7.0.1-preview-3d44b965",
  "@metamask-previews/approval-controller": "7.1.1-preview-3d44b965",
  "@metamask-previews/assets-controllers": "44.0.0-preview-3d44b965",
  "@metamask-previews/base-controller": "7.0.2-preview-3d44b965",
  "@metamask-previews/build-utils": "3.0.1-preview-3d44b965",
  "@metamask-previews/chain-controller": "0.1.3-preview-3d44b965",
  "@metamask-previews/composable-controller": "9.0.1-preview-3d44b965",
  "@metamask-previews/controller-utils": "11.4.3-preview-3d44b965",
  "@metamask-previews/ens-controller": "15.0.0-preview-3d44b965",
  "@metamask-previews/eth-json-rpc-provider": "4.1.6-preview-3d44b965",
  "@metamask-previews/gas-fee-controller": "22.0.1-preview-3d44b965",
  "@metamask-previews/json-rpc-engine": "10.0.1-preview-3d44b965",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.5-preview-3d44b965",
  "@metamask-previews/keyring-controller": "18.0.0-preview-3d44b965",
  "@metamask-previews/logging-controller": "6.0.2-preview-3d44b965",
  "@metamask-previews/message-manager": "11.0.1-preview-3d44b965",
  "@metamask-previews/multichain": "0.0.0-preview-3d44b965",
  "@metamask-previews/name-controller": "8.0.1-preview-3d44b965",
  "@metamask-previews/network-controller": "22.0.2-preview-3d44b965",
  "@metamask-previews/notification-controller": "7.0.0-preview-3d44b965",
  "@metamask-previews/notification-services-controller": "0.13.0-preview-3d44b965",
  "@metamask-previews/permission-controller": "11.0.3-preview-3d44b965",
  "@metamask-previews/permission-log-controller": "3.0.1-preview-3d44b965",
  "@metamask-previews/phishing-controller": "12.3.0-preview-3d44b965",
  "@metamask-previews/polling-controller": "12.0.1-preview-3d44b965",
  "@metamask-previews/preferences-controller": "14.0.0-preview-3d44b965",
  "@metamask-previews/profile-sync-controller": "1.0.0-preview-3d44b965",
  "@metamask-previews/queued-request-controller": "7.0.1-preview-3d44b965",
  "@metamask-previews/rate-limit-controller": "6.0.1-preview-3d44b965",
  "@metamask-previews/selected-network-controller": "19.0.0-preview-3d44b965",
  "@metamask-previews/signature-controller": "22.0.0-preview-3d44b965",
  "@metamask-previews/transaction-controller": "39.0.0-preview-3d44b965",
  "@metamask-previews/user-operation-controller": "18.0.0-preview-3d44b965"
}

@MajorLift
Copy link
Contributor Author

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "19.0.0-preview-335c4644",
  "@metamask-previews/address-book-controller": "6.0.1-preview-335c4644",
  "@metamask-previews/announcement-controller": "7.0.1-preview-335c4644",
  "@metamask-previews/approval-controller": "7.1.1-preview-335c4644",
  "@metamask-previews/assets-controllers": "44.0.0-preview-335c4644",
  "@metamask-previews/base-controller": "7.0.2-preview-335c4644",
  "@metamask-previews/build-utils": "3.0.1-preview-335c4644",
  "@metamask-previews/chain-controller": "0.1.3-preview-335c4644",
  "@metamask-previews/composable-controller": "9.0.1-preview-335c4644",
  "@metamask-previews/controller-utils": "11.4.3-preview-335c4644",
  "@metamask-previews/ens-controller": "15.0.0-preview-335c4644",
  "@metamask-previews/eth-json-rpc-provider": "4.1.6-preview-335c4644",
  "@metamask-previews/gas-fee-controller": "22.0.1-preview-335c4644",
  "@metamask-previews/json-rpc-engine": "10.0.1-preview-335c4644",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.5-preview-335c4644",
  "@metamask-previews/keyring-controller": "18.0.0-preview-335c4644",
  "@metamask-previews/logging-controller": "6.0.2-preview-335c4644",
  "@metamask-previews/message-manager": "11.0.1-preview-335c4644",
  "@metamask-previews/multichain": "0.0.0-preview-335c4644",
  "@metamask-previews/name-controller": "8.0.1-preview-335c4644",
  "@metamask-previews/network-controller": "22.0.2-preview-335c4644",
  "@metamask-previews/notification-controller": "7.0.0-preview-335c4644",
  "@metamask-previews/notification-services-controller": "0.13.0-preview-335c4644",
  "@metamask-previews/permission-controller": "11.0.3-preview-335c4644",
  "@metamask-previews/permission-log-controller": "3.0.1-preview-335c4644",
  "@metamask-previews/phishing-controller": "12.3.0-preview-335c4644",
  "@metamask-previews/polling-controller": "12.0.1-preview-335c4644",
  "@metamask-previews/preferences-controller": "14.0.0-preview-335c4644",
  "@metamask-previews/profile-sync-controller": "1.0.0-preview-335c4644",
  "@metamask-previews/queued-request-controller": "7.0.1-preview-335c4644",
  "@metamask-previews/rate-limit-controller": "6.0.1-preview-335c4644",
  "@metamask-previews/selected-network-controller": "19.0.0-preview-335c4644",
  "@metamask-previews/signature-controller": "22.0.0-preview-335c4644",
  "@metamask-previews/transaction-controller": "39.0.0-preview-335c4644",
  "@metamask-previews/user-operation-controller": "18.0.0-preview-335c4644"
}

@@ -16,8 +15,18 @@ import type { Patch } from 'immer';

export const controllerName = 'ComposableController';

export const INVALID_CONTROLLER_ERROR =
'Invalid controller: controller must have a `messagingSystem` or be a class inheriting from `BaseControllerV1`.';
// TODO: Replace with type guard once superclass for messageable non-controllers has been defined.
Copy link
Contributor

Choose a reason for hiding this comment

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

If the goal is to figure out which controllers do not have state, can we check for the absence of the state property?

Copy link
Contributor Author

@MajorLift MajorLift Nov 19, 2024

Choose a reason for hiding this comment

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

Unfortunately only AssetsContractController lacks a state property.

NftDetectionController and TokenDetectionController both have a state property which is assigned an empty object of type Record<never, never>, because both inherit from BaseController.

Checking for an empty state object is not useful because there could be controllers with non-empty state that are initialized with empty state objects.

These inconsistencies make the changes in this PR less type-safe than they could be. This ties in to one of the motivations for the MessengerClient/Messageable ADR. which is to align inheritance for stateless non-controllers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I see. My thought is that we could clean this up by assuming that the non-controllers do not inherit from BaseController. I forgot that we will do that through the MessengerClient/Messageable ADR though.

Copy link
Contributor Author

@MajorLift MajorLift Nov 19, 2024

Choose a reason for hiding this comment

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

There's currently no common supertype for the stateless non-controllers that is narrower than an object with a 'name' property.

Ideally, we would be able to exclude all classes with properties 'name', 'messagingSystem' and without 'state', 'metadata', but since that's currently not an option until the ADR is implemented, I went for just explicitly specifying the non-controllers, so we wouldn't have to deal with edge cases in the input.

I refactored the logic a bit. Hopefully this makes the intention a bit clearer: 85389bb

@MajorLift MajorLift marked this pull request as ready for review November 19, 2024 16:58
@MajorLift MajorLift requested a review from a team as a code owner November 19, 2024 16:58
TODO: move to `@metamask/base-controller` once we have a type and type guard for stateless non-controllers
@Gudahtt
Copy link
Member

Gudahtt commented Nov 20, 2024

The issue suggests two solutions: supporting controllers with no state, or rejecting them. Why not reject instead, what advantage is there to passing in a "controller" with no state?

@MajorLift
Copy link
Contributor Author

MajorLift commented Nov 20, 2024

@Gudahtt The motivation for this PR is to make the ComposableController API more accommodating rather than require code changes downstream with stricter guardrails, but maybe that's the wrong direction to take.

My initial inclination was also to reject, which is reflected in the current state of the ComposableController. This had become a blocker for the update to v9 in mobile because the Engine class uses a single controllers array that includes non-controllers to instantiate both the datamodel and context fields.

One concern I had regarding this was performance impacts on mobile Engine initialization time, since we would need to create a new filtered copy of the array that excludes non-controllers to pass into ComposableController.

However, I've implemented a refactor here 5d0807f05c40fd667dfef28a1ae1233e9bc95ef7 that removes a costly reduce call by directly creating the Engine context property as an object keyed with controller names (this is also the approach taken by MetamaskController on extension).

If this doesn't cause any additional issues, then we could close this PR, and stricter safeguards on ComposableController could be preserved that enable it to avoid handling non-controllers altogether.

@MajorLift
Copy link
Contributor Author

Closing as wont-fix. Downstream consumers will need to ensure that only stateful controllers are present in the input array they pass into the ComposableController constructor.

Concerns over imposing performance penalty to mobile resolved here: MetaMask/metamask-mobile#12374

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
team-tiger Tiger team (for tech debt reduction + performance improvements) team-wallet-framework
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[composable-controller] Enable handling of input that includes non-controllers with empty state
3 participants