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

Operator opt-out #171

Merged
merged 13 commits into from
May 23, 2024
Merged

Operator opt-out #171

merged 13 commits into from
May 23, 2024

Conversation

emlautarom1
Copy link
Contributor

Implements #170

@emlautarom1 emlautarom1 self-assigned this May 20, 2024
@emlautarom1 emlautarom1 linked an issue May 20, 2024 that may be closed by this pull request
@emlautarom1
Copy link
Contributor Author

I need to explore how to test this code first so marked as a draft for now.

operator/avs_manager.go Outdated Show resolved Hide resolved
operator/avs_manager.go Outdated Show resolved Hide resolved
Copy link
Contributor

@taco-paco taco-paco left a comment

Choose a reason for hiding this comment

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

Good job!
One thing that I noticed is that plugin function doesn't return error so our app always exists gracefully with code 0 even if error occurred.

Notice here we use deprecated signature. I would propose to change this

plugin/cmd/main.go Outdated Show resolved Hide resolved
@emlautarom1
Copy link
Contributor Author

After spending quite some time trying to test the code I'm struggling to come up with a solution. My intent is to write a test that registers an operator, checks that it's registered, unregisteres it and checks that it's no longer registered.

Our entry point is plugin/cmd/main.go:

https://github.com/NethermindEth/near-sffl/blob/47e84e0fe342e19d2b7a3cfcbb4189d9fd13e3a0/plugin/cmd/main.go#L190-L203

There is no point in testing the CLI since it's responsible for reading the config values and passing them to AvsManager (the component that actually performs the logic), and passing back the resulting values. Thus, we're interested in testing AvsManager

operator/avs_manager.go

https://github.com/NethermindEth/near-sffl/blob/47e84e0fe342e19d2b7a3cfcbb4189d9fd13e3a0/operator/avs_manager.go#L289-L302

Here is the actual logic that we want to test, but in order to do so we need to construct an AvsManager. This struct is opaque, only exposing a new method that uses real dependencies (ex. chainio.BuildAvsWriter):

https://github.com/NethermindEth/near-sffl/blob/47e84e0fe342e19d2b7a3cfcbb4189d9fd13e3a0/operator/avs_manager.go#L62

We could add an additional constructor that allows us to pass the dependencies or expose the AvsManager struct fields but then we would need to mock things like chainio.AvsWriter which is the one doing the real work

https://github.com/NethermindEth/near-sffl/blob/47e84e0fe342e19d2b7a3cfcbb4189d9fd13e3a0/operator/avs_manager.go#L294

If we mock it, then the only thing we can assert is that we're calling the DeregisterOperator method, which is useless information.


At this point, I don't see any reason to continue pushing for a unit test: to test this behavior an integration test would be more reasonable since all the code is eventually coupled to an on-chain interaction with the AVS contracts.

I'd appreciate any suggestions on how this topic: @taco-paco @Hyodar

@emlautarom1 emlautarom1 marked this pull request as ready for review May 21, 2024 18:45
@emlautarom1 emlautarom1 requested review from taco-paco and Hyodar May 21, 2024 18:45
@Hyodar
Copy link
Contributor

Hyodar commented May 21, 2024

I agree, this is better fitted for an integration test. Actually, we don't even use the plugin deposit, so we could remove it, and in the end this just initializes and wraps an AvsManager. That said, I don't think it's too relevant having a unit test for this.

We could still do it if it's desirable somehow, like:

  1. We define an OperatorPlugin struct and turn our actions into receivers:
type OperatorPlugin struct {
    blsKeyPair *bls.KeyPair
    ecdsaPrivateKey *ecdsa.PrivateKey
    avsManager AvsManagerer
}

func NewOperatorPlugin(/* same fields as struct */) *OperatorPlugin {
    // just create struct
}

func (*OperatorPlugin) OptOut() error {
    // ...
}
  1. We generate a gomock from AvsManagerer
  2. We use the mock in a test and expect calls to be made like that
func createMockOperatorPlugin(mockCtrl *gomock.Controller, /* keypairs... */) (*OperatorPlugin, *mocks.MockAvsManagerer) {
    mockAvsManager := mocks.NewMockAvsManagerer(mockCtrl)
    return NewOperatorPlugin(/* keypairs... */, mockAvsManager), mockAvsManager
}

func SomeTest(t *testing.T) {
    mockCtrl := gomock.NewController(t)
    defer mockCtrl.Finish()

    mockOperatorPlugin, mockAvsManager := createMockOperatorPlugin(mockCtrl, /* ... */)
    
    mockAvsManager.EXPECT().DeregisterOperator(blsKeypair).Return(nil)
    err := mockOperatorPlugin.OptOut()
    assert.Nil(t, err)
}

Then, having unit tested AvsManager, we are assuming relaying the call is enough. You could also implement a more complete mock for AvsManager there that tracks the registrations and uses it for checking if an operator is registered.
Again, at this point I don't think it makes sense, and I'd agree in this case we care about the side effects that just mocking may not be enough, just a quick sketch showing one way to do it.

Copy link
Contributor

@Hyodar Hyodar left a comment

Choose a reason for hiding this comment

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

LGTM!

@emlautarom1
Copy link
Contributor Author

I ended up separating the OperatorPlugin into an interface with the three operations ("opt-in", "opt-out", "deposit") and a concrete implementation that uses the CLI to construct all dependencies (ex. AvsManager). This would allow us to construct an OperatorPlugin with explicit dependencies but testing is still hard: some method like OptOut are just getting data from the environment/filesystem and passing them to the AvsManager.

Again, at this point I don't think it makes sense, and I'd agree in this case we care about the side effects that just mocking may not be enough, just a quick sketch showing one way to do it.

I agree and I don't think its worth pursuing any further with this task. I'll mark it as complete and we can revisit it if we find issues.

@emlautarom1 emlautarom1 requested a review from Hyodar May 22, 2024 16:55
plugin/cmd/operator_plugin.go Show resolved Hide resolved
plugin/cmd/operator_plugin.go Show resolved Hide resolved
operator/avs_manager.go Outdated Show resolved Hide resolved
@Hyodar
Copy link
Contributor

Hyodar commented May 22, 2024

Agreed. Only a few comments, LGTM.

@emlautarom1 emlautarom1 merged commit 6fbe460 into main May 23, 2024
4 checks passed
@Hyodar Hyodar deleted the feature/opt-out-operator branch October 3, 2024 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for Operator opt-out
3 participants