-
Notifications
You must be signed in to change notification settings - Fork 2
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
Conversation
14ec6ea
to
32fbbdb
Compare
7d6fac7
to
630f4bc
Compare
// ContractDiscoveryProcessor is a plugin processor for discovering contracts. | ||
type ContractDiscoveryProcessor struct { | ||
lggr logger.Logger | ||
reader *reader.CCIPReader |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
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() |
There was a problem hiding this comment.
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}
630f4bc
to
53517d6
Compare
|
||
// Outcome isn't needed for this processor. | ||
type Outcome struct { | ||
// TODO: some sort of request flag to avoid including this every time. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong comment?
There was a problem hiding this comment.
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.
execute/plugin.go
Outdated
if err != nil { | ||
return nil, fmt.Errorf("unable to process outcome of discovery processor: %w", err) | ||
} | ||
p.initialized = true |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Test Coverage
|
Implements a new discovery processor to observe contract addresses and bind the results.