Skip to content

[BCI-3989][common] - CR methods err when service unstarted#705

Draft
Farber98 wants to merge 38 commits intomainfrom
BCI-3989-cr-methods-error-when-unstarted
Draft

[BCI-3989][common] - CR methods err when service unstarted#705
Farber98 wants to merge 38 commits intomainfrom
BCI-3989-cr-methods-error-when-unstarted

Conversation

@Farber98
Copy link
Contributor

@Farber98 Farber98 commented Aug 14, 2024

solana ref: BCI-3989-cr-methods-error-when-unstarted
core ref: BCI-3989-cr-methods-error-when-unstarted

Task Description:

We only want GetLatestValue, BatchGetLatestValue and QueryKey to be called when CR service is in Started state. If CR is not started yet, we should return an error.

This PR:

Ticket:

Unblocks:

Merging flow:

  1. merge common (point to solana and core for CI to pass)
  2. Then merge Solana by pointing to merged common
  3. Finally merge core after changing Solana and common refs

Comment on lines +291 to +301
func (it *fakeChainReaderInterfaceTester) Start(t *testing.T) {
fake, ok := it.impl.(*fakeChainReader)
assert.True(t, ok)
require.NoError(t, fake.Start(context.Background()))
}

func (it *fakeChainReaderInterfaceTester) Close(t *testing.T) {
fake, ok := it.impl.(*fakeChainReader)
assert.True(t, ok)
require.NoError(t, fake.Close())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need these, why not directly call Start and Close on the ChainReader returned from GetChainReader

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It fails when we run the LOOPs tests. It tries to execute this Start() func which is a no-op. Exposing these 2 funcs in the InterfaceTester was a workaround on this issue.

Let me know your thoughts if there's other way of achieving this without exposing these methods. I'm not very familiar with LOOPs yet and might be missing some context

Copy link
Contributor

Choose a reason for hiding this comment

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

Casting the interface to a specific struct type does not affect which method is called by name via Start/Close. Please see my other comment: #705 (comment)

Comment on lines +315 to +325
func (it *fakeContractReaderInterfaceTester) StartContractReader(t *testing.T) {
fake, ok := it.impl.(*fakeContractReader)
assert.True(t, ok)
require.NoError(t, fake.Start(context.Background()))
}

func (it *fakeContractReaderInterfaceTester) CloseContractReader(t *testing.T) {
fake, ok := it.impl.(*fakeContractReader)
assert.True(t, ok)
require.NoError(t, fake.Close())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm having trouble seeing why we need these helper methods. impl is already a ContractReader, so we don't need to cast it to a specific fake type:

Suggested change
func (it *fakeContractReaderInterfaceTester) StartContractReader(t *testing.T) {
fake, ok := it.impl.(*fakeContractReader)
assert.True(t, ok)
require.NoError(t, fake.Start(context.Background()))
}
func (it *fakeContractReaderInterfaceTester) CloseContractReader(t *testing.T) {
fake, ok := it.impl.(*fakeContractReader)
assert.True(t, ok)
require.NoError(t, fake.Close())
}
func (it *fakeContractReaderInterfaceTester) StartContractReader(t *testing.T) {
require.NoError(t, it.impl.Start(context.Background()))
}
func (it *fakeContractReaderInterfaceTester) CloseContractReader(t *testing.T) {
require.NoError(t, it.impl.Close())
}

Additionally, there is already a GetContractReader method, so callers that are doing this:

	tester.StartContractReader(t)
	defer tester.CloseContractReader(t)

Could instead do this:

	tester.GetContractReader(t).Start(ctx)
	defer tester.GetContractReader(t).Close()

Or better yet:

	servicetest.Run(t, tester.GetContractReader(t))

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jmank88 @silaslenihan I couldn't do it this way. Tests fail for the loop relay client.

Pushed some changes with a new idea:

  • Modify testCases and Setup to receive a startCR flag and control whether we should start CR or not
  • With this, Start and Close will be handled by Setup with Cleanup

All tests are passing, including new ones where we test that the service does not start. Let me know your thoughts

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please update the description? I'm having trouble understanding the fundamental problem we are trying to solve.

Copy link
Contributor

@jmank88 jmank88 Sep 16, 2024

Choose a reason for hiding this comment

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

Since the BasicTester interface is used more generally than for just contract reader, I don't think it makes sense to add a contract-reader-only argument to BasicTester.Setup.

Copy link
Contributor Author

@Farber98 Farber98 Sep 16, 2024

Choose a reason for hiding this comment

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

Fundamental problem we are trying to solve: We only want GetLatestValue, BatchGetLatestValue and QueryKey to be called when the service CR is in Started state. If CR has not yet been started, we should return an error.

To test this new behavior, we need a way to get the CR in both states (started and not started).

We can't just call Start and Close directly on the return of GetContractReader to achieve this. This is because we also run relay client loop tests here. We are calling Start and Close on the client and not on the server as would be required.

The two solutions I proposed were aimed at solving this problem, the one with the helper methods and now the one with the flag. I feel like the one with the flag is cleaner. We are providing in the testCase the startCR flag to determine if the underlying Setup function should return the service in started or unstarted state

What do you think about defining a new interface similar to BasicTester but use it specifically on contract-reader?

Copy link
Contributor

Choose a reason for hiding this comment

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

We can't just call Start and Close directly on the return of GetContractReader to achieve this. This is because we also run relay client loop tests here. We are calling Start and Close on the client and not on the server as would be required.

The two solutions I proposed were aimed at solving this problem, the one with the helper methods and now the one with the flag.

Something isn't adding up here. The modification that I proposed to your helper methods was intended to execute exactly equivalently. It was not proposing a change in behavior - definitely not one that switched from client to server or vice versa. If the helper method solution was valid, then we should revisit it, but in the most simplified form.

Copy link
Contributor Author

@Farber98 Farber98 Sep 17, 2024

Choose a reason for hiding this comment

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

Pushed a new solution with interfaces refactor and comments on changes. If do you think it's okay we can go with it. Else, I'll revisit the one with the helper methods in the most simplified form

Copy link
Contributor

@jmank88 jmank88 left a comment

Choose a reason for hiding this comment

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

It looks like there are still some simplifications available

Comment on lines +302 to +303
require.NoError(t, it.impl.Start(context.Background()))
t.Cleanup(func() { require.NoError(t, it.impl.Close()) })
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use servicetest.Run. Notice it uses assert during cleanup.

)

type ChainComponentsInterfaceTester[T TestingT[T]] interface {
type ChainComponentsTester[T any] interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • defined ChainComponentsTester so we use the Setup(t T, startCR bool) only for chainComponents tests
  • defined chainComponentsTestcase so we use the startCR testcase flag only for chainComponents testcases
  • defined runChainComponentsTests to execute chainComponents specific tests using the ChainComponentsTester interface with Setup(t T, startCR bool)

TestOn string
}

type codecTestcase[T any] struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • defined CodecTester so we use the Setup(t T) without the startCR flag for the codec
  • defined codecTestcase so we don't use the startCR testcase flag for codec testcases
  • defined runCodecTests to execute codec specific tests using the CodecTester interface with Setup(t T)

fake.stored = []TestStruct{}

if startCR {
servicetest.Run(t, it.impl)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

added servicetest.Run(t, it.impl)

Copy link
Contributor

@ilija42 ilija42 left a comment

Choose a reason for hiding this comment

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

Wdyt about this solution

#777
smartcontractkit/chainlink#14480

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.

4 participants