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

Contract Discovery Processor. #129

Merged
merged 7 commits into from
Sep 19, 2024
Merged

Contract Discovery Processor. #129

merged 7 commits into from
Sep 19, 2024

Conversation

winder
Copy link
Contributor

@winder winder commented Sep 18, 2024

Implements a new discovery processor to observe contract addresses and bind the results.

@winder winder force-pushed the will/onramp-observations-pt4 branch 2 times, most recently from 14ec6ea to 32fbbdb Compare September 18, 2024 12:44
@winder winder marked this pull request as ready for review September 18, 2024 13:10
@winder winder requested a review from a team as a code owner September 18, 2024 13:10
@winder winder changed the base branch from main to will/onramp-observations-pt3 September 18, 2024 13:12
Base automatically changed from will/onramp-observations-pt3 to main September 18, 2024 13:36
@winder winder force-pushed the will/onramp-observations-pt4 branch 2 times, most recently from 7d6fac7 to 630f4bc Compare September 18, 2024 14:40
// ContractDiscoveryProcessor is a plugin processor for discovering contracts.
type ContractDiscoveryProcessor struct {
lggr logger.Logger
reader *reader.CCIPReader
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why a pointer to an 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.

We need to make sure that when the reader is updated here it is also updated in other parts of the plugin.

It works without the pointer because the shallow copies mean each slice refers to the same array, but will that always be the case?

Copy link
Collaborator

@makramkd makramkd Sep 18, 2024

Choose a reason for hiding this comment

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

The implementation is a pointer receiver so it should work without the pointer. However, I kind of see what you're saying about the shared aspect of it.

The way the reader is used right now, its basically a singleton. The way things are designed right now, I don't see this not being the case. Feels like a change in this would cause a super subtle bug though, gotta think

Copy link
Collaborator

Choose a reason for hiding this comment

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

Correct me if I'm wrong on this but I think the type assertion on the ccipChainReader type helps us avoid this problem.

// Interface compliance check - on the pointer type
var _ CCIPReader = (*ccipChainReader)(nil)
// Interface compliance check - on the value type - FAILS
var _ CCIPReader = ccipChainReader{}

So as long as:

  • CCIP reader is a singleton
  • interface compliance is on the pointer receiver

It should be safe to have this as just the interface rather than pointer to interface. We can put a comment on the compliance check to ensure that this cannot change and what the implications are.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe there's a test we can write to ensure that the pointer type implements the interface - not sure though, seems out of scope of the Go testing facilities since its part of the type system.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are examples in the stdlib of tricks to avoid accidentally copying things that shouldn't be copied.

strings.Builder

// A Builder is used to efficiently build a string using [Builder.Write] methods.
// It minimizes memory copying. The zero value is ready to use.
// Do not copy a non-zero Builder.
type Builder struct {
	addr *Builder // of receiver, to detect copies by value
	buf  []byte
}

io.File

// File represents an open file descriptor.
type File struct {
	*file // os specific
}

Maybe we should adopt one of these

Copy link
Collaborator

Choose a reason for hiding this comment

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

Or maybe leverage sync.Mutex not being copyable:

type nonCopyable struct {
    _ sync.Mutex
}

type ccipChainReader struct {
  nonCopyable
// .. rest
}

func (cdp *ContractDiscoveryProcessor) Observation(
ctx context.Context, _ dt.Outcome, _ dt.Query,
) (dt.Observation, error) {
contracts, err := (*cdp.reader).DiscoverContracts(ctx, cdp.dest)
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you remove the pointer-to-interface this can be simplified to

Suggested change
contracts, err := (*cdp.reader).DiscoverContracts(ctx, cdp.dest)
contracts, err := cdp.reader.DiscoverContracts(ctx, cdp.dest)

@@ -190,7 +216,7 @@ func (p *Plugin) Observation(
}

// TODO: truncate grouped to a maximum observation size?
return exectypes.NewObservation(groupedCommits, nil, nil, nil).Encode()
return exectypes.NewObservation(groupedCommits, nil, nil, nil, discoveryObs).Encode()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggestion for future PRs: maybe have a special constructor for the observation that is expected to be returned in each state, e.g NewGetCommitReportsObservation which would only take two args in this case groupedCommits and discoveryObs to avoid this nil, nil, nil dance which is not super readable. Alternatively we could instantiate the struct directly Observation{CommitReports: groupedCommits, DiscoveryObs: discoveryObs}


// Outcome isn't needed for this processor.
type Outcome struct {
// TODO: some sort of request flag to avoid including this every time.
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this what the initialized state do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The contracts are observed and updated every round, the initialized flag only prevents the other observation logic from running before the readers have been configured.


import "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"

// Outcome isn't needed for this processor.
Copy link
Contributor

Choose a reason for hiding this comment

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

wrong comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is referring to the type. I'm not including the contracts in the final outcome.

if err != nil {
return nil, fmt.Errorf("unable to process outcome of discovery processor: %w", err)
}
p.initialized = true
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to check that the Outcome actually did sync contracts before initializing here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea was that no error means that everything has initialized.

Copy link

Test Coverage

Branch Coverage
will/onramp-observations-pt4 70.6%
main 70.4%

@winder winder merged commit 94ac9fd into main Sep 19, 2024
3 checks passed
@winder winder deleted the will/onramp-observations-pt4 branch September 19, 2024 15:56
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.

3 participants